
简介面向医疗信息化开发者的通用功能模块医疗小程序V5.9.4包含最新微信小程序前端与后端完整实现适用于需要快速搭建预约挂号、在线咨询、电子病历等医疗场景的项目团队或中高级开发者。压缩包共2000个文件约8.84MB核心以PHP后端文件为主辅以JavaScript交互脚本、WXML/WXSS小程序页面结构、JSON配置及PNG图标素材同时包含数据库、证书等资源整体结构完整可直接部署学习。已有132人浏览学习。从内容预览看内置基于AmazeUI、Bootstrap等前端框架的界面样式便于二次开发与功能扩展。该版本涵盖患者管理、医生排班、支付结算等通用模块并具备数据安全与应用集成考虑是理解医疗小程序全栈架构、进行功能迭代和平台适配的实用参考。1. 医疗小程序V5.9.4的通用功能模块不是demo而是能上线的骨架医疗类小程序和电商小程序最大的差别不在页面而在通用功能模块的边界。挂号、问诊、报告查询、支付、消息推送这些模块每家医院都要做但真正能复用的不是源码而是把「谁在用、数据归谁、状态怎么流转」定义清楚的那套约定。V5.9.4 这种迭代版本号说明前后端已经历过多轮发布页面结构与接口契约是绑定的。接下来按前端登录链路、后端模块拆分、医疗订单状态机、版本发布与备案四个方向展开把一套带最新小程序前端和后端的医疗项目讲清楚怎么理解、怎么跑通、怎么改成自己的业务。适合正在接手医疗小程序、或者准备从零搭建前后端分离团队的开发。文末给了可以直接用的冒烟命令改改域名就能验证整条链路。2. 小程序前端登录态、请求封装与动态标题先把通用层立住医疗小程序的前端高不高级不取决于用了多炫的组件库而取决于三个通用能力登录态能不能静默续期、请求层能不能统一处理业务错误码、页面标题能不能按业务动态设置。这三个能力在 V5.9.4 里通常已经被封装成公共目录业务页面只负责调用。拿到项目先读这三个文件比先看页面更有效率。2.1 微信小程序的登录链路code 换 token 与静默续期微信小程序的登录和网页端完全不同——没有密码而是通过wx.login()拿到临时 code再由后端调微信的code2Session接口换取 openid 和 session_key。session_key 不能下发到前端只留在后端会话里前端拿到的应该是一对自有 token。// app.js 中的登录入口 App({ onLaunch() { this.login() }, login() { wx.login({ success: (res) { if (res.code) { wx.request({ url: https://api.example.com/medical/auth/login, method: POST, data: { code: res.code }, success: (resp) { if (resp.data.code 0) { wx.setStorageSync(access_token, resp.data.data.accessToken) wx.setStorageSync(refresh_token, resp.data.data.refreshToken) } } }) } else { console.error(wx.login 失败, res.errMsg) } } }) } })代码说明wx.login()返回的 code 有效期只有五分钟且只能用一次后端拿到后必须立即调jscode2session换取 openid返回的accessToken存到本地存储refreshToken用于过期后的静默续期。参数上要注意 code 不能缓存每次登录流程必须重新获取否则后端会报 invalid code。注意不要把 session_key 或者 openid 直接存到前端 storage。openid 相当于用户在微信体系里的身份标识一旦泄露到前端配合后端接口可能被用来遍历用户数据医疗项目尤其要避免这类问题。2.1.1 refresh token 失效后的降级处理续期也会失败——用户超过三十天没打开小程序refresh_token 过期这时候前端不能卡在静默续期的死循环里。常见做法是清空本地 token、跳转到登录引导页、让用户重新走一遍授权流程。判断依据是用业务码区分场景比如约定 40101 表示 access_token 过期可续期40102 表示 refresh_token 过期必须重新登录两种场景的处理路径完全不同。2.2 通用 request 封装把业务错误码和 HTTP 状态码分开很多前端页面报错难排查根源是只处理了 HTTP 200 而没处理业务码。医疗小程序的通用做法是后端返回统一结构{code, msg, data}HTTP 层一律返回 200真正的错误由 code 表达。前端封装一层 request把「登录过期跳转、业务失败弹 toast、成功返回 data」统一掉。// utils/request.js —— 所有业务页面统一走这里 const BASE_URL https://api.example.com/medical function request(path, { method GET, data {}, needAuth true } {}) { return new Promise((resolve, reject) { const header { content-type: application/json } if (needAuth) { const token wx.getStorageSync(access_token) if (!token) { reject(new Error(未登录)) return } header[Authorization] Bearer token } wx.request({ url: BASE_URL path, method, data, header, success(res) { const body res.data if (body.code 0) { resolve(body.data) } else if (body.code 40101) { wx.removeStorageSync(access_token) wx.navigateTo({ url: /pages/login/index }) } else { wx.showToast({ title: body.msg || 系统繁忙, icon: none }) reject(body) } }, fail(err) { console.error(网络异常, err) reject(err) } }) }) } module.exports { request }参数说明BASE_URL在开发、测试、生产环境要能切换V5.9.4 这类工程一般放在config/env.js单独维护不写死在业务代码里needAuth参数用于登录接口本身避免请求头上带一个空 token 被拦截器误判Authorization用 Bearer 前缀是约定后端拦截器按这个格式解析。40101 的跳转要加一个全局标志位锁住否则多个接口同时返回 40101 时会出现连续跳转登录页的叠加路由问题。提示wx.request的域名必须在微信公众平台配置 request 合法域名否则真机会直接报 fail。医疗项目经常联调多个环境域名要提前加到白名单里每次新增子域名都要在后台同步。2.3 动态设置标题复用页面靠参数分流医疗小程序里预约挂号和图文问诊两个入口进的可能是同一个列表页通过路由参数区分业务场景标题也跟着参数走。微信原生提供了wx.setNavigationBarTitle在页面的 onLoad 里调用即可覆盖 json 配置的navigationBarTitleText。// pages/doctor/list.js —— 页面级动态标题 Page({ onLoad(query) { // query.deptType 由入口 url 传入例如 ?deptTyperegistration const titleMap { registration: 预约挂号, consultation: 图文问诊, report: 报告查询 } wx.setNavigationBarTitle({ title: titleMap[query.deptType] || 医疗服务 }) } })如果不调用这个 API所有入口共用一个页面时标题永远一样用户分不清自己从哪个入口进来客服排查问题也要多问一句你从哪儿点的。动态设置之后导航栏标题、分享卡片标题都保持一致页面栈返回时也能看清层级。这里的参数query.deptType是前端传参的典型用法——入口页通过 url 带参目标页按参数映射标题字段值建议用英文枚举而不是中文避免 URL 编码问题。2.4 原生微信小程序还是 uniapp医疗项目怎么选这个选择题在带前后端的项目里很现实。原生小程序性能好、开发者工具调试链路短但只服务微信一个平台uniapp 用 Vue 语法写一套代码可以编到微信、支付宝、字节等多个小程序端代价是部分设备能力蓝牙、NFC需要条件编译处理。医疗项目里如果涉及报告文件预览、设备读数这类场景原生调用更直接。维度原生微信小程序uniapp多平台支持仅微信微信、支付宝等多端组件库生态Vant Weapp、TDesignuView、uni-ui设备能力接入直接调用 API需条件编译或原生插件团队技能要求小程序语法Vue 语法加平台差异学习医疗场景如果确定只在微信里运营原生是稳妥选择如果后面要做支付宝医疗健康或抖音小程序uniapp 能省掉一整轮前端重写。判断标准就一条用户入口是不是只有微信。3. 后端工程Spring Boot 模块拆分、统一鉴权与跨域配置前端通用层立住之后后端才是通用功能模块的主战场。医疗小程序后端最常见的形态是 Spring Boot 单体加模块化目录而不是一上来就拆微服务。原因很实际预约、问诊、支付这些模块在大多数医院的并发量没那么高单体能保证事务一致性和排查效率部署也只需要一个 jar 包。3.1 后端工程结构与模块依赖方向V5.9.4 这类项目的后端一般分四层common统一返回体、异常、工具、auth登录鉴权、token 管理、business预约、问诊、报告等业务模块、infra数据库、Redis、文件存储配置。关键约束是依赖方向business 只能依赖 common 和 infra不能反向依赖也不允许 business 内部各模块互相依赖。依赖方向错了模块就失去复用的意义。// common/api/R.java —— 统一返回体所有 Controller 的出口 public class RT { private Integer code; private String msg; private T data; public static T RT ok(T data) { RT r new R(); r.code 0; r.msg success; r.data data; return r; } public static T RT fail(int code, String msg) { RT r new R(); r.code code; r.msg msg; return r; } // getter / setter 省略 }这段代码的用意code0 表示业务成功非 0 是业务错误码。前端在 request 层只判断 code所以后端 HTTP 状态码统一返回 200业务出错也返回 200配合第 2 章的 request 封装就能完整闭环。这样做的代价是排查问题时要看响应体而不是状态码用 curl 或浏览器 Network 面板都能看到完整结构。3.1.1 业务错误码的分段规划错误码要留够余量医疗场景建议按模块分段10000 段给用户模块20000 段给预约模块30000 段给支付和退款模块40000 段给报告查询。比如 20001 表示号源已被抢完20002 表示患者时间冲突20003 表示排班已停诊。不要把错误码编成连续的小数字后面加模块时容易撞号前端也要把常用错误码的文案集中管理而不是散落在每个页面的 catch 里。3.2 统一鉴权拦截器把 token 解析收敛到一个点通用功能模块里鉴权拦截器是最容易被复制粘贴出问题的地方。常见错误是每个 Controller 里都写一遍从 Header 取 token 的逻辑改密钥时要到处翻代码。正确做法是写一个HandlerInterceptor在 preHandle 里完成 token 解析把用户 ID 放进 request 属性业务层直接取不再重复解析。// auth/interceptor/AuthInterceptor.java Component public class AuthInterceptor implements HandlerInterceptor { Autowired private TokenService tokenService; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录和刷新 token 接口 if (request.getRequestURI().contains(/auth/)) { return true; } String auth request.getHeader(Authorization); if (auth null || !auth.startsWith(Bearer )) { throw new BizException(40101, 未登录或 token 缺失); } String token auth.substring(7); Long userId tokenService.parseToken(token); // 用户 ID 放进 request 属性业务层从这里取 request.setAttribute(currentUserId, userId); return true; } }说明/auth/路径做白名单放行避免登录接口自身被拦截token 解析失败时抛BizException由全局异常处理器统一转成R.fail(40101, msg)返回前端会走到第 2 章 request 封装的 40101 分支。把用户 ID 放进 request 属性而不是每次都查一遍 TokenService减少重复解析的开销。后端做项目的团队很多直接基于 RuoYi 脚手架改这类框架的核心价值就在拦截器、权限注解和代码生成器上——通用模块的通用就是把基座能力稳定住业务代码只写自己的 Service 和 Mapper。3.3 后端跨域配置小程序端其实不需要但调试工具需要一个容易误解的点微信小程序的 request 请求不经过浏览器同源策略所以理论上后端不需要配 CORS。但如果你用浏览器调试接口、或者项目里带一个 H5 管理后台跨域问题马上就会出现。医疗项目后端一般还是会把 CORS 配置加上一次配好小程序、浏览器、运营后台多个入口共用。// config/CorsConfig.java Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }参数说明addAllowedOriginPattern(*)允许任意来源适合联调期上线前应收敛成具体域名比如https://admin.example.com。setAllowCredentials(true)表示允许携带 Cookie与通配 Origin 不能同时使用所以这里必须用addAllowedOriginPattern做模式匹配而不是addAllowedOrigin。配好 CORS 之后前端请求能通、后端拦截器能识别 token整条后端通用基座就算闭环了。注意上线前忘记收敛 CORS 通配、密钥还留在代码里这类问题是医疗项目安全验收的常见驳回项。发布清单里要固定加一条检查 CORS 和配置文件的敏感信息。4. 预约挂号与问诊订单医疗小程序后端最值得抠的状态机设计通用模块能复用业务模块能沉淀靠的不是把每一个接口都写出来而是把「状态怎么流转」设计清楚。预约挂号是医疗小程序里最典型的订单场景涉及号源这种稀缺资源、涉及支付金额、涉及取消和退款的人工干预状态即使再复杂也必须保证任何时候都只有一个合法值。4.1 订单表结构状态、金额、时间戳一次拆干净预约挂号的订单核心是「号源」和「支付」两件事。号源是稀缺资源必须保证同一时段不能卖两次支付涉及金额状态不能模棱两可。通用做法是业务订单号自己生成不依赖数据库自增主键状态用数字枚举所有状态变更写进状态流水表时间字段拆成创建、支付、取消三个独立列方便查询和核对。CREATE TABLE medical_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键内部使用, order_no VARCHAR(32) NOT NULL COMMENT 业务订单号对外展示和查询, patient_id BIGINT NOT NULL COMMENT 患者ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, schedule_id BIGINT NOT NULL COMMENT 排班号源ID, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已取消 3已完成 4已退款, pay_amount DECIMAL(10,2) NOT NULL COMMENT 支付金额元, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL COMMENT 支付时间, cancel_time DATETIME DEFAULT NULL COMMENT 取消时间, KEY idx_order_no (order_no), KEY idx_patient_id (patient_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT预约挂号订单表;结构说明order_no建议格式M{yyMMddHHmmss}{6位随机数}对外查询时不暴露自增 IDstatus用数字而不是字符串减少存储和索引开销字段注释里写清数字含义另建一张字典表解释patient_id和schedule_id必须建索引因为查询场景基本就是某个患者的订单列表和某个号源的占用检查两句话的事。4.2 状态流转规则谁允许到谁写死在代码里订单状态不能任意跳转——已完成的订单不允许取消已退款的订单不允许再次支付。通用做法是建一张状态机映射表在 Service 层统一校验而不是把判断逻辑散落在一堆 if 里。医疗项目里还要额外注意支付结果是通过微信支付回调异步通知的不能在前端点击支付成功后就改订单状态要等回调验签通过再变更——这条规则和购物类小程序的商城支付回调逻辑完全一致。当前状态触发动作后置状态允许角色待支付用户支付回调已支付微信支付系统待支付用户取消已取消患者待支付超时关单已取消定时任务已支付申请退款已退款患者/客服已支付就诊完成已完成医生/系统已退款再次支付不允许-实现时可以用一个Map当前状态, List允许动作动作定义成枚举避免魔法数字。比如用户尝试取消订单时先查当前状态是否在允许列表里不在就抛BizException(20004, 当前状态不允许取消)。这条规则的收益在投诉排查时特别明显患者说我明明取消了为什么还扣费查状态流水表就能看到订单从待支付到已支付再到已退款的时间线每一跳都有时间戳和触发方。4.3 超时关单与号源释放乐观锁防止双卖用户下单后如果 15 分钟不支付系统要自动关单并把号源释放回去。常见方案有两种定时任务扫描创建时间超时且状态为待支付的订单批量关单或者用 Redis 延迟队列下单时设置一个 15 分钟过期的 key过期事件触发关单。前者实现简单、适合大多数医院场景后者适合高并发大流量。// business/order/TaskService.java —— 定时关单每 2 分钟跑一次 Scheduled(fixedDelay 120000) public void closeExpiredOrders() { Date deadline new Date(System.currentTimeMillis() - 15 * 60 * 1000); ListOrderDO expired orderMapper.selectByStatusAndTime(0, deadline); for (OrderDO order : expired) { // 乐观锁只有 status 还是 0 的订单才能关单成功 int rows orderMapper.compareAndSetStatus( order.getId(), 0, 2, order.getVersion()); if (rows 1) { // 关单成功才释放号源顺序不能反 scheduleMapper.releaseStock(order.getScheduleId()); } } }逻辑说明selectByStatusAndTime查出待支付且创建时间早于 15 分钟前的订单compareAndSetStatus是带版本号的乐观锁更新where 条件带上 status0返回影响行数——只有影响 1 行才说明关单成功避免和用户手动取消同时发生时重复处理releaseStock把号源库存加回去这一步必须在关单成功之后执行顺序反了会出现号源已释放但订单还在待支付的脏状态。医疗场景里号源释放涉及号源表、排班表、医生工作量统计三处数据建议把释放逻辑收敛到一个被事务包裹的服务方法里局部失败就整体回滚不允许出现号源库存加了但排班人数没减的情况。5. 版本发布与备案V5.9.4 前后端对齐、构建与提审前后端分离项目最大的发布事故是前端已经上线了后端接口还是旧版页面白屏或者接口 404。V5.9.4 这种版本号要前后端共用后端打 tag 的时候前端也要在同一个版本号下构建、联调、提审。版本号不是代码里的字符串而是整个发布流程的锚点。5.1 版本号约定与分支管理通用做法是维护 release 分支发版时从 develop 合并过去打上带前缀的 tag前端后端用同一个数字版本。tag 消息里写明本次变更涉及的接口和字段方便后续回溯。# 后端发布 git checkout release/5.9.4 mvn clean package -DskipTests git tag -a medical-backend-5.9.4 -m release 5.9.4: 订单状态机增加超时关单 # 前端构建以 uniapp 工程为例 git checkout release/5.9.4 npm ci npm run build:mp-weixin参数说明mvn clean package -DskipTests跳过单元测试加速构建但上线前建议去掉-DskipTests完整跑一遍测试npm ci比npm install更严格按 package-lock.json 精确安装版本避免某天依赖漂移导致构建产物和上次不一致build:mp-weixin是 uniapp 的微信小程序构建命令产物输出到dist/build/mp-weixin这个目录就是上传微信开发者工具的东西。前端构建产物和后端 jar 包要同步归档命名带上版本号比如medical-mp-5.9.4.zip。这样线上出问题时运维能快速定位当前跑的是哪一版代码不用翻聊天记录。5.2 小程序备案备注信息怎么填医疗类目的审核要点微信小程序新提交的版本需要完成 ICP 备案才能正常发布医疗类目的备案备注是审核重点。填写原则是把业务范围写具体把资质出处写清楚不要写模糊的医疗健康服务这种词。常见的备注写法是本小程序用于 XX 医院的在线预约挂号、图文问诊、报告查询服务不提供线上药品销售。备案主体持有《医疗机构执业许可证》许可证编号为 XXX。如果涉及在线问诊还需要按平台要求提交互联网诊疗相关资质审核人员会对照页面实际功能检查备注。这里有个实际操作细节备注内容要和页面里出现的功能完全一致。备注写了不提供药品销售但某个页面露出了药品购买入口提审会被驳回。建议发版前把页面路由清单导出来和备案信息逐条核对一遍比被打回再改高效得多。5.3 环境配置隔离一套代码三套配置开发、测试、生产三套环境的 baseURL 和密钥绝不能写死在前端代码里。通用做法是前端维护一个 env.js构建时根据环境变量注入对应配置。// config/env.js —— 构建时按 process.env.NODE_ENV 区分 const ENV { development: { baseUrl: https://dev-api.example.com/medical, mock: true }, production: { baseUrl: https://api.example.com/medical, mock: false } } module.exports ENV[process.env.NODE_ENV || development]说明development环境打开 mock 开关前端可以脱离后端独立开发页面接口返回假数据production环境关闭 mock所有请求走真实接口。这里容易踩的坑是有人把测试环境地址写死在代码里打包上线用户手机上请求的是测试域名证书过期或者接口变更马上就出问题。打包前检查 env.js 的指向应该成为发版清单的固定一条。6. 上线前验证用一组 curl 命令跑通前端后端整条链路通用模块改完、版本号打出来最后一步是用脚本验证前后端是否真的对齐而不是等用户点出来问题。下面这组命令覆盖医疗小程序三个最关键的环节版本号、登录换 token、带 token 查订单对应前端三条主链路的冒烟验证。#!/bin/bash # smoke.sh —— 发版前冒烟验证脚本 APIhttps://api.example.com/medical VERSION5.9.4 # 1 版本号必须与发布的 VERSION 一致 echo 后端版本: $(curl -s $API/common/version) # 2 code 换 tokencode 由前端 wx.login 扫码获取 LOGIN$(curl -s -X POST $API/auth/login \ -H content-type: application/json \ -d {code:test_code}) echo $LOGIN | python3 -c import sys,json;djson.load(sys.stdin);print(code:,d[code]) # 3 带 token 访问业务接口验证鉴权链路 TOKEN$(echo $LOGIN | python3 -c import sys,json;print(json.load(sys.stdin)[data][accessToken])) curl -s $API/order/list -H Authorization: Bearer $TOKEN参数说明第 1 步的/common/version在真实项目里建议返回三个值——后端版本、数据库版本号、最近一次迁移时间方便定位新旧代码混跑第 2 步的test_code是模拟值真实环境要由前端 wx.login 生成第 3 步用 python3 从登录响应里提取 accessToken如果你环境里没有 python3换成 jq 的jq -r .data.accessToken效果一样。脚本跑完看三个输出版本号匹配、登录 code0、订单接口返回正常结构任一步不符就阻断发版。真机侧的验证再补三个检查点冷启动登录——杀进程重新打开小程序确认 login 请求发出、token 写入 storage支付回调——支付完成后立刻切 Wi-Fi回列表页看订单状态是否按 4.2 节的回调逻辑刷新动态标题——从预约和问诊两个入口进同一个复用页面确认导航栏标题正确切换。这三个检查点配合上面的冒烟脚本把登录、数据、页面路由三条主链路全部覆盖。把冒烟脚本挂进 Jenkins 或流水线每次发版自动跑一遍后端 jar 包和前端构建产物用同一个版本号归档线上出问题就能在十分钟内定位到具体是哪一层的哪次变更引入的。本文还有配套的精品资源点击获取