☰
Spring Boot医院管理系统实战:从并发挂号到权限设计
2026/10/18 9:27:01 网站建设 项目流程

前几天一个读者把一套“基于Spring Boot的医院管理系统”的源码甩给我,说自己照着网上的教程拼了好久,答辩前发现挂号余数变成负数,药房发药的记录和收费对不上。我说这事太典型了,这套系统我前后评审过好几版,自己也从零搭过一版,坑踩得比代码写得多。今天就把这些经验整理出来,希望能让正在做毕设、实训,或者想搞一个靠谱医院管理系统的你少走点弯路。

这套系统表面上是个“增删改查大礼包”,但真正落地时你会发现,挂号余号的并发扣减、病历和处方的状态流转、三类角色的权限边界,每一个都是实打实的工程问题。文章会从业务边界、技术选型、数据库建模、核心流程实现,一直聊到排查过的典型坑和扩展方向,尽量做到拿过来就能用。

1. 这类项目到底解决了什么问题:先把业务边界画清楚

1.1 医院里真正让人头疼的事

医院门诊最原始的运行方式是这样的:患者在窗口排队挂号,拿到一张纸质的挂号单;医生看诊后在病历本上手写诊断;患者拿着处方去收费窗口缴费,再去药房取药。这套流程看起来没毛病,但对于管理者来说,处处都是问题。

挂号高峰期的排队人数没法预测,医生一天的接诊量没有数字化记录,药品库存只能靠人工盘点,财务对账靠的是一叠手写单据。更重要的是,各个部门之间的信息是割裂的:收费窗口不知道药房还有没有库存,医生看不到患者上一家医院的检查结果,管理者也拿不到实时的门诊统计。

医院管理系统要解决的,就是把这些割裂的环节串成一条数字化的链路。挂号的号源是先在数据库里定好的,医生登录系统可以看到候诊列表,开处方时系统会校验库存,收费完成后药房自动接收到发药任务,管理者在后台看到的是一张张实时更新的统计报表。业务边界一旦画清楚,后面所有设计都有了依据。

1.2 为什么Spring Boot是这个题目的默认答案

我不止一次遇到过有人问:做个医院管理系统,用Servlet+JSP行不行?用Python Flask行不行?答案都是“行”,但综合成本最高的还是Spring Boot。

Spring Boot最讨喜的地方是“开箱即用”。内嵌的Tomcat省掉了独立部署中间件的步骤,自动配置把以前Spring和Spring MVC那一堆XML配置全部藏到了背后,Maven管理依赖也足够省心。对于一个以业务逻辑为主的项目来说,技术栈的不折腾就是最大的效率。

另一方面,Spring Boot的生态实在太完整了。做权限可以接Spring Security或者Shiro,做缓存可以接Redis,做持久层有MyBatis Plus和Spring Data JPA,做接口文档有Swagger。每一个模块都有成熟方案,你需要做的只是组合。这个生态带来的另一个隐性好处是:大多数公司和高校都在用这套技术栈,做完这个项目写进简历,面试官一眼就能看懂你在做什么。

1.3 谁适合做这套系统:毕设、练手、还是小诊所

按照我的经验,做这类系统的通常分三种人,需求完全不一样。

第一种是做毕业设计或课程设计的学生。他们最需要的是“模块完整、流程清晰、能跑起来、答辩有的讲”,选Spring Boot+MyBatis Plus+Thymeleaf或者Vue都合适,重点是把挂号、就诊、收费这条主线做扎实,再配上数据库设计文档和部署说明,就是一份很不错的作品。

第二种是想通过练手提升工程能力的开发者。这类人我反而不建议直接下载一套现成的源码改一改,而是应该从建表开始自己写一遍。只有亲手设计过那张“挂号记录表”,你才会知道为什么余号字段要放在医生排班表里,而不是放在挂号记录表里。

第三种是小型诊所或者校医院的信息化需求。市面上商用HIS系统又贵又重,一个小型门诊可能只需要几十个并发、几个科室、几名医生,这种场景下Spring Boot单体应用完全够用。不过做真实部署时,医院流程、处方规范和财务对账要比毕设复杂得多,只适合在非常明确的前提下落地,别指望一套通用源码直接换肤成正式系统。

2. 技术选型与整体架构:不是越新越好,而是越合适越好

2.1 后端框架对比:Spring Boot赢在“不费脑子”

曾经有一段时间,做Java Web项目的主流方式是SSH(Struts+Spring+Hibernate),再早一点还有纯粹的Servlet+JSP。现在回头看,都不是不能写,而是把太多精力浪费在了配置和模板代码上。

纯Servlet需要自己处理请求转发、参数封装、事务管理,业务代码还没写几行,基础设施的重复代码已经堆起来了。Struts的问题则是配置路径和Action管理太繁琐,现在的新项目基本不会有人选它。

Spring Boot把Servlet容器、事务管理器、数据源、JSON序列化、参数校验这些组件,全部通过自动配置帮你接好了。你只需要在application.yml里写几行配置,然后一门心思去写Service和Mapper。对一个以CRUD和复杂业务流转为核心的医院管理系统来说,“不费脑子”就是最大的竞争力。

2.2 持久层选择:MyBatis Plus用起来最顺手

持久层我见过三种做法:原生MyBatis写XML、Spring Data JPA、MyBatis Plus。这个项目里我最推荐第三种。

原生MyBatis的优势是SQL完全自己控制,缺点是单表CRUD也要写一堆重复的XML片段。Spring Data JPA开发速度快,但动态查询和复杂SQL优化起来,反而不如直接写SQL直观。

MyBatis Plus相当于在两者之间取了一个平衡。单表操作直接继承BaseMapper,分页用一个内置插件就能搞定,代码生成器可以一键生成实体类、Mapper、Service、Controller,特别是医院管理系统这种实体多、字段多的项目,生成器能省下大量体力活。复杂查询就自己写XML,比如“按医生名称、时间段、患者姓名组合条件查挂号记录”这种多条件动态SQL,MyBatis Plus提供的Wrapper也能支持。

2.3 前端方案:模板渲染还是前后端分离

前端方案是这个项目里最容易纠结的选择。实际做下来,我的建议是看你的部署场景和答辩需求。

如果追求简单,选Thymeleaf服务端渲染。后端用一个模板,循环遍历挂号列表,表单提交走form或者简单的Ajax,整个项目打包成一个jar就能部署,新手理解起来几乎没有负担。

如果希望简历上多一个关键词“前后端分离”,就选Vue + Element UI + Axios这种组合。后端只提供JSON接口,前端单独跑在Nginx上或者由Spring Boot托管静态资源。代价是你需要处理跨域、Token存储、前端路由守卫、接口联调这些额外问题。

我个人在这类项目里更偏向Vue + Element UI,因为医院管理系统页面多、表格多、表单交互频繁,纯模板引擎写出来的交互体验会比较僵硬。但如果你时间紧,比如只有两周就要交,那就老实选Thymeleaf,先把业务跑通再说。

2.4 工程目录和配套文档:源码之外的加分项

一个“源码+文档”的医院管理系统,文档往往是被忽略的加分项。源码能跑起来只是及格线,文档才能证明你真的理解这个系统。

工程结构我习惯这样分:

  • config:存放Knife4j配置、MyBatis Plus分页插件配置、跨域配置、拦截器配置
  • controller:按模块拆,比如前端门户的controller、医生端controller、管理后台controller
  • service:接口+impl实现类,业务逻辑统一放这里
  • mapper:数据访问层,复杂SQL写在XML里
  • entity:数据库表对应的实体类
  • dto / vo:入参对象和返回给前端的视图对象,避免直出实体
  • common:统一返回结果封装、异常处理、工具类

配套文档至少要包括三份:数据库设计文档(每个表的字段说明和关联关系)、接口文档(Knife4j自动生成)、部署文档(JDK版本、MySQL初始化、配置文件修改、启动方式)。很多人在答辩或面试时被追问“你这个表为什么这么设计”,如果当时写了数据库设计文档,面对这种问题会从容很多。

3. 数据库建模:先设计好这些表,功能开发就顺了一大半

3.1 用户、角色、权限:三张表起步

医院管理系统里至少有三种角色:管理员、医生、收费员,有些系统还有药师和护士。如果每种角色单独建一张表,后面想加角色就得加表,维护起来非常痛苦。

正确的做法是经典的RBAC模型,拆成用户表、角色表、关联表。用户表放登录账号、BCrypt加密后的密码、姓名、手机号、状态。角色表放角色编码和角色名。用户角色关联表虽然很薄,但它是多对多关系的关键。

如果后面想做得更细,可以再加一张菜单权限表,把用户能看到的菜单和按钮权限都做成数据,这样管理后台的动态菜单就有了依据。不过对于第一版,这三张表已经足够支撑“拦截器校验角色”这个核心诉求了。

3.2 科室、医生、排班:挂号的源头数据

患者挂号之前,系统里必须先有科室、医生和排班数据。这三张表的设计会直接影响挂号模块的代码复杂度。

科室表比较好理解,存科室名称、所在楼层位置、联系电话、科室介绍。医生表需要注意关联关系:医生本质上也是一个用户,所以doctor_info表里要有一个user_id字段关联到sys_user表,同时还要关联科室表,再加上职称、擅长方向、简介、是否出诊状态。

排班表是整个挂号功能的命门。医生排班表要记录医生ID、科室ID、排班日期、上午或下午时段、总号数、剩余号数、是否停诊。两个容易出错的设计点是:日期最好存date类型,余号字段一定放在排班表里而不是挂号记录表里,否则挂号时没法原子性地判断“还剩几个号”。

3.3 挂号与就诊记录:主流程的中间载体

挂号记录表是整个系统的核心流水表。它记录了谁在什么时间挂了哪个科室哪个医生的号,以及这条挂号记录当前的状态。字段至少包括:挂号单号、患者姓名、患者证件号、患者手机号、科室ID、医生ID、排班ID、挂号日期、就诊时段、状态、创建时间。

挂号单号建议单独生成,不要用自增ID直接当单号给患者看。可以用时间戳加随机数,或者用一个流水号生成逻辑。患者证件号和手机号要在挂号时录入,因为后面打印病历、发送通知都要用。

患者就诊之后,还需要一张诊断记录表来存放医生的接诊结果。它关联挂号记录,包含主诉、现病史、初步诊断、处理意见、医嘱内容、下次复诊时间等。这里要注意的是,一次就诊可以对应多次开药,所以处方不能直接挂在诊断记录表上,而应该单独建处方主表和处方明细表。

3.4 药品、库存与处方:药房最关心的三张表

药品相关的表在这个系统里可以设计得非常复杂,但对于第一版,三张表就够了。

药品信息表保存药品的固定信息:药品编码、药品名称、规格、生产厂家、单位、销售单价、有效期。药品库存表要关联药品ID,记录当前库存数量和预警阈值。处方表拆成主表和明细表:处方主表关联就诊记录和医生ID,记录开方时间、处方状态;处方明细表每一行是一个药品,记录药品ID、数量、单价、小计金额。

这样的好处是,医生开处方时只需往明细表里插入数据;收费时遍历明细计算总价;药房发药时逐条扣减库存。三个环节各管各的,不会互相阻塞。

3.5 收费与统计:钱的事不能含糊

收费记录表的字段要足够严谨:收费单号、挂号记录ID或就诊记录ID、收费类型(挂号费、药费、检查费)、收费金额、支付方式(现金、扫码、银行卡)、收费操作人ID、收费时间。

这里我强烈建议单独建一张收费流水表,不要直接把金额挂在挂号记录表上。原因很简单:一次就诊可能会有多次收费,比如挂号时收挂号费,就诊后开药再收药费,需要做检查的话还要再收检查费。如果只给挂号记录留一个金额字段,这些明细就存不下来了。

有了收费流水表,每天的日报统计就很简单了:按操作人分组、按时间段过滤、按收费类型汇总。答辩或者交付时能拿出一张每日收费汇总表,系统完整度会明显上一个档次。

3.6 建表时我坚持的几个习惯

这些习惯是踩过坑之后才总结出来的,写在这里希望你能直接用上。

主键统一用自增ID或者雪花ID,不要用业务字段做唯一键。有些人喜欢把手机号当用户表主键,一旦患者更换手机号,修改逻辑会变得非常麻烦。

时间字段一律用datetime,并且统一保存到秒级。挂号日期、收费时间、创建时间这些字段,如果只存日期,后续统计“上午挂号多少人”这种问题就没法查了。数据库连接串里记得加serverTimezone=Asia/Shanghai,否则本地时间和数据库时间容易出现8小时偏差。

状态字段用tinyint并配上注释,比如1表示待就诊,2表示已完成,3表示已取消。接口返回值里再映射成中文展示,这样数据库可读性最好,程序里也比用字符串判断靠谱。

下面是挂号记录表的简化建表语句,可以作为参考。

CREATE TABLE registration_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, registration_no VARCHAR(32) NOT NULL COMMENT '挂号单号', patient_name VARCHAR(50) NOT NULL COMMENT '患者姓名', patient_id_card VARCHAR(18) NOT NULL COMMENT '患者证件号', patient_phone VARCHAR(20) COMMENT '患者手机号', dept_id BIGINT NOT NULL COMMENT '科室ID', doctor_id BIGINT NOT NULL COMMENT '医生ID', schedule_id BIGINT NOT NULL COMMENT '排班ID', register_date DATE NOT NULL COMMENT '挂号日期', time_slot TINYINT NOT NULL COMMENT '时段:1上午 2下午', status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1待就诊 2已完成 3已取消', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_schedule_status (schedule_id, status), KEY idx_doctor_date (doctor_id, register_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='挂号记录表';

4. 核心业务链路:挂号、就诊、收费这三件事是怎么串起来的

4.1 挂号下单:余号扣减为什么不能“先查再改”

挂号是整个系统里第一个真正涉及并发的业务。回看读者发给我的那套源码,余号变成负数的原因非常典型:代码先查排班表,判断剩余号数大于0,然后插入挂号记录,最后更新余号减一。

问题在于,当两个人同时挂最后一个号时,两个请求都会先读到剩余号数为1,都认为“这号我挂上了”,于是各自插入了一条挂号记录,最终余号被减成了-1。数据库层面缺少任何约束来阻止这个过程。

正确的做法有几种。最简单高效的是条件更新:

@Override @Transactional public boolean register(RegisterDTO dto) { // 1. 扣减号源,靠条件更新保证不超卖 int rows = doctorScheduleMapper.deductRemaining(dto.getScheduleId()); if (rows == 0) { throw new BusinessException("该时段号源已满"); } // 2. 创建挂号记录 RegistrationRecord record = buildRecord(dto); registrationRecordMapper.insert(record); return true; }

对应的SQL长这样:

UPDATE doctor_schedule SET remaining = remaining - 1 WHERE id = #{scheduleId} AND remaining > 0;

这条UPDATE语句利用数据库的行锁,扣减操作是原子的,后面的remaining > 0条件保证了不会出现负数。执行结果影响行数为0时,说明号已经被抢完了。

4.2 就诊与病历:医生端操作要注意什么

医生登录系统后,看到的是当前时段待就诊的患者列表。这里有个隐藏的设计问题:医生登录用的是sys_user表,而医生信息在doctor_info表里,怎么知道“当前登录用户是哪位医生”?答案是先根据当前用户ID去doctor_info表里查一次,拿到doctor_id,再用doctor_id去查挂号列表。

接诊时医生要做两件事:更新挂号状态为“已完成”,创建诊断记录。这两个操作必须在同一个事务里。否则就可能出现状态没更新,但病历已经写入的脏数据。

处方录入时还要做一件事:给处方明细里的每个药品检查库存。这里的检查只是“友好提示”,真正的库存扣减放在药房发药环节,不要把“校验库存”和“扣减库存”混在同一个操作里。医生开方只是写文档,不能把药先扣了,否则患者还没缴费,药就已经被别人扣光了。

4.3 收费与发药:状态不能乱跳

收费员打开一条待缴费的处方,系统自动计算出总价,收费完成之后更新处方状态。这个更新同样有条件限制:只有处于“待缴费”状态的处方才能被改成“已缴费”,这样能防止重复收费。

药房发药环节类似。药师通过处方编号查询已缴费的处方明细,点击发药后逐步扣除库存,同时把处方状态改成“已取药”。这里要注意,发药扣库存必须按药品维度去更新,而且最好也用条件更新。

药品库存不够但处方已经缴费了,这在真实医院里是常见情况。简单的处理方式是允许发药时提示库存不足,然后走人工缺药登记,先进货再发药。第一版可以只做一个缺药提示,方便患者去药房窗口沟通。

4.4 把业务流程做成状态机,前端后端都好管

一开始很多人会把状态字段单纯当成一个数据库里的数字,前端用if判断来切换按钮。这种写法在流程短的时候没问题,但挂号、就诊、收费、发药这条链路拉长以后,很容易出现“已发药但处方还没缴费”这类逻辑漏洞。

提前定义好状态机,是成本最低的防错手段。比如处方状态按四个阶段流转:

  • 0 草稿:医生还在录入,患者不感知
  • 1 待缴费:医生开方完成,等待收费
  • 2 已缴费:收费完成,等待药房发药
  • 3 已取药:药房完成发药,流程结束

后端在每次状态更新时检查当前状态是否等于允许流转的前置状态,前端再配合脚本去控制按钮的展示,相当于上了双保险。这套思维在后续做住院流转、退费、退药时同样适用,值得多花点时间设计清楚。

5. 权限体系与前后端安全:三类角色一次说清

5.1 Session还是JWT:按部署方式来选

权限认证方案的选择,我一直觉得不该盲目追新。做毕设或者单体应用时,Session方案完全够用:用户登录后把用户信息放进Session,后端用拦截器统一判断是否登录、是什么角色。

如果选了前后端分离,就大多会使用JWT。前端登录后拿一个Token,每次请求放到请求头里,后端解析出用户身份。好处是后端不存会话状态,水平扩展时不需要考虑Session共享;坏处是开发者要处理Token过期、密钥管理、用户修改密码后旧Token失效等问题。

对于这个项目,我的建议很简单:纯Thymeleaf渲染就选Session+拦截器;Vue前后端分离就选JWT+拦截器。不要一上来就上Spring Security的完整OAuth2框架,因为学习成本和配置复杂度会盖过业务开发本身的价值。

5.2 后端权限拦截:一个注解搞定角色校验

权限难点不在登录,而在接口的细粒度控制。我的做法是自定义一个注解,比如叫@RequireRole,配一个拦截器统一处理。

先定义角色常量:

public class RoleConstant { public static final String ADMIN = "ADMIN"; public static final String DOCTOR = "DOCTOR"; public static final String CASHIER = "CASHIER"; }

在需要限制角色的Controller方法上加注解:

@RequireRole(RoleConstant.DOCTOR) @GetMapping("/doctor/patientList") public Result patientList(@RequestParam Long scheduleId) { // 医生端专属接口 }

拦截器读取注解值,再跟当前登录用户的角色列表比对:

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (handler instanceof HandlerMethod) { HandlerMethod method = (HandlerMethod) handler; RequireRole annotation = method.getMethodAnnotation(RequireRole.class); if (annotation != null) { String role = getCurrentUserRole(request); if (!annotation.value().equals(role)) { throw new BusinessException("无权访问"); } } } return true; }

这套方案侵入性小,比在业务代码里到处判断角色干净很多。把“当前用户是谁”和“能不能访问这个接口”两件事分开,逻辑会清晰不少。

5.3 数据权限:医生只能看自己的患者,这是硬约束

很多人做权限只做了接口权限,比如医生能访问患者列表接口,管理员也能访问。但真正的隐患在数据层面:医生A登录后,能不能查看到医生B名下的患者记录?如果SQL里直接写死查询当前医生ID,就没问题;如果漏了条件,医生的请求就能把全院的患者数据拉出来。

我在设计医生端查询时,Service层强制从会话中获取当前用户对应的doctorId,所有列表查询都带上这个条件,不允许前端传doctorId进来。遇到特殊场景,比如护士需要协助某位医生查看患者列表,单独开一个明确标注“越权代办”的接口,而不是放宽核心接口的查询条件。

数据权限的校验逻辑要比接口权限更早做,因为它直接影响患者隐私,也不是前端隐藏按钮能兜得住的。

6. 落地时最常踩的几个坑:并发、序列化、时区与事务

6.1 并发超卖:挂号余数变负数怎么排查

这类问题在测试环境很难复现,因为单人操作根本没有并发。交付前压力一上来,问题就暴露了。排查链路一般是这样的:先看数据库,发现余号是-1,第一反应是“什么代码把它减成负数的”;再看日志,两个请求在同一毫秒级打印了“查询剩余号数=1”的记录;最后看代码,果然是先查询再更新的经典错误写法。

修复方法前面已经说了,用UPDATE加remaining > 0条件做原子扣减。还有一个隐形问题是事务内要先写后读,避免直接在代码里先执行查询再执行更新,尤其不要跨方法跨事务去查。

6.2 序列化与懒加载:实体类直接返回的代价

我见过不少项目为了让返回方便,直接把Mapper查出来的实体类扔给Controller返回。短期看很省事,但踩坑时也很痛。

典型报错有两个。第一个是循环引用:实体A持有实体B的引用,实体B又持有实体A的集合,JSON序列化时无限递归,最后直接栈溢出。解决办法是给关联字段加@JsonIgnore,或者干脆定义一个VO类,只把需要的字段放进去。

第二个是懒加载异常。比如排班实体关联了医生实体,MyBatis查询时用了懒加载,在事务没关闭的时候访问医生属性没问题,但一旦事务关闭后前端拿不到延迟加载的数据,就会报错。最省心的方案是查询时使用关联查询一次性查出所有需要的字段,放进VO对象,避免返回原始实体。

6.3 日期时间错乱:前端少一天的问题出在哪

“数据库存的是2019-07-11 20:30,前端展示出来变成2019-07-11 08:30”“我查今天的数据,结果前端显示昨天”的这种问题,基本上都是时区问题。

排查时先看数据库连接串,很多人在application.yml里只写了数据库地址、账号、密码,漏掉了serverTimezone。MySQL默认时区跟北京时间不一致时,datetime字段读写就会差出8小时。连接串里加上serverTimezone=Asia/Shanghai,是一种最有效的修复方式。

还有一种情况是前端展示后端传递的JSON时间,默认格式带T带毫秒,看起来很不专业。建议在配置里全局统一序列化格式:yyyy-MM-dd HH:mm:ss,前端直接拿来用就行。

6.4 事务悄悄失效的三个场景

@Transactional不是加了就生效,这里有三个很容易踩中的场景。

第一个是同类内部调用。一个方法调另一个方法,比如Service实现类里register()调用deductRemaining(),如果第二个方法标注了@Transactional,但它是被this调用的,事务管理是通过代理对象生效的,this调用绕过代理,于是事务就失效了。解决办法是把事务放到入口方法上,或者注入自身的代理对象。

第二个是异常被吃掉。事务默认在RuntimeException时回滚,但代码里如果catch住异常并且没有重新抛出,事务框架根本不知道出了问题,数据就提交了。记住,事务方法内捕获异常后要么抛出去,要么手动调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。

第三个是数据库引擎不是InnoDB。如果某张表用的是MyISAM,它本身不支持事务,无论你加多少注解都没用。实际部署时统一用InnoDB,能少掉一大批诡异的“事务不生效”问题。

7. 性能优化与扩展方向:别急着上微服务

7.1 数据库索引与查询优化:先解决90%的慢查询

医院管理系统的数据量在初期不会很大,常见的性能瓶颈几乎都来自索引设计。打开数据库慢查询日志看一眼,慢的往往集中在挂号记录查询、收费用统计这类表上。

挂号记录表需要重点创建组合索引。最常用的查询条件是“某医生某天的挂号列表”,对应(dcotor_id, register_date);其次是“某排班下待就诊的患者列表”,对应(schedule_id, status)。药品表按药品名称模糊查询时,如果只是开头匹配,普通索引就够用。

分页也要注意,MyBatis Plus的分页插件默认生成的是limit offset形式,当页码特别大的时候,offset越大性能越差。比如查询到第1000页,数据库要扫描前几万行再丢弃,很浪费。实际中我们可以限制查询深度,只允许查前50页,或者改用基于上一页最大ID的查询方式。

7.2 缓存与异步:Redis和@Async的正确使用姿势

这个项目最值得加缓存的地方是科室列表、医生排班表、药品基础信息这些读多写少的静态数据。

科室列表几乎不会变,每次请求都查数据库完全没必要。用Redis缓存一份,设置10分钟过期,或者后台修改科室信息时主动删除缓存,就能极大降低数据库压力。

排班数据的缓存要注意失效问题:挂号成功之后,医生排班表的余号变动了,缓存不能是旧的。一个简单的做法是挂号操作成功后立即删除相关排班的缓存,下一次请求再重新加载。用“删除缓存”而不是“更新缓存”,能减少并发写缓存导致的脏数据问题。

异步处理方面,别一上来就引入消息队列。挂号成功后给患者发短信、给医生推送提醒这类操作,用Spring的@Async就能解决。只有当消息种类变多、需要可靠投递和重试机制时,才需要考虑更重的方案。做技术选型时想清楚“现在需要什么”比“以后可能用到什么”更重要。

7.3 从单体到多院区:预留扩展而不是提前过度设计

这个标题的扩展方向,最常被问到的问题是“如果要做连锁医院怎么办”。我的看法很明确:在当前阶段不要提前做微服务,更不要为了“以后要扩展”就设计一堆分布式中间件。

单体应用只要架构合理,撑到一个小型医院系统完全没有问题。如果真的有从单院区到多院区的需求,核心动作是在主要表里增加一个hospital_id字段,比如科室、医生、排班、挂号记录、收费记录都带上院区标识,报表明细查询时按院区分组。这样的改造在数据库层面是可控的,远比一开始就拆服务要简单。

真正需要拆分服务的时候,通常不是性能问题,而是团队协作成本问题。如果是一个人维护的毕设或小项目,踏踏实实把单体做好,比什么分布式架构都更实际。

8. 最后聊几句实在的:这套系统到底值不值得做

8.1 从代码之外的收获

做完这套系统后最深的体会是,真正难的不是写代码,而是把一个业务流程理清楚并转成数据模型。挂号、就诊、收费、发药,每一个环节都有它自己的规则和边界。状态机怎么设计、余号如何扣减、权限如何拦截,这些问题在你没动手之前,都只是面试题里的抽象概念;动手做完之后,它们会变成你判断一个系统是否靠谱的本能。

我评审过的那些“翻车”项目,仔细看下来,问题几乎都出在流程理解上,而不是技术不会写。所以如果你正在做这个题目,我建议你多花点时间画业务流程图,把“什么角色在什么状态下能做什么操作”提前列出来,再开始写代码,后面会省掉大量返工时间。

8.2 做毕设或开源项目最容易被低估的一件事

最后想强调一个很多人会忽略的点:一份好的数据库设计文档,价值不亚于源码本身。

源码是给人运行的,文档是给人理解的。把每个表的字段说明、状态枚举、关联关系写清楚,把每个核心接口的入参出参记录下来,把部署环境要求和初始化步骤写明白,这份项目的可读性会完全不同。答辩时老师问“为什么这个状态要这么设计”,你能拿出文档里的状态机说明;面试官问“这个系统的表结构是怎么规划的”,你能把ER图讲得头头是道——这就是“源码+文档”里“文档”二字的真正意义。

我在实际搭建这类系统时,还有个习惯是先搭好一个数据库初始化脚本,每个表都带注释,每条枚举状态都写清楚含义。这样做的好处是,开发过程中任何一个人打开数据库,不需要看代码也能明白这张表是干嘛的。把这一步做扎实,后续无论是自己维护还是交给别人接手,都会顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询