ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Spring Boot医院排班系统开发实战:数据库设计、自动排班与论文写作

Spring Boot医院排班系统开发实战:数据库设计、自动排班与论文写作 简介排班系统是医院信息化建设中业务逻辑最复杂的场景之一它不只是简单的日历管理更涉及连续24小时覆盖、人员资质匹配、换班审批状态流转和历史数据追溯等核心需求。本文从业务痛点出发讲解如何基于Spring Boot搭建医护人员排班系统重点拆解数据库表结构设计包括用户、班次、排班计划及换班申请等关键表深入分析自动排班算法的约束建模与Java实现以及换班场景下的并发控制与事务处理。同时文章结合工程实践分享排班日历展示、统计报表、定时提醒等功能的实现细节并给出毕业设计论文的写作框架与答辩注意事项。对于正在学习Spring Boot开发或准备医院排班系统毕业设计的开发者这是一份从源码到数据库、再到论文撰写的完整实战参考。根据医院排班系统、Spring Boot源码、排班算法等高频搜索需求本文提供了可直接落地的设计思路与代码片段。 我做了几年Java开发经手过的管理系统没有二十个也有十几个但医院排班系统是少数让我觉得业务逻辑比技术本身更有意思的项目。网上关于Java医院排班系统源码 springboot医护人员排班系统源码源码数据库论文这类标题的资源很多但大多数下载下来要么缺表结构要么论文跟代码对不上真正能跑起来、能写进毕业设计答辩PPT的少之又少。这篇文章我打算不讲虚的直接拆解一个基于Spring Boot的医护人员排班系统到底该怎么设计、怎么建表、怎么处理排班里的那些坑以及论文部分该怎么和代码呼应。1. 医护人员排班这件事到底难在哪业务需求拆解排班系统听起来就是个日历换班申请但实际上手之后才发现它跟普通的企业排班系统完全是两码事。医院排班有几个特征是其他行业不太会遇到的。1.1 连续的24小时覆盖需求工厂排班可以有三班倒但医院的值班不是简单拆成早中晚三个班就完了。急诊、重症监护、产房这些科室必须保证全天候有人而且夜班结束之后必须安排休息时间不能连续排两个夜班。这意味着排班引擎需要处理班次连续性疲劳度限制跨天日期归属这类规则。很多新手在数据库设计阶段只建了一张排班表字段就放一个日期和一个班次名称结果到后面写自动排班算法的时候才发现根本无法判断连续工作了几小时是否满足夜班后休满48小时这类需求。1.2 人员属性维度多一个科室里的护士并不是同质化的护师和护士能承担的职责不同有带教资质的护士才能带实习生有些岗位必须由主管护师以上的人来顶。系统必须支持按职称过滤按资质过滤按科室过滤三重筛选才能在自动排班的时候排出合规的班表。我见过一个医疗项目里排班表上把主治医生排到了普通护理岗就是因为人员表里没有区分人员的岗位类型。1.3 换班和调休的状态流转排班系统上线之后用户用得最多的功能其实是换班申请。护士A周二想请假需要和护士B换班两个人协商好之后在系统里发起申请护士长审批通过排班表才正式变更。这个场景涉及原班次锁定目标班次预占审批通过后原子切换审批拒绝后释放预占四个状态如果没有处理好状态流转上线后会出现同一时间两个人都在值班表上的问题这在医院是会出安全事故的。1.4 排班结果要能追溯医院有质控要求某一天某个护士在哪一个病区值班后续需要翻查历史记录。所以系统里的排班数据不能删了就没了每一次调班、换班、请假都要留下操作日志。这一点直接决定了数据库表要不要做逻辑删除、要不要建操作记录表。基于这些分析我对这个项目的技术选型建议是Spring Boot作为后端框架MyBatis-Plus或Spring Data JPA作为持久层框架MySQL存储业务数据Redis缓存科室的排班概览前端可以用Vue 3 Element Plus做一个管理后台也可以直接用Thymeleaf模板渲染减少前后端分离的复杂度。下面所有内容我都会围绕这套技术栈展开。2. 从零到一搭建Spring Boot排班服务工程结构与核心依赖这一节先解决项目怎么搭起来的问题。很多拿到的源码本身能跑但里面依赖版本混乱、包结构没有分层二次开发的时候非常痛苦。我建议按照标准的四层结构来组织代码。2.1 工程模块划分hospital-schedule/ ├── hospital-common/ // 通用工具类、统一返回体、异常处理 ├── hospital-system/ // 系统管理用户、角色、科室、菜单 ├── hospital-schedule/ // 排班核心模块排班、换班、统计 ├── hospital-admin/ // Spring Boot启动模块对外暴露REST接口 ├── sql/ // 数据库初始化脚本 └── docs/ // 论文相关图表、说明文档这种分模块的方式核心优点是让排班业务和系统管理解耦。实际项目中如果所有Controller、Service、Mapper都堆在一个模块里到后期同事一多合并代码必冲突。2.2 pom.xml中依赖选择的细节父POM中我建议统一锁定Spring Boot版本。当前2025年这个时间点Spring Boot 3.x早就稳定了但如果你是拿这个项目做毕业设计我反而推荐Spring Boot 2.7.x。原因很简单网上资料多、兼容性问题少、答辩时老师问什么你都能接得住。Spring Boot 2.7用的还是javax命名空间3.x换成了jakarta很多老教程的代码直接复制会报红。没必要在版本上给自己挖坑。核心依赖就这几个dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency特别说一下Spring Security。排班系统涉及医护人员的个人信息和排班数据不能裸奔。但Security的配置很多人一上来就被它折磨我的建议是先用最简单的方案基于JWT的无状态认证把登录接口放行其余接口全部要求携带token。权限上先只做角色级别的控制管理员能访问所有接口护士长能访问本科室排班接口普通护士只能查看自己的排班和发起换班申请。等系统跑通了再逐步细化到细粒度权限。2.3 统一响应与异常处理这一步虽然不起眼但直接决定你后期调接口的效率。我习惯定义这样的统一返回结构Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T ResultT fail(String message) { ResultT r new Result(); r.setCode(500); r.setMessage(message); return r; } }全局异常处理类用RestControllerAdvice统一捕获业务异常、参数校验异常和兜底Exception避免出现Java空指针堆栈直接打到前端的情况。这些代码每个Spring Boot项目都差不多但它是后端工程质量的底盘答辩的时候老师也会看这些细节。3. 数据库模型设计那些必须提前想清楚的表关系医院排班系统的核心在数据库。表设计得好不好直接决定后面的自动排班、调班审批、工时统计是顺滑还是卡壳。3.1 基础表用户、角色、科室、病区用户表不能简单叫user因为user在MySQL里是保留字容易出问题。我习惯叫sys_user。字段包括id、username、password、real_name、gender、title职称、job_number工号、dept_id关联科室、phone、email、status、create_time、update_time、deleted。注意这里的deleted字段是逻辑删除标记。原因前面说过了排班数据需要留痕。但用户数据要不要逻辑删除其实可以讨论我建议保留因为用户一旦被物理删除历史排班记录里的nullable关联就会变成空后续统计就不知道该算谁的。科室表sys_deptid、dept_name、parent_id、leader_id、sort_order。parent_id是为了支持内科部-呼吸内科这种层级结构。排班的时候可能需要按一级科室汇总护士人数这个树形结构就很有用。病区表nurse_stationid、station_name、dept_id、location、bed_count、nurse_manager_id。有些医院一个科室有多个病区病区才是排班的实际单位。如果忽略病区概念后期增加病区的时候就要改表结构非常麻烦。角色表sys_role和用户角色关联表sys_user_role是标准的RBAC设计Spring Security里直接能映射。3.2 核心表班次定义表班次定义表是排班系统的字典源头。很多排班系统设计失败就是因为把班次硬编码在代码里。实际上不同科室的班次差异很大门诊部可能是上午班8:00-12:00下午班14:00-17:30住院部可能是白班8:00-18:00小夜班18:00-24:00大夜班0:00-8:00急诊可能是8-16、16-24、0-8三班倒还有24小时值班制。所以班次表应该这样设计字段shift_id、dept_id、shift_name、start_time、end_time、work_hours、is_night是否夜班、need_rest_hours班后最短休息时长、color_hex前端展示用、sort_order。is_night字段非常重要自动排班算法在判断夜班后不能继续排早班这类规则时直接依赖这个布尔值。3.3 核心表排班计划表排班计划表是整个系统的核心是最容易出问题的地方。我的设计如下CREATE TABLE schedule_plan ( plan_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 排班记录ID, station_id BIGINT NOT NULL COMMENT 病区ID, department_id BIGINT NOT NULL COMMENT 科室ID, target_user_id BIGINT NOT NULL COMMENT 被排班人员ID, shift_id BIGINT NOT NULL COMMENT 班次ID, shift_date DATE NOT NULL COMMENT 值班日期, shift_start DATETIME NOT NULL COMMENT 值班开始时间(冗余存), shift_end DATETIME NOT NULL COMMENT 值班结束时间(冗余存), audit_status TINYINT NOT NULL DEFAULT 0 COMMENT 状态: 0草稿 1已发布 2调班申请中 3已变更, source_type TINYINT NOT NULL DEFAULT 0 COMMENT 来源: 0自动排班 1手动排班 2换班生成, created_by BIGINT COMMENT 创建人, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_station_date_user (station_id, shift_date, target_user_id), KEY idx_user_date (target_user_id, shift_date), KEY idx_station_date (station_id, shift_date) ) ENGINEInnoDB COMMENT排班计划表;这个表里值得解释的有几个点第一shift_date和shift_start/end是冗余存储的。理论上只要有了shift_date和shift_id就能join出班次的起止时间。但排班查询的频次很高每次查询都join班次表会浪费数据库资源所以冗余存了一份快照。更重要的是未来如果班次定义调整了起止时间历史排班记录不应该跟着变——这时候冗余字段反而保证了历史数据的正确性。这是数据仓库里常见的思想叫缓慢变化维度。第二唯一索引uk_station_date_user保证了同一个护士在同一个病区的同一天只有一条排班记录。这个约束能直接拦截双排和并发冲突问题。我还加了一个uk_station_date_shift类似的唯一约束来保证同一个病区同一时间不能排两个同班次的人吗这个不一定要加因为一个病区可以同时有多个同班次的人。3.4 辅助表换班申请表与操作日志表换班申请是整个系统里事务性最强的功能。我设计一张schedule_change表字段change_id、plan_id原排班记录ID、origin_user_id、target_user_id、target_plan_id目标排班记录ID、change_type1换班 2调休 3代班、status0待审批 1同意 2拒绝 3已撤销、apply_time、approve_time、approver_id、reason。当时做这个表的时候我踩过一个坑没有存target_plan_id只存了目标日期和目标班次结果审批通过的时候发现目标人那天已经被换了三次了。后来加上了target_plan_id并且在提交申请时就要对目标人目标班次加锁防止并发冲突。下一步我会详细说这个事。操作日志表schedule_log则非常简单id、plan_id、operator_id、operation_typecreate/update/publish/change、content_json、operation_time。所有对排班数据的修改都写进去。有些团队觉得日志表是多余的会增加代码量但真到了答辩或者项目复盘的时候它是证明系统完整性最有力的设计之一。4. 自动排班算法从规则到可落地的实现排班系统最大的亮点通常是自动排班。但自动排班并不像想象中那么难——它本质上是一个约束满足问题。难点在于约束条件怎么建模、冲突发生怎么处理。4.1 约束规则建模我把排班规则分成了四类硬性约束必须满足一个护士同一天不能排两个班次夜班结束后需要休息满指定时长比如大夜班到次日8点最早要到隔日8点才能再排班每个病区每天每个岗位至少N人法定节假日按医院要求配置值班人员数量软性约束尽量满足每人每月夜班次数尽量均衡不超过X次连续工作日不超过Y天个人偏好的休息日尽量满足排班结果尽量按白班-小夜-大夜-休-休的循环顺序走4.2 自动排班的实现思路自动排班我这里分享两个思路。思路一循环轮转法。适合固定班次、人员构成比较稳定的病区。将护士按一定顺序排成一个队列每天从队列中依次取出需要的人数放到当天的班次上。一天结束队头移到队尾。这种方式代码量最少但缺陷是完全没有考虑个人偏好而且遇到有人请假就会全部错位。思路二规则引擎回溯。这是生产中比较实用的方案。核心逻辑是这样的1. 获取排班周期比如下一个自然月 2. 获取病区所有在岗护士列表 3. 获取病区在周期内每天的岗位需求工作日/周末/节假日需求数不同 4. 初始化所有护士的已排班集合为空 5. 从周期第一天开始遍历每一天 a. 计算当天需要的所有班次和人数 b. 为每个班次依次挑选候选人 - 过滤掉当天已有排班的护士 - 过滤掉不满足资质要求的护士 - 过滤掉上一个班次结束后休息时长不足的护士 - 按软性约束打分夜班次数少的优先、已连续排班多的靠后 - 选择分数最高的人 c. 如果当天某班次找不到人触发冲突处理从后续日期里找一个人换过来或标记为手动处理 6. 生成草稿排班提交护士长确认打分规则可以用一个简单公式表达score 0.4 * (目标夜班次数 - 已经夜班次数) 0.3 * (连续工作天数上限 - 当前连续工作天数) 0.2 * (距离上次休息天数) 0.1 * (是否满足个人休息偏好)具体权重可以根据科室实际运行情况调整。这个打分选人的方案看起来简单但在实际项目里比上复杂算法好用得多因为护士长能看懂、能调整权重、出问题能解释。4.3 一个具体的Java实现片段我来写一个核心排班生成器的骨架。这里用Java 8的LocalDate处理日期非常顺手。public ListSchedulePlan generateSchedule(ScheduleGenerateRequest request) { LocalDate startDate request.getStartDate(); LocalDate endDate request.getEndDate(); Long stationId request.getStationId(); // 1. 获取病区人员列表 ListUser nurses userMapper.selectNursesByStation(stationId); // 2. 获取病区的班次配置 ListShift shifts shiftMapper.selectShiftsByStation(stationId); // 3. 初始化候选池 MapLong, NurseWorkload workloadMap initWorkload(nurses, startDate, endDate); ListSchedulePlan plans new ArrayList(); for (LocalDate date startDate; !date.isAfter(endDate); date date.plusDays(1)) { // 根据日期类型获取当天班次需求 DateType dateType getDateType(date); ListShiftRequirement dailyShifts shiftRequirementMapper .selectByStationAndDateType(stationId, dateType); for (ShiftRequirement requirement : dailyShifts) { Shift shift requirement.getShift(); int requiredCount requirement.getCount(); // 过滤出符合条件的护士 ListUser candidates nurses.stream() .filter(n - isAvailable(n, date, shift, workloadMap)) .sorted(Comparator.comparingDouble(n - calculateScore(n, shift, workloadMap))) .collect(Collectors.toList()); // 如果候选人数不足记入异常列表 if (candidates.size() requiredCount) { conflictHandler.handle(date, shift, requiredCount - candidates.size()); continue; } // 选择前N名生成排班记录 for (int i 0; i requiredCount; i) { User selected candidates.get(i); SchedulePlan plan new SchedulePlan(); plan.setStationId(stationId); plan.setDepartmentId(selected.getDeptId()); plan.setTargetUserId(selected.getId()); plan.setShiftId(shift.getShiftId()); plan.setShiftDate(date); plan.setShiftStart(computeStartTime(date, shift)); plan.setShiftEnd(computeEndTime(date, shift)); plan.setAuditStatus(0); plan.setSourceType(0); plan.setCreatedBy(request.getOperatorId()); plans.add(plan); workloadMap.get(selected.getId()).addShift(shift, date); } } } return plans; }这个骨架省略了很多前置校验但主要思路已经出来了。里面最关键的是isAvailable方法和calculateScore方法。isAvailable要判断当天是否已有排班、班次类型是否与职称匹配、夜班后休息是否充足、连续工作天数是否越界。calculateScore则按前面说的权重计算。当时在实际调试中发现一个盲区isAvailable只检查候选人在排班周期内的排班记录但如果这个护士上一周期的最后一天是大夜班当前周期第一天的早班他根本来不了。所以检查休息时长时必须把上一周期末尾的排班记录也加载进来。这个bug还是护士长打电话反馈排班表上第一天有人根本来不了才发现后来在代码里加了一个参数offSetDays向前多捞两天的排班记录来判断。5. 换班审批与状态机并发场景下如何保证数据不错乱排班生成只是第一步系统上线后真正频繁使用的是换班申请。这块业务并发冲突极多我来详细拆解一下。5.1 换班业务流程的状态设计排班记录本身有一个状态字段audit_status我用下面这个状态机草稿(0) - 已发布(1) - 调班申请中(2) - 已变更(3) \- 调班申请中(2) - 已发布(1) // 审批拒绝回到原状态发起换班申请时原排班记录进入调班申请中状态目标排班记录也进入调班申请中状态。审批通过之后原班次的target_user_id变成目标人目标班次的target_user_id变成原发起人相当于两个人互换了位置然后两条记录状态都变成已变更。审批拒绝时两条记录回滚到已发布状态同时释放掉对这两条记录的锁定。5.2 并发冲突的根因和解决方案如果两个人同时看中了同一个人的同一个班次呢如果护士A已经申请了换班护士A的班次在调班申请中状态此时护士C也发起申请想把班次换到这一天系统必须拦截。最省事的方案是在schedule_change表里加一个唯一索引保证同一时间同一计划只能有一条待审批的换班申请。ALTER TABLE schedule_change ADD UNIQUE KEY uk_plan_pending (plan_id, status_pending_flag);但status是可变枚举不能用静态字段做唯一约束。一个更实用的方案是引入预占概念发起申请时通过数据库锁把目标排班记录锁住。我推荐优先用UPDATE ... WHERE audit_status 1这样的乐观锁思路来做状态流转。代码大概是Transactional public ChangeResult applyChange(ChangeApplyDTO dto) { // 1. 校验班次状态 SchedulePlan originPlan schedulePlanMapper.selectByIdForUpdate(dto.getOriginPlanId()); // selectByIdForUpdate 底层是 SELECT ... FOR UPDATE if (originPlan.getAuditStatus() ! 1) { throw new BizException(原排班记录当前状态不可申请换班); } SchedulePlan targetPlan schedulePlanMapper.selectByIdForUpdate(dto.getTargetPlanId()); if (targetPlan.getAuditStatus() ! 1) { throw new BizException(目标排班记录当前状态不可申请换班); } // 2. 预占状态 originPlan.setAuditStatus(2); targetPlan.setAuditStatus(2); schedulePlanMapper.updateById(originPlan); schedulePlanMapper.updateById(targetPlan); // 3. 创建换班申请记录 ScheduleChange change new ScheduleChange(); change.setPlanId(originPlan.getPlanId()); change.setOriginUserId(originPlan.getTargetUserId()); change.setTargetUserId(targetPlan.getTargetUserId()); change.setTargetPlanId(targetPlan.getPlanId()); change.setStatus(0); scheduleChangeMapper.insert(change); return ChangeResult.success(); }selectByIdForUpdate依赖数据库行锁是处理这种并发业务最简单可靠的手段。有人会问用Redis分布式锁行不行行但没必要引入额外的中间件复杂度单库场景下数据库行锁完全够用。5.3 审批通过时的原子性审批操作同样要加事务。代码逻辑是Transactional public void approveChange(Long changeId, Long approverId) { ScheduleChange change scheduleChangeMapper.selectByIdForUpdate(changeId); if (change null || change.getStatus() ! 0) { throw new BizException(申请记录不存在或已处理); } SchedulePlan originPlan schedulePlanMapper.selectByIdForUpdate(change.getPlanId()); SchedulePlan targetPlan schedulePlanMapper.selectByIdForUpdate(change.getTargetPlanId()); // 交换值班人 Long tempUserId originPlan.getTargetUserId(); originPlan.setTargetUserId(targetPlan.getTargetUserId()); targetPlan.setTargetUserId(tempUserId); originPlan.setAuditStatus(3); targetPlan.setAuditStatus(3); schedulePlanMapper.updateById(originPlan); schedulePlanMapper.updateById(targetPlan); change.setStatus(1); change.setApproveTime(LocalDateTime.now()); change.setApproverId(approverId); scheduleChangeMapper.updateById(change); // 写日志 scheduleLogMapper.insert(...); }整个换班流程必须包裹在同一个事务里中途任何一步抛异常都必须让所有数据回滚。如果不在事务里你可能会遇到班次换了但申请单还是待审批或者申请单已通过但排班表没变这种数据不一致。这绝对是要写进论文系统测试部分的重点场景。6. 排班日历的查看与导出前端展示的方案拆解排班做出来了最终要给人看。医院的排班查看场景和普通OA还不一样护士长要按病区看某个月的排班总表普通护士要只看自己的班次还要支持导出Excel去打印贴在护士站。这节讲前端怎么设计、后端接口怎么给数据。6.1 后端接口设计我建议按场景来拆接口不要一个万能接口一把梭。GET /api/schedule/plans?stationIdmonth 返回某病区某月所有排班记录按日期分组给护士长页面用。GET /api/schedule/my-plans?month 根据当前登录用户返回个人排班。GET /api/schedule/export?stationIdmonth 导出Excel。对于第一个接口返回结构可以这样{ month: 2025-06, stationName: 呼吸内科一病区, staff: [ {userId: 12, realName: 张三, title: 护师} ], schedules: [ {date: 2025-06-01, items: [ {shiftName: 白班, users: [12, 13]}, {shiftName: 小夜班, users: [14]} ]} ] }后端写好之后前端的日历渲染就很直白了。另外一个重点接口不要返回明文timestamp的排班数据给前端让前端再转换时区而是要后端直接返回格式化好的日期时间字符串不然不同浏览器渲染会差8个小时。6.2 Excel导出实现用EasyExcel或者Apache POI都行。我用的是EasyExcel因为写起来快、内存占用小。导出的时候要考虑合并单元格同一个病区同一个日期的数据要纵向合并同一个人同一个月的夜班次数最后生成一行统计。核心代码片段public void exportStationSchedule(Long stationId, YearMonth month, HttpServletResponse response) { ListSchedulePlan plans schedulePlanMapper.selectByStationAndMonth(stationId, month); ListScheduleExcelRow rows plans.stream() .map(plan - { ScheduleExcelRow row new ScheduleExcelRow(); row.setDate(plan.getShiftDate()); row.setUserName(userMapper.selectById(plan.getTargetUserId()).getRealName()); row.setShiftName(shiftMapper.selectById(plan.getShiftId()).getShiftName()); row.setStartTime(plan.getShiftStart()); row.setEndTime(plan.getShiftEnd()); return row; }) .collect(Collectors.toList()); EasyExcel.write(response.getOutputStream(), ScheduleExcelRow.class) .sheet(排班表) .doWrite(rows); }这里有一个比较容易忽略的坑如果你的用户表里做了逻辑删除Mapper默认会带WHERE deleted 0但在导出历史月份排班时那个月值班的护士可能已经调走或离职了。此时如果直接selectById查出来是null导出就会空行。所以导出功能里要绕过逻辑删除过滤直接查全量用户表。6.3 排班页面的交互细节前端这一块护士长最关心的是修改某个班次的人。我在项目里做了拖拽调整功能把护士卡片拖到某个班次的槽位里后端就发一个POST请求更新排班。这里拖拽是纯前端交互后端只需要提供一个修改排班记录的接口。但是要注意不是所有状态下都能拖拽修改如果排班已经发布并且开始执行就应该只读。比如今天已经是6月15日6月15日的排班就不能随便改了。所以接口里要判断目标日期是否早于今天早于今天直接拒绝。对于普通护士用户我的排班页面要突出展示夜班数量和最近的休息时间。一条一条列出来反而没有价值我会做成月度时间轴护士能一眼扫到自己哪天是白班、哪天是夜班、哪天休息。7. 值班统计报表用SQL还是用代码计算排班数据攒了一个月之后护士长一定会问张三这个月上了几个夜班王五上周连续工作了几天整个病区的护师和护士的班次结构合理吗值班统计报表就是用来回答这些问题的。7.1 统计维度设计我做的统计报表分了几个维度按人统计每人当月白班数、夜班数、总工时、休息天数。按病区统计病区每日白班覆盖率、夜班覆盖率、临时换班次数。按职称统计护师/护士/主管护师的夜班占比防止夜班总是压在低年资护士头上。这些统计如果全部在Java里循环算数据量小的时候没问题但如果是一个三甲医院几百个护士按月查询一次就非常慢。我建议把核心指标落到SQL里。举一个例子SELECT target_user_id, COUNT(CASE WHEN s.is_night 0 THEN 1 END) AS day_shift_count, COUNT(CASE WHEN s.is_night 1 THEN 1 END) AS night_shift_count, SUM(si.work_hours) AS total_work_hours FROM schedule_plan sp JOIN shift si ON sp.shift_id si.shift_id WHERE sp.shift_date BETWEEN #{startDate} AND #{endDate} AND sp.station_id #{stationId} GROUP BY target_user_id7.2 一个值得注意的统计口径问题夜班次数怎么算有些人会直接把每个夜班都记一次但如果值班是三班倒一个夜班跨了自然日比如22:00到次日8:00就涉及归属日期的问题。医院通常会把排班归属到开始上班的那一天也就是夜班日期按开始时间所在的日期归属。这样统计起来不会重复计算。我在项目里专门写了这个逻辑当班次的end_time小于start_time时跨天班次排班日期仍然取start_time对应的日期但在日历展示时用特殊颜色标注跨天。这一个口径问题看起来很小但决定了整个统计报表的数据含义。7.3 工时的法律与制度校验排班系统最后交付之前我建议加入一条合规校验功能检查每个人连续上班天数是否超过医院制度规定比如连续工作不能超过7天、月总工时是否超过上限。这个校验写成定时任务每天早上跑一次如果有违规给护士长推送提醒。这其实比写花哨的报表更有价值因为它是从记录排班向管理排班转变的关键功能。实现不复杂就是在前面排班记录的基础上做一个聚合查询然后用规则判断。论文的系统的亮点与创新章节这个地方是可以重点展开的——它不只是CRUD而是把医院管理规则落进了系统。8. 定时任务与消息提醒值班提醒和排班发布通知排班系统里一定会有通知的需求排班发布之后护士长要通知所有护士护士当天有班最好提前一天收到提醒。这些功能我用Spring自带的任务调度和消息推送来搞定。8.1 定时任务的实现Spring Boot的Scheduled注解足够满足需求。我在启动类上加上EnableScheduling然后写一个定时任务类Component public class ScheduleRemindTask { Scheduled(cron 0 0 18 * * ?) // 每天18点执行 public void sendTomorrowScheduleRemind() { LocalDate tomorrow LocalDate.now().plusDays(1); ListSchedulePlan plans schedulePlanMapper.selectByDate(tomorrow); // 按用户分组发送站内通知或短信 MapLong, ListSchedulePlan userPlanMap plans.stream() .collect(Collectors.groupingBy(SchedulePlan::getTargetUserId)); userPlanMap.forEach((userId, userPlans) - { String content buildRemindContent(userPlans); notificationService.send(userId, content); }); } }cron表达式的时区问题要特别注意——如果部署服务器的时区不是Asia/Shanghai定时任务会按服务器时区执行。在application.yml里显式配置spring.jackson.time-zone和JVM的user.timezone这个坑我当时调了半天。8.2 消息提醒的实现方案提醒的目标是让护士在手机上也能看到但大多数排班系统并没有独立的App。最简单的方案是接入企业微信或者钉钉的机器人Webhook把提醒消息发到科室群里。这个方案的优点是零成本、不需要开发App、也不需要短信服务只要有一个Webhook地址就行。用Java代码调Webhook没有想象中复杂用RestTemplate或OkHttp扛一个HTTP请求就能完成public void sendDingTalkMessage(String webhookUrl, String content) { MapString, Object body new HashMap(); body.put(msgtype, text); MapString, String text new HashMap(); text.put(content, content); body.put(text, text); RestTemplate restTemplate new RestTemplate(); restTemplate.postForEntity(webhookUrl, body, String.class); }提醒内容里我会带上科室、班次、日期和值班地点尽量让护士一条消息看完所有信息。另外提醒时间绝对不是越早越好——提前太早容易被忽略提前一天是最合适的。班次开始前4小时再补一条提醒效果也很好。这些运营策略都可以写进论文的未来展望部分。9. 从源码到毕业设计论文怎么把项目和论文统一起来标题里写了源码数据库论文这说明用户十有八九是拿这个项目做毕业设计的。论文怎么写才能和代码对得上我以过来人的经验给你几个切实建议。9.1 论文结构怎么搭毕业设计的论文通常有固定的章节要求核心部分一般包含第一章 绪论写背景和意义。这里重点写医院排班从人工排班到信息化排班的演进以及排班效率低、易出错、公平性难保证这些痛点。这是展示你调研能力的地方不要抄百度百科要写成你真正理解了这个行业的问题。第二章 相关技术介绍Spring Boot、MyBatis-Plus、MySQL、Redis。每个技术写它是什么、为什么选它。这里容易出现的问题是技术名词堆砌但看不出你用过它。正确写法是结合你的系统说说本系统使用Redis缓存科室排班概览数据减少数据库压力排班表页面的响应时间从1.2秒降低到300毫秒以内。第三章 需求分析功能需求用户管理、排班管理、换班管理、统计报表和非功能需求性能、安全性、可用性。画用例图、活动图这些图要和你后面章节的代码功能一一对应不能画一套图写一套代码。第四章 系统设计总体架构图、功能模块划分、数据库E-R图和数据表设计。数据表字段不要全贴选核心表排班计划表、换班申请表、班次表详细说明字段含义和设计理由。第五章 系统实现这是最难的因为很多人的论文这里写成了流水账——描述每个页面有什么。正确写法是选取3到4个核心功能点做深入讲解比如自动排班算法、换班审批的状态流转、数据导出。贴关键代码片段每段代码后写一段设计思路说明。第六章 系统测试功能测试用例表测试项、输入、预期结果、实际结果、性能测试结果并发登录、排班查询响应时间。9.2 答辩时的高频提问根据我当年答辩和后来指导学弟学妹的经验老师最喜欢问这几个问题排班冲突你是如何解决的——回答方向唯一索引约束 状态锁 事务。自动排班的算法是什么——回答方向基于打分排序的贪心选择匹配规则过滤冲突时人工介入。不要说自己用了人工智能那是给自己挖坑。如果两个护士同时申请换同一个班会发生什么——回答方向数据库行锁 唯一索引保证只有一个申请成功。你用的这些技术相比传统的SSH框架有什么优势——这个比较基础Spring Boot的自动配置、内嵌容器、生态丰富展开说即可。9.3 源码和数据库脚本的注意事项下载下来的源码如果要直接跑大概率会遇到几个问题第一个是JDK版本和Spring Boot版本不匹配。Spring Boot 2.7用JDK 8或11Spring Boot 3.x必须JDK 17以上。你导入项目前先看一眼pom.xml的parent标签把本地JDK版本对齐这是最常见的启动失败原因。第二个是数据库编码。MySQL 8默认字符集是utf8mb4但如果建库脚本里用了utf8排班数据里存中文是没问题可一旦出现生僻字或表情就报错。我建议初始化脚本开头加上CREATE DATABASE hospital_schedule DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;第三个是数据库账号密码。很多源码的application.yml里写的是root/123456你要改成自己本地的账号密码。数据库不存在时先执行sql目录下的init.sql。10. 上线后必踩的坑来自真实项目的排错记录最后分享三个我在排班系统实际部署上线过程中遇到过的具体问题。这些内容不是教科书上写的但真实项目里大概率会遇到。10.1 服务器时区问题系统上线第一周护士长反馈排班表的夜班时间比实际早了8小时。刚开始我以为是前端渲染问题后来查了后端返回的JSON数据发现接口返回的shiftEnd是服务器当前时区转换后的结果。服务器是CentOS默认时区是UTC而MySQL连接串里也没有指定服务器时区。解决方案连接MySQL的URL加参数serverTimezoneAsia/Shanghai同时确保JVM启动参数带上-Duser.timezoneAsia/Shanghai。我推荐在启动脚本里固定写好这两个配置不要依赖服务器默认设置。10.2 Decimal与整数的工时精度问题计算工时的时候如果班次是8:00到12:00工时是4小时整数没问题。但如果班次是8:30到12:00工时是3.5小时Java的整数除法会出问题。我一开始用的int存work_hours后来发现统计总工时算出来全是整数导致值班补贴计算不准。改成BigDecimal或者用分钟为单位存储统计时才不会丢失精度。10.3 排班记录被误删的问题开发阶段为了方便测试我直接在数据库管理工具里删过排班记录。后来发现操作日志里那条排班的创建记录还在但排班表里已经查不到数据了导致日志和实际数据对不上。后来我给所有的排班操作接口都做了逻辑删除日志记录禁止在业务接口中使用物理删除。如果给你源码的人没有做这个设计建议你自己改造一下这也是论文系统改进的好素材。11. 二次开发建议从能跑到好用拿到一套能跑的源码只是开始。作为Spring Boot项目要让它真正成为一个能展示你能力的作品我给出几个性价比最高的二次开发方向。第一优先是值班偏好设置。让护士可以在系统里提交下个月的期望休息日自动排班时把这些偏好作为权重分数纳入计算。这个功能实现不复杂就是在用户表或者独立偏好表里多存几个字段排班打分时加一项但效果非常直观能大幅提升用户的认可度。第二优先是移动端适配。很多排班系统前端是桌面端后台手机上看排班表要缩放到看不见。做一个移动端H5页面或者在现有页面上做响应式适配护士就能随时随地查看班次。对H5实现不熟悉的话可以考虑用Bootstrap的栅格系统做基础适配成本不高效果却很明显。第三优先是智能提醒的升级。从固定的定时提醒升级为排班变动即时提醒一旦某条排班被修改马上通知相关人。实现可以用WebSocket推送这让系统显得更加活。这三个方向做下来你的项目既有了独创性又有了实用性答辩的时候可讲的内容会丰富很多。我当时就是把偏好设置做了深入优化从最简单的双休偏好做成了月度偏好日历整个系统的说服力立刻不一样。说到底医院排班系统的价值不在技术它有多前沿而在于你能否把一个复杂业务约束转换成清晰的数据模型和可靠的工程实现。把上面这些表结构、状态流转、事务设计、统计口径都捋清楚了无论拿到的源码是什么样的你都能把它变成自己的作品。本文还有配套的精品资源点击获取
返回列表