尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微信小程序医院管理系统:从预约挂号到支付报告的全栈实践

微信小程序医院管理系统:从预约挂号到支付报告的全栈实践 这套基于微信小程序的医院管理系统是我从需求分析、UI设计、后端接口、小程序前端、文档整理到联调上线整个流程都完整走过一遍的项目。源码、文档、调试这三样东西不是靠拼凑凑出来的而是开发过程中真正在用的东西。它不是一个只有登录页和几个空页面的练手作品而是一个能覆盖挂号、缴费、报告查询、消息推送、后台管理的紧凑版医院信息系统。如果你正在找毕业设计、课程项目或者想给中小型诊所做一套轻量级预约管理系统这篇文章里的选型思路、核心实现、文档规范和排坑记录应该对你有用。1. 为什么是“微信小程序医院”这个组合项目定位与整体拆解1.1 业务痛点与场景判断先聊聊医院场景里最真实的痛点。去三甲医院看一次病要经历挂号、候诊、看诊、缴费、检查、取报告、复诊开药这一串流程每个环节都可能要排队。真正让人抓狂的不是排队本身而是你根本不知道前面还有多少人、这个科室今天开不开诊、报告到底出来没有。患者端的核心诉求其实很简单想知道挂哪个科、有没有号、几点能看、检查结果啥时候出来、费用怎么缴。对医院来说门诊信息系统的核心诉求是降低窗口压力、减少退号纠纷、精准掌握排班。所以一个医院管理系统的第一版不该贪大先把“预约挂号、在线缴费、报告查询、排班管理”这四条主线走通就解决了大部分实际问题。系统适合两类场景一类是学校里的完整项目开发训练覆盖前后端、数据库、第三方登录、移动端适配属于非常典型的全栈练习另一类是社区卫生服务中心、民营诊所这类小型机构它们没有能力上大型HIS系统但需要把门诊流程搬到线上小程序恰好是成本最低的入口。我在这套系统里选了患者端和医生端两个视角患者端面向老百姓医生端面向门诊医生和管理员。整体定位是“让预约、缴费、报告三条链路闭环”不做电子病历、药库管理这种大而全的功能因为那是HIS系统的范畴加进去之后项目周期会翻倍价值却没有明显增加。1.2 技术选型不选App、H5的3个理由为什么第一版选择微信小程序而不是原生App或者H5三个原因很现实。第一是触达成本。患者去看病前最自然的动作是在微信里搜索医院小程序或者扫一个医院门口的二维码不需要下载安装用完即走。原生App要下载、注册、绑定手机号一个普通患者没有理由为了看一次病专门装一个App。第二是开发效率。微信小程序提供了一套完整的开发工具、组件库、API体系一套代码同时覆盖iOS和Android不用分别维护两套原生工程。第三是资金成本。小程序不需要上架各大应用商店不用交企业开发者账号年费个人主体可以注册但部分功能受限一台普通云服务器就能跑起来。为什么不用H5H5在加载体验、摄像头调用、微信登录态获取这些能力上明显弱于小程序。H5的优势是跨平台、可被搜索引擎收录但医院挂号是高信任场景用户更愿意在一个看起来“正规”的小程序里完成操作。支付环节更是如此微信小程序可以直接唤起微信支付H5调用微信支付在浏览器环境下有非常多的限制。1.3 系统架构与源码目录是怎么设计的系统采用前后端分离架构小程序端负责展示和交互后端提供API数据库处理数据持久化。我选的是Spring Boot做后端MyBatis-Plus操作数据库MySQL存业务数据Redis做token和缓存管理后台用Vue实现。小程序前端用原生语法开发没有引入uni-app因为项目只面向微信生态原生语法在组件调试、基础库适配方面更直接。源码目录分成四块块与块之间通过接口契约衔接hospital-miniapp/ ├── miniprogram/ # 小程序前端 │ ├── pages/ │ │ ├── index/ # 首页 │ │ ├── department/ # 科室列表 │ │ ├── doctor/ # 医生列表 │ │ ├── appointment/ # 预约挂号 │ │ ├── payment/ # 缴费 │ │ ├── report/ # 报告查询 │ │ └── mine/ # 个人中心 │ ├── components/ # 自定义组件 │ ├── utils/ # 请求封装、工具函数 │ └── app.js ├── admin-web/ # Vue后台管理 ├── server/ # Spring Boot 后端 │ ├── src/main/java/ │ ├── src/main/resources/ │ └── sql/ # 初始化建表脚本 └── README.md这么划分的好处是职责清楚小程序代码和后端代码可以分别打包、分别部署。后端按业务模块分包管理controller、service、mapper、entity、common不会出现几百个类堆在一个包里的混乱局面。sql目录下放初始化脚本新同事或者评审老师拿到仓库后导入数据库就能启动不需要再东拼西凑找表结构。2. 核心模块设计与数据结构拆解2.1 患者端预约挂号、在线缴费、报告查询预约挂号是患者端的第一个核心功能。首页展示医院简介、科室入口、今日放号数量。用户选择科室后进入医生列表医生列表显示职称、擅长领域、剩余号源。点进医生详情后选择日期和时间段确认订单前会展示就诊人信息和温馨提示。这里有一个容易被忽略的细节挂号订单生成前必须先查一遍号源状态否则会出现“页面显示有号提交时已满”的并发问题。我在后端统一加了Redis分布式锁和数据库唯一索引双保险保证同一个时间段同一个医生不会被重复预约。在线缴费处理的是待支付费用和诊间费用。当患者就诊完医生在后台开出检查单或处方缴费列表里就会出现待支付账单。缴费模块用状态机管理订单状态待支付、已支付、已退款、已关闭。支付成功后通过微信支付异步回调更新订单状态而不是在前端拿到支付成功就立刻改状态这一点非常关键。前端可能会在断网、杀进程等情况下丢消息后端回调才是最终依据。报告查询涉及两类数据检验报告和检查报告。报告在医生审核通过后写入report表小程序端定时轮询或者在下拉刷新时检查是否有新报告有结果后展示报告摘要、检验指标、参考范围异常指标用特殊颜色标出来。考虑到小程序消息订阅功能的限制我用的是“预约成功通知报告完成通知”两个订阅模板用户在关键节点主动订阅效果比连续推消息好得多。2.2 医生端与后台管理怎么在一个小程序里做角色隔离很多同类项目会把医生端单独做成一个小程序维护起来很麻烦两个AppID、两套审核流程、两套版本管理。我采用的做法是同一个小程序里做角色隔离一个微信OpenID只绑定一个角色登录后后端返回用户角色标识前端根据角色渲染不同菜单和页面。管理员和医生也能登录同一个代码编译出来的版本不需要两个独立入口。角色权限通过Spring Boot的拦截器实现。登录接口返回tokentoken里携带userId后续请求经过拦截器时从Redis中取出该用户的角色判断是否有权限访问某个接口。比如“查询全部患者列表”只有管理员能用“查看我的排班”医生能用患者调这些接口直接返回403。前端页面也做了一层控制没有权限的入口不渲染但后端校验必须无条件存在因为前端控制只能防君子不能防小人。后台管理端提供排班管理、医生管理、科室管理、订单管理四个页面。排班管理是最复杂的页面因为排班规则多种多样周一到周五上午放号30个下午放号20个专家号周一才放节假日停诊。我把排班规则做成一张独立表由管理员按周维度去配置模板生成具体日期的排班数据比直接手工每天建排班要省力很多。2.3 数据表设计与字段约定数据库设计决定了系统能撑多大数据量。核心表我控制在10张以内表名用途关键字段sys_user用户/医生/管理员openid、role、real_name、phonedepartment科室dept_name、intro、sort_orderdoctor医生信息dept_id、title、good_at、head_imgschedule排班表doctor_id、schedule_date、time_slot、total_count、surplus_countappointment预约挂号单schedule_id、user_id、status、cancel_reasonpay_order缴费订单appointment_id、order_no、amount、statusreport检查检验报告user_id、report_type、report_status、pdf_urlmessage消息通知user_id、msg_type、content、is_read几个字段约定在实际开发中让我省了不少事。订单金额一律用“分”存储数据库用int或bigint避免浮点数精度问题。所有表带create_time、update_time字段由MyBatis-Plus自动填充。逻辑删除用deleted字段不物理删除这样误删数据还能恢复。索引方面appointment表建了(user_id, status)联合索引schedule表建了(doctor_id, schedule_date)唯一索引这两个索引在实际压测中效果非常明显。支付订单号我统一用业务前缀时间戳随机数生成比如“PAY20250323123015001”接入微信支付时作为out_trade_no方便对账。3. 开发过程中的关键实现细节3.1 项目初始化配置AppID、域名与基础库第一步是去微信公众平台注册小程序账号拿到AppID。个人主体可以注册但支付功能、医疗类目会有不少限制所以这套系统面向的是诊所或者学校实验室建议注册企业主体。注册完在“开发管理-开发设置”里找AppID和AppSecretAppSecret千万不要出现在前端代码里它只能在后端使用。微信开发者工具里选择小程序项目后把AppID填进去。开发期间可以勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”这样本地调试后端接口比较方便不用非要配置HTTPS域名。但上线前必须把这个开关关掉否则真机预览会直接白屏。基础库版本建议设置在保守的范围不要追最新因为老用户手机里的微信版本比较旧。我这边设置的是最低基础库“2.21.0”同时开启“自动更新”这样既能用新API又不会因为版本太低导致大面积兼容问题。提到的“页面列表加载更多”是每个列表页都逃不掉的功能核心处理在小程序端的onReachBottom事件和后端的page分页参数。后面单独写一节这里先提醒一句不要一次性返回全量数据一定要分页。3.2 微信登录与token鉴权不要把openid直接放前端小程序登录流程是wx.login获取临时code把code传给后端后端调用微信接口换取openid和session_key然后用openid查数据库如果用户不存在就自动注册最后生成一个自定义token返回给前端。前端把token存到wx.setStorageSync后续所有请求在header里带Authorization字段。这个过程中的两个常见坑。第一个不在前端持久化openid只存token。openid是用户的唯一标识泄露出去会有安全隐患session_key更是只能在后端用不能下发到前端。第二个请求封装时对401统一处理token过期后清除本地缓存跳转到登录页重新触发登录。登录是静默的还是需要用户确认的取决于你调用的方法我用的是wx.login静默登录用户无感知整个会话维护在后端。请求封装的核心代码可以这样写const request (path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${path}, method, data, header: { Authorization: Bearer ${wx.getStorageSync(token)} }, success: (res) { if (res.statusCode 401) { wx.removeStorageSync(token); handleLoginAgain(); reject(new Error(登录状态已过期)); return; } if (res.data.code ! 0) { reject(new Error(res.data.msg || 请求失败)); return; } resolve(res.data.data); }, fail: reject }); }); };后端收到Bearer token后去Redis查对应的userId和角色。Redis里的token设置7天过期比JWT的好处在后端能主动剔除某个用户的登录态比如管理员封禁账号后立刻让这个token失效。3.3 列表页“加载更多”onReachBottom的正确姿势列表加载更多看似简单坑不少。我最初的做法是每次触底就请求下一页结果问题来了用户滑动太快触发多次onReachBottom后端收到一串重复请求数据重复插入列表出现相同记录。正确姿势是加一个“正在加载”开关请求期间拒绝新的加载请求只有上一次请求完全结束后才允许继续。另一个细节是分页判断后端返回data里面有records列表和totalCount前端判断有没有下一页不能只看records是否为空而要看返回条数是否等于pageSize。如果最后一页刚好是pageSize条下一轮返回空列表后要再判断一次。实现代码Page({ data: { appointments: [], page: 1, pageSize: 10, hasMore: true, loading: false }, onReachBottom() { if (this.data.loading || !this.data.hasMore) return; this.setData({ loading: true }); getAppointments(this.data.page 1, this.data.pageSize) .then((res) { this.setData({ appointments: this.data.appointments.concat(res.records), page: this.data.page 1, hasMore: res.records.length this.data.pageSize }); }) .finally(() { this.setData({ loading: false }); }); } });同时要在页面底部放一个状态提示loading时显示“加载中”没有更多数据时显示“没有更多了”否则用户不知道列表是不是卡住了。这个提示可以用自定义组件做也可以直接写在列表底部的view里经验是留一块50px高度的区域专门放状态文本避免被tabBar遮挡。3.4 订单、支付与状态机金额以分为单位支付模块的流程比较固定。前端请求后端创建支付订单后端生成订单并调用微信支付统一下单接口拿到prepay_id后组装支付参数前端用wx.requestPayment唤起收银台。支付完成后微信服务器会把结果异步通知到后端配置的回调地址后端处理回调时校验金额和状态然后更新订单。这里的魔鬼在细节里。金额必须用分我吃过用“元”做浮点运算的亏打折优惠后对不上账排查了整整一天。支付回调必须做幂等一个订单多次回调不能重复处理。订单超时未支付要取消释放号源我通过Redis的过期key监听实现比全表扫订单要高效得多。挂号单的状态我做了一个状态机从“已预约”到“已完成”中间可能经过“已取消”取消时释放surplus_count号源同时给排班表加回一个号。退号规则是就诊前一天24点前允许患者自助退号超过这个时间只能去窗口处理。这规则听起来简单写进代码里的判断条件很容易漏掉时间边界。4. 文档与源码管理为什么说这三件套要早准备4.1 README不是摆设快速启动文档怎么写很多人拿到项目后第一件事就是看README能不能让它跑起来。一个合格的README不需要花里胡哨但要包含六个部分项目简介、技术栈、目录结构、环境要求、快速启动步骤、常见问题。快速启动步骤越傻瓜越好。我会写清楚导入sql/init.sql到MySQL修改application.yml里的数据库连接启动Spring Boot服务用微信开发者工具导入miniprogram目录填写AppID本地勾选不校验域名然后就能看到首页。这些步骤必须实际验证过因为很多项目文档是开发中途写的到了交付阶段已经前后对不上了。README最后放一个“常见问题”小节把新人最容易碰到的三个问题写进去端口占用了怎么办、数据库连接失败提示access denied、小程序编译后request域名校验报错每一条给出具体解决办法。这个动作能帮你省掉大量答疑时间。4.2 接口文档统一返回体与错误码接口文档我用Apifox维护接口定义和Mock数据都在上面。但比工具更重要的是接口约定。所有接口统一返回结构{ code: 0, msg: success, data: { total: 100, list: [] } }code为0表示成功非0表示业务失败。错误码有统一规范10001参数错误10002未登录10003无权限20001号源已满20002订单状态异常。前端封装request时统一判断code无需每个页面都写错误处理逻辑。写接口文档时不要只写“参数名和返回值”要把业务含义写进去。比如“取消预约”接口的status字段到底传什么值传“CANCELLED”还是“2”文档里必须明确。后端枚举和前端常量保持一致我用一个common/Enums.java统一维护前端在一个constants.js文件里维护两边靠文档同步这比我见过那种接口文档只有URL和参数说明的项目靠谱得多。4.3 数据库设计文档与SQL脚本管理数据库设计文档是评审时最能体现专业度的地方。除了ER图更重要的是写明每张表的字段含义、状态值、关联关系。比如pay_order.status字段文档里要写清楚0待支付1已支付2已退款3已关闭别用充满想象力的英文单词。建表脚本我要求每个版本都保存在server/sql目录下文件名带版本号比如v1.0_init.sql、v1.1_add_report_remark.sql。这样从零搭建项目时可以一键执行全量脚本项目中途改动时也能追踪到哪个版本加了什么字段。不要直接在开发环境手动alter table改完不留痕迹后续部署生产库时根本不知道少了哪个字段。设计文档和实际代码不同步是最大的坑。我的习惯是字段改动当天同步修改设计文档和SQL脚本哪怕多花十分钟也比三个月后系统性对账来得轻松。4.4 源码目录规范与Git提交红线源码管理的第一条红线是敏感信息不进版本库。application.yml里的数据库密码、AppSecret、微信支付商户密钥全部放在环境变量或者application-local.yml并加入.gitignore。有段时间我把AppSecret写死在application.yml提交到了内网Git仓库后来意识到这个问题马上把所有敏感信息全部替换成${}占位符从环境变量读取。这类错误看似小事一旦代码被分享出去隐私就彻底暴露了。.gitignore里至少要有这些node_modules/ miniprogram_npm/ dist/ .env .env.local *.log .DS_Store .classpath .project target/Git提交信息我用了最简的规范type: subject 格式比如“feat: 新增预约取消接口”、“fix: 修复列表页重复加载问题”。分支用main主干、dev开发分支个人开发时直接push到dev验证完再合并主干。如果项目多人协作就再拉feat/xxx功能分支每个功能合并后关闭分支保持主干干净。5. 调试实战从开发者工具到真机再到线上5.1 开发者工具里的抓包与断点微信开发者工具自带的Network面板是调接口的第一利器。打开调试器切到Network能看到每个请求的URL、状态码、请求头、响应体基本能完成九成的前端问题定位。我的习惯是每个接口先在Network里确认返回结构是否符合预期再去看页面有没有渲染顺序很重要很多人一上来就啃WXML却发现字段名对不上白白浪费时间。后端接口调试我更习惯用代码日志和数据库查询配合定位。在Service层关键节点打日志记录入参、核心判断结果、返回值。出现问题时先看日志里这条请求走到哪一步断了再到数据库查对应订单状态基本能判断是前端传参错了、业务逻辑漏了还是数据库数据异常。如果前端请求的接口是HTTPS环境下的线上域名我又想确认返回内容可以用Charles抓包。Charles是一个HTTP抓包工具配置好证书后可以查看小程序发出的请求和响应排查线上环境问题比在开发者工具里勾选“不校验域名”要真实得多。5.2 登录态失效、上传失败、白屏三类高频问题排查我整理了一份高频问题速查表开发这套系统时反复用到问题可能原因排查路径解决方式请求返回401token过期或未传递看请求header是否带Authorization重新静默登录更新token图片上传失败后端未配置上传目录看后端日志报错检查Controller接口创建上传目录配置静态资源映射真机白屏域名未配置或基础库版本过低检查HTTPS证书、请求是否走合法域名配置业务域名提升基础库版本列表重复触底请求没加loading锁快速滑动触发多次onReachBottom增加loading判断支付一直失败商户号与AppID未绑定检查微信支付商户平台绑定关系在商户平台关联AppID白屏问题出现的概率最高。最常见的原因是开发期勾选了“不校验合法域名”但真机上这个设置不生效所有请求都因为域名非法被微信拦截。排查方式很简单打开手机调试模式的vConsole面板看请求报错信息十有八九都是域名问题。另外基础库版本过低也会导致新API找不到比如getUserProfile在老基础库上不存在真机会报undefined function。上传失败的问题我踩过比较大的坑。后端Spring Boot默认上传文件大小限制是1MB医生头像和报告图片稍微大点就报错。在application.yml里配置spring.servlet.multipart.max-file-size10MBmax-request-size20MB之后就解决了。这里还要同步检查Nginx的client_max_body_size配置否则后端限制调大了Nginx这一层还是会拦。5.3 真机调试与上线前的检查清单开发者工具模拟器上一切正常不代表真机就一定没问题。模拟器没有真实的摄像头、扫码、地理定位、网络切换环境。我在开发完成后固定安排一轮真机测试重点看三个场景弱网环境下请求超时是否有提示、支付流程是否流畅、消息订阅是否触发成功。真机调试用微信开发者工具的“真机调试”功能手机扫码后自动同步代码配合vConsole面板查看运行日志比普通预览多了日志输出能力排错效率高很多。另一个“远程调试”模式适合处理复杂的dom节点查看但连接过程比较慢我一般只用真机调试。上线前清单我列过一份每次发版前逐条打勾域名必须HTTPS且证书在有效期内开发者工具里的“不校验合法域名”已关闭AppSecret未出现在前端代码服务器带宽足够支撑高峰并发预约放号瞬间流量非常大数据库已备份基础库最低版本设置合理支付回调地址公网可访问后台管理账号密码已改掉默认值。这条清单帮我在正式上线时避免过两次事故一次是忘了关不校验域名导致真机白屏一次是服务器没配HTTPS导致iOS端请求全部失败。6. 部署上线、合规与后续扩展6.1 服务器、HTTPS与域名校验小程序正式环境要求所有接口必须是HTTPS。我在云服务器上用Nginx部署Spring Boot的jar包申请了免费的HTTPS证书然后在Nginx配置中开启HTTP/2并反向代理到后端端口。注意证书一定要放在证书链完整不要只贴PEM格式的正文就完事。域名配置是上线前最容易卡住的点。微信公众平台后台的“开发设置-服务器域名”要把request合法域名、uploadFile合法域名、downloadFile合法域名全部填上。规则很严格域名不能带端口必须HTTPS且证书要在有效期内。如果你后端用的是IP地址那基本没戏必须绑域名。uploadFile合法域名独立配置很多项目漏了这一项导致图片上传在真机上一直失败。后端服务的健康检查也不能省。我写了一个简单的actuator health接口部署后先在浏览器访问确认返回UP再配置到小程序后台。如果没有这一步就急着发布大概率会被用户骂。6.2 小程序审核与医疗资质合规小程序审核是上线流程里最不确定的一环。医疗健康类小程序在微信的类目管理里是非常敏感的涉及在线问诊、处方药销售这些功能必须有互联网医院资质大多数学校项目和个人开发者根本不具备这个条件。我做的处理是把功能定位于“门诊预约信息服务和报告查询”不碰在线诊断、用药指导、医生文字回复。医生端只做排班确认、患者信息查看不做线上开药。小程序简介里也特意避开了“医疗问诊”这类高风险词全部以“预约服务”和“信息查询”为主。额外在关于页加了一条声明本系统仅提供预约和信息查询服务不提供诊断和治疗建议紧急情况请急诊就医。审核被拒的情况我还是遇到过几次每次被拒后先看“拒绝原因”大部分问题集中在“类目选择”和“医疗资质材料”上。我的经验是先把基础类目写成“工具-信息查询”核心功能都是查询和预约就不涉及医用器械、处方等敏感类目。6.3 还能往哪扩展报告PDF、位置服务、蓝牙设备这套系统跑通之后后续扩展空间其实很大。当前报告查询只是展示文字摘要如果要支持查看完整PDF报告就在生成报告时用后端把结果渲染成PDF文件上传到OSS或本地目录前端用web-view或者downloadFile接口打开文件。注意小程序对web-view的域名有单独的白名单要求preview必须走业务域名。位置服务也是一个有价值的方向。科室导航、附近医院查询这些功能可以接入地图服务微信小程序的基础API也提供了强位置能力适合做一些医院内的科室定位。蓝牙设备联动是另一个面向慢病管理的扩展方向比如血压计、血糖仪通过蓝牙连接小程序患者在家测完数据直接同步到个人档案这对复诊随访场景非常有吸引力。如果后续有扩大客户端范围的需求前端代码可以迁移到uni-app重构一次编写打包成小程序、App、H5多端。项目当前的接口设计是标准的RESTful风格迁移时后端完全不用动只是替换掉小程序端的页面层调用。不过我要提醒一句不要一开始就上uni-app小程序官方原生功能在调试和基础库适配上有天然优势跨端需求明确出现了再迁也不迟。我在这套系统上吃过不少亏尤其是登录态维护、支付幂等、列表加载重复、审核类目这四个坑每一个都花过超过半天时间去解决。如果你正在做类似的项目照着我的排查路径去走应该能少走很多弯路。医院系统最怕的不是代码写不出来而是业务需求边界没聊清楚把排班规则、退号时限、报告类型这些问题在数据层面设计妥当开发过程会顺利得多。
返回列表