资讯动态

基于Spring Boot的医护人员排班系统:规则建模与轮转算法实践

发布时间:2026/9/16 23:41:15 来源:尧图企业网站定制
简介这份基于Spring Boot的医护人员排班系统设计与实现资料包包含完整毕业设计/课程设计所需的论文正文、可运行源码与开题报告适合计算机相关专业学生、Java开发入门者以及需要快速搭建排班管理系统的开发者参考。内容从研究背景、相关技术到系统分析、概要设计、详细实现与功能测试逐章展开覆盖MySQL数据库设计、B/S结构、Spring Boot框架应用以及医护类型、排班类型、科室信息、医院信息和医护信息管理等核心模块。资源包约22.79MB以论文文档、Java源码、开题报告等类型文件为主目录结构清晰便于按章节阅读或导入IDE运行验证。目前已有73人学习适合用于理解完整的业务闭环、复用基础代码骨架也可作为毕业论文写作与项目答辩的参考资料。1. 医护人员排班系统为什么值得用 Spring Boot 重新做一遍医院排班从来不是一个“填表格”问题而是一个带约束的资源调度问题有人要上夜班有人必须下夜班后休息满 24 小时有人考勤要求每月夜班数不超过 6 次还有法定节假日的班次权重不一样。Excel 能排但排完没人能说清“为什么这周 A 护士单休”更没人能快速回答“本月每个人夜班次数是否超标”。这正是把排班逻辑代码化的价值所在规则从人的脑子里搬到程序里每次排班生成结果可追溯、可统计、可反推规则缺陷。基于 Spring Boot 来做是因为这类“权限模型固定、流程相对标准、数据量不大但对可靠性和可追溯性有要求”的业务系统正好落在 Spring Boot 最舒适的区域起步快、生态成熟、招人容易。同一个项目用 Python Flask 也行但放在一个需要持续维护、需要接医院既有统一认证或后续升级为微服务的环境里Spring Boot 的工程化优势会更明显。这篇就顺着“排班需求建模 → Spring Boot 接口实现 → 排班算法与冲突检测 → 权限与部署收尾”这条主线把一套能写进论文、也能直接跑起来的实现路径讲清楚。2. 排班需求建模先把班次、人员、规则拆成表结构排班系统的技术难点不在 CRUD而在需求分析阶段能不能把“模糊的排班习惯”翻译成结构化规则。绝大多数开题报告里写的“系统功能结构图”到数据库设计时都会漏掉一个关键表——规则配置表。没有这张表所有排班逻辑都会散落在 Service 代码里改一个参数就得改代码重新编译这在医院环境里是不可接受的。2.1 领域模型优先级人员、班次、排班计划、规则先看核心实体怎么划分。我一般会把排班领域拆成四个主体人员医生/护士、班次早班/晚班/夜班等、排班计划某科室某周期内的人员-班次映射、班次规则约束条件。这四类对应到表结构上就是四张基础表加若干关联表。实体拆分的依据是“谁能独立变化”。人员的职称、科室会变班次的起止时间会变但排班计划一旦发布就不允许随意改只能走换班审批。所以排班计划表要单独加状态字段比如DRAFT、PUBLISHED、LOCKED这是很多第一版设计容易漏掉的。-- 班次表 CREATE TABLE shift ( id BIGINT PRIMARY KEY AUTO_INCREMENT, shift_code VARCHAR(20) NOT NULL COMMENT 班次编码: MORNING/EVENING/NIGHT, shift_name VARCHAR(50) NOT NULL COMMENT 班次名称, start_time TIME NOT NULL, end_time TIME NOT NULL, weight DECIMAL(3,1) DEFAULT 1.0 COMMENT 班次权重, 夜班权重高, need_count INT DEFAULT 1 COMMENT 每天该班次需要人数 ); -- 排班计划主表 CREATE TABLE schedule_plan ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dept_id BIGINT NOT NULL COMMENT 科室ID, plan_start_date DATE NOT NULL, plan_end_date DATE NOT NULL, status VARCHAR(20) DEFAULT DRAFT, publish_time DATETIME NULL ); -- 排班明细表 CREATE TABLE schedule_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, plan_id BIGINT NOT NULL, staff_id BIGINT NOT NULL, work_date DATE NOT NULL, shift_code VARCHAR(20) NOT NULL, is_swap TINYINT DEFAULT 0 COMMENT 是否换班产生, UNIQUE KEY uk_staff_date (staff_id, work_date) );这段 SQL 里最值得关注的是schedule_detail的唯一键uk_staff_date。它保证了“同一个人同一天只能有一个班次”这个约束用数据库兜底比用代码判断更可靠。班次权重的用途是统计工作量夜班权重 1.3、白班 1.0月底核算时直接用SUM(weight)就能排序。2.2 把口头规则变成配置规则参数放数据库“护士长说排班要公平”这句话没法写代码。必须拆成可量化的硬约束和软约束。硬约束是违反就排不出来的软约束是尽量满足、不满足要给警告的。约束类型参数名称示例值说明硬约束max_night_per_month6每月夜班次数上限硬约束rest_after_night24 小时夜班后必须休息小时数硬约束consecutive_work_limit6 天连续上班天数上限软约束weekend_work_balance尽量均衡周六日值班次数差不超过 2软约束shift_stability尽量少换班同一人一周内班次变动次数这些参数放到一张schedule_rule配置表里然后由后台管理页面维护。真正实现时Spring Boot 的一个配置类去读取这张表缓存在本地 Map 里排班算法运行时只认这份参数不认代码里的魔法数字。这样换了一个护士长不用改程序改数据库记录就够了。2.3 Service 层怎么承接这种规则规则一旦变多Service 就会膨胀。常见做法是把约束判断单独抽一个RuleValidator组件每个约束一条方法返回ListString收集违规信息。Controller 只管接收请求和返回结果排班算法只负责生成候选方案validator 负责否决和警告。Component public class ShiftRuleValidator { private final RuleConfig ruleConfig; public ShiftRuleValidator(RuleConfig ruleConfig) { this.ruleConfig ruleConfig; } public ListString validate(StaffShiftPlan plan) { ListString errors new ArrayList(); // 检查连续夜班次数 int nightCount 0; for (DayShift ds : plan.getDays()) { nightCount NIGHT.equals(ds.getShiftCode()) ? nightCount 1 : 0; if (nightCount 2) { errors.add(plan.getStaffName() 连续夜班超过2天); break; } } // 检查夜班后休息时长 // ... return errors; } }这里的逻辑说明validator 不修改任何数据只返回违规信息。一个排班方案生成后先跑一遍 validator有硬约束错误则整周方案推倒重排只有软约束警告则保留方案并且提示护士长确认。这种“生成-校验-重试”的结构在论文里可以对应一章“排班规则校验模块的设计”在工程上也是最容易测试的形态。3. Spring Boot 排班后端从实体到接口的最小实现需求建模完成之后进入 Spring Boot 工程实现阶段。这里以 MyBatis-Plus 作为持久层框架来演示理由是毕设场景里 service 层生成代码快分页和条件构造器对“按科室、按日期范围查询排班”这种需求非常顺手。工程结构用一个典型的分层包controller、service、mapper、entity、dto。3.1 项目结构与核心依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency依赖没有用最新版本而是锁了一个目前社区里大量项目实际在用的 MyBatis-Plus 3.5.x。选它的一个潜在问题是它和 Spring Boot 3.2 的兼容性需要额外确认但排班系统本质上业务复杂度远高于技术复杂度不需要追新。如果你用 Spring Boot 2.7.x 那套生态这套依赖关系会更稳定建议优先选 2.7.x。3.2 排班生成的 Service 实现核心 Service 接口是generateSchedule(GenerateRequest request)。入参包含科室、起止日期、需要的人员列表。返回值建议是一个ScheduleResult对象包含三块排班明细列表、硬约束违规列表、软约束提示列表。这样前端可以同时展示“生成成功”和“有警告”两种状态。Service public class ScheduleServiceImpl implements ScheduleService { private final ScheduleDetailMapper detailMapper; private final ShiftRuleValidator validator; Override Transactional(rollbackFor Exception.class) public ScheduleResult generate(GenerateRequest request) { ListStaff staffList staffMapper.selectByDept(request.getDeptId()); ListScheduleDetail detailList new ArrayList(); // 按日期逐个生成 for (LocalDate date request.getStartDate(); !date.isAfter(request.getEndDate()); date date.plusDays(1)) { ListStaff working availableStaff(date, staffList); MapString, ListStaff grouped groupBySkillLevel(working); // 每个班次分配人员 for (Shift shift : shiftMapper.selectByDept(request.getDeptId())) { ListStaff assigned assignToShift(grouped, shift); for (Staff staff : assigned) { detailList.add(new ScheduleDetail(request.getPlanId(), staff.getId(), date, shift.getShiftCode())); } } } // 规则校验 ListString hardErrors new ArrayList(); ListString softWarnings new ArrayList(); for (Staff staff : staffList) { ListDayShift staffPlan extractStaffPlan(detailList, staff.getId()); hardErrors.addAll(validator.validateHard(staffPlan)); softWarnings.addAll(validator.validateSoft(staffPlan)); } if (hardErrors.isEmpty()) { detailMapper.batchInsert(detailList); } return new ScheduleResult(detailList, hardErrors, softWarnings); } private ListStaff availableStaff(LocalDate date, ListStaff all) { // 过滤休假、请假、培训状态的人员 return all.stream() .filter(s - !leaveMapper.existsOnDate(s.getId(), date)) .collect(Collectors.toList()); } }逻辑说明集中在三个地方。第一Transactional保证排班明细要么全部插入要么全部回滚避免生成了一半程序异常退出留下脏数据。第二availableStaff要查请假表这是排班系统最容易忽略的实际业务前置条件。第三硬约束一旦有错误当前实现的策略是直接不落库返回错误列表给前端而不是生成了再用这样可避免库里有半成品数据。参数名planId是在调用前由前端或上层服务先创建好的计划主键否则明细无从归属。3.3 Controller 层路径设计和返回包装RestController RequestMapping(/api/schedule) public class ScheduleController { PostMapping(/generate) public ResultScheduleResult generate(RequestBody GenerateRequest request) { return Result.success(scheduleService.generate(request)); } GetMapping(/list) public ResultPageScheduleDetailDTO list(RequestParam Long deptId, RequestParam String startDate, RequestParam String endDate, RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { return Result.success(scheduleService.pageQuery(deptId, startDate, endDate, pageNum, pageSize)); } PostMapping(/swap) public ResultVoid swap(RequestBody SwapRequest request) { scheduleService.swapShift(request.getPlanId(), request.getStaffAId(), request.getStaffBId(), request.getDate(), request.getReason()); return Result.success(); } }Controller 层的三个接口分别对应排班管理的三个核心动作生成、查询、换班。换班接口单独提出来是因为它不是一个简单的 update而是一组事务操作——A 和 B 在某天交换班次需要检查两个人在目标日期是否本来有班、是否违反连续工作限制还要记录换班原因以便日后溯源。返回结果统一用ResultT包装code/message/data 三段式和前端约定好错误码规范避免后端返回裸对象前端每个接口都要单独判断。4. 排班冲突检测与轮转算法在贪心和约束之间取舍排班问题在算法层面是典型的约束满足问题医院场景下常用“周排班 月统计”模式实际不需要求解大规模最优解而是求可行解加尽量均衡。上一章代码里用的就是贪心思路这一章把贪心的动机、边界和轮转逻辑讲透。4.1 为什么不用回溯或运筹优化如果护士人数少于 15用回溯法穷举每天的人员组合完全可行。但真实科室人员是 30 到 50 人且每人的资质不同——有的不能独立值夜班有的正在规培轮转每月可用日期不同。这种情况下回溯法的分支因子爆炸式增长几秒内未必能出结果。而运筹优化比如整数规划效果虽好但对大多数排班系统使用者来说有三个硬伤一是依赖额外工具包部署复杂二是模型求解时间不稳定遇到节假日调休这种特殊日期要重新建模三是结果难解释护士长问“为什么这周我没休息”时你没法用变量赋值来解释。所以常见做法是“轮转优先贪心微调”先用轮转保证长期公平再用当日人员池和权限过滤保证当天不排错人。4.2 固定轮转 局部贪心的组合做法轮转表的含义是规定一组序列例如早班、白班、夜班、休、休、白班、早班每个人从不同的起点进入这个序列。这样每个月每个人夜班次数天然均衡而且换班需求会大幅减少。public class RotationScheduler { private static final String[] ROTATION { MORNING, DAY, EVENING, NIGHT, REST, REST, DAY }; public ListScheduleDetail buildRotationPlan(ListStaff staffList, LocalDate startDate, int periodDays) { ListScheduleDetail result new ArrayList(); int offset 0; for (Staff staff : staffList) { // 每个员工从不同起点开始轮转, 避免所有人同一天休息 int startIndex (staff.getEmployeeNo().hashCode() Integer.MAX_VALUE) % ROTATION.length; for (int i 0; i periodDays; i) { LocalDate date startDate.plusDays(i); String shiftCode ROTATION[(startIndex i offset) % ROTATION.length]; if (!REST.equals(shiftCode)) { result.add(new ScheduleDetail(null, staff.getId(), date, shiftCode)); } } offset; } return result; } }这里的核心参数是ROTATION数组和offset。数组长度等于一周天数offset保证相邻员工进入轮转序列的相位不同避免全科室同一批人同时休息。hashCode() Integer.MAX_VALUE是为了让起点分布均匀又不依赖数据库自增 ID因为在并发插入时自增 ID 的分布是连续的可能导致同科室员工起点全部扎堆。这个实现的缺陷也很明显它完全不考虑个人因素比如有人周三固定不能上夜班。所以这个方法的输出只能作为“初稿”要接上一章的 validator发现冲突再微调。4.3 冲突检测的落点放在保存前还是保存后我的建议是放在保存前且用数据库事务配合。先跑规则引擎再落库。但要注意规则引擎检测通过后到实际落库之间理论上可能有并发修改比如另一管理员刚把一个员工设为休假。所以真正严谨的做法是落库时再查一遍状态。UPDATE schedule_detail sd LEFT JOIN leave_record lr ON sd.staff_id lr.staff_id AND sd.work_date BETWEEN lr.start_date AND lr.end_date SET sd.status CANCELLED WHERE lr.id IS NOT NULL;这条 SQL 是“事后悔血”的兜底策略批处理夜里跑把所有已经排班但被休假记录覆盖的明细自动置为取消。在论文里可以写成“异常数据补偿机制”。它不是主流程的一部分却是真实运营中一定会遇到的边界情况。5. 权限控制、前端联调与部署验证的 3 个收尾点排班系统对外只开放给三种角色系统管理员、护士长、普通医护人员。护士长能生成和发布排班普通人员只能查看自己的班表和提交换班申请。这个权限模型用 Spring Security 加 JWT 可以清楚实现。这一章不做完整配置展示只把三个最容易被拖到上线前才发现的坑讲明白。5.1 用 Spring Security 的注解方法做角色隔离PreAuthorize(hasRole(ADMIN) or hasRole(HEAD_NURSE)) PostMapping(/generate) public ResultScheduleResult generate(RequestBody GenerateRequest request) { return Result.success(scheduleService.generate(request)); } PreAuthorize(hasAnyAuthority(SCHEDULE_VIEW)) GetMapping(/my) public ResultListScheduleDetailDTO mySchedule(RequestParam String startDate, RequestParam String endDate) { String staffId JwtUtil.getCurrentUserId(); return Result.success(scheduleService.queryByStaff(staffId, startDate, endDate)); }在 Spring Boot 3 里hasRole会自动加ROLE_前缀权限表里要存成ROLE_ADMIN而不是ADMIN这是配置时最容易报 403 的位置。JWT 工具类从 token 里取当前用户 ID而不是让前端把用户 ID 放请求体里因为后者任何人都能伪造。5.2 联调里最隐蔽的三个坑跨域、时区、休假遮挡前端页面经常直接用 Vite 的 dev server 起在 5173 端口后端跑在 8080跨域是必然的。Spring Boot 的全局 CORS 配置要写在WebMvcConfigurer里而不是只靠CrossOrigin加在单个 Controller 上否则被拦截器拦掉的请求根本到不了 Controller。第二个坑是日期传输格式前端传2025-03-03字符串后端用LocalDate接收时如果没配置spring.jackson.date-format和JavaTimeModule就会报反序列化错误。第三个坑最隐蔽排班查重时只看了排班表没看休假表。一个人已提交休假却仍在轮转表里被排了班护士长看到结果会觉得系统像开玩笑。联调时最好先在测试库造一批休假数据和排班数据混跑而不是只用干净数据验证。5.3 部署验证的最小命令集部署不需要一开始就上 K8s一台有 Docker 的服务器足够跑通所有功能验证。# 1. 构建可执行 Jar mvn clean package -DskipTests # 2. 启动 MySQL docker run -d --name schedule-mysql \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEschedule_db \ -p 3306:3306 mysql:8.0 # 3. 初始化表结构 mysql -h127.0.0.1 -uroot -proot123 schedule_db doc/schema.sql # 4. 启动应用 nohup java -jar target/schedule-system.jar \ --spring.datasource.urljdbc:mysql://127.0.0.1:3306/schedule_db?useUnicodetruecharacterEncodingutf8 \ --spring.datasource.usernameroot \ --spring.datasource.passwordroot123 \ app.log 21 # 5. 验证健康检查 curl -s http://localhost:8080/api/schedule/list?deptId1\startDate2025-03-01\endDate2025-03-07数据库连接串里的useUnicodetruecharacterEncodingutf8必须加。MySQL 8 默认字符集已经是 utf8mb4但排班系统里涉及科室名称、人员姓名传参过程中的中文编码问题经常等到查不到数据时才暴露。启动方式用nohup加日志重定向测试阶段足够用后续接 systemd 统一管理再补启动脚本。健康检查用一个真实接口而不是 Spring Boot 默认的/actuator/health因为后者只能证明应用内存里活着不能证明数据库连接和 Mapper 没问题。本文还有配套的精品资源点击获取

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

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

免费获取报价