在快节奏的现代生活中,共享茶室作为新兴的社交休闲空间正逐渐流行。传统茶室预约依赖电话或现场登记,常出现时段冲突、信息错漏等问题。去年我在杭州某茶室调研时,发现店主每天要接听30+预约电话,手工记录常出现重复预订的情况。这正是我们开发这款微信小程序的核心驱动力——用技术解决传统行业的低效痛点。
Java作为企业级开发的首选语言,结合微信小程序轻量级特性,能构建出稳定且用户体验优秀的管理系统。系统需要实现三个核心目标:
采用经典的三层架构模式,具体技术组合如下:
| 层级 | 技术选型 | 选型理由 |
|---|---|---|
| 前端 | 微信小程序+WXML/WXSS | 无需安装即用即走,开发成本低,用户覆盖广 |
| 后端 | Spring Boot 2.7 + JDK8 | 自动配置简化部署,内置Tomcat容器,与微信生态对接成熟 |
| 数据库 | MySQL 8.0 | 事务支持完善,JSON类型字段适合存储茶室特色描述等非结构化数据 |
| 缓存 | Redis 6.2 | 应对预约高峰期的秒杀场景,保证时段库存的原子性操作 |
| 消息队列 | RabbitMQ 3.9 | 异步处理预约成功通知、订单超时取消等非核心流程 |
特别注意:MySQL必须使用5.7以上版本,低版本不支持窗口函数会导致预约冲突检测SQL无法优化
核心表关系采用星型模型,以茶室表为中心:
sql复制CREATE TABLE `tea_room` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '茶室名称',
`style` enum('中式','日式','现代') COLLATE utf8mb4_bin DEFAULT '中式',
`max_persons` tinyint unsigned DEFAULT '4',
`hourly_price` decimal(10,2) unsigned NOT NULL,
`cover_img` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`status` tinyint DEFAULT '1' COMMENT '0-维护中 1-可预约',
PRIMARY KEY (`id`),
KEY `idx_style` (`style`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
预约表设计特别注意时间冲突检测:
sql复制CREATE TABLE `reservation` (
`id` bigint NOT NULL AUTO_INCREMENT,
`room_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`start_time` datetime NOT NULL COMMENT '精确到半小时',
`end_time` datetime NOT NULL,
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-待确认 1-已预约 2-已取消',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_room_time` (`room_id`,`start_time`),
KEY `idx_user` (`user_id`),
CONSTRAINT `fk_room` FOREIGN KEY (`room_id`) REFERENCES `tea_room` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
采用时间窗口重叠检测法,关键Java实现:
java复制@Transactional
public synchronized ReservationResult makeReservation(Long roomId, Long userId, LocalDateTime start, int hours) {
// 参数校验省略...
LocalDateTime end = start.plusHours(hours);
// 使用SELECT FOR UPDATE加行锁
List<Reservation> conflicts = reservationMapper.selectConflicts(
roomId, start, end);
if (!conflicts.isEmpty()) {
return ReservationResult.error("该时段已被预约");
}
Reservation reservation = new Reservation();
reservation.setRoomId(roomId);
reservation.setUserId(userId);
reservation.setStartTime(start);
reservation.setEndTime(end);
reservationMapper.insert(reservation);
// 异步发送微信模板消息
rabbitTemplate.convertAndSend("reservation.notify",
new NotifyMessage(userId, start, roomId));
return ReservationResult.success(reservation.getId());
}
采用V3版微信支付API,注意证书加载方式:
yaml复制wx:
pay:
app-id: wx123456789
mch-id: 1230001
api-v3-key: 32位密钥
cert-path: classpath:/cert/apiclient_cert.p12
java复制public PaymentResponse createOrder(Long reservationId) {
Reservation reservation = checkReservation(reservationId);
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
request.setOutTradeNo(generateOrderNo());
request.setAmount(new Amount().setTotal(calcTotal(reservation)));
request.setDescription(reservation.getRoom().getTitle() + "预约");
// 关键:处理证书加载
try (InputStream cert = resourceLoader.getResource(
wxPayConfig.getCertPath()).getInputStream()) {
return wxPayService.createOrderV3(request, cert);
}
}
问题现象:压力测试时出现超卖(同一时段被重复预约)
解决方案:
uk_room_timesynchronized方法锁java复制public ReservationResult makeReservationWithLock(...) {
String lockKey = "reservation:" + roomId + ":" + startTime;
try {
// 获取分布式锁,超时时间3秒
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
if (!locked) {
return ReservationResult.error("系统繁忙请重试");
}
// 使用版本号乐观锁
int updated = reservationMapper.insertWithVersion(
reservation, 0);
if (updated == 0) {
return ReservationResult.error("预约冲突");
}
// ...后续操作
} finally {
redisTemplate.delete(lockKey);
}
}
问题现象:频繁登录导致用户体验差
优化方案:
java复制public WxAuthResult login(String code) {
WxMaJscode2SessionResult session = wxMaService.getUserService()
.getSessionInfo(code);
String accessToken = JwtUtil.generateToken(session.getOpenid(), 2 * 60 * 60);
String refreshToken = JwtUtil.generateToken(session.getOpenid(), 7 * 24 * 60 * 60);
// 存储refreshToken到Redis,有效期7天
redisTemplate.opsForValue().set(
"refresh:" + session.getOpenid(),
refreshToken, 7, TimeUnit.DAYS);
return new WxAuthResult(accessToken, refreshToken);
}
采用Docker Compose编排服务:
yaml复制version: '3'
services:
app:
image: openjdk:8-jre
ports:
- "8080:8080"
volumes:
- ./app.jar:/app.jar
command: java -jar /app.jar
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:6.2
ports:
- "6379:6379"
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
在开发过程中最大的收获是:对于传统行业的数字化改造,不能简单照搬电商模式。比如茶室预约需要特别处理"整点时段"的特殊性,我们最终采用了30分钟为最小时间粒度的设计,既符合行业习惯又提升了资源利用率。