尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
医院排班系统开发实战:Spring Boot+MySQL排班算法与数据库设计
简介这是一份 Java 医院排班系统源码包基于 Spring Boot、Vue 和 MySQL 技术栈开发采用 B/S 架构主要面向医院管理人员和医护工作者解决排班管理信息化、规范化问题同样适合 Java 学习者用于毕业设计或项目实战。压缩包大小约 16.57MB内含完整项目源码、数据库脚本和配套论文可直接支撑系统部署、功能扩展与二次开发。当前已有 267 人学习下载具备一定参考热度。系统支持管理员与医护双角色操作管理员可管理个人中心、医院信息、医护信息、医护类型、排班信息、排班类型、科室信息以及投诉信息医护人员可维护个人信息、查看个人排班和收藏内容。界面清晰、操作简单能够有效提升医院排班管理效率并帮助读者掌握 Spring Boot 与 Vue 前后端分离开发以及 MySQL 数据库设计的关键思路。1. 医院排班系统为什么手工排班最后都会走向代码化先别急着搜“医院排班系统源码”去下载一个就跑我见过太多人拿到一套 springboot 排班系统后第一步就卡在“这表怎么建”“排班规则写在哪”。医院排班系统——不管是门诊排班、护士排班还是手术室排班——本质都是同一个问题在“科室人力有限”和“每天每个班次都有人”这两个约束里找一组不冲突的安排并且让每个人感受到“规则透明不偏不倚”。手工用 Excel 排班最痛苦的不是排不出来而是排完之后的“换班连锁反应”一个人调班整周全乱护士长周末还要对着表格瞪眼。这套 Java 排班系统的价值就是把排班规则、人员可用性、换班申请这三件事收拢到一套代码里让数据库来决定谁哪天值什么班。它适合谁适合正在做毕业设计的计算机专业学生适合医院信息科想用 springboot 快速搭一套内部工具的工程师也适合接私活的外包开发者——排班这类需求在中小企业里出镜率极高。这套系统的落地路径不算复杂Spring Boot 做后端服务MySQL 存人员和排班数据排班算法写在 Service 层前端可以是 Vue 也可以是 Thymeleaf论文则重点描述需求分析、E-R 图、算法设计和测试过程。后面几章我按“数据库设计 → 排班算法 → Spring Boot 接口 → 避坑 → 验证技巧”的顺序把整个方案拆开每一步都给可复现的代码你照着能跑通也能在答辩或交付时说得清楚。2. 排班系统的领域建模数据库表怎么设计才能让规则不散落排班系统最忌讳的是一上来就写“排班表”。如果你只建了一张 schedule 表把“医生A周二上午出门诊”写成一行那后面所有需求——请假、换班、按权限看排班——都会逼着你不停改表结构。正确的做法是先做领域建模把“人员”“班次模板”“排班规则”“实际排班”拆成四个独立概念再落成表。2.1 从需求到表结构人员、科室、班次模板的拆分逻辑第一张表是科室和人员的基础信息。科室表 department 只需要 id、name、description 三个字段但人员表 staff 必须多设计几个和排班强相关的字段工种类型医生/护士/技师、是否参与夜班、每周最多班次数、职称。这些字段直接参与算法逻辑不能放在备注里手写。第二张表是班次模板 shift_template它定义“医院有哪些班型”早班 07:30-15:30晚班 15:30-23:30夜班 23:30-次日 07:30还要一个字段标记这是白班还是夜班因为夜班和休息的算法权重完全不同。第三张表是排班规则表字段包括生效周、硬性约束和软性偏好。最后才是排班结果表它只存“某人在某天的某个班次”一行的 id 由 staff_id shift_date shift_template_id 唯一确定。这里有个容易被忽略的设计决策排班规则为什么要单独建表而不是写死在代码里因为医院排班规则每半年可能变一次——比如夜班从“每人每月 6 次”改成“每人每月 4 次”如果规则写在常量类里每次改动都要重新部署规则入库运营人员改一条记录算法下次运行时自动读新值。这个设计在答辩时可以着重讲评委会觉得你有工程思维。实际建表时我一般会给每张表都加上 create_time 和 update_time 两个审计字段后续排错查数据时真的能救命尤其是“上周排班怎么突然变了”这类问题一查 update_time 就知道是谁在什么时候改过数据。2.2 可复现的 MySQL DDL主键、唯一索引和字段注释一个都不能少直接给可以落库的 DDL字段和注释我都按医院真实场景写过一遍。你复制到 MySQL 8.0 直接执行注意把字符集统一成 utf8mb4不然排班备注里一出现“崔”字或表情符号就可能乱码。CREATE TABLE department ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 科室ID, name VARCHAR(64) NOT NULL COMMENT 科室名称, description VARCHAR(255) DEFAULT COMMENT 科室说明, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT科室表; CREATE TABLE staff ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 员工ID, dept_id BIGINT NOT NULL COMMENT 所属科室ID, name VARCHAR(32) NOT NULL COMMENT 姓名, job_type TINYINT NOT NULL COMMENT 1医生 2护士 3技师, title VARCHAR(32) DEFAULT COMMENT 职称, night_enabled TINYINT DEFAULT 1 COMMENT 是否参与夜班1是 0否, max_weekly_shifts INT DEFAULT 6 COMMENT 每周最多班次数, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT人员表; CREATE TABLE shift_template ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 班次模板ID, shift_name VARCHAR(32) NOT NULL COMMENT 班次名如早班/晚班/夜班, start_time TIME NOT NULL COMMENT 开始时间, end_time TIME NOT NULL, night_flag TINYINT DEFAULT 0 COMMENT 是否夜班1是 0否, need_staff_count INT DEFAULT 1 COMMENT 该班次需要人数 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT班次模板表; CREATE TABLE schedule_rule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dept_id BIGINT NOT NULL COMMENT 规则所属科室, rule_key VARCHAR(32) NOT NULL COMMENT 如night_count_per_month, rule_value VARCHAR(128) NOT NULL COMMENT 规则值如4, effective_week VARCHAR(16) COMMENT 生效周格式2025-W01, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT排班规则表; CREATE TABLE schedule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, staff_id BIGINT NOT NULL COMMENT 人员ID, dept_id BIGINT NOT NULL COMMENT 科室ID冗余加速查询, shift_date DATE NOT NULL COMMENT 排班日期, shift_template_id BIGINT NOT NULL COMMENT 班次模板ID, status TINYINT DEFAULT 1 COMMENT 1正常 2换班待审 3已换班, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_staff_date_template (staff_id, shift_date, shift_template_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT排班结果表;这套 DDL 里最关键的三个设计是schedule 表加了唯一索引 uk_staff_date_template防止同一人同一天被排两个相同班次——这是数据层给算法兜底night_flag 字段单独抽取因为算法判断“这人今晚值没值夜班”时不能只看班次名如果科室自定义了“大夜”“小夜”这种名字代码就写死了schedule_rule 表用 key-value 结构存规则虽然查询时要转一下但换来的是规则“可插拔”。如果你用的是 MyBatis-Plus想根据实体类自动生成创建表的 SQL 语句记得在实体类上把 TableName、TableId 注解写全否则生成的 SQL 里主键和表名会对不上这是我们项目里吃过亏的地方。2.3 换班申请和排班状态为什么需要状态机而不是删除重排医院排班最核心的业务动作是“换班”而换班不是一个简单的 update 操作。A 和 B 换班中间经历了“A 发起申请 → B 确认 → 护士长审批”三步每一步失败都不能让排班数据处于半更新状态。所以 schedule 表的 status 字段专门设计了三个值1 正常、2 换班待审、3 已换班。换班操作的正确实现方式是把 A 和 B 的两条 schedule 记录都改成待审状态事务提交后审批通过再统一翻转成正常状态。这里最容易踩的坑是有人用“删掉 A 的记录插入 B 的记录”来实现换班。这么做的代价是失去了审计轨迹——一个月后想查“这个班到底怎么变成小张上的”发现那条记录已经没了。排班系统本质上是一个“排班 审批”的双层结构不能当普通的数据增删改查来做。如果你只想要一个简单能跑的源码数据库至少要把 schedule 和 schedule_rule 这两张表建好换班状态可以先不做但表的字段要预留 status。后面接排班算法时你会发现这个预留的字段能省很多事。3. 排班算法的核心逻辑从常规约束满足到可落地的贪心实现数据库建好后整个项目的灵魂就是排班算法。很多毕业设计或者小项目里排班就是随机分——把人员列表 shuffle 一下按顺序往班次里填。这只能应付演示真正能用的排班算法至少要满足三类约束硬性约束每天每个班必须有足够人数、同一个人同一天不能排两个班、政策约束夜班次数上限、白班和夜班之间必须有足够休息间隔、软性偏好尽量保证每人每周休息天数均衡、尽量不连续值夜班。3.1 约束条件拆解硬约束、政策约束、软性偏好如何用代码表达先把约束写清楚再写算法这是排班系统区别于普通 CRUD 工程的关键。硬约束其实有三条覆盖约束——每个班次的实际人数必须等于该班次 need_staff_count唯一性约束——同一员工同一时刻只能出现在一个班次连续性约束——按医院标准夜班结束后至少休息 24 小时也就是昨天 23:30 值了夜班的人今天不能安排任何班次。政策约束通常是可配置的比如“医生每月夜班不超过 4 次”“连续白班不超过 5 天”。软性偏好不强制比如“尽量让每个人夜班数差值不超过 2”。代码里表达约束不能用一堆 if 堆在循环里那样排班函数超过一百行就没人看得懂了。把每个约束写成一个校验函数返回 boolean 和一个失败原因字符串算法主体调用这些函数来决策。这样做的好处是换一条政策时你只需要改对应函数不需要动排班主流程写论文时把每个函数对应到一张约束表逻辑特别清晰答辩老师问什么都能答上。3.2 一种可落地的排班实现先按优先级填夜班再用回溯补位我一般用的排班算法思路是“按优先级填坑”夜班要先排因为它人数需求少而且参与人数有限然后排周末白班最后的工作日白班是最灵活的可以用简单贪心补齐。夜班排法是从夜班人员池里按“本月已排夜班数从小到大”排序取最少的那个填入空缺班次——这保证公平性也避免某个人被反复排夜班。如果一个班次没人可填就触发回溯判断上一个被填的人能不能和这个位置的人交换。下面这个代码是我在 springboot 项目里常用的核心排班方法用 Java 写的直接放在 Service 层。它接收排班周期和科室 ID生成该周期内所有排班记录并批量插入 schedule 表。public ListSchedule generateSchedule(Long deptId, LocalDate startDate, int days) { // 1. 加载该科室参与排班的人员夜班启用和非夜班人员分开 ListStaff staffList staffMapper.selectByDeptAndNight(deptId, true); ListShiftTemplate templates shiftTemplateMapper.selectAll(); MapLong, ListSchedule result new HashMap(); ListShiftTemplate nightShifts templates.stream() .filter(ShiftTemplate::getNightFlag).toList(); // 2. 先排夜班遍历每一天每个夜班缺人时选择本月夜班次数最少的人 for (int i 0; i days; i) { LocalDate date startDate.plusDays(i); for (ShiftTemplate night : nightShifts) { int need night.getNeedStaffCount(); ListSchedule exist scheduleMapper.findByDateAndTemplate(date, night.getId()); if (exist.size() need) continue; ListStaff candidates staffList.stream() .filter(s - canAssign(s, date, night.getId())) .sorted(Comparator.comparingInt(this::getNightCountInMonth)) .toList(); for (int j 0; j need - exist.size() j candidates.size(); j) { Schedule s new Schedule(); s.setStaffId(candidates.get(j).getId()); s.setDeptId(deptId); s.setShiftDate(date); s.setShiftTemplateId(night.getId()); result.computeIfAbsent(date.toEpochDay(), k - new ArrayList()).add(s); } } // 3. 本日夜班排完后如果人数不足记录缺口并触发告警 if (needMissing 0) log.warn(科室 {} 日期 {} 夜班缺口 {}, deptId, date, needMissing); } // 4. 批量保存 scheduleMapper.batchInsert(allSchedules); return allSchedules; }这段代码的核心参数有两个canAssign校验函数负责硬约束和政策约束它内部会查这人那天有没有已排班、如果前一天的班次是夜班是否已满 24 小时休息、本月夜班数是否已达上限getNightCountInMonth是公平性排序函数保证夜班次数少的人优先被选中。代码逻辑按夜班优先、贪心补位、缺口告警三层设计批量插入放在最后一步避免一条条插入造成大量数据库往返。实际跑起来一个 20 人的科室排 28 天班生成速度可以压到 1 秒以内。如果你已经有现成的排班源码核心要审查的就是这两个函数有没有写全——很多劣质源码会直接跳过 canAssign 的休息间隔校验导致夜班连轴转真实医院里根本不可能接受。3.3 参数怎么调夜班上限、休息间隔、每周班次数三组数值的经验值排班算法里可调参数不是越多越好真正影响排班质量的是三组。第一组是夜班月上限一般医生设 4、护士设 6值越小公平性越好但可能排不满夜班空缺如果发现某个班次老是缺人先把上限调大而不是增加招聘。第二组是夜班后休息小时数至少设 24这属于安全线不能随便调低。第三组是每周最大班次数建议 6有些源码默认值是 7等于默认一周上满——这在真实场景里是违规的。这三个参数我都放在 schedule_rule 表中而不是写死方便运营调整。调参时有一个判断技巧如果生成结果里夜班缺口超过总班次的 5%优先检查人员池 night_enabled 的人数占比而不是调算法——很可能 20 人里只有 8 人开了夜班权限排不出来是数据问题不是算法问题。4. Spring Boot 工程搭建与核心接口分层结构、配置和可复现的增删改查拿到一套排班系统源码第一件事不是看算法而是看工程结构和配置是否完整。很多源码在本地跑不起来十有八九是配置文件缺东西、包名和 Mapper 扫描路径不一致、或者 Lombok 版本和 JDK 打架。这一章我按一套标准的 springboot MyBatis-Plus 结构把整个工程骨架拆开每个关键位置给出代码和说明。4.1 工程包结构与启动类按 controller、service、mapper、entity 四层划分一个合格的 Spring Boot 排班系统包名结构至少要能看到业务边界。我常用的分包是controller接收 HTTP 请求、service排班算法和业务事务、mapper数据库访问、entity数据库实体映射、config安全、跨域、MyBatis-Plus 配置、common统一返回体和异常处理。排班算法会单独放一个core包因为它是整个系统最独立的模块以后可能抽出来给别的科室用或者换算法重写不能和业务 Service 纠缠在一起。启动类上要做两件事加SpringBootApplication无非多谈关键是MapperScan(com.hospital.schedule.mapper)的路径必须和你的 mapper 包实际路径一字不差否则 Spring 容器里根本找不到 Mapper Bean启动直接报错。MyBatis-Plus 的高版本对包扫描路径非常敏感包名多了个后缀都能让你排查半小时。启动类里我一般还会加一个CommandLineRunner做启动自检——检查数据库连接、检查 shift_template 是否有数据、检查 schedule_rule 是否配置了必要参数任何一个不满足就在日志里醒目地打出警告。这个小动作在答辩演示时特别好用评审老师问“如果规则没配怎么办”你可以当场演示启动告警比嘴上说“有校验”强得多。4.2 application.yml 配置清单数据库、MyBatis-Plus、时区三处最容易出错配置是整个项目拿来能不能跑的关键直接贴一份我在实际项目里反复验证过的配置重点地方都写了注释。Spring Boot 2.7 和 3.x 的配置项略有差异但下面这份两代版本通用性都很高。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hospital_schedule?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0配置里最坑的是时区serverTimezoneAsia/Shanghai必须加否则你的排班日期会出现 8 小时偏差——今天生成的排班记录数据库日期显示昨天。这个问题在真实医院环境暴露过护士看着周五的排班表数据库里存的是周四原因就是数据库连接时区默认为 UTC。MyBatis-Plus 的逻辑删除配置也会引发一个隐蔽的坑如果 schedule 表没有 deleted 字段而配置了逻辑删除那么所有查询都会自动带上WHERE deleted 0导致查出来的排班记录永远为空而且不报错。如果你的源码里配置了这一项务必确认每张表都有对应的 deleted 字段。日志配了StdOutImpl后每个 SQL 都会在控制台输出排错阶段先开着上线后记得去掉否则生产环境日志会迅速膨胀。4.3 核心接口实现排班生成、查询人员和换班审批的请求链路排班系统的接口不在多而在三条链路是否通生成排班、查询排班、换班审批。生成排班接口的请求体一般是{ deptId: 1, startDate: 2025-06-01, days: 28 }Service 层调用上一章的generateSchedule然后把返回的排班记录逐条写入 schedule 表。查询排班接口要支持按科室和日期范围查询方便前端按周视图展示。换班审批链路最复杂需要两个接口一个是提交换班申请一个是审批通过下面给出换班申请的典型实现。Transactional public boolean submitSwap(Long scheduleAId, Long scheduleBId) { Schedule a scheduleMapper.selectById(scheduleAId); Schedule b scheduleMapper.selectById(scheduleBId); // 校验两张排班记录属于同一天且班别不同否则换班无意义 if (!a.getShiftDate().equals(b.getShiftDate())) { throw new BusinessException(换班必须发生在同一天); } if (a.getStatus() ! 1 || b.getStatus() ! 1) { throw new BusinessException(只有正常状态的班次可以申请换班); } // 查出两个员工的工号和姓名写入日志留痕 Staff sa staffMapper.selectById(a.getStaffId()); Staff sb staffMapper.selectById(b.getStaffId()); log.info(换班申请{} 与 {} 于 {} 的班次互换, sa.getName(), sb.getName(), a.getShiftDate()); // 用乐观锁更新状态防止并发环境下两人同时提交导致状态覆盖 int countA scheduleMapper.compareAndSetStatus(scheduleAId, 1, 2, a.getVersion()); int countB scheduleMapper.compareAndSetStatus(scheduleBId, 1, 2, b.getVersion()); if (countA ! 1 || countB ! 1) { throw new BusinessException(排班状态已变化请刷新后重试); } return true; }这段代码最重要的设计是把“校验”和“更新”分成两步并且更新时用带 version 的乐观锁语句而不是直接 update。换班场景是典型的并发冲突场景——两个护士同时看到同一个班次都想和对方换如果不用乐观锁后提交的人会覆盖先提交的人的状态。Transactional保证两次更新要么都成功要么都失败不会出现只有一条记录变成“待审”的中间态。你拿到任何排班源码重点看换班接口有没有这三样事务注解、乐观锁、状态校验。缺任何一个演示时可能没问题真实用起来一定出乱子。审批接口的写法类似只是把状态从 2 改成 3同时要把两个人的 staff_id 对调写入班次记录这一步同样要在一个事务里完成。5. 排班系统避坑指南5 个让排班系统“当场翻车”的隐蔽问题排班系统看起来简单实际跑起来有一堆黑匣子。这一章我把我做过和见过的问题按“现象 → 原因 → 解决”的方式列出来每条都是花了时间排查换来的血泪经验。如果你在部署或二次开发这套源码时碰到类似症状先对照这里排查。5.1 排班日期无故偏移一天时区配置不一致数据库存的日期和页面显示对不上现象生成的排班表在页面上显示周五但打开数据库看 shift_date 却是周四。原因数据库连接串没带 serverTimezoneMySQL 驱动用了默认的 UTC 时区而 JVM 用的是 Asia/Shanghai两者有 8 小时差。排班当天 0 点到 8 点之间生成的记录就会落到“昨天”。解决在 JDBC 连接串显式加上serverTimezoneAsia/Shanghai同时把服务器的spring.jackson.time-zone也设成同一时区双保险。如果改完还偏移检查是不是用了 Docker 而容器时区不是 Asia/Shanghai进入容器执行date看一眼不对就在启动命令里加-e TZAsia/Shanghai。5.2 单机运行正常多人同时排班时数据错乱缺少事务和乐观锁现象两个护士长同时打开排班页面A 提交了换班B 也提交了同一条记录的换班最后数据库里状态变成了 B 的申请结果A 的操作凭空消失。原因代码里直接用了update ... set status ? where id ?后提交的人覆盖了先提交的人。解决给 schedule 表加 version 字段更新时带上where version ?更新成功则 version 1MyBatis-Plus 里可以用Version注解配合乐观锁插件实现也可以像上一章的compareAndSetStatus一样手写 SQL。如果不想加字段至少要在 update 语句里带上原状态条件where status 1这样只有状态仍为正常的记录才能被更新。5.3 夜班人员总是只有那几个人排班算法按顺序选人没有做公平性处理现象生成的排班表里张三每个星期值两次夜班其他人一次都没有护士长质疑“这系统不对”。原因排班代码里选人用的是order by id或者随机 shuffle没按本月夜班次数排序——排班算法缺少公平性约束。解决选人 SQL 或 Java 排序逻辑按night_count_in_month升序排夜班次数最少的人优先被选中同时加一个“如果某人本月夜班次数已经等于规则表里的上限直接过滤掉”。代码改起来很小但效果立竿见影。这个坑在几乎所有自己写的排班源码里都存在评审时也容易被老师揪出来建议拿到源码后先检查选人逻辑里有没有类似ORDER BY RAND()的写法有就要改掉。5.4 Spring Boot 版本太高老配置全部失效3.x 和 2.x 的兼容性差异现象源码用的是旧版 springboot你本地 JDK 是 17直接把 springboot 升级到 3.x启动报一堆错。原因Spring Boot 3.x 基于 Jakarta EEjavax包全部换成jakarta同时很多自动配置类的路径变了比如spring.datasource部分配置不再生效需要用spring.datasource.druid前缀或引入专门的 starter。解决如果只是跑这套排班源码优先保持原源码的 Spring Boot 版本别为了“用新版”而升级如果必须升 3.x就同时换掉所有javax.servlet、javax.validation等 import 语句还要确认 MyBatis-Plus 用了支持 Spring Boot 3 的mybatis-plus-spring-boot3-starter版本号以官方支持的为准。顺带说一个容易忽略的问题Spring Boot 版本太高时spring.banner.image在线生成的自定义 banner 也不一定兼容虽然不影响业务但启动输出乱码会让人误以为项目有问题。5.5 排班结束后想要“后悔药”数据被覆盖而没有快照导致没法回滚现象某次排班调参后重新生成之前排好的班次全被覆盖了想恢复成上周的版本数据库里却没有备份。原因生成排班的代码先delete from schedule where dept_id ? and shift_date between ? and ?再插入新数据而且删除是在事务里提交的。解决生成新排班前先执行一个“归档动作”把旧排班复制进schedule_history表保留生成时间和当时使用的规则参数这样即使新排班排砸了也能从历史表恢复。这个表的结构和 schedule 一样多三个字段archive_time、create_by、rule_snapshot存当时规则表的 JSON 快照。我建议把归档写成独立的 Service 方法并在生成排班接口里强制先调用没有归档就拒绝生成。这一步虽然简单但在真实医院场景里是“保命”的设计护士长最怕的就是“昨天的排班表又不见了”。6. 排班结果交付的最后一步用测试代码自动验证硬约束别用眼睛盯排班算法写完、接口调通不等于系统就合格了。真实医院对排班结果的审查非常严格——万一某天夜班缺人责任很大。我的习惯是交付前写一套自动化约束验证把“人眼检查”变成“机器断言”。这一章给你一个可以抄的测试思路和代码跑通后你就知道自己这套源码到底能不能交付。核心思路是排班结果生成后写一个验证器遍历结果集逐条检查第 3 章定义的硬约束。验证器不需要引入测试框架直接在 Service 里加一个validateSchedule(ListSchedule schedules)方法返回违规记录列表。但如果你的源码是用 Maven 管理的我还是建议写成 JUnit 测试因为每次排班参数调整后都能一键回归。下面是一个简化版验证器我通常把它放在core包的validator子包里逻辑直接可复制。public ListString validate(ListSchedule schedules) { ListString violations new ArrayList(); MapLong, ListSchedule byDate schedules.stream() .collect(Collectors.groupingBy(s - s.getShiftDate().toEpochDay())); // 校验1同一人同一天不能有两个班次 MapLong, ListSchedule byStaffAndDate new HashMap(); for (Schedule s : schedules) { String key s.getStaffId() _ s.getShiftDate(); byStaffAndDate.computeIfAbsent(key, k - new ArrayList()).add(s); } byStaffAndDate.forEach((key, list) - { if (list.size() 1) violations.add(人员 key 同日存在多个班次); }); // 校验2夜班后24小时休息检查 ListSchedule sorted schedules.stream() .sorted(Comparator.comparing(Schedule::getShiftDate)) .toList(); for (int i 1; i sorted.size(); i) { Schedule prev sorted.get(i - 1); Schedule cur sorted.get(i); if (!prev.getStaffId().equals(cur.getStaffId())) continue; if (isNightShift(prev) ChronoUnit.HOURS.between(prev.getEndTime(), cur.getStartTime()) 24) { violations.add(人员 cur.getStaffId() 夜班后休息不足24小时); } } // 校验3所有夜班是否人数达标 MapLocalDate, Long nightCount schedules.stream() .filter(s - isNightShift(s)) .collect(Collectors.groupingBy(Schedule::getShiftDate, Collectors.counting())); nightCount.forEach((date, cnt) - { if (cnt nightShiftNeedCount) violations.add(日期 date 夜班人数不足); }); return violations; }这段验证代码里我把最容易出问题的三条硬约束提取出来同日多班、夜班休息不足、夜班缺人。isNightShift判断方法很简单就是查该记录的 shift_template_id 对应的模板里 night_flag 是否为 1。实际项目里我会再补一条“夜班月次数超限”的校验这次没有写出来是因为它依赖 schedule_rule 表的值需要额外传参。你在跑通源码后把验证器接在一个跑批入口上每次生成排班如果 violations 非空就拒绝保存并列出违规明细。这是非常实用的一道安全闸门能挡住大量肉眼看不出的数据问题。最后说说我自己的习惯交付这套排班源码时我不光交付能跑的代码还会顺手把这个验证器的输出格式做成一张简单的报表——按天列出夜班人员名单、休息人数、违规数量。这样护士长能看到“这个排班方案为什么合理”而不是只看到一堆数据。排班系统真正的价值不在代码写得多漂亮而在使用者能不能信任它。信任是拿数据说话建立的不是“这是系统算出来的不会错”这种话。希望这套方案和验证思路能帮到你哪怕只让你躲开一两个我踩过的坑也算值得。本文还有配套的精品资源点击获取
RELATED

相关推荐

数据岗位考证指南:能力比证书更重要,避免盲目内卷

数据岗位考证指南:能力比证书更重要,避免盲目内卷

这两年咨询我数据岗怎么入行的人特别多,十个里有八个第一句话就是“你说我是不是该去考个证”。翻简历的时候也经常看到一整排证书,分析工具、数据库、项目管理、行业认证,能考的几乎都考了。但真正面试起来,很多人连最基础的指标…

📅 2026/10/10 4:24:23
豆包智能体聊天记录管理指南:查找、导出、备份与迁移

豆包智能体聊天记录管理指南:查找、导出、备份与迁移

1. 先搞清楚:豆包智能体的聊天记录到底存在哪很多人第一次用豆包智能体,聊了几天发现记录找不到了,或者换了个设备登录,之前的对话全没了,第一反应是“是不是被删了”。其实不是,是你没搞清楚它的存储逻辑。…

📅 2026/10/10 4:24:23
AnyPS5解析:跨平台适配PS5手柄的兼容层设计与实践

AnyPS5解析:跨平台适配PS5手柄的兼容层设计与实践

很多接触过 PS5 手柄(DualSense)的人都有过这种经历:在主机上用得挺好,一拿到电脑或者安卓设备上,要么连不上,要么按键错乱,要么干脆只能当个普通手柄用,自适应扳机、触觉反馈这些特…

📅 2026/10/10 4:24:23
MORE NEWS

更多资讯

📰

AnyPS5:面向PS5硬件确定性的底层开发范式

1. “AnyPS5”不是产品代号,而是开发者社区里一个隐秘的共识性称呼最近在几个硬核技术论坛和跨平台开发群组里,“AnyPS5”这个词频繁出现在讨论帖标题和代码注释中。它既不是索尼官方发布的型号,也不是某款第三方配件的注册商标,更…

📰

PS5远程串流实战:从书房到手机的AnyPS5搭建指南

PS5 入手一年半,我的体验经历了典型的三阶段:头三个月新鲜感拉满,游戏一碟接一碟拆封;中间半年开始吃灰,因为客厅电视被家里人占着的频率实在太高;最近半年倒是焕发第二春,起因就是折腾了一个叫…

📰

AnyPS5:一个语义不明的技术代号解析困境

项目标题中仅出现“AnyPS5”这一字符串,无其他上下文、无正文描述、无关键词列表、无摘要描述,亦无任何可验证的网络搜索内容填充(输入中相关热搜词与网络搜索内容均为空白)。根据你设定的核心创作原则第一条:“忠于原…

📰

医疗器械运输验证必知:ASTM D999振动测试方法详解

医院设备科拆开那台监护仪的包装箱时,屏幕已经碎成了雪花状。物流单上写着"小心轻放",可实际上它在货车车厢里颠簸了三天。这种场景做医疗器械研发的人都见过:产品本身设计得没问题,坏在运输路上。振动测试做的不到位&a…

📰

餐饮预约小程序毕设全解析:从源码到LW文档实战拆解

毕业设计做小程序,十个人里八个选餐饮相关,但真正能拿得出手的答辩项目不多。今天我把“基于微信的好吃哒餐饮预约小程序”这个毕设项目从源码到LW文档完整拆一遍,包含技术选型的理由、预约核心流程的设计思路、后端接口和数据表的规划方式&a…

📰

NYU-DLSP20 自监督学习(一):从 ImageNet 标注瓶颈到 Pretext 任务——相对位置、旋转预测、Shuffle Learn 与 Jigsaw 拼图

示例工程 【免费下载链接】NYU-DLSP20 NYU Deep Learning Spring 2020 项目地址: https://gitcode.com/gh_mirrors/pyt/pytorch-Deep-Learning 点击查看 免费下载 本文基于 NYU-DLSP20(NYU 2020 春季深度学习课程)第 10 周讲义 A「Self-Supervised Learning - Pret…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬