去年年底我们信息科接到后勤管理部的需求医院目前的餐补管理太乱了财务每个月对账单要加班三天职工投诉餐补不到账的工单一个月能接到近百条。领导拍板说这个系统你们信息科主导得尽快上线。刚开始我以为这无非就是个充钱-扣钱-查余额的CRUD系统结果需求评审一开发现事情远比想象中复杂。多院区多食堂多消费终端的数据同步、HRP系统的实时对接、定时任务的可靠性保障、高并发消费场景下的数据一致性还有各种诡异的补贴清零规则哪个都不是省油的灯。这篇文档把我们在好伙狮平台基础上做二次开发和对接落地的过程梳理出来给同行做个参考。一、系统架构设计先给个整体架构的大图。我们采用了微服务消息队列的架构核心服务拆分为五大模块补贴人员管理服务User-Wallet Service负责职工信息同步、钱包创建、补贴账户管理。这个模块是基础所有后续的发放和消费都依赖它。我们和HRP系统做了双向对接HRP推职工变更事件过来本服务消费后更新本地缓存。补贴发放引擎Subsidy-Dispatch Engine核心发放逻辑的承载模块。支持批量导入发放、定时发放任务调度、发放规则校验。这里我们用Quartz做调度Redis做分布式锁防止重复执行。消费交易服务Transaction Service处理各个消费终端的实时交易请求。这个模块对并发和响应时间要求最高午间高峰时段TPS能达到三百左右。我们用了本地缓存Redis二级缓存来扛住读压力。补贴清零服务Clearance Service管理各种清零规则。按月清零、按日清零、按人员分组差异化清零。这个模块的难点在于规则引擎的设计后面会详细展开。统计报表服务Report Service定时跑批生成月度汇总表和发放明细表数据量大时走ClickHouse做OLAP查询避免拖垮主库。二、核心数据流程从发放到消费再到清零的完整链路先说最重要的主流程。一笔餐补从财务发起发放到最终被职工消费掉的完整链路是这样的第一步财务人员在PC管理端导入补贴发放Excel或者设定定时发放任务。系统拿到发放数据后先做基础校验职工ID是否存在、金额是否合法、发放周期是否重复。校验通过后生成一批待发放状态的补贴记录写入MySQL的subsidy_dispatch表。第二步发放引擎异步处理。每批发放记录按职工ID哈希分片投递到不同的Kafka分区各消费线程并行处理。对于每个职工的补贴记录先扣减财务账户的预算额度再增加职工补贴钱包的余额最后更新发放记录状态为已发放。这里必须保证财务账户扣减和职工钱包增加要么同时成功、要么同时失败所以我们引入了TCC分布式事务方案——try阶段锁定双方账户余额confirm阶段执行真正的加减操作cancel阶段回滚。第三步职工在食堂消费终端刷卡或刷脸。消费终端发起的请求量在午餐高峰时段非常集中我们的消费交易服务采用了一致性哈希路由确保同一职工的请求始终落在同一台服务器上这样可以直接使用本地内存中的余额缓存来判断是否足以支付而不需要每次都查数据库。余额扣减走Redis的Lua脚本原子性地完成余额检查和扣减。第四步到了清零时间点清零服务被触发。它会扫描所有符合清零规则的职工账户执行余额清零操作并生成清零日志。这个操作必须非常谨慎——我们曾经因为一个清零规则的BUG把全院两千多职工的当月餐补全部清掉了那次应急恢复熬了个通宵。后面会细说这个教训。三、HRP系统对接的设计与踩坑HRP系统的对接是项目中最让人头疼的部分。医院用的是某品牌的老版本HRP接口文档不全而且只支持被动查询模式——也就是说我们没法让HRP在人员变动时主动推送消息过来只能我们定时去拉。最初的方案是全量拉取每天凌晨2点定时任务走HTTP接口拉取全院职工列表拿回来和本地数据库做diff找出新增的、离职的、部门变动的然后更新补贴系统中的职工档案。这个方案一开始能跑但问题很快就暴露了。凌晨跑全量同步时全院一万多名职工的数据拉取需要差不多十五分钟接口还时不时超时。更要命的是上午十点如果有新职工入职他要等到第二天凌晨才能用上餐补后勤那边意见很大。后来我们做了两点优化。第一跟HRP厂商协商后开放了一个增量查询接口按lastModifyTime分页拉取变更记录每五分钟轮询一次每次只拉变更了的那几十条数据效率提升了两个数量级。第二对于紧急入职场景增加了管理后台的手动同步按钮管理员可以手动触发单个人的信息同步。对接中另一个坑是数据格式不一致。HRP中的部门编码和我们补贴系统中的科室编码完全是两套体系——HRP用的是三位数字编码我们用的是科室拼音首字母缩写。最初我们写死了一个映射表来转换结果每次医院调整科室架构时都要更新映射表。最终的方案是在中间层建了一个组织架构字典表HRP和补贴系统各自维护自己的编码通过字典表建立映射关系新增科室时管理员在后台配置一下就行。四、定时发放任务的可靠性设计财务部门对定时发放的要求非常明确每月1号上午9点准时到账一次都不能出错、一次都不能延迟。这个看似简单的要求在分布式环境下其实有不少坑。第一个坑是重复执行。Quartz在集群模式下通过数据库锁来保证同一时刻只有一个节点执行某个任务但网络抖动时可能出现锁超时释放导致两个节点同时拿到了执行权。我们的解决方式是增加业务层面的幂等校验每次发放任务执行前先检查当天是否已经有过成功的发放记录如果有则跳过。发放批次号用日期递增序号组成数据库做了唯一约束防重复写入。第二个坑是执行中断。全院六千多职工的补贴发放如果中途服务挂了已经发放的两千多人的补贴不能丢也不能重发。我们采用了分批提交的策略每处理完两百人的发放提交一次事务。服务重启后从未完成的批次继续执行已完成的批次不受影响。第三个坑是时间漂移。有一段时间我们用的服务器没有配置NTP系统时间比实际时间慢了大概三分钟。结果就是职工在群里刷屏说好九点到账的餐补呢而我们查日志发现任务明明执行了仔细一看是还没到时间。后来强制所有服务器走NTP同步并且在发放任务执行前加了一个对时校验如果和NTP时间偏差超过三十秒就报警。五、清零规则的配置化引擎清零逻辑看起来简单——到时间了把余额归零就行。但医院的实际规则非常复杂普通职工月底清零但临床一线人员可以保留半年外包服务人员按日清零有些科室领导有免清零特权还有春节加班期间发放的专项补贴不能清零。如果把这些规则硬编码到代码里每次政策调整都得改代码发版。我们的做法是设计一套规则配置引擎。清零规则抽象为三个维度对象范围全员/部门/分组/个人、时间周期按日/按周/按月/按年/不执行、豁免条件指定人员/指定时间段/指定补贴类型。每个规则是一条配置记录清零引擎在执行时按优先级加载所有适用规则对每个职工账户计算出最终的清零策略。值得一提的是我们踩过的一个大坑。上线初期清零服务是每天凌晨跑一个全表扫描把所有余额不为零且已过清零日期的账户找出来清零。这个SQL在数据量小的时候没问题但运行半年后历史账户记录超过几十万条全表扫描直接拖死了数据库。后来的优化方案是建一个待清零索引表只记录当前有余额且设置了清零规则的账户清零服务只扫描索引表。同时把清零执行时间从凌晨挪到凌晨三点避开备份窗口。六、消费终端的接口设计食堂档口的消费机是一个典型的嵌入式设备Android系统内存有限。我们的消费接口必须足够轻量响应时间要控制在五百毫秒以内——你肯定不想看到职工刷了脸还要站着等三秒钟才扣款成功。接口设计上消费请求的报文非常精简请求只包含三个字段用户标识手机号或工号、消费金额、消费终端编号。服务端拿到请求后依次校验用户是否存在、补贴钱包余额是否充足、消费金额是否超过单笔限额、当天消费次数是否超限。校验通过后扣减余额并写入消费流水。所有校验和扣减都在Redis中通过一个Lua脚本原子完成避免了网络往返带来的性能损耗和并发问题。消费接口的另一个要点是组合支付。医院的要求是消费时优先扣减补贴钱包补贴不够的部分从充值钱包补足。这个逻辑在Lua脚本中实现先尝试从补贴钱包扣减全部金额如果余额不足则扣减剩余部分到补贴为零再从充值钱包扣减差额。整个过程在一个Redis事务中完成保证原子性。我们还做了一个关键的容灾设计。万一Redis挂了或者网络断了消费终端不能完全停摆。我们在消费终端上做了本地缓存每次成功交易后终端把用户当前余额缓存到本地。断网时终端可以读取本地缓存的余额做离线扣减恢复网络后批量同步交易记录到服务端。当然这个离线模式的余额不是实时准确的所以我们限定了离线模式下的单笔消费上限和累计消费上限控制风险。七、数据一致性的兜底方案再好的架构也架不住各种意外。数据库主从切换、消息队列堆积、网络分区这些在生产环境迟早会碰上。我们为补贴系统设计了多层兜底机制。第一层是实时对账。每一笔发放和消费交易都生成一个全局唯一的交易流水号写入MySQL的同时也发一条Kafka消息给对账服务。对账服务消费消息后写入Elasticsearch提供实时的交易查询能力。如果服务端标识交易成功但Kafka消息没有送达对账服务会通过定时任务扫描MySQL的流水表补齐。第二层是T1日终对账。每天凌晨跑批把前一天的发放总额、消费总额、各账户余额汇总与财务系统做交叉核对。如果出现差异——比如所有职工的余额总和与财务账面不符——立即告警并触发人工核查。第三层是月末全量对账。每月最后一天生成完整的对账报告包括期初余额、当月发放、当月消费、当月清零、期末余额每个数字都有明细支持确保账账相符、账实相符。我们曾通过这个对账机制发现了一个隐蔽的BUG在某些特定条件下消费接口超时后事务回滚了但Redis中的余额已经扣减导致数据库和Redis数据不一致。发现问题后我们立刻修复了事务边界的问题。八、上线效果与总结系统上线运行至今已经超过一年覆盖了医院三个院区、六个食堂、近万职工。几个关键指标的变化足以说明效果财务月末对账时间从近三天压缩到了半天以内对账准确率从约九成提升到超过百分之九十九职工关于餐补的咨询工单减少了约八成补贴发放从过去由专人花费半天手动操作变成了一键导入后系统自动完成。回顾整个项目我最大的体会是做医院内部的数字化系统技术难点往往不在于某个高深算法或前沿框架而在于对业务规则的精确理解和边界条件的妥善处理。一个补贴清零规则没想清楚就可能让全院职工的餐补一夜归零一个HRP接口的超时没处理好新入职职工就要饿一天肚子。这些细节恰恰也是好伙狮数字食堂这类垂直行业平台的价值所在——它们沉淀了大量医院场景的实践经验让你不需要从零开始踩坑。如果你也正在或将要负责类似的医院后勤数字化项目希望这篇记录能提供一点参考。遇到坑是难免的但踩得越早上线就越稳。