返回笔记列表
Note Detail

门诊结算一致性:状态条件更新、库存扣减与事务回滚

门诊结算与发药的一致性由就诊状态条件更新、事务内库存扣减和失败回滚共同保障;Redis 在本项目中用于候诊和缓存。

分类:Java 后端发布时间:2026年7月16日更新:2026年10月2日原创内容
PostgreSQLJSONBRedisTransaction

先说结论

门诊系统里的患者、处方、库存和结算不是四组独立 CRUD。只要模拟结算成功但库存未扣,或者一个处方被重复结算,页面看起来正常也不代表业务成立。

当前项目的模拟结算与发药链路主要依赖数据库状态和事务边界:

  1. 业务 ID 把患者、就诊单、处方和库存记录关联起来。
  2. 就诊单状态条件更新让并发确认只有一个请求能继续结算。
  3. 库存扣减、发药状态和结算记录在同一 Spring 事务中处理。
  4. 数量不合法或发药失败时中止流程并回滚状态和库存。

关系字段和 JSONB 怎么分工

患者 ID、就诊单 ID、药品编码、状态、金额等高频关联和强约束字段,适合保留在普通关系列中。它们需要清楚的类型、索引和外键语义。

过敏史、症状补充、既往史等结构可能变化、字段相对稀疏的病历内容,可以放入 PostgreSQL JSONB。这样能减少频繁改表,同时保留结构化查询能力。

JSONB 不是“所有字段都塞进去”的理由。稳定字段放进 JSONB 后,会让约束、关联和统计变得更困难。

GIN 索引适合加速 JSONB 包含、键和值检索,但它有写入和存储成本,也不是所有查询都会自动变快。是否加索引应该由真实查询条件决定,而不是看到 JSONB 就机械添加。

状态条件更新决定结算是否能继续

模拟结算以就诊单的待处理状态作为条件更新。并发请求只有一个能将该状态推进;其他请求因条件不再成立而不能重复完成结算。这是结算防重的数据库边界。

库存扣减和发药状态变更在同一个事务中完成。若校验或发药失败,抛出异常后回滚本次状态与库存变更,避免只完成一部分。

不要把其他写入场景使用的 UPSERT 泛化为当前结算的保证;本项目的并发确认依赖就诊状态条件更新,库存与发药一致性依赖同一事务。

Redis 在当前项目中的职责

Redis 用于候诊队列和若依基础缓存,不是结算发药流程的业务锁。把 Redis 当作结算并发保证会错误描述当前实现,也会掩盖数据库真正执行的状态判断。

即使其他系统在入口使用 Redis 锁,它也不能替代数据库约束和事务:

  • 锁可能超时。
  • 服务可能在持锁期间宕机。
  • 解锁实现不当可能误删别人的锁。
  • 其他写入口可能绕开 Redis。

因此结算流程仍需由数据库条件更新和事务兜住最终状态;当前项目没有以 Redis 锁保证结算防重。

事务回滚解决什么

假设一张处方包含多种药,其中一种无法发药。如果每次库存扣减各自提交,系统会留下“部分发药”的中间状态。

把结算状态、库存扣减和发药记录放进清晰的事务边界后,任一药品处理失败都应终止流程,使先前的扣减与状态修改一起回滚。仓库 README 记录的 PostgreSQL 集成测试覆盖并发确认、重复确认和失败回滚;该测试记录不等于生产医疗流程验收。

事务也有边界。跨外部服务、消息队列或第三方支付后,单库事务不再足够,需要幂等、补偿、事件状态或更完整的分布式一致性方案。当前项目并没有把自己包装成这一规模的生产医疗系统。

状态为什么不能只写“完成”

患者、就诊和库存真正串起来,需要状态能表达业务阶段。当前实现使用了简化状态;如果面向更严格的生产流程,更合理的表达应至少区分:

待缴费 -> 已缴费待发药 -> 已发药 -> 已归档

状态拆清楚后,系统才能判断哪些操作允许执行、失败应该回到哪里、重复请求是忽略还是报错。

项目边界

该项目基于 RuoYi 生态进行业务化二次开发,重点展示门诊流程、PostgreSQL 建模和一致性措施,不把通用权限框架包装为个人原创,也不宣称已经达到成熟医疗 SaaS 的生产合规水平。