简介这份源码资源面向Java后端开发者、微信小程序学习者及医疗信息化方向的学生与开发者提供一套完整的陪诊服务预约平台实现方案可用于课程设计、毕业设计或二次开发参考。项目以Java作为后端语言结合微信小程序前端技术覆盖预约陪诊、住院陪护、待办取药、待办挂号、检查结果领取等核心场景并包含服务展示、时间选择、预约完成、历史订单查看与预约状态跟踪等模块。压缩包共518个文件约9.42MB其中119个JavaScript文件负责小程序逻辑106个wxss与88个wxml文件完成样式与页面结构78个JSON文件用于全局配置77个Java源文件承担后端业务处理另有36张PNG图片及少量xml、yml、sql、docx等辅助文件目录结构清晰。已有192人学习下载适合希望理解小程序与Java后端联调、掌握预约类业务完整实现思路的读者参考。1. 陪诊预约平台为什么值得用小程序 Java 重做一遍陪诊这件事真正跑起来之后你会发现难点从来不在“预约”两个字而在预约前后那一堆琐碎状态谁下单、陪谁、几点到、去哪个院区、临时改期怎么算、服务完成后钱怎么结。很多团队第一版用表单工具或者通用预约系统凑合单量一上来就崩——改期没人通知、订单状态对不上、陪诊员和患者互相找不到人。基于微信小程序的 Java 陪诊服务预约平台本质是把“下单—派单—履约—结算”这条链路做成一个可追踪的状态机小程序负责触达和轻交互Java 后端负责订单、排期和权限。它适合两类人一类是接课程设计或毕设、需要一套能跑通全流程的源码参考另一类是真想做本地陪诊撮合、想先低成本验证供需的从业者。下面我按自己搭这类系统的顺序把选型、表结构、接口和踩过的坑讲清楚。2. 技术选型与整体架构小程序端和 Java 后端怎么分工2.1 为什么端上用微信小程序而不是 App陪诊的用户画像很集中中老年患者家属、异地就医的人、临时需要搭把手的人。这类人不会为了一个低频服务去装 App但几乎人人有微信。小程序即用即走分享一个卡片就能让家属帮老人下单这是 App 做不到的触达效率。从开发成本看一套小程序代码同时覆盖 iOS 和 Android省掉双端适配对个人开发者和小团队是实打实的减负。端上我一般只放三类逻辑登录鉴权、表单提交、订单状态展示。不要把排期计算、价格计算放到前端这些必须由后端算否则改个价、调个时段就得发版。小程序端用原生框架就够页面不多的话没必要上 uniapp 这类跨端方案——跨端在需要调用原生能力时反而多一层坑陪诊场景用不到那么重的跨端能力。2.2 后端为什么选 Java 而不是 Node 或 Python订单、支付、排期这类业务对事务一致性要求高Java 生态在这块最成熟。Spring Boot 起步快MyBatis 或 MyBatis-Plus 处理订单这种多表关联查询很顺手遇到并发下单、库存陪诊员时段扣减时Java 的锁和事务控制手段也更清晰。如果团队后面要接对账、发票、短信通知Java 的第三方 SDK 覆盖也最全。技术栈我通常这样定层次选型说明小程序端原生小程序框架页面少不引入跨端后端框架Spring Boot 2.7.x稳定社区资料多持久层MyBatis-Plus单表 CRUD 省代码数据库MySQL 8.0事务、索引成熟缓存Redis存登录态、时段锁鉴权JWT无状态适合小程序2.3 一套能跑通的最小目录结构后端按职责分包别把所有 Controller 堆一起。我一般这样分peizhen-server/ ├── controller/ # 对外接口只做参数校验和转发 ├── service/ # 业务逻辑订单状态机在这里 ├── mapper/ # 数据访问 ├── entity/ # 数据库映射对象 ├── dto/ # 前端传入参数 ├── vo/ # 返回给前端的视图对象 ├── config/ # 拦截器、跨域、Redis 配置 └── common/ # 统一返回体、异常、常量这样分的好处是订单状态流转全部收敛在 service 层排查“订单为什么卡在待接单”时只看一个包。小程序端按页面分目录pages/order、pages/companion、pages/user各管各的公共请求封装在utils/request.js。3. 数据库表设计与订单状态机陪诊平台的地基3.1 五张核心表怎么定陪诊平台看着功能多核心就五张表用户、陪诊员、服务项目、订单、排期时段。表设计错了后面全是补丁。下面是我常用的字段结构重点看订单表和排期表。-- 用户表 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信openid唯一标识, nickname VARCHAR(64), phone VARCHAR(20) COMMENT 联系电话下单必填, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_openid (openid) ) COMMENT用户表; -- 陪诊员表 CREATE TABLE companion ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, phone VARCHAR(20) NOT NULL, id_card VARCHAR(32) COMMENT 实名信息脱敏存储, status TINYINT DEFAULT 1 COMMENT 1可接单 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT陪诊员表; -- 服务项目表 CREATE TABLE service_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 如半天陪诊、全天陪诊, price DECIMAL(10,2) NOT NULL, duration_hours INT NOT NULL COMMENT 服务时长, status TINYINT DEFAULT 1 ) COMMENT服务项目表; -- 排期时段表陪诊员某天某时段可接单 CREATE TABLE schedule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, companion_id BIGINT NOT NULL, work_date DATE NOT NULL, time_slot VARCHAR(16) NOT NULL COMMENT 如 08:00-12:00, booked TINYINT DEFAULT 0 COMMENT 0未订 1已订, UNIQUE KEY uk_schedule (companion_id,work_date,time_slot) ) COMMENT排期时段表; -- 订单表 CREATE TABLE order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 业务订单号, user_id BIGINT NOT NULL, companion_id BIGINT COMMENT 接单后写入, service_item_id BIGINT NOT NULL, schedule_id BIGINT NOT NULL COMMENT 锁定的时段, hospital VARCHAR(128) COMMENT 就诊医院, patient_name VARCHAR(32), amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 见状态机, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no), KEY idx_user (user_id), KEY idx_companion (companion_id) ) COMMENT订单表;schedule表上的唯一索引是关键它保证同一个陪诊员同一天同一时段不会被重复预订这是防超卖的第一道闸。订单表里schedule_id直接指向锁定的时段而不是存一个时间字符串这样改期时只需换schedule_id不用解析文本。3.2 订单状态机把“改期、取消、完成”画清楚订单状态用数字枚举别用字符串比较和索引都更快。我一般定这几个状态状态值含义可流转到0待支付1、51待接单2、52已接单/待服务3、53服务中44已完成-5已取消-状态流转必须写在 service 层并且每次变更都校验“当前状态是否允许流转到目标状态”。我见过太多项目直接在 Controller 里setStatus结果出现“已完成的订单又被取消”这种脏数据。正确做法是抽一个OrderStateMachine类把合法流转写成映射表任何状态变更都走它。public class OrderStateMachine { // 合法流转key当前状态 - 允许的目标状态集合 private static final MapInteger, SetInteger ALLOW Map.of( 0, Set.of(1, 5), 1, Set.of(2, 5), 2, Set.of(3, 5), 3, Set.of(4) ); public static void check(int from, int to) { SetInteger allow ALLOW.get(from); if (allow null || !allow.contains(to)) { throw new BizException(订单状态不允许从 from 变到 to); } } }这段逻辑说明ALLOW用不可变 Map 定义避免运行时被误改check在每次状态变更前调用不合法直接抛业务异常。参数from是数据库里读出来的当前状态to是本次要改成的状态绝不能信任前端传来的状态值。3.3 下单时怎么锁定时段下单的核心是“锁时段 建订单”两步必须在一个事务里。先用UPDATE schedule SET booked1 WHERE id? AND booked0看影响行数是否为 1为 0 说明被别人抢先直接返回“该时段已被预订”。这一步靠数据库行锁保证原子性比先查再改可靠得多。Transactional public String createOrder(CreateOrderDTO dto, Long userId) { // 1. 原子锁定排期时段 int rows scheduleMapper.lockSlot(dto.getScheduleId()); if (rows 0) { throw new BizException(该时段已被预订请换一个); } // 2. 生成订单号并落库 String orderNo PZ System.currentTimeMillis() String.format(%04d, new Random().nextInt(10000)); Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setScheduleId(dto.getScheduleId()); order.setServiceItemId(dto.getServiceItemId()); order.setAmount(dto.getAmount()); order.setStatus(0); // 待支付 orderMapper.insert(order); return orderNo; }lockSlot对应的 SQL 是UPDATE schedule SET booked1 WHERE id#{id} AND booked0。参数scheduleId来自前端选择的具体时段userId从 JWT 里解析不从前端传防止伪造。订单号用时间戳加随机数够用且不依赖额外发号器。4. 小程序端与 Java 接口联调登录、下单、订单列表怎么打通4.1 微信登录换 JWT 的完整链路小程序端调wx.login拿到临时 code传给后端后端拿 code 去微信接口换 openid再签发 JWT 返回。整个过程前端不接触 openid只存 token。// 小程序端登录并保存 token wx.login({ success(res) { wx.request({ url: https://your-domain/api/auth/login, method: POST, data: { code: res.code }, success(r) { // 后端返回 { token, userId } wx.setStorageSync(token, r.data.token); } }); } });后端处理逻辑用 code 调微信jscode2session接口拿到 openid 后查user表没有就插入新用户然后生成 JWT。JWT 里放userId有效期设 7 天小程序端每次请求在 header 里带Authorization: Bearer token。注意 code 只能用一次且有效期很短所以换 openid 的请求要放在登录接口里同步完成不要异步延迟。4.2 下单接口的参数校验与防重下单接口最容易出问题的是重复提交。用户手抖点两下就生成两笔订单。解决办法是在前端按钮加 loading 禁用同时后端做幂等用userId scheduleId做唯一约束或者用 Redis 存一个短期的提交锁。// 下单前先抢 Redis 锁key 用 userIdscheduleId String lockKey order:lock: userId : dto.getScheduleId(); Boolean ok redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (Boolean.FALSE.equals(ok)) { throw new BizException(请勿重复提交); } try { return orderService.createOrder(dto, userId); } finally { redisTemplate.delete(lockKey); }setIfAbsent对应 Redis 的 SETNX10 秒过期是防止用户操作失败后锁一直不释放。参数lockKey把用户和时段绑在一起不同用户订同一时段互不影响同一用户重复提交才会被拦。这里要注意锁只是防重复真正的时段占用还是靠数据库那步UPDATE两者不能互相替代。4.3 订单列表的分页与状态筛选订单列表要支持按状态筛选用户最常看的是“待服务”和“已完成”。接口用分页别一次全查。GetMapping(/orders) public PageResultOrderVO list(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, RequestParam(required false) Integer status, RequestParam Long userId) { PageOrder p new Page(page, size); LambdaQueryWrapperOrder qw new LambdaQueryWrapper(); qw.eq(Order::getUserId, userId); if (status ! null) { qw.eq(Order::getStatus, status); } qw.orderByDesc(Order::getCreateTime); return PageResult.of(orderMapper.selectPage(p, qw)); }参数page和size控制分页status为空时查全部。userId必须从 token 解析后覆盖不能直接信任前端传的值否则用户能查到别人的订单。orderByDesc按创建时间倒序最新的订单排最前符合用户预期。5. 避坑与排查陪诊预约平台最容易翻车的五个地方5.1 时段超卖两个人订到同一个陪诊员现象是同一陪诊员同一时段出现两笔有效订单。原因多半是先SELECT查booked0再UPDATE两步之间被另一个请求插进来。解决就是前面说的原子UPDATE ... WHERE booked0靠影响行数判断不要先查后改。如果已经出现脏数据加一条schedule的唯一索引能挡住后续重复写入。5.2 订单状态乱跳已完成的单被取消现象是用户投诉“服务都做完了订单还能取消”。原因是状态变更没走校验任何地方都能setStatus。解决是强制所有状态变更走OrderStateMachine.check并在数据库层加触发器或应用层统一入口。排查时直接搜代码里所有setStatus调用点逐个确认是否经过校验。5.3 小程序登录态失效token 过期没提示现象是用户操作到一半突然所有接口报 401页面白屏。原因是 JWT 过期后前端没处理。解决是在request.js里统一拦截 401清掉本地 token 并跳回登录页同时给用户一个“登录已过期请重新进入”的提示。别让用户对着白屏发呆。5.4 改期后原时段没释放现象是用户改期成功但原来的时段还显示“已预订”别人订不了。原因是改期逻辑只更新了订单的schedule_id忘了把旧schedule的booked置回 0。解决是把“释放旧时段 锁定新时段 更新订单”放进同一个事务任何一步失败整体回滚。排查时对比订单的schedule_id和schedule表的booked状态是否一致。5.5 陪诊员端看不到新订单现象是用户下单成功陪诊员端列表里没有。原因通常是派单逻辑写成了“用户下单时直接指定陪诊员”但实际是抢单模式陪诊员端查的是status1且companion_id is null的订单。解决是明确派单模式抢单就查待接单池派单就在下单时写入companion_id。两种模式不要混用否则订单会“消失”。排查时先看订单的status和companion_id两个字段的实际值。6. 让这套源码真正跑起来本地联调与压测的两个技巧把项目跑起来只是第一步能稳定扛住并发下单才算数。我一般会做两件事本地用内网穿透把后端暴露给真机调试以及用 JMeter 压下单接口。真机调试时小程序开发者工具能模拟请求但支付、定位这些能力必须真机验证。后端跑在本地8080用内网穿透工具映射一个临时域名填到小程序的request合法域名里。注意开发阶段可以在开发者工具里勾选“不校验合法域名”但真机预览必须配好域名否则请求直接被拦。这一步的坑在于穿透域名每次重启会变建议固定一个二级域名省得反复改配置。压测下单接口时重点看两个指标时段锁的失败率和数据库连接池等待时间。用 JMeter 开 50 个线程同时打同一个scheduleId预期是只有 1 个成功、49 个返回“已被预订”。如果成功数大于 1说明锁没生效回去检查UPDATE语句的WHERE条件。连接池等待时间如果飙升把 HikariCP 的maximumPoolSize从默认 10 调到 20 再测但别盲目调大先确认慢查询。# JMeter 命令行压测示例 jmeter -n -t order_test.jmx -l result.jtl -e -o report-n表示非 GUI 模式-t指定测试计划-l输出结果文件-e -o生成 HTML 报告。压测前把测试计划的线程组设为 50 并发、循环 1 次断言里加上“响应包含已被预订”的检查这样报告里能直接看出锁是否生效。最后说个我自己的习惯每次改完订单状态相关的代码我都会手动走一遍“下单—接单—改期—完成”全流程并且故意在改期那步断网重试看会不会产生脏数据。这个笨办法帮我拦下过好几次状态不一致的问题。陪诊平台的核心不是页面多好看而是每一笔订单的状态都能对得上、查得到。希望帮到你。本文还有配套的精品资源点击获取