1. 项目背景与需求分析
社区休闲场地预约系统是解决现代社区公共资源分配难题的数字化方案。随着城市居民对健身、娱乐需求的增长,篮球场、游泳池、活动室等公共设施的使用冲突日益突出。传统的人工登记方式存在三大痛点:信息不透明导致空置率高、现场排队造成时间浪费、纸质记录难以统计使用数据。
我们设计的这套系统需要实现以下核心功能:
- 居民通过微信小程序实时查看场地空闲时段
- 支持在线预约/取消/改签操作
- 管理员后台进行场地管理与数据统计
- 支付押金与信用积分体系防止爽约
技术选型上采用SpringBoot+UniApp组合,主要基于:
- 微信小程序覆盖率高,UniApp可跨平台输出iOS/Android/Web版本
- SpringBoot的快速开发特性适合社区级应用迭代
- 两者都有丰富的生态插件(如支付、地图等)
2. 技术架构设计
2.1 整体架构分层
code复制前端层:UniApp跨端框架
↓ HTTP/HTTPS
API网关:SpringCloud Gateway
↓
业务层:SpringBoot微服务
↓ JDBC
数据层:MySQL + Redis
↓
第三方服务:微信支付、地图API
2.2 数据库关键表设计
场地表(venue)
sql复制CREATE TABLE `venue` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '场地名称',
`type` enum('篮球场','游泳池','会议室') NOT NULL,
`location` point NOT NULL COMMENT 'GIS坐标',
`open_time` time NOT NULL,
`close_time` time NOT NULL,
`price_per_hour` decimal(10,2) DEFAULT NULL,
`cover_img` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
SPATIAL KEY `idx_location` (`location`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
预约表(reservation)
sql复制CREATE TABLE `reservation` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`venue_id` int NOT NULL,
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
`status` enum('待支付','已预约','已完成','已取消') NOT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`payment_no` varchar(64) DEFAULT NULL COMMENT '微信支付单号',
PRIMARY KEY (`id`),
KEY `idx_venue_time` (`venue_id`,`start_time`,`end_time`),
KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.3 接口安全设计
- JWT鉴权:采用HS256算法生成token,包含用户ID和权限标识
- 预约冲突校验:使用Redis分布式锁防止超卖
java复制// 伪代码示例 String lockKey = "venue:"+venueId+":"+timeSlot; boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS); if(!locked) throw new BizException("该时段已被预约"); - 防刷策略:Guava RateLimiter限制接口调用频率
3. UniApp前端实现要点
3.1 跨端适配方案
通过条件编译处理平台差异:
javascript复制// #ifdef MP-WEIXIN
const login = () => wx.login()
// #endif
// #ifdef APP-PLUS
const login = () => uni.getProvider({service: 'oauth'})
// #endif
3.2 核心页面实现
场地列表页关键技术点:
- 滑动分页加载使用
<scroll-view>+onReachBottom - 距离计算调用微信
wx.getLocation+腾讯地图API - 使用
<map>组件展示场地位置标记
javascript复制export default {
data() {
return {
venues: [],
loading: false,
pageNo: 1
}
},
onReachBottom() {
if(this.loading) return;
this.pageNo++
this.loadData()
},
methods: {
async loadData() {
this.loading = true
const res = await uni.request({
url: '/api/venue/list',
data: {pageNo: this.pageNo}
})
this.venues = [...this.venues, ...res.data]
this.loading = false
}
}
}
3.3 状态管理优化
采用Pinia管理全局状态:
javascript复制// stores/venue.js
export const useVenueStore = defineStore('venue', {
state: () => ({
currentType: 'all',
sortBy: 'distance'
}),
actions: {
changeFilter(payload) {
this.$patch(payload)
}
}
})
4. SpringBoot后端核心逻辑
4.1 预约业务流程图
code复制 +---------------+
| 校验时间合法性 |
+-------┬-------+
|
+-------▼-------+
| 检查场地可用性 |
+-------┬-------+
|
+-------▼-------+
| 生成预支付订单 |
+-------┬-------+
|
+-------▼-------+
| 扣减库存+锁座 |
+---------------+
4.2 并发控制实现
使用MySQL乐观锁+Redis原子操作:
java复制@Transactional
public ReservationDTO createReservation(ReservationRequest request) {
// 1. 查询场地信息
Venue venue = venueMapper.selectForUpdate(request.getVenueId());
// 2. 检查时间冲突
int conflictCount = reservationMapper.checkTimeConflict(
request.getVenueId(),
request.getStartTime(),
request.getEndTime());
if(conflictCount > 0) {
throw new BizException(ErrorCode.TIME_CONFLICT);
}
// 3. Redis原子递减
String key = "inventory:" + request.getVenueId();
long remain = redisTemplate.opsForValue()
.decrement(key);
if(remain < 0) {
redisTemplate.opsForValue().increment(key);
throw new BizException(ErrorCode.SOLD_OUT);
}
// 4. 创建预约记录
Reservation reservation = new Reservation();
BeanUtils.copyProperties(request, reservation);
reservationMapper.insert(reservation);
// 5. 返回DTO对象
return convertToDTO(reservation);
}
4.3 定时任务设计
- 过期预约清理:每天凌晨释放未支付的预约
java复制@Scheduled(cron = "0 0 3 * * ?") public void releaseExpiredReservations() { List<Reservation> list = reservationMapper.selectExpired(); list.forEach(item -> { redisTemplate.opsForValue() .increment("inventory:"+item.getVenueId()); item.setStatus(CANCELED); reservationMapper.updateById(item); }); } - 信用分统计:每月1号计算用户信用等级
5. 典型问题解决方案
5.1 微信支付接入问题
常见报错处理:
requestPayment:fail access denied:检查小程序后台支付配置白名单- 支付成功但状态未更新:实现微信支付回调接口的幂等处理
java复制@PostMapping("/pay/callback") public String paymentCallback(@RequestBody String xmlData) { WxPayNotifyResponse response = wxPayService.parseNotify(xmlData); if("SUCCESS".equals(response.getResultCode())) { // 通过outTradeNo查询订单 Reservation order = reservationMapper.selectByPayNo( response.getOutTradeNo()); if(order != null && order.getStatus() == UNPAID) { order.setStatus(PAID); reservationMapper.updateById(order); } } return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>"; }
5.2 性能优化实践
-
缓存策略:
- 场地信息使用Redis缓存,设置30分钟过期
- 热门场地预约状态使用BitMap存储
-
SQL优化:
sql复制/* 反例 - 全表扫描 */ SELECT * FROM reservation WHERE DATE(start_time) = '2023-08-01'; /* 正例 - 走索引查询 */ SELECT * FROM reservation WHERE start_time >= '2023-08-01 00:00:00' AND start_time < '2023-08-02 00:00:00'; -
前端图片优化:
- 使用WebP格式减少体积
- 实现懒加载技术
html复制<image :src="item.coverImg" mode="aspectFill" lazy-load @error="handleImageError" />
6. 部署与运维方案
6.1 小程序发布流程
- 通过HBuilderX打包生成微信小程序代码包
- 登录微信公众平台提交审核
- 配置服务器域名(需HTTPS)
- request合法域名
- socket合法域名
- uploadFile合法域名
- downloadFile合法域名
6.2 服务端部署
宝兰德中间件适配要点:
yaml复制# application-bes.yml
server:
bes:
context-path: /reservation
port: 8080
servlet:
encoding:
charset: UTF-8
force: true
K8S部署示例:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: reservation-backend
spec:
replicas: 3
selector:
matchLabels:
app: reservation
template:
metadata:
labels:
app: reservation
spec:
containers:
- name: app
image: registry.example.com/reservation:1.0.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: prod
---
apiVersion: v1
kind: Service
metadata:
name: reservation-service
spec:
selector:
app: reservation
ports:
- protocol: TCP
port: 80
targetPort: 8080
6.3 监控方案
- SpringBoot Actuator 暴露健康检查端点
properties复制management.endpoints.web.exposure.include=health,metrics management.endpoint.health.show-details=always - ELK日志收集:Filebeat采集日志到Kibana展示
- 小程序监控:使用腾讯云移动分析(MTA)
7. 扩展功能探讨
7.1 智能推荐算法
基于用户历史预约数据实现:
python复制# 协同过滤推荐示例
from surprise import Dataset, KNNBasic
data = Dataset.load_builtin('ml-100k')
trainset = data.build_full_trainset()
sim_options = {'name': 'cosine', 'user_based': False}
algo = KNNBasic(sim_options=sim_options)
algo.fit(trainset)
# 为用户推荐场地
user_inner_id = algo.trainset.to_inner_uid(user_id)
user_neighbors = algo.get_neighbors(user_inner_id, k=3)
7.2 物联网设备集成
通过MQTT协议连接智能门锁:
- 预约成功后下发临时开锁密码
- 使用时间范围限制密码有效性
- 开锁记录同步到云端核销
SpringBoot整合RocketMQ示例:
java复制@RestController
@RequestMapping("/iot")
public class IotController {
@Resource
private RocketMQTemplate rocketMQTemplate;
@PostMapping("/unlock")
public void sendUnlockCommand(@RequestBody UnlockRequest request) {
Message<String> message = MessageBuilder
.withPayload(JSON.toJSONString(request))
.setHeader("KEYS", request.getDeviceId())
.build();
rocketMQTemplate.send("UNLOCK_TOPIC", message);
}
}
7.3 语音交互功能
UniApp实现语音播报:
javascript复制const innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = true;
innerAudioContext.src = 'https://example.com/audio/remind.mp3';
innerAudioContext.onPlay(() => {
console.log('开始播放语音提醒');
});
在实际开发中,我们发现三个关键经验:首先,场地时间冲突检测必须考虑时区问题,建议所有时间存储为UTC时间;其次,微信小程序审核时需特别注意"虚拟支付"限制,体育场地预约不属于虚拟商品;最后,Redis库存扣减需要配合本地缓存标记,避免频繁访问Redis造成性能瓶颈。
