news 2026/9/16 23:41:11

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Spring Boot的医护人员排班系统:规则建模与轮转算法实践

简介:这份基于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 领域模型优先级:人员、班次、排班计划、规则

先看核心实体怎么划分。我一般会把排班领域拆成四个主体:人员(医生/护士)、班次(早班/晚班/夜班等)、排班计划(某科室某周期内的人员-班次映射)、班次规则(约束条件)。这四类对应到表结构上,就是四张基础表加若干关联表。

实体拆分的依据是“谁能独立变化”。人员的职称、科室会变,班次的起止时间会变,但排班计划一旦发布就不允许随意改,只能走换班审批。所以排班计划表要单独加状态字段,比如DRAFTPUBLISHEDLOCKED,这是很多第一版设计容易漏掉的。

-- 班次表 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组件,每个约束一条方法,返回List<String>收集违规信息。Controller 只管接收请求和返回结果,排班算法只负责生成候选方案,validator 负责否决和警告。

@Component public class ShiftRuleValidator { private final RuleConfig ruleConfig; public ShiftRuleValidator(RuleConfig ruleConfig) { this.ruleConfig = ruleConfig; } public List<String> validate(StaffShiftPlan plan) { List<String> 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 层生成代码快,分页和条件构造器对“按科室、按日期范围查询排班”这种需求非常顺手。工程结构用一个典型的分层包:controllerservicemapperentitydto

3.1 项目结构与核心依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</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) { List<Staff> staffList = staffMapper.selectByDept(request.getDeptId()); List<ScheduleDetail> detailList = new ArrayList<>(); // 按日期逐个生成 for (LocalDate date = request.getStartDate(); !date.isAfter(request.getEndDate()); date = date.plusDays(1)) { List<Staff> working = availableStaff(date, staffList); Map<String, List<Staff>> grouped = groupBySkillLevel(working); // 每个班次分配人员 for (Shift shift : shiftMapper.selectByDept(request.getDeptId())) { List<Staff> assigned = assignToShift(grouped, shift); for (Staff staff : assigned) { detailList.add(new ScheduleDetail(request.getPlanId(), staff.getId(), date, shift.getShiftCode())); } } } // 规则校验 List<String> hardErrors = new ArrayList<>(); List<String> softWarnings = new ArrayList<>(); for (Staff staff : staffList) { List<DayShift> 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 List<Staff> availableStaff(LocalDate date, List<Staff> 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 Result<ScheduleResult> generate(@RequestBody GenerateRequest request) { return Result.success(scheduleService.generate(request)); } @GetMapping("/list") public Result<Page<ScheduleDetailDTO>> 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 Result<Void> swap(@RequestBody SwapRequest request) { scheduleService.swapShift(request.getPlanId(), request.getStaffAId(), request.getStaffBId(), request.getDate(), request.getReason()); return Result.success(); } }

Controller 层的三个接口分别对应排班管理的三个核心动作:生成、查询、换班。换班接口单独提出来是因为它不是一个简单的 update,而是一组事务操作——A 和 B 在某天交换班次,需要检查两个人在目标日期是否本来有班、是否违反连续工作限制,还要记录换班原因以便日后溯源。返回结果统一用Result<T>包装,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 List<ScheduleDetail> buildRotationPlan(List<Staff> staffList, LocalDate startDate, int periodDays) { List<ScheduleDetail> 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 Result<ScheduleResult> generate(@RequestBody GenerateRequest request) { return Result.success(scheduleService.generate(request)); } @PreAuthorize("hasAnyAuthority('SCHEDULE_VIEW')") @GetMapping("/my") public Result<List<ScheduleDetailDTO>> 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-formatJavaTimeModule,就会报反序列化错误。第三个坑最隐蔽:排班查重时只看了排班表,没看休假表。一个人已提交休假,却仍在轮转表里被排了班,护士长看到结果会觉得系统像开玩笑。联调时最好先在测试库造一批休假数据和排班数据混跑,而不是只用干净数据验证。

5.3 部署验证的最小命令集

部署不需要一开始就上 K8s,一台有 Docker 的服务器足够跑通所有功能验证。

# 1. 构建可执行 Jar mvn clean package -DskipTests # 2. 启动 MySQL docker run -d --name schedule-mysql \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=schedule_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.url="jdbc:mysql://127.0.0.1:3306/schedule_db?useUnicode=true&characterEncoding=utf8" \ --spring.datasource.username=root \ --spring.datasource.password=root123 \ > app.log 2>&1 & # 5. 验证健康检查 curl -s http://localhost:8080/api/schedule/list?deptId=1\&startDate=2025-03-01\&endDate=2025-03-07

数据库连接串里的useUnicode=true&characterEncoding=utf8必须加。MySQL 8 默认字符集已经是 utf8mb4,但排班系统里涉及科室名称、人员姓名,传参过程中的中文编码问题经常等到查不到数据时才暴露。启动方式用nohup加日志重定向,测试阶段足够用,后续接 systemd 统一管理再补启动脚本。健康检查用一个真实接口而不是 Spring Boot 默认的/actuator/health,因为后者只能证明应用内存里活着,不能证明数据库连接和 Mapper 没问题。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 23:40:17

Transformer架构核心:自注意力机制与多头注意力详解

1. Transformer架构全景解析Transformer模型自2017年由Vaswani等人提出后&#xff0c;彻底改变了序列建模的范式。这个完全基于注意力机制的架构&#xff0c;摒弃了传统RNN的循环结构和CNN的卷积操作&#xff0c;通过自注意力机制实现了对序列数据的全局建模能力。其核心设计思…

作者头像 李华
网站建设 2026/9/16 23:38:21

VS Code接入Minimax API:自己动手打造AI编程助手

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 23:36:30

EPS扭矩测试五路同步采集关键技术解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 23:36:27

忆阻器存内计算破解通信译码功耗瓶颈

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华