ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于微信小程序的Java陪诊预约平台源码实战:时间冲突检测与订单状态机

基于微信小程序的Java陪诊预约平台源码实战:时间冲突检测与订单状态机 简介这份源码资源面向Java后端与微信小程序开发者提供一套完整的陪诊服务预约平台实现方案可用于毕业设计、课程实训或医疗类小程序二次开发。项目围绕预约陪诊、住院陪护、待办取药、挂号及检查结果领取等场景覆盖服务展示、时间选择、订单生成、历史订单与预约状态跟踪等核心流程。压缩包共518个文件约9.42MB其中119个JavaScript文件承担小程序逻辑106个wxss与88个wxml负责样式与页面结构78个json用于配置管理77个Java文件实现后端服务另有36张png及sql、yml等辅助文件前后端分层清晰。资源内附安装使用手册与示例模块便于快速理解目录组织与接口调用关系。目前已有192人学习下载适合希望掌握小程序与Java后端联调、积累医疗预约类项目经验的开发者参考。1. 陪诊预约平台为什么值得用小程序重做一遍陪诊这件事真正跑起来才知道麻烦在哪。家属临时有事老人独自去医院从挂号、候诊、缴费到取药每一步都可能卡住而愿意接单的陪诊员又常常靠微信群、朋友圈接活时间冲突、服务内容说不清、费用结算靠转账截图。把「基于微信小程序的 Java 陪诊服务预约平台」当成一个正经项目来做核心不是炫技而是把下单、接单、订单状态、服务评价这几条链路用一套可追溯的数据结构固定下来。微信小程序负责患者端和陪诊员端的轻量入口Java 后端负责订单、用户、支付回调和权限校验源码层面最值得复现的是「预约时间冲突检测」和「订单状态机」这两块。适合有 Java 基础、想做一个完整业务闭环的开发者也适合需要给本地陪诊团队搭一套内部工具的人。2. 陪诊预约平台的技术选型与数据模型怎么定2.1 为什么是微信小程序加 Java 后端而不是纯 H5 或 App微信小程序最大的优势是触达成本低。患者家属不需要下载安装包搜一下或者扫个码就能进入预约页陪诊员端同样用小程序接单提醒可以走订阅消息。纯 H5 在微信里也能打开但页面列表加载更多、顶部导航栏高度适配、单选框这些交互细节小程序原生组件比 H5 稳定得多尤其是微信小程序页面列表加载更多这种高频操作用原生scroll-view配合onReachBottom比 H5 的滚动监听少很多兼容问题。后端选 Java不是因为 Java 一定比别的语言好而是这个业务里订单状态流转、时间冲突判断、支付回调验签都需要强类型和成熟的生态。Spring Boot 加 MyBatis 是常见组合源码结构清晰后续加行级权限或者对账逻辑也容易扩展。数据库用 MySQL订单表和服务时间表分开避免把时间区间塞进一个字段里导致查询困难。提示如果团队里有人熟悉 uniapp 微信小程序打包也可以一套代码同时出小程序和 H5但陪诊业务里陪诊员端对订阅消息依赖较强原生小程序更省心。2.2 核心表结构用户、陪诊员、服务时段、订单陪诊预约平台的数据模型不需要太复杂但几个关键字段必须提前定好。下面这张表是我在实际项目里会用的最小字段集省略了通用的创建时间和逻辑删除标记。表名关键字段说明userid, openid, phone, rolerole 区分患者家属和陪诊员companionid, user_id, real_name, id_card, status陪诊员资质和接单状态service_slotid, companion_id, start_time, end_time, price可预约时段按小时或半天切orderid, user_id, companion_id, slot_id, status, amount订单主表status 用枚举order_logid, order_id, from_status, to_status, operator状态流转日志排查用service_slot表里start_time和end_time用datetime类型不要用字符串。订单状态建议用整数枚举10 待支付、20 已支付待接单、30 已接单、40 服务中、50 已完成、60 已取消、70 退款中。状态机一旦定下来后面所有接口都围绕它做校验避免出现「已取消的订单还能评价」这种脏数据。2.3 用 Java 写一个时间冲突检测的最小方法陪诊预约最怕的是同一个陪诊员在同一时间段被重复下单。下面这段代码是服务层里的冲突检测逻辑放在创建订单之前调用。/** * 检查陪诊员在指定时间段是否已有未取消的订单 * param companionId 陪诊员ID * param startTime 服务开始时间 * param endTime 服务结束时间 * return true 表示有时间冲突不能下单 */ public boolean hasTimeConflict(Long companionId, LocalDateTime startTime, LocalDateTime endTime) { // 查询该陪诊员所有未取消且未完成的订单 ListOrder existingOrders orderMapper.selectByCompanionIdAndStatusIn( companionId, Arrays.asList(OrderStatus.PAID, OrderStatus.ACCEPTED, OrderStatus.IN_SERVICE) ); for (Order order : existingOrders) { LocalDateTime existStart order.getServiceStartTime(); LocalDateTime existEnd order.getServiceEndTime(); // 两个区间相交的条件新开始 旧结束 且 新结束 旧开始 if (startTime.isBefore(existEnd) endTime.isAfter(existStart)) { return true; } } return false; }这段逻辑的关键在最后那个判断条件。很多人会写成startTime.isBefore(existEnd) endTime.isAfter(existStart)的反面结果把相邻时段也判成冲突。参数上startTime和endTime必须来自service_slot表不要用前端传的任意时间否则陪诊员可以绕过时段限制。如果业务允许陪诊员手动调整时间那冲突检测要放在调整接口里再调一次。2.4 小程序端预约页面的最小实现小程序端不需要一上来就做很花哨的 UI先把「选陪诊员、选时段、提交订单」跑通。下面是一个简化后的页面逻辑用picker选时段提交时调后端接口。// pages/booking/booking.js Page({ data: { companionId: null, slots: [], selectedSlotIndex: -1 }, onLoad(options) { this.setData({ companionId: options.companionId }); this.loadSlots(); }, loadSlots() { wx.request({ url: https://your-domain.com/api/slot/list, data: { companionId: this.data.companionId }, success: (res) { // 后端返回的时段列表每个元素包含 id、startTime、endTime、price this.setData({ slots: res.data.data }); } }); }, onSlotChange(e) { this.setData({ selectedSlotIndex: e.detail.value }); }, submitOrder() { const slot this.data.slots[this.data.selectedSlotIndex]; if (!slot) { wx.showToast({ title: 请选择服务时段, icon: none }); return; } wx.request({ url: https://your-domain.com/api/order/create, method: POST, data: { slotId: slot.id }, success: (res) { if (res.data.code 0) { wx.showToast({ title: 预约成功 }); } else { // 后端返回冲突或余额不足等具体原因 wx.showToast({ title: res.data.msg, icon: none }); } } }); } });这里slotId是唯一需要传给后端的参数陪诊员 ID 和时间都从slot记录里取避免前端篡改。wx.request的域名必须在小程序后台配置合法域名本地调试可以在开发者工具里勾选「不校验合法域名」。如果遇到微信小程序 10002 这类错误通常是域名没配或者证书有问题先检查这两处。3. 订单状态机与支付回调怎么落地3.1 状态流转的 Java 枚举与校验方法订单状态机是陪诊平台最容易出 bug 的地方。我一般会写一个枚举类把允许的流转关系定义清楚任何状态变更都走同一个方法。public enum OrderStatus { WAIT_PAY(10, 待支付), PAID(20, 已支付待接单), ACCEPTED(30, 已接单), IN_SERVICE(40, 服务中), FINISHED(50, 已完成), CANCELLED(60, 已取消), REFUNDING(70, 退款中); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } // 定义允许的流转当前状态 - 可流转到的状态集合 public static boolean canTransfer(OrderStatus from, OrderStatus to) { switch (from) { case WAIT_PAY: return to PAID || to CANCELLED; case PAID: return to ACCEPTED || to REFUNDING; case ACCEPTED: return to IN_SERVICE || to REFUNDING; case IN_SERVICE: return to FINISHED; case REFUNDING: return to CANCELLED; default: return false; } } }canTransfer方法里把每个状态能去的下一步写死业务层在更新订单前先调这个方法不通过就直接抛异常。这样即使前端传了奇怪的状态值后端也不会把订单改成非法状态。参数上from和to都必须是枚举实例不要用整数直接比较否则以后加状态容易漏改。3.2 支付回调里必须做的三件事微信支付回调是另一个高频翻车点。回调接口收到通知后不管业务处理成功还是失败都要给微信返回正确的应答否则微信会一直重试。我一般会在回调里做三件事验签、更新订单状态、写日志。PostMapping(/api/pay/notify) public String payNotify(RequestBody String body, HttpServletRequest request) { // 1. 验签确认是微信发来的 if (!wechatPayService.verifySignature(request)) { return FAIL; } // 2. 解析通知拿到 out_trade_no 和 transaction_id PayNotify notify wechatPayService.parseNotify(body); // 3. 根据商户订单号查订单做幂等判断 Order order orderMapper.selectByOrderNo(notify.getOutTradeNo()); if (order null) { return SUCCESS; // 订单不存在也返回成功避免重复通知 } if (order.getStatus() OrderStatus.PAID.getCode()) { return SUCCESS; // 已经处理过直接返回 } // 4. 更新订单状态写日志 orderService.transferStatus(order.getId(), OrderStatus.PAID, 微信支付回调); return SUCCESS; }验签这一步不能省否则有人伪造回调就能白嫖订单。幂等判断也很重要微信可能重复通知同一笔支付。返回SUCCESS的时机是业务处理完之后如果业务抛异常应该返回FAIL让微信重试但要注意重试次数有限最好在本地记录失败原因。3.3 陪诊员接单接口的并发控制陪诊员接单时两个陪诊员同时点「接单」是可能的。如果只是简单查询订单状态再更新会出现超卖。常见做法是用数据库的行锁或者乐观锁。-- 乐观锁方式更新时带上版本号 UPDATE order SET status 30, companion_id #{companionId}, version version 1 WHERE id #{orderId} AND status 20 AND version #{version};执行后检查受影响行数如果为 0 说明订单已经被别人接走或者状态不对。version字段在订单表里初始为 0每次更新加一。这种方式比SELECT ... FOR UPDATE轻量适合并发不高的陪诊场景。如果平台陪诊员很多可以考虑用 Redis 分布式锁但会增加运维成本小团队用乐观锁就够了。4. 避坑与常见问题排查4.1 小程序端拿不到用户手机号现象调用getPhoneNumber后返回errMsg: fail或者解密出来是空。原因通常是按钮的open-type没写对或者用户拒绝授权。解决确认按钮是button open-typegetPhoneNumber bindgetphonenumberonGetPhone并且后端解密用的session_key是最新的。如果用户拒绝过需要引导用户去设置页重新授权不能反复弹窗。4.2 订单列表加载更多时重复数据现象微信小程序页面列表加载更多第二页出现了第一页的记录。原因一般是分页查询没有稳定排序MySQL 在LIMIT时如果ORDER BY的字段有重复值返回顺序可能变化。解决分页查询必须带一个唯一字段排序比如ORDER BY id DESC并且前端在追加数据前用订单 ID 去重。4.3 支付回调验签失败现象微信支付回调一直返回 FAIL日志里验签不通过。原因可能是证书路径不对、平台证书没更新或者请求体被框架提前读取导致验签用的原始字符串不一致。解决确认使用的是微信支付 v3 的验签方式请求体不要用RequestBody直接映射成对象先用String接收原始内容。证书文件放在项目外部目录不要打包进 jar。4.4 陪诊员端订阅消息收不到现象用户下单后陪诊员没有收到接单提醒。原因通常是订阅消息模板 ID 没配或者用户没有授权订阅。解决在小程序后台申请「新订单提醒」模板前端在陪诊员点击「开始接单」时调wx.requestSubscribeMessage请求授权。注意订阅消息是一次授权一次发送不能永久授权所以每次接单前都要重新请求。4.5 时间冲突检测漏判跨天时段现象陪诊员晚上 10 点到第二天早上 6 点的时段和第二天早上 5 点的订单没有判冲突。原因是用LocalDateTime比较时跨天的时间没有正确处理日期部分。解决service_slot表里start_time和end_time必须存完整的日期时间不要只存time类型。冲突检测方法里用isBefore和isAfter比较完整时间戳不要只比较小时。5. 把源码跑起来之后我习惯先做这三件事第一件事是造一批测试数据。不要手动一条条插写一个 SQL 脚本生成 10 个陪诊员、每人 7 天、每天 4 个时段再生成 50 个患者账号。这样跑订单流程时不会因为数据太少而看不出分页和冲突检测的问题。-- 生成陪诊员时段示例实际用存储过程或脚本循环 INSERT INTO service_slot (companion_id, start_time, end_time, price) VALUES (1, 2025-06-01 08:00:00, 2025-06-01 12:00:00, 200.00), (1, 2025-06-01 13:00:00, 2025-06-01 17:00:00, 200.00), (2, 2025-06-01 08:00:00, 2025-06-01 12:00:00, 180.00);第二件事是用 Postman 或 curl 把订单状态机完整走一遍创建订单、模拟支付回调、接单、开始服务、完成、评价。每一步都检查order_log表里有没有对应记录。如果某一步状态没变先看日志表再看canTransfer的返回值。第三件事是压一下冲突检测接口。用 JMeter 或者简单的并发脚本让两个线程同时给同一个陪诊员的同一个时段下单看是否只有一个成功。如果两个都成功说明乐观锁或者冲突检测没生效回去检查version字段和事务边界。我自己的习惯是每次改完订单状态相关的代码都会把状态流转图打印出来贴在显示器边上改完对着图走一遍。这个习惯帮我省了很多次回滚。陪诊预约平台的技术难度不算高但业务状态多细节没对齐就容易出玄学问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表