资讯动态

基于Spring Boot的医护人员排班系统:从数据库设计到贪心算法实战

发布时间:2026/9/13 9:17:24 来源:尧图企业网站定制
简介基于SpringBoot的医护人员排班系统毕业设计资源包面向计算机相关专业学生与需要快速构建排班管理原型的中小医疗机构解决医护类型、排班规则、科室、医院信息及人员档案的统一管理难题。资源共4个文件包含源码压缩包、MySQL数据库脚本、运行说明文档与万字论文文档整体约15.99MB可直接在IDEA/Eclipse中导入运行支持Windows与macOS环境。系统采用SpringBootVue前后端分离架构提供医护类型、排班类型、科室信息、医院信息、医护信息五大管理模块并覆盖管理员与用户两种角色权限。数据库脚本开箱即用论文文档清晰阐述设计思路与实现流程便于答辩展示、二次开发或课程设计参考。目前已有40人学习下载适合作为毕业设计起步模板。1. 基于 Spring Boot 的医护人员排班系统先认清它是约束调度问题科室排班在护士长手里通常是一张 Excel 格子图横轴是日期纵轴是人员格子里填白班、夜班、休息。看着简单排起来却要同时满足夜班连上不超过两天每天每班最低三人休假不能和班次冲突等规则手工排一次月度班表往往要半天。基于 Spring Boot 的医护人员排班系统核心就是把这张格子图落成数据库里的排班记录把规则写成可执行的约束检查再配一个自动生成算法和一个手动调整入口。标题里的源码数据库万字文档说明它是可交付的课程设计或毕设项目真正值得研究的是三件事数据模型怎么建、约束怎么写、事务边界在哪里。2. 数据库设计排班系统的表结构决定算法能不能跑自动排班算法再精巧底层还是人员 × 日期 × 班次三个维度的组合求解。如果表结构把班次写死在字段名里比如 morning_1、night_1 各占一列后面每加一个班次就要改表结构。所以第一步先把业务对象拆清楚把核心表建出来再考虑业务逻辑怎么写。这也是数据库课程设计里最常见的评分点表设计合理后面所有功能都是顺水推舟。2.1 五张核心表把谁、哪天、上什么班拆成独立实体排班系统的最小数据模型通常包含五张表它们之间的关联关系是一个科室下有多个人员一个人员在一个日期上产生一条排班记录一个班次类型被多条排班记录引用表名职责关键字段department科室id, namestaff医护人员id, dept_id, staff_no, name, job_title, role_typeshift_type班次定义id, code, name, start_time, end_time, required_countschedule排班记录id, dept_id, staff_id, work_date, shift_code, source_typeleave_request请假申请id, staff_id, leave_date, status这里有两个常见设计决策值得说明。第一班次必须独立成表不写死在代码或字段里。不同医院的班次定义差异很大有的分白班、中班、夜班有的还有主班、副班、24 小时值班班次起止时间直接影响工时计算。把班次抽成 shift_type 表后新增班次只是 INSERT 一条记录不需要改 Java 代码这就是数据驱动配置和代码写死的本质区别。第二排班表 schedule 是事实表每行表示某员工在某个日期被排了某个班次不要设计成一行一个日期、每列一个班次的宽表。宽表查询某天有谁上夜班时要扫描整行统计某个护士一个月上了几个夜班要写好几层嵌套而窄表模型只需要按 staff_id 或 work_date 过滤。后面所有自动排班算法都是围绕这张窄表做集合运算宽表会让算法代码复杂度成倍上升。2.1.1 为什么来源标志必须留下schedule 表里的 source_type 字段很多人会忽略实际它是排班系统能否人工干预的关键。自动生成的结果 source_type 1护士长手动调整的记录 source_type 2。后面做重新自动排班时只删除 source_type 1 的数据保留手动调整记录一键重排不会把人工决定全冲掉。这个字段在第四章的 Service 层代码里会反复用到现在建表时就要留好。2.2 staff 表医护人员的字段比通用用户表多在哪通用用户表一般只有账号、密码、角色但排班系统的人员表必须带上排班相关属性否则跨科室调班、统计夜班数、按职称配班都无从谈起CREATE TABLE staff ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, dept_id BIGINT NOT NULL COMMENT 所属科室ID, staff_no VARCHAR(32) NOT NULL COMMENT 工号, name VARCHAR(32) NOT NULL COMMENT 姓名, job_title VARCHAR(32) DEFAULT NULL COMMENT 职称如主治医师/主管护师, role_type TINYINT NOT NULL DEFAULT 2 COMMENT 1医生 2护士, max_consecutive_night INT DEFAULT 2 COMMENT 个人最大连续夜班数默认2, is_active TINYINT NOT NULL DEFAULT 1 COMMENT 1在职 0离职, PRIMARY KEY (id), UNIQUE KEY uk_staff_no (staff_no), KEY idx_dept (dept_id) ) ENGINE InnoDB COMMENT 医护人员表;关键点在于 max_consecutive_night 这个字段。第三章的自动排班算法会把它当作硬约束读取不同人员的夜班承受能力不一样放在个人维度比放在全局配置更符合实际业务。role_type 用来区分医生和护士排班时每个科室可以分别设置医生和护士的最低在岗人数。staff_no 加唯一索引是为了防止 Excel 导入人员时出现重复工号这类数据错误越早拦截成本越低。2.3 schedule 表与联合唯一索引数据库层的兜底约束排班记录表是最容易产生脏数据的地方同一个人同一天被排两个班次、同一天同一班次人数超过配置值。后一种要交给算法或人工去判断前一种完全可以在数据库层用联合唯一索引拦住CREATE TABLE schedule ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, dept_id BIGINT NOT NULL COMMENT 科室ID冗余字段便于按科室查询, staff_id BIGINT NOT NULL COMMENT 人员ID, work_date DATE NOT NULL COMMENT 上班日期, shift_code VARCHAR(16) NOT NULL COMMENT 班次编码对应shift_type.code, source_type TINYINT NOT NULL DEFAULT 1 COMMENT 1自动生成 2手动调整, PRIMARY KEY (id), UNIQUE KEY uk_staff_date (staff_id, work_date), KEY idx_dept_date (dept_id, work_date) ) ENGINE InnoDB COMMENT 排班记录表; ALTER TABLE schedule ADD CONSTRAINT fk_schedule_staff FOREIGN KEY (staff_id) REFERENCES staff (id);uk_staff_date (staff_id, work_date) 联合唯一索引是这个表最重要的设计一个人在某一天只能有一条排班记录无论应用层代码怎么漏判数据库层都会拒绝重复插入。dept_id 在 schedule 表里属于冗余字段因为通过 staff_id 关联 staff 表也能拿到科室但排班查询最常见的场景是按科室拉整月日历冗余一个 dept_id 可以少一次 JOIN配合 idx_dept_date 联合索引直接走索引覆盖。外键要不要加看团队习惯MySQL 的 InnoDB 支持外键但很多生产团队会刻意不加把引用完整性交给应用层减少锁竞争毕设和课程设计里加上外键反而更容易讲清楚表关系。2.4 增删改查之外的数据库层设计要点排班系统表面上是数据库增删改查但数据库层多做一件事能省掉大量排错时间把业务规则里最硬的一条下沉到唯一约束。上面已经用 uk_staff_date 锁死一人一天一班再遇到同一班次一天最多 N 人这类硬规则可以用触发器实现但一般不建议——班次人数上限是可配置的写成触发器反而难维护。我的建议是数据库只锁唯一性类约束人数类和连续天数类规则交给算法层。3. 排班核心算法先定义硬约束与软约束再写贪心生成器自动排班本质是约束满足问题。常见做法是先用贪心算法生成一版可用的班表再用约束校验找出违规项最后靠手动调整兜底。完全用回溯或整数规划求最优解在科室 30 人 × 月度 30 天 × 4 个班次的规模下不是不能做但对中小医院信息科和课程设计场景来说维护成本远高于收益。这一章的代码也是整个系统里真正有含金量的部分面试时讲清楚硬约束过滤 软约束排序这个思路比报一堆框架名有用得多。3.1 把排班规则拆成硬约束和软约束写代码之前先把规则分类因为处理方式完全不同。硬约束违反就出排班事故必须在候选人筛选中直接淘汰软约束是尽量满足资源不足时允许妥协通过排序优先级来体现约束类型规则内容违反后果硬约束同一员工同一天最多一个班次人员分身乏术硬约束连续夜班不超过 N 天通常 2 天疲劳导致医疗风险硬约束每个班次最低在岗人数科室无人值班硬约束请假当天不能排任何班次与审批流程冲突软约束一个月内夜班次数尽量均衡分配不均引发矛盾软约束夜班结束次日尽量不排白班休息不充分硬约束在算法里直接作为过滤条件候选人不满足直接剔除软约束通过排序实现——所有人满足硬约束时把当前排班负担最重的人排在前面让夜班次数自然趋于均衡。3.2 贪心生成器逐日逐班次填充候选不足时记录冲突生成器核心逻辑是按日期从 1 号走到月末每天按班次顺序填充每个班次从当天满足硬约束的候选人里挑累计负担最小的几个人。负担函数可以简单到只看本月已排班次数 本月已上夜班次数 × 权重public ListSchedule generateMonthly(Long deptId, YearMonth month, RuleConfig cfg) { ListStaff staffList staffMapper.selectByDept(deptId); ListLocalDate dates monthDates(month); // 本月全部日期 ListSchedule result new ArrayList(); SetLong scheduledToday new HashSet(); // 当天已排人员 MapLong, Integer nightStreak new HashMap(); // 当前连续夜班天数 for (LocalDate date : dates) { scheduledToday.clear(); for (String shiftCode : cfg.shiftSequence(date)) { int need cfg.requiredCount(deptId, date, shiftCode); ListStaff candidates staffList.stream() .filter(s - canAssign(s, date, shiftCode, scheduledToday, nightStreak)) .sorted(Comparator.comparingInt(s - workloadOf(s, nightStreak))) .collect(Collectors.toList()); for (int i 0; i need i candidates.size(); i) { Staff s candidates.get(i); Schedule record new Schedule(); record.setDeptId(deptId); record.setStaffId(s.getId()); record.setWorkDate(date); record.setShiftCode(shiftCode); record.setSourceType(1); // 1 表示自动生成 result.add(record); scheduledToday.add(s.getId()); updateNightStreak(nightStreak, s.getId(), shiftCode); } } } return result; }代码里两个内部方法决定了排班质量。canAssign 是硬约束检查器依次判断今天是否已排班、是否在请假、连续夜班是否超限workloadOf 是软约束评分返回本月总班次数 夜班次数 × 2值越小越优先被选中。updateNightStreak 维护连续夜班计数当天排的是夜班就在原值上加 1否则清零重置。执行顺序有个容易被忽略的细节每天先排约束最紧的班次。医院场景里夜班永远是最难排的候选人池小、连续天数限制又严所以 shiftSequence 返回的顺序应该把夜班放在最前面。如果先排白班把人都占满到夜班时能选的人就很少冲突记录会大量出现。3.3 约束检查的实现注意连续夜班计数的重置时机private boolean canAssign(Staff s, LocalDate date, String shiftCode, SetLong scheduledToday, MapLong, Integer nightStreak) { if (scheduledToday.contains(s.getId())) { return false; // 一人一天最多一个班次 } if (leaveMapper.existsByStaffAndDate(s.getId(), date)) { return false; // 请假当天不排班 } if (NIGHT.equals(shiftCode)) { int current nightStreak.getOrDefault(s.getId(), 0); if (current s.getMaxConsecutiveNight()) { return false; // 连续夜班达到个人上限直接淘汰 } } return true; } private void updateNightStreak(MapLong, Integer nightStreak, Long staffId, String shiftCode) { if (NIGHT.equals(shiftCode)) { nightStreak.merge(staffId, 1, Integer::sum); // 夜班天数 1 } else { nightStreak.put(staffId, 0); // 非夜班日清零重新计数 } }连续夜班的计数逻辑要放在生成器每次排班之后同步更新不能在 canAssign 里读取时顺带修改。判断和写入分开保证同一个循环里先过滤再改状态顺序不会乱。workloadOf 除了算班次数还要结合 nightStreak 里的值比如给连续夜班的人加一个临时惩罚分让算法优先把夜班分配给昨天没上夜班、累计夜班数少的人。请假检查在 canAssign 里每次都查一次数据库月度排班 30 天 × 30 人会产生约一千次查询可接受科室人数上百时把当月请假记录一次性加载进内存 Map 再查别让 SQL 成为排班生成的瓶颈。3.4 规则参数配置化核心参数表与调整建议自动排班能不能换一家医院直接用取决于规则参数是否外置。常见做法是把参数放到一张 rule_config 表或 application.yml启动时加载成 RuleConfig 对象避免改一个参数就重新编译参数名默认值说明max_consecutive_night2连续夜班上限硬约束night_interval1夜班后至少休息 1 天硬约束nurse_per_shift3每班最低护士人数硬约束doctor_per_shift1每班最低医生人数硬约束night_weight2.0夜班负载权重软约束评分用夜班后休息天数这个约束和连续夜班不同它不是看未来而是看过去。排某天班时要检查这个人前一天或前两天是否上过夜班。实现时在 canAssign 里查最近 N 天的排班记录即可排班记录已经在内存的 result 列表里不需要额外查库。检查时注意边界每月 1 号要取上月最后一天的记录否则月度边界上的连续夜班会漏判。3.4.1 参数放在 yml 还是数据库表里单科室排班系统参数放 application.yml 完全够用改配置重启即可。但排班规则以后可能按科室差异化比如 ICU 连续夜班上限 1 天、普通病房 2 天这时参数就要挪到 rule_config 表并加上 dept_id 字段启动时按科室加载成 MapLong, RuleConfig。做毕设或课程设计用 yml 即可文档里更好解释做生产系统用表。这个选择本质上是在改配置要重启和加一张配置表的维护成本之间做取舍。4. Spring Boot 落地从实体映射到 REST 接口的完整链路数据模型和算法定下来后Spring Boot 项目就是把这两块能力串起来的工程化工作。标准结构是 Controller → Service → Mapper 三层加上 entity、dto、config 几个辅助包。排班系统的工程难点不在某个高深框架特性而在把前面的表和算法正确接起来并把事务边界划清楚。4.1 分层与持久层选型为什么这个项目适合 MyBatis-Plus排班系统是典型的数据库增删改查 一段调度逻辑持久层选 MyBatis-Plus 比 JPA 顺手。第一Mapper 接口继承 BaseMapper 后单表 CRUD 不用写 XML几个核心表的增删改查都是直接调用第二条件构造器用起来快按科室、月份、状态过滤排班记录就是一行 QueryWrapper第三对跟着教程做项目的开发者MyBatis-Plus 的报错信息比 Hibernate 的 HQL 报错更容易定位。最小依赖集合如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependencystarter-web 提供 MVC 和嵌入式 TomcatMyBatis-Plus 负责持久层mysql-connector-j 连接数据库Lombok 去掉实体类的 getter/setter 模板。如果标题里的源码指的就是这套工程骨架这四个依赖就是最小集合权限控制要做得完整再往上加 spring-boot-starter-security 或 Sa-Token。4.2 application.yml 配置时区和驼峰映射两个必调参数spring: datasource: url: jdbc:mysql://localhost:3306/hospital_schedule ?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root jackson: date-format: yyyy-MM-dd time-zone: Asia/Shanghai mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: autoserverTimezone 不设会报 MySQL 经典错误连接 URL 中的时区无法识别设成 Asia/Shanghai 同时解决日期偏移问题。map-underscore-to-camel-case 让数据库的 work_date 自动映射成实体属性 workDate排班表里一堆下划线字段全靠这个配置省掉手写 ResultMap。id-type: auto 对应数据库自增主键插入时不用显式赋值 id。jackson 的 date-format 控制接口返回的日期格式避免前端拿到2025-06-10T00:00:00这种带 T 的字符串又要二次处理。4.3 Service 层实现事务、先删后插与批量插入自动排班的核心方法在 Service 层和第三章的生成器配合。Service 层额外解决两件事重复生成和事务边界Service RequiredArgsConstructor public class ScheduleService { private final ScheduleMapper scheduleMapper; private final ScheduleGenerator generator; Transactional(rollbackFor Exception.class) public GenerateResult generate(Long deptId, YearMonth month) { // 1. 删除该科室当月自动生成的排班保留手动调整记录 scheduleMapper.deleteByDeptAndSource(deptId, month, 1); // 2. 重新执行贪心算法生成排班 ListSchedule schedules generator.generateMonthly(deptId, month, ruleConfig); // 3. 批量插入避免逐条 insert 造成大量网络往返 scheduleMapper.batchInsert(schedules); // 4. 返回生成记录数和冲突列表 return new GenerateResult(schedules.size(), validate(schedules)); } }事务注解必须加在 generate 方法上。步骤 1 和步骤 3 是先删后插如果插入到一半抛异常又没有事务会出现排班表里只剩半个月记录、该月排班直接缺失的脏状态。rollbackFor 指定任何异常都回滚不要只依赖默认的 RuntimeException。注意Transactional 默认只对 RuntimeException 生效检查异常默认不触发回滚。排班生成里的校验逻辑如果声明了受检异常必须靠 rollbackFor 显式指定否则会出现接口返回了错误提示数据却已提交的诡异现象。deleteByDeptAndSource 这个 SQL 的关键在于第三个参数只删 source_type 1 的自动记录护士长手动调过的班次保留下来一键重排不会覆盖人工决定delete iddeleteByDeptAndSource DELETE FROM schedule WHERE dept_id #{deptId} AND work_date BETWEEN #{monthStart} AND #{monthEnd} AND source_type #{sourceType} /delete4.4 接口设计与权限控制排班系统的 REST 接口按资源划分核心接口如下方法路径说明POST/api/schedule/generate触发某科室整月自动排班GET/api/schedule/month按科室和月份查排班日历PUT/api/schedule/adjust手动调整某天的班次POST/api/schedule/validate校验当前排班的违规项POST/api/leave提交请假申请GET/api/staff按科室查人员列表generate 接口的入参只需要 deptId 和 month 两个字段返回生成数量和违规项数量前端拿这个数字做二次确认。手动调整接口是另一个关键方法前端传 staffId、workDate、新班次编码后端在更新前先调约束检查违反硬约束直接返回 400不要拿数据库异常当业务提示PostMapping(/adjust) public ResultVoid adjust(RequestBody AdjustRequest req) { ListViolation violations scheduleService.validateSingle(req); if (!violations.isEmpty()) { return Result.fail(400, 调整后存在硬约束冲突 violations.get(0).getMessage()); } scheduleService.adjust(req); return Result.ok(); }权限控制上医护排班系统通常只需要两种角色管理员护士长或排班专员和普通员工。管理员能调 generate 和 adjust 接口普通员工只能看自己的排班和提交请假。引入 Spring Security 后给 /api/schedule/generate 和 /api/schedule/adjust 加上管理员角色即可员工接口保持登录可访问。4.5 日历视图的数据结构前端直接可用的返回格式排班系统前端最核心的页面是日历视图接口返回建议按日期聚合好省掉前端二次处理{ date: 2025-06-10, items: [ { staffId: 12, name: 王芳, shiftCode: NIGHT, shiftName: 夜班, sourceType: 1 }, { staffId: 18, name: 李丽, shiftCode: OFF, shiftName: 休息, sourceType: 2 } ] }返回按 date 分组、items 按班次排序的原因是前端日历组件每天一个格子拿到这个结构直接渲染列表即可。sourceType 用来区分颜色自动生成显示为灰色、手动调整显示为橙色护士长一眼就能看出哪些班次是人工改过的。这个接口用 QueryWrapper 按 dept_id 和月份查出来在 Service 层用 Collectors.groupingBy 按日期分组一次查询搞定不需要联表查三次。5. 跑通排班系统初始化、验证 SQL 与三个高频坑这部分落在三个动作上把数据库脚本变成可运行状态、用 SQL 验证自动排班结果、识别最容易卡人的版本坑。5.1 数据库初始化的两条路径数据库脚本最常见的导入方式是命令行执行mysql -uroot -p hospital_schedule.sql导入后执行 SHOW TABLES 确认五张核心表都已创建。第二条路径是把 sql 文件放进 resources 目录配置 spring.sql.init.modealways 让应用启动时自动建表。注意第二条路径每次启动都会执行脚本如果脚本里有初始化数据要配合 continue-on-errortrue 或保证脚本幂等我一般只在开发环境用这一条交付文档里写第一种。5.2 用三条 SQL 验证自动排班生成排班后先别急着看页面用 SQL 做三项检查。第一项查一人一天多班正常情况下返回空集SELECT staff_id, work_date, COUNT(*) AS cnt FROM schedule GROUP BY staff_id, work_date HAVING cnt 1;第二项查连续夜班超过两天用连续区间分组实现SELECT staff_id, MIN(work_date) AS start_date, COUNT(*) AS days FROM ( SELECT staff_id, work_date, DATE_SUB(work_date, INTERVAL ROW_NUMBER() OVER ( PARTITION BY staff_id ORDER BY work_date ) DAY) AS grp FROM schedule WHERE shift_code NIGHT ) t GROUP BY staff_id, grp HAVING COUNT(*) 2;子查询里连续夜班日期减去按日期排序的序号会落在同一个 grp 值上中间断一天grp 就跳变。外层按 staff_id 和 grp 分组统计出的天数就是连续夜班数超过 2 的就是违规项。第三项查每天每班最低在岗人数阈值要和 rule_config 的 nurse_per_shift 保持一致SELECT work_date, shift_code, COUNT(*) AS cnt FROM schedule WHERE shift_code ! OFF GROUP BY work_date, shift_code HAVING cnt 3;5.3 Spring Boot 版本坑与 MyBatis-Plus 更新陷阱三个坑出现频率最高。第一是 Spring Boot 版本太高引发的 javax/jakarta 命名空间问题3.x 要求 Java 17、把 javax 包名换成 jakarta.*第三方依赖还在用 javax 会启动报 NoClassDefFoundError统一基线即可Java 8 用 2.7.xJava 17 用 3.x。第二是 MyBatis-Plus 的 updateById 默认不更新 null 字段手动调整排班时想置空某字段会静默失败需加 TableField(updateStrategy FieldStrategy.IGNORED) 或用 UpdateWrapper 显式 set。第三是时区偏移连接串少了 serverTimezoneAsia/Shanghai 且系统时区是 UTC 时DATE 类型可能整体偏移一天连续夜班校验全部算错。这三处优先排查比逐行断点快得多。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价