ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

微信小程序预约挂号系统开发:Django/Flask后端与医患交互实战

2026/9/26 17:48:05 拓冰建站 浏览量
微信小程序预约挂号系统开发:Django/Flask后端与医患交互实战 这段时间我一直在折腾一个医院门诊挂号系统的完整实现。整个项目选型就是用微信小程序作为患者端后端走Python生态Django和Flask两个框架我都实际跑过一轮最后沉淀出了一套在线医患交互预约方案。这套系统既要支撑传统挂号的“选科室—选医生—选时间段—支付—取号”还要把在线图文咨询、复诊随访、预约提醒这些交互做进去。不管你是想给中小医院做信息化改造还是拿它当毕业设计、开源参赛项目这篇从需求拆解到部署避坑的完整记录都能给你一条靠谱的路线。1. 项目整体设计与功能拆解1.1 为什么是微信小程序而不是App或H5做医疗类产品第一步要确认的不是技术栈而是“患者凭什么愿意装你这个东西”。很多人一上来就想做原生App结果卡在下载安装这一关用户流失率高得吓人。微信小程序最大的优势就是“即用即走”患者扫个码、搜一搜就能打开不需要经历“下载—安装—注册—授权”这一连串劝退流程。对医院来说小程序挂在公众号菜单里、贴在诊室门口推广成本几乎为零。另外微信生态里天然带了身份识别能力wx.login可以直接换取 openid配合手机号授权基本能做到“一次授权永久识别”。这在预约挂号场景里特别重要因为挂号必须实名制身份信息一旦错后续就诊、取号、退费全是问题。H5 虽然也能做但微信内置浏览器的接口能力有限推送提醒、支付回调、地理位置这些能力都隔了一层体验和稳定性都不如原生小程序外壳。选型结论很直接患者端用微信小程序医生端和管理后台用 Web 页面后端统一提供 RESTful API。这样患者侧轻量快捷医生侧功能复杂也不怕浏览器兼容问题前后端彻底分离后续加管理功能、报表功能都很方便。1.2 核心角色与功能清单整个系统按角色拆成三块每一块的痛点不一样功能设计自然也不一样。患者端微信小程序注册登录、浏览医院和科室、查看医生排班、选择时间段预约挂号、在线支付或到院支付、取消预约、在线图文问诊、查看就诊记录和待办提醒。这里最核心的是“排班可约性”的实时展示患者选医生时看到的是剩余号源不能是死数据。医生端Web/小程序查看自己的出诊排班、开启或停诊、处理患者的在线咨询、标记复诊、补录诊后随访信息。很多医院系统把医生端做得极其复杂我实际落地的经验是先做“出诊管理 消息回复”两个主功能其他权限控制后面再慢慢加。管理后台Web维护科室、医生信息、排班规则、号源总量、停诊通知、黑名单管理、订单对账。后台是给挂号处和系统管理员用的操作必须清晰尽量用表格化界面少搞花哨交互。在线医患交互预约的核心不是简单把线下挂号流程搬到线上而是要把“预约前咨询”和“预约后随访”这两个非结构化场景做出来。预约前患者不确定自己的症状挂哪个科可以在线留言问医生医生有空了回复预约后医生可以发复诊提醒、开检查注意事项患者也可以直接反馈恢复情况。这些功能做出来后整个系统就不是一个冷冰冰的挂号机而是一个有温度的随访工具。2. Django还是Flask后端框架选型与API设计2.1 两个框架都试过之后的选择先讲实话我一开始是用 Flask 快速搭的原型因为 Flask 轻路由、视图、请求上下文都特别直观适合验证业务逻辑。但项目推进到用户体系、订单状态机、排班并发控制这些模块时Flask 需要自己拼的零件越来越多数据库迁移、表单校验、Admin 后台、认证鉴权每一样都要找第三方库再组合组合久了反而是负担。后来我切换到 Django核心原因有三点。第一Django 自带 ORM 和 migration排班表、订单表这种强关系数据结构用 Django ORM 管理起来非常顺手改字段后一句makemigrations就能生成迁移脚本不至于上线前手改数据库表结构。第二Django 自带 Admin 后台管理科室和医生这种低频但必须有的功能直接配置一下 ModelAdmin 就能用省掉一整套后台开发时间。第三Django REST FrameworkDRF把序列化、视图集、权限、限流都封装好了API 开发的效率比 Flask 裸写高出一大截。但这不代表 Flask 不行。如果你的项目只做几个接口、不需要复杂后台或者团队对 Flask 更熟用 Flask Flask-SQLAlchemy Flask-Migrate 也完全能把在线预约做出来。Django 和 Flask 的选择本质是“全家桶”和“乐高积木”的区别。做医疗这种领域模型多、状态流转复杂的项目我建议直接上 Django短期学习成本换来的是长期维护成本的大幅下降。维度DjangoFlaskORM与迁移自带开箱即用需要外接 SQLAlchemy后台管理自带 Admin配置即可需要第三方扩展REST APIDRF生态成熟Flask-RESTful 或手动实现技术栈统一性高约定优于配置自由度高但需要自己定规范适合场景完整业务系统、多人协作原型验证、轻量API服务2.2 RESTful接口设计与DRF示例前端小程序只认接口后端无论用哪个框架接口设计都必须清晰。我按资源维度拆不按页面维度拆这样每个接口可以复用到小程序端、医生Web端、管理后台甚至未来可能的App端。核心接口清单大致是/api/hospitals/医院列表/api/departments/?hospital1科室列表支持按医院过滤/api/doctors/?department2医生列表带出职称、擅长、排班概览/api/schedules/?doctor3某医生的排班信息返回日期、时段、剩余号源/api/appointments/创建预约、查询我的预约、取消预约/api/orders/订单信息支付回调后更新状态/api/messages/在线问诊留言列表和发送用 DRF 写排班接口非常快。举个例子排班模型Schedule序列化器里直接返回医生姓名和剩余号源视图集配合 DjangoFilterBackend 做筛选几行代码就能给小程序一个干净的数据结构class ScheduleSerializer(serializers.ModelSerializer): doctor_name serializers.CharField(sourcedoctor.name, read_onlyTrue) department_name serializers.CharField(sourcedoctor.department.name, read_onlyTrue) class Meta: model Schedule fields [id, doctor, doctor_name, department_name, date, start_time, end_time, slot_count, remaining_count] class ScheduleViewSet(viewsets.ReadOnlyModelViewSet): queryset Schedule.objects.select_related(doctor__department).filter(remaining_count__gt0) serializer_class ScheduleSerializer filterset_fields [doctor, date]接口设计时的关键心法是“一次给够别让前端调二次”。小程序的网络请求在弱网环境下很慢如果排班接口只给 schedule id前端还要再调一次医生详情才能显示姓名体验就废了。所以我习惯把医生姓名、科室名、医院名全部嵌套进排班数据里前端拿一个列表直接渲染省时省力。3. 微信小程序端核心模块落地3.1 登录授权与数据请求封装小程序端第一个坑就是登录。现在很多教程还停留在老的wx.getUserInfo弹窗授权这套方案已经被微信废弃了隐私接口规范调整之后头像昵称和手机号都必须走新的“头像昵称填写能力”和“手机号快速验证组件”。正确做法是用wx.login拿 code把 code 传到后端后端再调微信的code2Session接口换 openid然后把 openid 作为业务唯一标识自己生成 token 返回给小程序。后续所有请求都在 header 里带 token后端校验 token 后就知道是哪个患者。手机号这块不要用普通的输入框收集直接在小程序里用button open-typegetPhoneNumber拿到动态令牌后传给后端后端再向微信接口换取真实手机号。这一步对挂号系统尤其重要因为取号通知、停诊短信都靠手机号触达。因为接口权限和审核规则会变上线前一定去微信公众平台确认最新要求别拿旧代码硬套。请求封装用wx.request做一个统一模块好处是拦截器逻辑只写一遍。我在request.js里做了三件事统一拼接 baseURL、加 token 到 header、统一处理 HTTP 状态码和业务 code。遇到 401 自动跳转登录页遇到 500 弹 toast 而不是让页面白屏。代码大概长这样const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method, data, header: { Authorization: Bearer ${wx.getStorageSync(token)}, Content-Type: application/json }, success: (res) { if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }); reject(res); } else if (res.statusCode 200 res.statusCode 300) { resolve(res.data); } else { wx.showToast({ title: res.data.message || 请求失败, icon: none }); reject(res); } }, fail: reject }); }); };3.2 预约挂号全流程实现预约挂号流程是系统的心脏页面再多也逃不过“选日期→选时段→确认患者信息→支付→完成”这几步。我实现的时候把整个流程拆成了三个关键页面排班列表页、预约确认页、支付结果页另外加了一个“预约成功”作为状态展示不单独设页面。排班列表页的数据依赖接口返回的date和remaining_count。我默认做了一个“近7天可约”的横向日历日期切换时重新请求/api/schedules/?doctorxxxdateyyyy-mm-dd。这里有个细节slot_count是初始号源数remaining_count是当前剩余数前端显示的是“剩余 x 号”当remaining_count为 0 时这个时段的按钮要直接置灰不能等用户点击后才提示。我在界面层用disabled属性控制实测比按钮可点击再弹窗提示的体验好很多。用户点击“预约”之后跳到确认页。确认页要做的不只是展示信息还要做一个重要的校验再次向后端确认该时段仍有剩余号源。因为患者在前一个页面停留太久时号源可能已经被别人抢走。这个二次确认接口的返回结果直接决定是进入支付流程还是弹窗提示“该时段已被约满请选择其他时间”。支付环节我优先接微信支付。小程序里用wx.requestPayment后端预下单接口返回支付参数前端拉起支付。支付回调走后端异步通知更新订单和号源状态。测试阶段没有商户号的话可以用模拟支付开关正式上线前必须把 mock 支付关掉否则审核和财务对账都会出问题。3.3 在线医患交互从留言到WebSocket推送在线医患交互是这个系统区别于普通挂号系统的地方。患者挂号后如果还有问题不需要再跑一趟医院直接在订单详情页发起在线问诊把症状、图片发过去医生在 Web 端或小程序端打开消息列表看到后回复。我最初用最简单的“留言板”方式实现就是一张message表患者发一条医生回一条页面靠下拉刷新拉新消息。这个方案在测试阶段没问题但真机体验不佳患者发完消息后要反复下拉非常煎熬。后来我改用 WebSocket 做消息实时推送。后端如果是 Django用channels写一个 WebSocket consumer如果还是 Flask则用flask-socketio。前端小程序里用wx.connectSocket建立长连接服务端有消息时主动推给小程序。核心流程是患者进入对话页时先拉历史消息同时建立 WebSocket 连接医生回复后WebSocket 把新消息推给患者前端追加到聊天记录里患者发送消息时先通过普通 API 写入数据库再通过 WebSocket 通知对方。这里要提醒的是WebSocket 不能作为唯一的数据通道。微信小程序在切后台、网络切换时WebSocket 很容易掉线所以必须在onShow生命周期里做一次“重新拉取未读消息”的逻辑。也就是说实时推送是增强体验的手段数据落库和重拉兜底才是保证不丢消息的关键。团队如果不想引入 WebSocket 维护成本用“轮询 未读数角标”也能满足绝大多数复诊沟通场景。4. 数据库设计与号源并发控制4.1 核心表结构速览预约系统的数据库设计比很多人想象的要重。因为医疗场景涉及实名、排班、订单、支付、消息多条链路表之间关系复杂。我落地的核心表大概有这些User患者或医生账号包含微信 openid、手机号、姓名、身份证号脱敏存储、角色类型。Department科室表包含医院、科室名称、门诊位置。Doctor医生表包含姓名、职称、擅长领域、所属科室、头像、简介。Schedule排班表包含医生、日期、开始时间、结束时间、总号源数、剩余号源数、状态。Appointment预约记录表包含患者、排班、预约时间、状态、就诊序号。Order订单表包含预约、支付金额、支付状态、第三方支付流水号。Message问诊消息表包含会话ID、发送者、内容、图片路径、已读状态。这中间最容易忽略的是“就诊序号”。部分医院要求患者到院后按序号排队呼叫所以我在Schedule里存了start_number每成功预约一个号就在排班下递增生成序号返回给前端。如果不做这个字段后面接院内叫号系统时会非常痛苦。Django 里创建 App 时要顺手把User模型改成自定义的因为后续要挂身份证、微信 openid、角色这些字段默认的 auth.User 不够用。方法是在settings.py里设置AUTH_USER_MODEL accounts.User继承AbstractUser扩展字段。这个配置最好在项目初始化时做完项目跑到一半再换用户模型Django 的 migration 会很折腾。4.2 号源不超卖的关键操作在线挂号和秒杀系统本质上是一类问题多个患者同时抢同一时段的号怎么保证不超卖最稳妥的办法不是先查再减而是用一条条件更新 SQL 把“判断剩余号源”和“扣减号源”合并成一个原子操作。在 Django ORM 里可以这么写from django.db.models import F from django.db import transaction with transaction.atomic(): updated Schedule.objects.filter( idschedule_id, remaining_count__gt0, statusopen ).update( remaining_countF(remaining_count) - 1 ) if updated 0: # 说明这一时刻号源已经被抢完 raise AppointmentConflict(当前时段已约满) appointment Appointment.objects.create(...)核心就是update()只在remaining_count 0时才会生效并且返回受影响行数。如果返回 0就说明条件不满足不创建预约记录。这么做既避免了“先 select 再 update”之间的时间差也不需要牺牲太多性能。支付环节我建议把号源锁定和订单创建放在同一个事务里防止出现“订单没支付成功号却已经扣了”的脏数据。针对超时未支付的问题我做了“15分钟未支付自动取消”的定时任务每小时扫描一次Order表找到超过15分钟仍处于待支付状态的订单取消订单并把号源回补。回补时同样用条件更新把remaining_count加回去但要注意不能超过slot_count上限SQL 里加remaining_count__ltF(slot_count)作为守卫。4.3 预约状态机与取消规则预约记录的状态不能只用一个字符串存否则业务逻辑里到处是if status waiting的脏判断。我定义了四个主要状态外加两个终态状态含义可操作场景pending已创建订单等待支付患者可以取消超时自动关闭confirmed支付成功号源已锁定就诊日未开始时可以申请退号completed就诊完成医生标记或系统自动更新支持复诊随访cancelled已取消或已退号终态不可进行任何操作noshow爽约对黑名单和信用分有影响取消规则的难点在于“退号退费”和“号源回补”不能直接变成一行。我处理的逻辑是就诊日期前一天23:59前取消订单全额退款号源回补当日取消需要线上提出申请由后台人工审核号源暂时冻结而不是立刻释放。这是为了避免有人反复抢占和退订把排班表刷成一片混乱。5. 部署上线与微信侧审核避坑5.1 小程序合法域名与后端部署小程序上线前必须在微信公众平台的“开发设置”里配置服务器域名。request合法域名必须是 HTTPS且域名不能带端口号。这意味着如果你的后端跑在http://192.168.1.100:8000本地调试可以勾选“不校验合法域名”但正式版一律拦截。所以上线的第一个前提是准备好一个已备案的域名给它配上 HTTPS 证书。后端部署方案我推荐用 Nginx Gunicorn Django 这种组合。Nginx 负责静态文件、HTTPS 证书、反向代理Gunicorn 负责跑 Python 应用。配置文件时注意把静态文件和 media 文件的 location 单独定义不然你会发现 VSCode 里写的img标签在 Django 的 static 文件中显示不了。这个问题多半是STATIC_URL配置不对或者DEBUGFalse之后 Django 不再自动服务静态文件需要用collectstatic收集后才能由 Nginx 提供访问。Flask 部署也是同理只是应用启动入口不同。有一个常见问题是“Flask 如何绑定到网页元素”这其实是没搞清楚 Flask 只管后端逻辑网页里的按钮、输入框要靠 HTML/JS 去渲染。如果你需要在 Flask 里返回一个带交互的页面正确做法是用render_template渲染 Jinja2 模板在模板里引 CSS 和 JS再通过 fetch 或 AJAX 调后端接口。这个思路和小程序完全一致只是把小程序端换成了浏览器端。5.2 高频问题排查实录这个项目从开发到测试我踩过的坑还真不少。挑几个典型的记录一下给后来人省点时间。现象可能原因解决办法小程序 request 报url not in domain list合法域名未配置或没等生效在公众平台添加域名确认证书链完整本地开发时临时勾选不校验号源明明还有剩余仍然提示约满数据库行锁竞争或事务隔离级别过高检查是否用了select_for_update()后又长事务尽量锁范围小、提交快Django 静态图片加载不出来STATIC_URL错误或DEBUGFalse未收集静态文件配置 Nginx alias 指向staticfiles执行collectstaticFlask 页面点击按钮没有任何效果后端只返回了模板前端 JS 路径或接口地址写错打开浏览器控制台观察 Network 请求是否 404/500微信支付回调后订单状态没更新回调验签失败或幂等处理缺失先校验签名再判断订单状态重复通知时直接返回成功还有一个容易被忽略的坑微信小程序顶部导航栏高度。安卓和 iOS 的状态栏高度不一样如果用了自定义导航栏不要写死高度应该用wx.getSystemInfoSync()获取statusBarHeight再动态计算。我早期就是写死 64px结果 iPhone 上按钮被刘海挡住被测试同事拿截图怼了好几次。6. 几点实操心德项目做到最后我发现最难的不是写代码而是把“线下流程”翻译成“线上系统逻辑”。比如线下挂号时患者到窗口问一句“还有号吗”工作人员看一眼登记本就能回答但线上系统必须在同一时刻面对几百个查询还要保证每个人看到的号源是准的。这个问题的解法前面讲的remaining_count条件更新只是第一步更关键的是整个团队都要建立“并发敏感”的思维每个时序操作都要问一句如果两个请求同时进来系统会不会乱还有一点医患交互功能上线前一定要做隐私合规自查。昵称、头像、手机号、聊天图片这些都属于用户敏感信息小程序端不能随便存到本地接口传输要用 HTTPS后台展示时要打码。我在患者列表里默认只显示“张*”身份证号也只保留前四位和后四位完整信息只在管理员授权后才能看到。这个设计听起来简单但真到了审核环节它能帮你省掉一大半被驳回的风险。如果后面你想继续扩展我建议优先做“候诊队列提醒”和“电子病历档案”。排队叫号信息推送到小程序患者拿号后不用站在诊室门口等去看检查、买药也能收到提醒电子病历则把每次问诊记录和检查结果沉淀下来复诊时医生一眼看到历史比让患者翻纸质病历本强太多。在线医患交互预约这套底座搭好之后这些功能都只是往上加模块的事。