ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot基层智能化人员调度系统:规则驱动排班实践

SpringBoot基层智能化人员调度系统:规则驱动排班实践 1. 项目概述这个系统到底在干什么每年到毕业设计季总有一批人被“人员调度系统”这类题目绕进去。这里说的 springboot 基层智能化人员调度系统编号 14154是我最近重新梳理得比较完整的一套毕设题。它的核心场景不复杂基层单位里的人手、班次、请假、换班这些事原来靠 Excel 和微信群就能凑合现在要做成一个系统管起来并且排班的时候能根据规则自动给建议减少人工算班次的时间。这个项目适合计算机、软件工程、信息管理专业的学生做毕设也适合想用 SpringBoot 练手的人当作课程设计。它的知识点覆盖很典型权限管理、人员档案、排班计划、请假调班、统计报表前端再搭一个 Vue 后台管理页面。整体难度中等偏上一点但只要你把“排班规则”这件事讲清楚答辩时比普通 CRUD 项目高出一个档次。很多人一看到“智能化”三个字就开始慌以为要上机器学习、神经网络。真做毕设完全不用那么玄。基层人员调度的“智能化”更准确地说是一种“规则驱动的辅助决策”系统先拿到人员、班次、已排计划、请假申请然后通过可配置规则自动筛选出“今天能排谁”“连续值班超没超”“夜班后有没有休息”最后给管理员推荐候选人和冲突提示。把这套规则做好就已经是货真价实的“智能调度”。2. SpringBoot 技术选型与项目结构2.1 技术栈选型为什么大家都选 SpringBoot先列我这次使用的技术栈都是毕业设计里最容易交代、也最稳的组合层次选型核心理由后端框架Spring Boot 2.7.x自动配置、起步依赖多、社区资料丰富ORMMyBatis-Plus单表 CRUD 几乎不用写 SQL内置分页插件数据库MySQL 8.0关系型数据最合适排班数据有强约束缓存Redis缓存人员列表、分布式锁、临时校验数据权限Spring Security JWT毕设答辩高分点能讲清楚 RBAC接口文档Knife4j自动生成 Swagger 文档前端联调方便定时任务Spring Task每周自动生成排班计划代码量最少文件导入导出EasyExcel管理员批量导入人员导出排班表前端Vue 3 Element Plus后台管理系统开发效率高页面好看很多人纠结要不要用 Spring Cloud、Dubbo、消息队列这些分布式组件。基层人员调度系统本质上是一个单体业务系统用户量不会大到需要微服务硬上分布式反而暴露“过度设计”的问题。SpringBoot 的价值在于它把数据库连接、Web 容器、JSON 序列化、日志、监控这些脏活都通过 starter 自动处理了你可以把精力集中在业务规则上。2.2 SpringBoot 自动装配到底要懂多少项目做完了答辩时老师大概率会问“SpringBoot 为什么能自动配置”。这个问题不能只说“约定大于配置”最好能说出底层机制SpringBootApplication实际上组合了EnableAutoConfiguration、ComponentScan、Configuration。SpringBoot 在启动时会加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里面列了一堆自动配置类。每个自动配置类上都有ConditionalOnClass、ConditionalOnMissingBean之类的条件注解。比如你引入了spring-boot-starter-data-redis类路径里有RedisOperationsRedis 的自动配置才会生效。如果自己定义了一个RedisTemplateBeanSpringBoot 会因为你容器里已经存在这个 Bean 而不再创建默认的。把这个机制想清楚之后自定义自动配置也能看懂。比如做一个工具包对外提供XxxAutoConfiguration通过AutoConfiguration.imports注册再加上ConditionalOnProperty控制是否开启。虽然毕设一般用不上但这一小段知识在简历里写“熟悉 SpringBoot 自动配置原理”就不是空话了。2.3 工程结构别把代码全塞进 controller我见过不少学生的毕设service 层形同虚设所有逻辑都写在 controller 里一个方法几百行。这个项目如果这样写后面排班规则、冲突校验、统计报表根本没法维护。推荐的分层结构是这样的com.example.schedule ├── ScheduleApplication.java ├── common // 统一返回结果、异常处理、常量、工具类 ├── config // SecurityConfig、RedisConfig、Knife4jConfig ├── controller // 只做参数接收和结果封装 ├── service // 业务接口 │ └── impl ├── mapper // MyBatis-Plus Mapper 接口 ├── entity // 数据库实体 ├── dto // 请求参数对象 ├── vo // 返回视图对象 ├── job // 定时任务 ├── rule // 排班规则校验器 └── utils // 日期计算、权重计算等工具设计接口的时候要记住一条原则controller 里不要出现业务判断。比如排班接口controller 只负责把前端传过来的ScheduleCreateDTO交给 service成功就返回Result.success(planId)失败就抛出业务异常由全局异常处理器转成统一错误信息。这样代码干净调试也方便。3. 核心模块拆解与数据库设计3.1 功能模块从一个普通管理后台变成调度系统如果只是人员管理这个项目和“员工管理系统”没区别。真正拉开差距的是下面四个模块模块功能说明系统管理用户、角色、菜单权限RBAC 权限控制基础数据部门、人员、班次类型、节假日、值班规则调度管理手动排班、智能推荐排班、调班申请、请假审批统计与出勤签到记录、工时统计、月度报表导出“基层”这个词决定了业务边界部门层级不用做得很深两到三级就够人员角色主要分管理员、调度员、普通员工班次常见的有白班、夜班、休息、备勤。不要把系统设计成大型企业的排产系统那样和题目不匹配工作量也会失控。3.2 数据库表设计排班表的坑要提前避开核心表我建议至少设计这些sys_user登录账号、密码、状态sys_role和sys_menu角色菜单权限base_department部门表base_staff人员表关联用户和部门记录工种、联系电话等信息base_shift班次表包含班次名称、开始时间、结束时间、是否夜班sched_plan排班计划主表记录某个部门某天的排班生成状态sched_detail排班明细表记录“某天某班次某个人”sched_rule调度规则表比如最大连续值班天数、夜班后的休息小时数leave_apply请假申请表swap_apply换班申请表attendance_record签到记录表排班明细表sched_detail是重点我建议这样做约束CREATE TABLE sched_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, plan_id BIGINT NOT NULL, staff_id BIGINT NOT NULL, shift_code VARCHAR(20) NOT NULL, duty_date DATE NOT NULL, create_time DATETIME, update_time DATETIME, UNIQUE KEY uk_staff_duty (staff_id, duty_date) ) COMMENT 排班明细表;关键在这里一个人同一天只能有一个班次所以唯一索引要建立在(staff_id, duty_date)上。很多学生把唯一索引建在(duty_date, shift_code)上这会导致同一个班次只能排一个人完全错误。同一个班次当天完全可以有多个不同人员值班只是每个人一天最多记录一条。另外不要只靠唯一索引解决所有冲突。如果值班可以跨天比如夜班从晚上 20:00 到次日 08:00那么“同一人同一天有记录”还不够还得校验时间段重叠。这种情况建议在 service 层做区间重叠判断再用数据库唯一索引做兜底。3.3 排班规则怎么落库和扩展规则如果不落库每次都写在代码里换一个单位就得改代码。比较好的做法是建一张sched_rule表用配置项表达规则规则编码规则名称参数值max_consecutive_days最大连续值班天数3min_rest_hours_after_night夜班后最少休息小时数24max_weekly_days每周最多值班天数5shift_count_per_day每天同一人员最多班次数1service 层把这些规则封装成一组校验器。每个校验器只做一件事实现同一个接口public interface ScheduleValidator { void validate(ScheduleValidateContext context); }比如连续值班校验器先查出该员工在目标日期之前连续值班的天数加上当前日期后如果超过配置的最大天数就抛出规则异常。再用一个ScheduleRuleManager把所有校验器统一跑一遍出现任何一条失败就拦截。这样后面要加规则不需要动排班主流程新增一个类就行。答辩时你把这个设计讲出来比贴一堆 CRUD 代码有用得多。4. 实操从 0 到 1 实现关键功能4.1 初始化工程与基础配置新建 SpringBoot 项目时我建议直接用 Spring Initializr 生成依赖选 Web、MySQL Driver、Redis、Validation、Security 这些。如果用 MyBatis-Plus注意选择与 SpringBoot 版本匹配的 starter。一个常见的坑是 SpringBoot 版本太高。SpringBoot 3.x 把javax.*换成了jakarta.*很多老教程里的代码在 3.x 上直接编译报错。做毕设建议用 2.7.x资料多、坑少。家长式提醒如果你不是非要尝鲜不要一上来就选 3.x除非你想踩一遍jakarta命名的坑。application.yml里核心配置如下server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/schedule?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 servlet: multipart: max-file-size: 10MB mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 knife4j: enable: true启动类加上MapperScan扫描 Mapper 接口再写一个测试接口确认启动成功。这个阶段不要急着写业务先把日志、统一返回结果ResultT、全局异常处理器搭好。否则后面联调会影响效率。4.2 智能推荐排班的核心实现这个项目的“智能化”主要体现在推荐候选人和冲突提示。我实现的时候先做两件事第一查询目标日期、目标班次下所有可用人员第二用规则过滤掉不可用人员再按优先级打分排序。简化版逻辑如下public ListStaffCandidateVO recommendCandidates(LocalDate date, String shiftCode, Long deptId) { ListBaseStaff staffList staffService.listAvailableStaff(deptId); ListStaffCandidateVO candidates new ArrayList(); for (BaseStaff staff : staffList) { // 1. 校验是否有已经排班 if (scheduleDetailService.existsStaffOnDate(staff.getId(), date)) { continue; } // 2. 校验是否请假 if (leaveApplyService.hasApprovedLeave(staff.getId(), date)) { continue; } // 3. 跑所有调度规则 ScheduleValidateContext ctx new ScheduleValidateContext(); ctx.setStaffId(staff.getId()); ctx.setDate(date); ctx.setShiftCode(shiftCode); if (!ruleManager.validateAll(ctx)) { continue; } // 4. 计算优先级得分越低越优先推荐 int score 0; int consecutiveDays scheduleDetailService.countConsecutiveDays(staff.getId(), date); score consecutiveDays * 10; if (isNightShift(shiftCode)) { LocalDateTime lastNightEnd scheduleDetailService.getLastNightShiftEndTime(staff.getId()); if (lastNightEnd ! null) { long restHours ChronoUnit.HOURS.between(lastNightEnd, LocalDateTime.now()); if (restHours 24) { continue; } score restHours 48 ? 5 : 0; } } candidates.add(new StaffCandidateVO(staff, score)); } candidates.sort(Comparator.comparingInt(StaffCandidateVO::getScore)); return candidates; }这里我说几个参数的设计思路consecutiveDays * 10代表连续值班惩罚连续天数越多优先级越靠后。夜班校准夜班结束到现在不足 24 小时直接排除不足 48 小时加 5 分惩罚。每周值班天数可以在统计时再累加不要每次都全表查询。这套逻辑不复杂但效果上能让人感觉到“系统会帮我判断谁最合适”。答辩的时候把打分规则讲清楚配一个手动排班和智能推荐排班的对比截图项目亮点就够了。前端调用的时候我提供了两个接口GET /api/schedule/recommend?date2026-05-20shiftCodeDAYdeptId1 POST /api/schedule/generategenerate接口接收推荐结果后台再校验一次规则然后保存到sched_detail。这里一定要记住前端展示的推荐结果不能直接信任后端生成时必须再跑一遍校验防止并发下出现同一人重复排班。4.3 定时任务与消息通知排班通常是按周生成可以在每周日晚上自动预排下周计划。SpringTask 实现起来最简单Configuration EnableScheduling public class ScheduleJob { Scheduled(cron 0 0 1 * * SUN) public void autoGenerateWeeklySchedule() { LocalDate nextMonday LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 如果下周排班还没生成则调用排班 service 生成 scheduleService.generateDefaultPlan(nextMonday); } }定时任务生成完计划后可以用 WebSocket 给管理员推送一条通知“下周排班已生成请确认。”WebSocket 在 SpringBoot 里集成也不复杂继承TextWebSocketHandler重写handleTextMessage即可。这个功能虽然小但能让系统看起来更完整。“通知”这块不要做得太重。毕设答辩最怕的就是你把功能铺得很开但每一个都只是半成品。定时预排班 待办确认比单独做一个聊天室或者站内信更有针对性。5. 常见问题与排查实录5.1 SpringBoot 启动就报数据库错误现象启动时报Failed to configure a DataSource。大部分原因是application.yml里数据源配置没被加载或者SpringBootApplication扫描的包路径不对。还有一种情况是本地 MySQL 服务没启动连接被拒。遇到这类问题我习惯的排查顺序是先看配置文件是否在resources目录下文件名是否是application.yml。检查 MySQL 连接地址、账号、密码。在数据库客户端里用同样参数测试连接。看启动日志是否有driver找不到缺 MySQL 驱动依赖。如果加了 Redis 并且报连接失败先确认 Redis 服务启动了。Windows 下 Redis 没有注册服务时每次开机都要手动启动很多学生在这里卡半天。5.2 并发排班导致同一个人被排两次两个管理员同时打开同一个日期页面都点了“生成排班”就可能重复插入。数据库唯一索引(staff_id, duty_date)能挡住第二次插入但会抛异常前端体验很差。更好的做法是加 Redis 分布式锁String lockKey schedule:lock: deptId : date; Boolean locked stringRedisTemplate.opsForValue().setIfAbsent(lockKey, 1, Duration.ofSeconds(30)); if (Boolean.TRUE.equals(locked)) { try { scheduleService.generatePlan(date, deptId); } finally { stringRedisTemplate.delete(lockKey); } }注意 lockKey 尽量细化到部门加日期不要用全局锁否则一个排班操作会把整系统其他模块锁住。Redis 锁不是银弹单机 Redis 也可能出问题但对毕设场景已经足够。你不用过度设计 Redisson 分布式锁能讲清楚防重思路就行。5.3 前端 Vue 打包后放进 SpringBoot项目本地跑没问题答辩前想部署到服务器前端npm run build后得到一个dist目录。最简单的方式是后端把dist放到src/main/resources/static下SpringBoot 会自动把静态资源映射到根路径。但我建议用前端vite.config.js设置base: ./否则部署到服务器子路径时JS、CSS 引用路径会出错。如果前端使用了 Vue Router 的 History 模式还需要在 SpringBoot 里做一个路由 fallback避免刷新页面变成 404。可以加一个配置类把非/api路径转发到index.htmlConfiguration public class WebMvcConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{spring:[^\\.]*}).setViewName(forward:/index.html); } }这个问题是高频坑很多人前端本地正常一打包放后端就打不开页面就是因为没处理路由模式的回退。5.4 部署环境问题如果要用宝塔面板部署建议直接走 Docker Compose。写一个简单的Dockerfile用多阶段构建打包 Vue 和 SpringBoot再用一个docker-compose.yml编排 MySQL、Redis、后端容器。我这里给一个小建议答辩演示用自己的电脑不要现场部署远程服务器。网络波动、端口占用、跨域问题都可能成为不可控因素。部署经验可以放在文档里写演示时保证本地能跑通最重要。6. 最后说点我自己的实践体会这个项目看起来是一个普通的管理系统但真正把“调度”这件事做深需要花不少时间在规则设计和边界情况上。我一开始也踩过坑以为排班就是往表里插数据后来发现涉及请假、调班、连续值班、夜班休息、节假日这些场景时所有判断都必须有数据支撑不能靠拍脑袋。如果想让这个题目的完成度再高一点我建议把精力花在这三件事上一是把排班规则页面做出来让管理员可以动态调整“最大连续值班天数”等参数而不是全靠改代码。这个功能不算难但能让“智能化”三个字落地答辩时非常出效果。二是把人员空闲时间纳入调度。给base_staff增加一个“不可排班时间段”的概念可以是每周固定时间也可以是一次性的时间段。系统推荐排班时自动排除这些时间比纯规则排班更贴近真实场景。三是做一张“排班合理性评分”的统计图。比如本月每人值班次数分布、周末值班次数、夜班次数用 ECharts 画出来。老师平时看到的是功能列表一张分布图能直观告诉评委你这个系统确实在辅助管理人员调度。我做的过程中最大的感受是SpringBoot 项目不用追求技术极大丰富重点是把业务闭环讲通。后端接口代码写得干净前端页面完整再有一个能讲清楚的设计亮点这套基层智能化人员调度系统就能稳稳落地。希望这篇整理能帮你少走点弯路也欢迎在评论区聊聊你踩过的排班系统的坑。
返回列表