做医院信息化这些年,我印象最深的项目就是预约挂号系统。项目代号weixin127——weixin代表整个系统跑在微信生态里,127则是因为立项那天正好是1月27日,版本号从v1.27一路延续下来。上线一年后,门诊部给的反馈很直接:非急诊窗口挂号的排队时间从平均47分钟压到了4分钟以内。但这组数据背后,是三个月和门诊部、财务科、信息科来回拉锯才磨出来的产物。这篇文章把这个系统的业务设计、技术实现和上线后踩过的坑完整复盘一遍。如果你是医院信息科的人、医疗信息化厂商的产品或开发,或者想给自家诊所搭一套轻量预约系统,这里面的经验应该能帮你少走不少弯路。
1. 从门诊大厅的乱象说起:预约挂号到底在解决什么问题
1.1 排队、黄牛与号源浪费是一笔糊涂账
先说说立项前医院的真实状况。三甲医院门诊大厅,早上六点就有家属在窗口前排队,队伍能排出二十多米。但真正的痛点不只是排队时间长,而是三个问题纠缠在一起:
- 号源分配不公平。专家号是硬通货,黄牛用大量手机号占号,然后高价转卖,患者花了冤枉钱还挂不上号。
- 号源利用率低。约了不来、来了又没带病历、临时停诊通知不到位,大量号源白白浪费。
- 数据一团模糊。哪个科室号源紧张、哪个时段患者流失最多、医生实际出诊率是多少,全靠手工台账,根本说不清。
立项前我们做了一次抽样统计:某热门科室专家号,爽约率达到18%,再算上临时停诊和迟到销号,每天挂出去的100个号里,将近30个是浪费掉的。这才是做预约挂号系统真正要解决的问题——不是做一个"在线选号"的界面,而是把号源变成可量化、可调控、可追踪的资源。如果只把窗口排队变成手机排队,那这个系统根本没有任何价值。
1.2 为什么选微信生态而不是自建App
这里曾经有过争论。医院管理层一开始想的是"做个自己的App",理由是"看起来正规、数据完全自主"。信息科结合实际情况否了这个方案,原因很现实:
- 患者没有下载陌生App的习惯。让患者为了挂号专门装一个App,在门诊现场指导下载注册的成本,比窗口挂号还高。
- 微信实名体系可以直接用。微信支付、手机号授权,天然把患者身份串起来了,省掉了自建账号体系和实名认证的合规成本。
- 通知触达顺畅。公众号模板消息、服务通知可以直接触达患者,自建App没有系统级的推送通道,离线就是失联。
- 小程序免安装,扫码就能进,对不熟悉智能手机的患者也比App友好得多。
所以最终方案是:C端用微信小程序加公众号H5,B端用Web管理后台。患者扫码关注公众号,点菜单进入挂号页。微信生态在医疗领域已经很成熟,合规性和患者接受度都经过市场验证,没必要重复造轮子。
1.3 医院管理端的隐藏诉求
很多外包项目死在"只做挂号"上。医院真正要的不只是患者能在手机上报一个号,而是科室能控号、医生能停诊、财务能对账、院长能看到数据。所以系统从第一天就规划了四类角色:患者、医生、导诊/分诊台、系统管理员(含财务)。每个角色都有自己的操作边界,后面章节详细讲。
这里特别提一下财务对账。预约挂号涉及线上支付,财务科最关心的不是患者挂没挂上号,而是每天的钱和单能不能对上。所以系统在支付设计上必须考虑对账能力,微信支付账单和本地订单逐笔核对,任何一笔不一致都要能追溯到原因。这一点很多早期方案都没考虑,我们是上线两周被财务科追着补了三个功能才补齐的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 业务模型怎么搭:挂号不只是"选医生"
2.1 从就医全流程倒推
设计业务模型时,我们没有直接照搬别人家系统的界面,而是把患者的就医路径完整画了一遍:挂号、候诊、就诊、缴费、检查/取药、复诊。预约挂号解决的是第一个环节,但它的数据必须能跟后面的HIS(医院信息系统)对接,否则挂号信息进不了临床系统,等于白挂。
这意味着系统从第一天就要考虑接口层。预约成功后生成的不是一条孤立的记录,而是要同步到HIS的挂号队列里。很多诊所和小医院没有HIS,那至少要能导出分诊台能用的号表,甚至直接联动分时段签到。我们当时做了个折中:有HIS接口的直接对接HIS,没有的走分诊台导诊单打印。后来诊所客户多了,又做了一个极简版HIS对接包,只同步患者基本信息、挂号科室和时段。
2.2 号源模型和时段粒度
这是预约挂号系统里最容易拍脑袋设计错的地方。号源怎么切?很多初版设计都是上午/下午两大段,结果上午段全挤在8点,下午段全挤在14点,等于把线下排队挪到了线上。我们必须把号源切成有时段的颗粒度。
时段的长度取决于科室的看诊节奏。我们最初统一用30分钟一个时段,后来发现儿科和内科的节奏完全不一样:
- 儿科患者平均看诊3-5分钟,疑难杂症少,节奏快。
- 内科慢病门诊平均看诊10-15分钟,问诊时间长。
- 专家号相对慢,复诊比例高,时段要比普通号更宽。
同一个科室、不同医生,时段分配也应该能配置。后来我们把排班表做成"按医生维度配置时段和号数"的模式:医生A可以上午放15个号、每次10分钟;医生B可以上午放8个号、每次20分钟。号源表结构上支持这种灵活性,运营时再根据实际看诊速度调优。
2.3 角色权限和状态流转
系统角色虽然多,但权限边界一定要清晰。以医生为例,他关心的不是"谁预约了我",而是"我今天有几号、几点能结束、有没有人要停诊处理"。所以医生端只开放三块:查看排班、执行停诊、标记已完成。分诊台权限更大一些,可以处理补号、改约、签到异常,但涉及退费的必须甩给财务角色。
号源状态是整个系统的核心状态机,必须定义清楚:
- 可预约(初始状态)
- 锁定中(用户选号后未支付,支付倒计时中)
- 已支付(支付回调成功)
- 已取号(患者到院报到)
- 已完成(就诊结束)
- 已取消(用户主动取消)
- 已释放(锁定时长到期或迟到销号)
- 已停诊(医生停诊触发)
这个状态机直接决定了后面所有容错逻辑怎么写。我们当时把状态机画在需求文档首页,开发前让全员过了一遍,就是为了避免后面出现"已取消的订单还能被支付回调改成已支付"这种逻辑漏洞。
2.4 数据库核心表设计
放一个简化版本的表结构供参考,正式项目里还会有日志、配置、审计等扩展表,但核心思路是这三张:
预约订单表(booking_order)
| 字段 | 说明 |
|---|---|
| id | 订单主键 |
| patient_id | 患者ID |
| schedule_id | 排班(号源)ID |
| booking_no | 预约号(给医院窗口看的流水号) |
| status | 订单状态(0待支付 1已支付 2已取消 3已超时) |
| pay_amount | 支付金额 |
| created_at | 创建时间 |
排班表(doctor_schedule)
| 字段 | 说明 |
|---|---|
| id | 排班主键 |
| doctor_id | 医生ID |
| dept_id | 科室ID |
| work_date | 出诊日期 |
| start_time | 时段开始 |
| end_time | 时段结束 |
| total_num | 总号数 |
| booked_num | 已预约号数 |
| status | 排班状态(0正常 1停诊) |
患者表(patient)
| 字段 | 说明 |
|---|---|
| id | 患者主键 |
| openid | 微信openid |
| real_name | 实名姓名 |
| id_card | 身份证号 |
| phone | 手机号 |
这三张表是经典的"档期+订单"模型。关键在于表的索引设计和并发控制,下一节直接讲。
3. 关键技术实现:锁号、防超卖与通知链路
3.1 号源并发控制
所有在预约挂号系统上踩过坑的人都懂一个词:超卖。用户同时点进去,一个热门专家的最后一个号被很多人同时抢,如果并发控制没做好,数据库里会出现booked_num大于total_num的脏数据,患者也挂不上号。这是绝对不能容忍的。
我们的方案分两层。
第一层,数据库层面用乐观锁。排班表里加一个版本号,或者直接用条件更新:
sql复制UPDATE doctor_schedule
SET booked_num = booked_num + 1
WHERE id = #{scheduleId}
AND booked_num < total_num
AND status = 0;
affectedRows等于1才说明扣号成功,等于0则说明号没了或排班停诊了。这是最底层的原子保障,任何应用层的查询都属于"预检查"——真正锁住号,只有这个UPDATE返回1才算数。
第二层,在热点号源上做Redis预扣。因为数据库UPDATE在极端并发下会产生排队和锁竞争,对超级热门专家号,可以在Redis里维护号源余量,先用DECR原子扣减,扣成功再走数据库落订单。但设计上必须小心:Redis预扣之后如果用户没有下单,需要把余量回补。回补逻辑要放在事务回调里,不能简单地在异常catch里加1,否则多个异常叠加会重复回补,把余量加超了。
我试过纯Redis方案,也试过纯数据库方案,最稳的其实是"Redis预扣+数据库乐观锁+异步对账"三者组合。极端场景下单量基本都在100毫秒内完成,上线一年多没有出现过一次超卖。
3.2 先锁号还是先支付?支付窗口的设计
这里有个业务上的纠结:用户选了号之后,什么时候支付?先支付后锁号,会导致用户付完钱发现没号,体验极差;先锁号不支付,万一用户占着号不付,号源就废了。
我们最终采用"先锁号+15分钟支付窗口"的模式:
- 用户选号,系统原子扣减号源,生成待支付订单。
- 前端开始15分钟倒计时,倒计时结束未支付,定时任务将订单置为超时关闭,号源回补。
- 支付成功回调后,订单状态置为已支付,号源状态保持不变,向患者推送预约成功通知。
15分钟这个值不是拍脑袋的。我们统计过从选定到完成支付的平均时间,90%的支付行为发生在3分钟以内,15分钟已经给了极大的余量。太短会导致用户退款重选,太长会把号源锁死在爽约用户手里。
支付回调时要特别注意幂等。微信支付回调可能重复推送,也可能因为网络延迟,所以回调处理函数必须先查订单状态,只有待支付状态才处理更新,否则直接返回成功。我们见过一个经典bug:开发环境正常,上线后偶尔出现支付成功但订单还是待支付,最后定位到是回调处理里先更新了订单又查订单,导致重复回调时判断错乱。
3.3 微信通知链路
预约成功、停诊、就诊提醒,这三条消息是患者感知系统存在的主要方式。我们用微信订阅消息和模板消息组合:
- 预约成功时,用户端主动触发订阅授权,申请"预约成功通知"和"就诊提醒"。
- 支付成功后,后端调用模板消息接口推送预约成功通知,内容和订单号、就诊时间、诊室位置有关。
- 停诊时,由医生端或管理后台发起停诊操作,系统批量给受影响患者推停诊通知,并附上自动改约链接。
这里有个容易被忽略的坑:订阅消息必须用户主动授权才能发,小程序里要在关键动作(如"确认预约"按钮点击)时调用订阅消息授权,否则后续推送权限拿不到。我们曾经出现过开发环境一切正常、上线后发现通知发不出去的故障,排查半天,原因就是测试账号订阅了,线上新用户没有走授权流程。所以一定要在真实用户链路里测试授权弹窗。
模板消息涉及内容审核,别把"爽约""欠费"这类负面字眼放进去,审核容易被打回。我们第一次提交模板时写了"您预约的号源已取消",审核被拒,改成"您预约的时段发生调整"才通过。
3.4 异常补偿机制
预约挂号系统最怕"静默失败"。支付成功但没收到回调、定时任务挂了没释放号源、消息推送失败没重试——这些问题如果只靠人工发现,必然出事。所以我们给系统设计了三个补偿任务:
- 每分钟扫描待支付且创建超过15分钟的订单,关闭订单并回补号源。
- 每5分钟扫描支付成功但未同步到HIS的订单,触发重推。
- 每日凌晨对账,拉取微信支付账单和订单表比对金额,差异自动告警。
这些补偿任务看起来是"脏活",但在实际运营里比主流程还重要。上线后有一半的线上问题都是靠对账任务先发现的,而不是患者投诉后才被动处理。
4. 上线之后的坑:停诊、号源释放与回调丢单
4.1 医生停诊引发的"改号雪崩"
系统上线第三周,第一次遇到大规模停诊。一位知名老专家临时请病假,当天下午到未来三天的号,已经预约了四十多人。这里的麻烦不只是取消四十个订单,而是:
- 已经支付的患者要退款,走原路退回流程。
- 没支付但锁定中的订单要立即释放。
- 停诊信息要通知到每一个患者,否则患者直接去医院才发现白跑一趟。
- 有复诊需求的患者还想改约到下周。
第一版我们只做了"批量取消+退款",结果被门诊部投诉:四十多个人同时接到"您的预约已取消"的推送,但没有任何改约指引,电话打爆了导诊台,现场秩序一度失控。
后来我们把停诊流程升级成"停诊工作流":医生停诊前,系统自动检查未来7天内所有相关号源;对已支付患者优先推荐同科室同时段的替代号源,允许一键改约;改约不成功才进入退款队列。替代号源由门诊部提前配置备选医生。这个过程走完,患者体验从"被甩开"变成"被照顾",投诉量明显下降。
这里要提醒:停诊流程必须有人工审核环节,不能让医生一键就散布出去。备选医生的推荐要做到同职称级别优先,主治的号推给主任医师的患者,病人不会满意。
4.2 号源释放的时机
另一个常见问题是迟到患者的号源。患者预约9:00-9:30的号,9:35才到医院,号源算不算浪费?
我们一开始的策略是"过点不候",迟到15分钟自动销号,号源重新释放。严格执行之后发现不行:三甲医院附近堵车太常见,患者晚来一两分钟就被销号,投诉率高得吓人。但完全不做释放,又会有人恶意占号。
权衡后的方案是"迟到缓冲期":超过预约时段15分钟未取号,系统标记为待补号;如果该号源没有被其他人预约,患者仍然可以取号就诊;如果已被预约,则自动安排到下一个可约时段。实际效果是,既保留了号源利用效率,也给了真实患者宽容度。这个策略门诊部的反馈很好。
4.3 微信支付回调丢单与幂等处理
上线一个多月,我们遇到过一次"患者明明付了钱,系统显示待支付"的事件。用户截图支付成功页面来投诉,我们查订单发现状态是待支付,又去微信账单里确认钱确实到了商户号。问题出在支付回调丢失上。
微信支付回调网关虽然会重试,但重试的间隔可能很久,并且在部分弱网环境、回调地址短时不可用的情况下,确实会出现回调丢失的情况。对策就是前面提到的对账补偿:每天凌晨拉取前一天的微信支付账单,逐笔比对订单状态,状态不一致的会自动"补单",将待支付订单核销为已支付。
这里最危险的操作是"人工手动改状态"。有一次同事直接改库把订单改成已支付,结果HIS那边已经同步了、微信账单还没确认,导致财务对账差异,最后花了一个多星期才平账。所以我们立了个规矩:任何订单状态变更必须通过接口操作,禁止直接改库。这个规矩后来救了我们很多次。
4.4 用风控规则挡住黄牛和恶意占号
黄牛问题在预约挂号系统里是逃不掉的。我们的反黄牛策略没有用复杂的人工智能,而是几条硬规则:
- 同一身份证号30天内最多预约同一科室2次,超出自动拦截。
- 同一手机号最多绑定3个患者档案,防止批量注册。
- 同一时段(同一排班ID)禁止同一身份证重复预约。
- 高风险号源(专家号)记录用户设备指纹,同一设备异常高频操作触发人工审核。
效果立竿见影:专家号黄牛占比从上线的12%降到了一周后的不到1%。代价是偶尔误伤老人帮子女挂号的情况(同一个设备给两位老人挂号被拦),后来加了容错:同一设备不同身份证号且间隔时间超过30分钟,只记录不拦截。
5. 复盘与迭代:数据、节奏与信用体系
5.1 数据看板要看什么指标
系统上线三个月后,我做得最多的事就是和门诊部每周看一次数据。真正有决策价值的指标不是"挂号总数",而是下面这几个:
| 指标 | 计算口径 | 关注点 |
|---|---|---|
| 预约率 | 已支付号源数/放号总数 | 号源利用率 |
| 出诊率 | 实际出诊医生数/排班医生数 | 排班计划合理性 |
| 爽约率 | 未取号且未取消的订单/已支付订单 | 号源浪费与黑名单依据 |
| 平均候诊时间 | 取号时间到叫号时间的平均差 | 时段粒度是否合理 |
| 取消率 | 取消订单/总订单 | 患者改约意愿与规则松紧 |
有一周我们发现某科室的候诊时间平均37分钟,但时段粒度是15分钟,明显不匹配。排查发现是复诊患者太多,复诊不需要挂号直接去找医生,打乱了叫号节奏。这个数据直接推动了复诊预约流程的立项。所以数据不只是汇报材料,它是排班和分诊优化的依据。
5.2 放号时间与取消截止时间的调优
放号时间是个运营细节,但影响很大。最初我们和很多系统一样,凌晨0点放号,结果患者凌晨守点抢号,很多人为了抢号熬夜。我们把热门科室的放号时间改到早上7点,配合微信的通知(关注公众号的用户会收到放号提醒),一周后热门科室预约率反而涨了5个百分点——大家不用半夜守着手机了,早上起来从容挂。
取消截止时间也很讲究。定在就诊前2小时,能够最大化保留号源重新分配的机会。太晚了患者忘记取消、号源就废了;太早了患者可能临时有事想取消却没权限,只能到现场排退号。
5.3 信用体系与黑名单的演进
黑名单机制从上线第一天就有,但最初的规则太粗——爽约3次拉黑30天,结果一位慢性病患者因为病情反复、连续两次没取号,直接被禁挂,患者投诉说"我病还没好,你就不让我挂号了"。
这让我们重新思考了黑名单的目标。黑名单不是为了惩罚患者,而是为了降低号源浪费。后来改成了信用分机制:正常就诊加信用分,爽约扣分,取消预约不扣分但降低优先级(比如系统推荐成功率和信用分挂钩)。同时,黑名单不再单纯禁止挂号,而是要求信用分低于阈值的用户线下签到后优先补号,既不耽误就医,也对占用号源的行为形成约束。
5.4 后续可以扩展的方向
系统跑稳定后,我们把目光放在了更大范围的资源预约上:复诊预约、检查预约(超声、CT)、门诊手术预约。逻辑上和挂号预约是相通的,但复杂在资源维度不同——检查预约还要考虑设备产能、技师排班、患者准备条件(空腹、憋尿等)。这一块还在迭代中,等有阶段性成果了再单独分享。
最后说一点个人体会:预约挂号系统看起来是个小系统,但它涉及的患者身份、资金流、医疗资源调度和异常容错,样样都是硬骨头。代码能跑通只是第一步,真正让医院和患者都说"好用"的,是业务细节的打磨和对异常情况的预判。如果你也在做类似系统,建议在需求阶段多花时间去挂号窗口、分诊台蹲几天,看看真正的现场,比看一百份需求文档都有用。
