SpringBoot+微信小程序:乡村医疗上门服务预约平台设计与实践
每年春节回老家总能碰到村里老人感冒发烧、换药不便子女在外地干着急的场景。我自己也是做Java后端出身见过太多“挂号去大医院”对乡村老人并不友好真正需要的是把基础医疗服务送到家门口。所以围绕“预约上门”这个场景我基于 Java SpringBoot 做了一套后端服务再配合微信小程序端完成了这个乡村医疗上门服务预约平台。整套内容包括源码、设计文档、运行视频和讲解视频适合准备做全栈项目、毕业设计或者想快速理解“小程序SpringBootMySQLRedis”这组合怎么落地的人。这套项目表面上是个预约系统做起来才发现里面全是细节医生排班怎么拆、订单状态怎么流转、怎么防止同一个时间段被重复约走、上门地址要不要存经纬度、后台审核怎么审批医生资质每一块都值得单独说清楚。下面我把整个项目的设计和实现思路完整拆开讲一遍。1. 项目定位与需求拆解很多人看到“乡村医疗上门服务预约平台”第一反应是“这不就是个家政预约吗”真做起来发现完全不是一回事。家政预约约的是时间和人医疗预约约的是信任和风险。乡村场景又叠加了老人不会用手机、信号不稳定、子女代下单、村医身兼数职这些特殊情况所以需求拆解必须先从场景出发。1.1 这个项目到底解决了什么痛点先说乡村就医的痛点。乡村卫生室覆盖的自然村范围广医生经常要骑着电动车入户但入户的时间往往由村民临时电话决定医生自己的路线安排被打得乱七八糟。很多慢病老人需要定期量血压、换导尿管、做康复理疗哪怕只是简单上门打针也需要有人对接时间、核对项目、留存记录。市面上大而全的线上问诊平台下探不到县域以下因为它们的模型是“医生在线接诊”解决不了上门履约的问题。这个项目要解决的是一个轻量但完整的预约闭环村民或子女在小程序里选服务项目、选医生、选时段、填地址医生在小程序端接单按预约地址上门服务服务完成之后订单进入可评价状态。整套流程带着“服务履约”属性而不是单纯的信息展示。1.2 用户、医生、管理员三端需求拆解整个系统涉及小程序用户端、医护人员端、后台管理端三个角色我把需求按角色拆成了三层。用户端浏览服务项目、查看可约医生、选择预约时段、填写患者信息和上门地址、支付预约费用、查看订单进度、取消订单、服务完成后评价。医护人员端维护个人信息和服务项目、配置自己的排班和可接单时段、接收新预约提醒、确认接单或拒单、标记开始上门、确认完成服务。管理后台审核医护人员的注册资质、管理服务项目和价格、查看所有订单流水、处理纠纷退款、查看业务统计报表。核心业务流程是用户下单 - 系统锁定医生排班库存 - 医生接单 - 上门服务 - 完成订单 - 评价。这套流程前后端所有设计都围着它转数据库的每一张表也都是这条流程上的一个节点。2. 技术选型为什么是SpringBoot微信小程序项目标题定了“Java SpringBoot 微信小程序”这组合不是随便选的。不是所有系统都适合上微服务这种项目用一个单体后端加一个小程序端就是最高性价比的方案。2.1 后端选型的底层逻辑SpringBoot 是目前 Java 后端最主流的 Web 框架选它核心理由是生态成熟、资料多、招聘要求里出现频率高。如果项目要拿来做学习或毕设SpringBoot 是最稳的选择遇到任何问题都能搜到解决方案。我当时落地用的是 SpringBoot 2.7.x 搭配 JDK 8。可能有人会问SpringBoot 3 都发布这么久了为什么不用新版因为很多生产环境服务器还跑着老 JDKJDK 8 的兼容性是最稳的。SpringBoot 3 必须 JDK 17 起步且javax包要换成jakarta对刚接触项目的同学来说这些迁移成本并不值得。持久层我选了 MyBatis-Plus 而不是 JPA。原因是预约系统里大量场景是动态条件查询按时间查排班、按状态查订单、按医生查评价MyBatis-Plus 的条件构造器写起来非常直观复杂 SQL 又能自己控制。JPA 虽然自动建表很方便但复杂查询一旦失控排查成本很高。缓存和分布式锁用了 Redis。这个项目里 Redis 至少承担两个职责缓存首页高频访问的服务项目列表和医生列表处理订单防重和排班时段的并发控制避免同一时段被约超。如果不想依赖外部中间件用 MySQL 的乐观锁也能做降级但加上 Redis 整个项目的技术含量和扩展性会明显上一个台阶。2.2 小程序端的三种实现路线对比微信小程序端落地有三条路线原生小程序、uni-app、Taro。原生小程序是微信官方方案API 覆盖最全调试最方便不需要转译层遇到平台兼容问题的概率最低。对只上微信一个渠道的项目原生是最好的选择。uni-app 的优势是一套代码多端发布以后想发支付宝小程序和 App 很方便但踩坑成本也高很多跨端兼容问题会浪费大量调试时间。Taro 是 React 语法体系适合有 React 基础的同学但生态相对小众。我最终选了原生小程序只依赖微信官方组件和 API。项目里的代码结构足够清晰能看懂wx.request就能看懂整个前端项目。等以后真要扩展多端再用 uni-app 重写也不迟。2.3 整体架构与工程结构规划系统采用前后端分离架构小程序端通过 HTTPS 调用后端 RESTful API后端按照 Controller - Service - Mapper 三层结构组织鉴权使用 JWT Token数据存储使用 MySQL热点数据缓存使用 Redis。后端工程目录大致规划成controller接收请求、参数校验、返回统一结果对象service业务逻辑层预约下单、接单流转都在这一层做事务控制mapperMyBatis-Plus 的数据访问层写 SQL 和自定义查询entity数据库表对应的实体类config拦截器、Redis 配置、跨域配置、微信小程序配置common统一返回结构、异常处理、工具类job定时任务处理超时未接单的订单自动释放小程序端目录按页面功能拆分首页、服务列表、医生列表、预约确认、订单列表、订单详情、个人中心、医生工作台。分包加载的页面可以后置保证主包体积在微信限制的 2MB 以内这一点在小程序上线时非常关键。3. 数据库设计预约系统的命根子预约类系统的核心不在代码在数据库表结构和事务控制。表设计一旦有问题后面写再多代码都是补救。这个项目我设计了 8 张核心表每张表的字段都从业务流程倒推出来。3.1 核心数据表结构与字段说明用户表t_user统一存放用户端和医生端账号用role字段区分身份。字段类型说明idbigint主键openidvarchar(64)微信 openid唯一nick_namevarchar(50)昵称avatarvarchar(255)头像 URLphonevarchar(20)手机号roletinyint1 用户 2 医生 3 管理员statustinyint账号状态0 禁用 1 正常create_timedatetime注册时间医生信息表t_doctor_info保存医生的执业信息和t_user一对一关联。user_id关联用户 IDreal_name、cert_no实名信息后台审核用title职称如医师、主管护师org_name所属卫生室/机构intro个人简介service_scope可上门服务的地理范围描述audit_status0 待审核1 审核通过2 审核驳回服务项目表t_service_item定义可预约的服务内容service_name服务名称比如上门换药、慢病随访duration预计服务时长分钟price基础服务费description项目说明和注意事项status上下架状态排班表t_doctor_schedule是后面并发控制的核心CREATE TABLE t_doctor_schedule ( id bigint NOT NULL AUTO_INCREMENT, doctor_id bigint NOT NULL COMMENT 医生ID, work_date date NOT NULL COMMENT 服务日期, start_time time NOT NULL COMMENT 时段开始, end_time time NOT NULL COMMENT 时段结束, max_orders int NOT NULL DEFAULT 1 COMMENT 该时段最大接单数, booked_orders int NOT NULL DEFAULT 0 COMMENT 已预约数, status tinyint NOT NULL DEFAULT 1 COMMENT 1可约 0停用, PRIMARY KEY (id), KEY idx_doctor_date (doctor_id, work_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;预约订单表t_appointment_order是整个系统数据量增长最快的表也是查询压力最大的表order_no订单号全局唯一用于用户端显示和后台检索patient_name、patient_phone实际服务对象信息注意不一定是下单人本人address上门详细地址latitude、longitude上门位置经纬度为后续地图轨迹和距离计算预留doctor_id、schedule_id关联医生和排班时段service_item_id关联服务项目同时冗余一个下单时的服务名称和价格快照status订单状态机字段pay_status支付状态0 未支付 1 已支付 2 已退款cancel_reason取消原因create_time、update_time创建和更新时间评价表t_order_evaluation记录服务完成后的反馈order_id关联订单user_id、doctor_id双方 IDscore评分 1-5content文字评价create_time评价时间3.2 排班为什么按时隙而不按具体时间点我见过一些项目把排班设计成“某医生某天 08:00 到 18:00 可约”然后用户随便选几点医生拿到订单后发现时间根本对不上。这个项目把一天拆成固定时隙比如上午 08:00-10:00、10:00-12:00下午 14:00-16:00、16:00-18:00。医生先定义哪天开启排班系统自动生成该天的可用时隙每个时隙有独立的库存字段。这样设计的原因有三个一是上门服务本身就有路程时间时隙颗粒度必须大于单次服务时长二是时隙库存可以精确控制一个时隙最多接 N 单避免医生忙不过来三是后台排班界面可以复用一个日历组件按天生成时隙运营成本极低。下单时前端把schedule_id一起提交后端先校验该时隙未被约满再在事务内做库存扣减。这里所有操作都落到一行记录上天然适合用行锁解决并发。3.3 订单状态机设计订单状态必须用明确的字典值定义不能在代码里用中文散落判断。我定义的状态流如下状态码含义说明0待支付用户提交预约但未支付1待接单支付成功等医生确认2已接单医生接单准备上门3服务中医生已开始服务4已完成服务完成可评价5已取消用户或后台取消6已退款取消且退款完成状态流转有几条硬规则待支付状态超时 30 分钟自动取消释放库存待接单状态如果医生超时未处理可以是系统自动取消也可以是转给其他医生我采用的是“超时后自动取消并原路退款同时释放排班库存”这样最安全。所有涉及状态变更的操作代码里都要校验原状态非法流转直接抛业务异常。4. 关键功能落地登录、预约、接单、履约数据库设计定完就等于把骨架搭好了。真正写功能逻辑时有四个点是最容易出问题也最体现项目质量的微信登录鉴权、预约下单并发控制、接单状态流转、评价履约闭环。4.1 微信登录与JWT无状态鉴权小程序端通过wx.login()拿到临时code后端拿着 code 调用微信接口换openid。需要注意这个 code 5 分钟有效且只能用一次。获取 openid 后如果用户表里查不到记录就自动注册一个账号查得到就直接登录。登录成功之后后端签发一个自定义 JWT Token前端把 token 存到wx.setStorageSync里之后每次请求在 Header 里带Authorization: Bearer token。后端用一个拦截器统一校验 token解析出 userId 后放到请求上下文里。这里不用传统 Session是因为小程序和后端之间天然是前后端分离结构JWT 无状态多实例部署也能共享。核心伪代码逻辑大致是这样MapString, String params new HashMap(); params.put(appid, wxAppId); params.put(secret, wxSecret); params.put(js_code, code); params.put(grant_type, authorization_code); String result restTemplate.getForObject(WX_SESSION_URL, String.class, params); JSONObject json JSONObject.parseObject(result); String openid json.getString(openid); User user userMapper.selectOne(new LambdaQueryWrapperUser() .eq(User::getOpenid, openid)); if (user null) { user registerNewUser(openid); } String token JwtUtil.createToken(user.getId(), user.getRole()); return Result.ok(token);有个实操细节手机号绑定不要用wx.getUserProfile拿手机号微信对用户头像昵称的获取早就有调整直接引导用户在小程序里手动填写手机号或者用getPhoneNumber按钮能力。开发调试阶段可以在后端预留一个测试登录接口传入模拟 openid方便本地跑通全流程。4.2 预约下单的并发控制预约下单是整个系统最核心的一个事务里面要做四件事校验用户登录和订单参数、校验排班时隙存在且可约、扣减排班库存、生成预约订单记录。最稳妥的写法是直接在排班表上做条件更新UPDATE t_doctor_schedule SET booked_orders booked_orders 1 WHERE id #{scheduleId} AND booked_orders max_orders AND status 1;用 MyBatis-Plus 执行完这个更新后如果update影响行数为 0说明该时段已被约满或者排班停用直接抛出“所选时段已约满”的业务异常。这个写法利用的是 MySQL 行锁天然防止两个用户同时约同一个时段出现超卖。必须保证排班行的更新和订单插入在同一个数据库事务里否则会出现库存扣了但订单没生成的情况。下单接口还需要处理幂等。小程序端网络不稳定时用户可能连点两次提交后端在入口处可以用 Redis 的SETNX加一个短时间锁key 设计成order:submit:{userId}过期时间设 3 到 5 秒。第二次请求直接提示“正在提交请勿重复操作”体验上也更友好。4.3 医生接单与状态流转联动医生端工作台的入口是待接单列表。医生点接单时接口要做的事校验当前登录人的医生身份、校验订单状态仍为待接单、把订单状态改成已接单、把医生 ID 冗余写入订单表、给用户端推送一条模板消息。这里容易犯的错误是直接用UPDATE t_appointment_order SET status 2 WHERE id ?不校验旧状态。状态机设计讲究的是“原状态到目标状态合法才允许更新”标准写法应该是UPDATE t_appointment_order SET status 2, doctor_id #{doctorId}, update_time NOW() WHERE id #{orderId} AND status 1;影响行数如果是 0说明订单已经被其他医生接走或者状态变了不能盲目提示接单成功。这个套路和库存扣减是同一个思路先条件更新再判断影响行数而不是先查出来再更新。医生上门服务完成后点击“完成服务”同样用条件更新把订单从服务中推到已完成。只有已完成状态的订单才允许用户评价后台也才能看到完整闭环的数据。4.4 评价闭环与预警设计服务完成后的评价不该是摆设。我在评价表里存了评分和内容用户提交后同步更新到t_doctor_info表里的累计评分字段后台医生列表按评分排序展示。医生评分高曝光就靠前这能激励医生把服务做好。评价触发时机也要控制用户只能在订单已完成状态下评价一次。接口层面我会加一个t_order_evaluation表order_id唯一索引即使前端连点两次提交数据库唯一索引也能兜底。评价拉低到 1-2 分时可以给后台管理员加一个待处理标识方便人工回访了解情况。5. 避坑清单这些坑我替你先踩了整个项目从开发到整理交付踩过的坑比写代码的时间还多。下面这些不解决项目跑不起来或者跑起来也是表面通顺、细看全是问题。5.1 小程序审核与医疗类目合规微信小程序对医疗相关内容审核极严。如果走正常上架类目选择会涉及医疗相关类目需要提供资质文件如《医疗机构执业许可证》。个人主体基本无法开通这类资质。对于学习、演示、毕设场景最务实的方法是不要走正式上架用体验版在开发者工具里运行把体验二维码发给导师或同学看。演示的时候要说明“业务逻辑完整、接口真实”千万别在简介里写医疗服务的宣传话术避免因为平台规则问题被限制。5.2 开发联调中的合法域名问题小程序开发工具里wx.request请求本机http://localhost:8080时经常报 “url not in domain list”。解决办法是在 开发者工具 详情设置里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。真正要上线时必须在微信公众平台后台配置 request 合法域名且必须是 HTTPS 并备案。如果服务器只有 HTTP 证书或者域名没备案这一步会卡死。演示项目走本地联调完全不担心。还有一个容易忽略的点真机预览时localhost指向的是手机自身不是电脑。手机和电脑必须在同一局域网后端地址要改成电脑的局域网 IP比如http://192.168.x.x:8080。这个细节对第一次做小程序联调的同学基本上是必踩坑。5.3 订单超时、重复预约等状态一致性村民约了医生结果一直没支付排班库存一直被占着这是最大的隐患。我的处理是待支付订单设置 30 分钟超时用一个定时任务每分钟扫描一次过期订单把状态改成已取消同时释放对应的排班库存。定时任务逻辑不复杂但要小心两个问题一是只处理“创建时间早于 30 分钟前且状态为待支付”的订单绝不能误伤已支付订单二是释放库存是booked_orders - 1必须用条件更新保证库存不会变负数。写成 SQL 就是SET booked_orders GREATEST(booked_orders - 1, 0)保证不会出现负库存。医生端超时未接单也同理我把“待接单超时”阈值设成 2 小时。这类场景多就要在代码里统一封装一个“释放库存”的服务方法订单取消和超时取消都调用同一个方法逻辑不会分散。5.4 代码层面的几个改进建议第一不要把所有校验都写在 Controller 里。我在项目里用了一个全局异常处理器所有业务异常都抛出BusinessException统一返回{ code: 500, msg: 具体原因 }由前端弹出提示。这样 Controller 非常薄只做参数包装业务规则都落在 Service。第二统一返回结构。后端所有接口返回格式固定成 Result 对象包含code、data、msg三个字段。小程序端封装一个request公共方法统一处理 401 Token 失效跳登录、500 业务报错弹提示。没有统一封装的话每个页面写一遍错误弹窗项目会迅速失控。第三数据库时间字段全用datetime不要用timestamp存订单时间。原因很简单一张订单表的数据量上来后范围查询非常依赖时间字段索引datetime在可读性和时区处理上都更直白。存储门铃人地址也要用utf8mb4不要用utf8mb3不然村民地址里的生僻字会变成问号。6. 从源码到落地交付内容与实际应用价值拿到整套源码、文档、运行视频和讲解视频之后怎么把它用起来可能是很多人最关心的。我按使用场景拆开说。6.1 这套项目能拿来做什么如果你是要完成毕业设计这套项目覆盖了 Java 后端、小程序前端、数据库设计、Redis 缓存、定时任务、微信登录等热门知识点答辩时可以从业务闭环、并发控制、权限校验几个角度展开几乎每一个点都能讲出设计理由而不是只会说“这个功能实现了”。如果你是想入门全栈开发跟着运行视频把项目跑起来再对着讲解视频理解代码结构是最快的上手路径。别急着改代码先把一条预约主流程走通用户注册登录 - 浏览医生 - 下单支付 - 医生接单 - 完成服务 - 评价。走通这一条链路整个项目的代码你就已经看懂了八成。如果你是在乡镇卫生院、社区卫生服务站这类机构做信息化这套源码可以作为原型参考。排班、预约、服务记录、评价反馈这些能力稍微改造就能支撑日常上门服务管理比如增加服务工单打印、费用报销字段、区域统计报表就是一套完整的内网服务管理工具。6.2 从教学项目到商业落地还差什么真实上线跑业务和做项目演示之间存在几个差距。支付要接入真实微信支付商户号需要企业主体资质而不是使用模拟支付按钮。医疗资质要合规上门服务的医生要完成身份核验和执业资质审核后台审核机制要改造得更严格。数据安全方面要增加隐私合规患者病历、地址等敏感信息需要脱敏存储日志访问要留痕。高可用方面要考虑服务器部署和备份不能像演示环境一样依赖本地数据库。这些差距不是代码复杂度的问题更多是运营和合规成本的问题。真要在乡镇推广需要地推人员帮助老年用户完成首次注册医生端也需要培训系统只是把管理链条理顺。6.3 我个人的一些实操体会做完整套系统我最大的体会是预约平台的技术难点不在“预约”本身而在“履约”的确定性。乡村医疗上门服务用户下单只是一个开始核心是医生能不能按时到、到了能不能找到门、服务完有没有记录可追溯。所以我的代码里订单地址字段存的是完整文本而不是只存一个社区名排班时隙直接决定医生一天能跑多少单后台订单列表永远优先看到待接单、待服务的单子因为那才是运营人员真正要盯的。最后分享一个小技巧开发这种前后端分离项目先别急着写页面。先把数据库表建好用接口调试工具把后端所有接口跑通再开始写小程序页面。页面写完后对接接口你会发现绝大多数问题都集中在字段名、时间格式和权限校验上定位非常快。反过来先写页面再建表改代码的时间和崩溃次数至少翻倍。这套工作流对做任何 SpringBoot 后端项目都适用。