ARTICLE DETAIL

资讯详情

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

基于Spring Boot+Vue的基层人员智能调度系统设计与实现

基于Spring Boot+Vue的基层人员智能调度系统设计与实现 做过基层调度系统的人应该都有同感需求听起来就几个字真做起来全是细节。拿我最近交付的一套基于 Spring Boot Vue 的基层智能化人员调度系统来说表面上解决的是“派活、排班、值班”三件事实际上背后是人员档案、任务建模、冲突检测、状态流转、消息通知、统计考核一长串问题。这篇文章把从设计到落地的整套思路拆开讲包括数据库怎么建表、后端智能派单怎么写、前端工作台怎么组织以及部署交付时那些不说就会踩的坑。想找源码参考做毕业设计、做单位内部工具或者网格化调度项目的开发人员都能把这篇当成一份完整的设计说明书。我一直觉得基层调度和互联网大厂的中台调度完全是两码事。大厂聊的是分布式任务、队列、实时大数据基层单位最需要的反而是“规则透明、能回滚、能统计、手机能用”。所以在动手之前我做的第一件事不是搭脚手架而是把“调度”这个词翻译成需求。1. 先把需求讲清楚基层的“调度”到底在调什么1.1 两种典型的基层调度场景基层这个词范围很宽街道、社区、工业园区、物业、政务窗口都可能叫基层。我在实际项目里会把调度需求拆成两个场景来看因为它们的表结构、业务流程、接口设计差别很大场景典型对象时间维度核心矛盾看板指标班次排班调度政务值班、窗口人员、物业保安长期、周期性公平性、换班繁琐值班次数是否均衡、休息时长任务工单调度网格巡查、维修工单、外勤任务单次、实时性响应慢、任务遗漏响应时长、完成率、人员负荷班次排班是“一段固定时间窗口内反复出现的人员安排”比如一个月有30天每个人该值几个白班几个夜班、节假日怎么轮休。任务工单调度则是“某个具体时刻产生了具体任务系统要在一堆候选人里选一个合适的并保证他在那个时间段是空闲的”。这两个逻辑混在一起做容易乱所以我建议在数据模型阶段就把它们分开一张shift_schedule表管周期排班一张dispatch_task表管即时任务两张表统一定义“哪个人、在哪个时间段、做什么事”冲突检测就能共用一套算法。1.2 从“人治”到“智治”系统到底要革谁的命这种项目上线前基层单位通常靠三样东西调度微信群、Excel、调度员脑子。听起来很原始但实际跑也能跑问题是一出矛盾就说不清楚。最常见的是这种情况任务来了调度员顺手在群里问了一圈谁有空谁接。看起来公平其实全凭印象有人长期在群里潜水有人因为不会拒绝就连轴转。一段时间后大家开始抱怨调度员也委屈因为没有客观依据。排班也一样Excel 里密密麻麻的色块看着好看真正换班的时候到处联系改表月底统计还要人手加总。所以智能化调度系统的第一个价值不是“自动”而是“留痕”。每一条任务派给谁、什么时候改派、为什么会退回都要有记录任何人打开系统都能看到。第二个价值是“规则固化”把技能标签、最大任务量、休息间隔这些约束写进系统派单不是拍脑袋而是基于评分。第三个价值才是“智能推荐”在规则允许的范围内选出当前最空闲、技能最匹配的人。1.3 圈定边界智能化不等于上AI痛点梳理完还要给业务方泼一盆冷水我不会给这套系统引入机器学习模型为什么因为基层调度的规则足够明确一个可配置的规则引擎加评分算法已经能解决九成问题。盲目上AI一是数据量不够二是业务解释不了调度员无法向同事解释“为什么派他不派我”系统很快就没了公信力。参照实际交付我将系统边界圈定为八个功能模块用户与权限管理员、调度员、执行人员三类角色数据按部门隔离人员与班组管理基本信息、技能标签、最大同时任务数任务管理任务的创建、导入、编辑、撤销排班管理周期班次生成、临时调整、换班申请智能派单按时间、技能、负荷、优先级综合评价调度记录全操作审计可回放消息通知站内消息、H5 推送、可选短信/企业微信 Webhook报表看板响应时长、完成率、负荷均衡度、值班统计。这八个模块就是后面所有设计的地基。2. 技术选型背后的逻辑Spring Boot、Vue 和数据库怎么配2.1 为什么还是选 Spring Boot而且选 2.7.18Java 生态在中小管理系统领域依然是挺稳的选择。Spring Boot 的好处是开箱即用内置 Tomcat打包成可执行 jar 就能跑很适合基层单位那种“一台 Windows 服务器或者 2 核 4G 的云主机”部署环境。版本上我特意选了Spring Boot 2.7.18 JDK 8没有追新上 Spring Boot 3。原因很现实很多底层单位的服务器上还跑着老 JDK强行用 Spring Boot 3 会逼着别人升级环境增加交付风险。而且 Spring Boot 2.7 已经是 2.x 的最终维护版本安全补丁一直在跟配合 MyBatis-Plus 和 Sa-Token做这类管理系统绰绰有余。如果你是自己公司内部用、服务器完全可控直接用 Spring Boot 3 也没问题Java 17 的新特性写起来确实香但本文这套方案按 2.7.18 来讲。2.2 Vue 3、Element Plus 与 Vite 的组合前端选型我直接用了 Vue 3 Element Plus Vite。为什么不选 Vue 2一个原因是 Vue 3 已经是绝对主流生态组件都已跟上另一个原因是 Element Plus 对应的组件和后台模板很成熟表格、表单、弹窗、树形控件开箱即用比从零写 UI 省太多时间。Vite 开发体验也明显比 Webpack 好冷启动快改完代码浏览器秒刷新。这里有个环境上的小提醒Vite 需要 Node.js 16.18 以上最好用 Node 18 LTS很多同学电脑上的 Node 太老跑npm install会报错第一反应以为是代码问题其实是 Node 版本问题。状态管理我用 Pinia不用 Vuex。Vue 3 配合 Pinia 几乎是官方推荐API 更简洁去掉了 mutations 那一层写起来负担小。路由用 vue-routerHTTP 请求统一封装 axios权限控制靠动态路由加路由守卫实现整个前端工程结构会非常干净。2.3 数据库与部署选型别一上来就整微服务数据库就是 MySQL 8.0引擎 InnoDB字符集 utf8mb4。MySQL 是普及率最高的开源关系型数据库任何会一点数据库的人都能维护对基层单位来说招聘和运维成本都低。字段设计上使用DATETIME存时间配合索引做好冲突检测这套系统不会有什么性能瓶颈。持久层用 MyBatis-Plus不是 JPA。理由很直白MyBatis-Plus 在国内管理类项目中普及度高CRUD 不用写 SQL分页插件一句配置就好逻辑删除、代码生成器这些功能对快速交付很有帮助。配合分页查询人员列表、任务列表这些接口基本是体力活。有人会问要不要上 Redis、MQ、ES我的建议是不要。一套单机 MySQL 能解决的调度系统加 Redis 只会增加部署复杂度。如果后期系统要横向扩展成多实例再加 Redis 做分布式锁也不迟后文我会讲怎么留这个扩展点。技术层选型备注后端框架Spring Boot 2.7.18JDK8 兼容稳定交付权限认证Sa-Token简单易用支持踢人下线ORMMyBatis-Plus 3.5.x分页、CRUD、逻辑删除前端框架Vue 3 Element Plus组件生态成熟构建工具Vite 4/5开发调试快状态管理Pinia现代、轻量数据库MySQL 8.0 InnoDButf8mb4事务和行锁3. 数据库建模支撑“智能调度”的表结构怎么设计3.1 基础信息表人员账号分离部门支持层级这个设计我踩过一次坑所以先讲不要用一个表既存登录账号又存人员信息。基层单位的实际情况很复杂一个人可能既是调度员又是执行人员也可能有人根本不登录系统但一样要被排班比如临时工。所以我把账号和人员拆成两张表sys_userid、username、password、status、create_time只负责登录认证personid、user_id、dept_id、name、phone、skills、max_load负责业务调度sys_deptid、parent_id、name树形部门结构比如“街道—社区—网格”。person.user_id允许为空。不登录的人也能被排班、被派单这是基层场景很常见的需求。skills字段我用逗号分隔的技能标签比如“电工,管道维修,高空作业”任务派发时用 LIKE 匹配。数据量大了以后可以拆技能表但中小项目用逗号分隔完全够用接口处理也简单。3.2 核心业务表任务、班次、调度记录任务工单表dispatch_task是系统最核心的表字段这么设计最够用CREATE TABLE dispatch_task ( id BIGINT NOT NULL AUTO_INCREMENT, task_no VARCHAR(32) NOT NULL COMMENT 任务编号如 D20250510001, task_type TINYINT NOT NULL COMMENT 1巡检 2维修 3值班, title VARCHAR(200) NOT NULL, description TEXT, address VARCHAR(255) COMMENT 发生地点可用于地图标注, priority TINYINT NOT NULL DEFAULT 3 COMMENT 优先级 1紧急 2高 3普通 4低, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0草稿 1待派 2已派 3进行中 4待验收 5完成 6退回 7取消, create_by BIGINT NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_task_status_time (status, start_time, end_time), KEY idx_task_assignee (assignee_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT任务工单表;start_time和end_time是冲突检测的关键建议在早期就加上联合索引。另外我单独建了一张assignee_id字段会需要索引所以上面给了一个idx_task_assignee的演示键实际建表后这些索引都要补上。排班表shift_schedule稍微不同它更强调“周期”。字段大约是id、dept_id、shift_name白班/夜班、work_date、start_time、end_time、assignee_id、status。班次排好后执行人员如果要换班可以通过“换班申请”创建一个关联记录审批后同时改两个人的行数据。调度记录表dispatch_record是审计重中之重id BIGINT task_id BIGINT 任务ID schedule_id BIGINT 排班ID可为空 source_user BIGINT 发生操作的人 target_user BIGINT 被派单的人 action_type TINYINT 1派发 2改派 3退回 4延期 5完成 6取消 before_status TINYINT 操作前状态 after_status TINYINT 操作后状态 reason VARCHAR(500) create_time DATETIME有了这一张表调度员换班、管理员追责、报表统计全都有着落。3.3 让数据库自带“防冲突”能力“智能调度”的最基本要求就是一个人不能在同一时间段被派两个互相冲突的任务。这个判断不能只靠代码控制因为并发时可能存在两个服务同时对一个人派单的情况。现实中我会用“重叠区间”的 SQL 判断SELECT COUNT(1) FROM dispatch_task WHERE assignee_id #{personId} AND status IN (2, 3) AND start_time #{endTime} AND end_time #{startTime}这个判断其实用了一个“两个区间相交”的数学条件只要新任务的开始时间小于已安排任务的结束时间并且新任务的结束时间大于已安排任务的开始时间就必然重叠。单靠这条 SQL 能挡住大部分正常情况。我还会在特殊表上做数据库级约束。比如排班表/schedule对(assignee_id, work_date, shift_name)建唯一索引防止同一天给同一个人重复排同一个班次。这样即使业务代码漏判断数据库也会抛约束异常避免脏数据。3.4 初始化脚本字典表和演示数据决定开发效率交付时要包含一个初始化 SQL里面至少要有三部分建表语句字典表数据比如任务类型、优先级、状态枚举演示数据比如两个部门、五个人员、一周任务样例。字典表设计可以很简单sys_dictdict_type、dict_label、dict_value、sort_order。系统里所有下拉框选项都从字典表来不在代码里写死枚举。这样业务方说“我们优先级要分成五级”你在库里加一行数据和调整排序就行不用改代码重新发布。演示数据特别重要别小看它。没有演示数据时系统跑起来全是空页面业务方看不出效果。我一般会造一批看起来非常真实的假数据比如“小区路灯不亮”“楼道堆物”“井盖破损”日期设置在最近三天这样打开任务列表立刻有业务画面验收通过率会高很多。4. 后端核心实现智能派单、冲突检测和状态流转4.1 智能派单接口的三层逻辑后端最核心的就一个方法dispatch(Long taskId)。有人会把它想得很玄其实拆开就是三步匹配、评分、执行。第一步是匹配。拿到任务的start_time、end_time、task_type先过滤出时间不冲突的人再过滤技能匹配的人。技能匹配可以用 SQL 的FIND_IN_SET但为了代码可读性我更愿意把候选人员查出来后在 Java 内存里做集合过滤。第二步是评分。给每个候选者计算一个分数分数越低越优先double score loadScore(person) * 0.4 priorityScore(task) * 0.3 skillScore(person, task) * 0.3;loadScore可以用当前待办任务数和最近三天完成任务数加权得到priorityScore表示紧急任务应该尽量指派给反应更迅速的人skillScore是技能标签匹配度。这套评分规则我特意做成可配置不写在 if 里以后业务方说“我觉得公平性应该更重要”直接调权重就行。第三步是执行调度。生成一条dispatch_record更新dispatch_task的assignee_id和status发送一条站内消息。三步必须在一个事务里要么全成功要么全失败。4.2 并发撞单问题数据库锁还是分布式锁这里讲一个真实发生过的问题两个调度员同时打开同一个任务池A 把任务派给了张三B 没刷新页面也把任务派给了张三。如果代码没有做并发控制最后结果就是两笔调度记录都生成张三莫名接了两个单状态还互相覆盖。解决思路我分两层。第一层是最简单的“目标人员行锁”。在事务里更新任务时先对候选人员行加锁Person p personMapper.selectByIdForUpdate(candidateId); // 校验是否已被占用selectByIdForUpdate会锁住person表那一行第二个事务只能等第一个提交。基层系统单机部署、并发量不大这个方案完全够用。第二层是考虑多节点部署。如果以后把一个 jar 包部署到两台机器Java 层的锁就没用了需要引入 Redis 分布式锁用SETNX拿到任务 ID 的锁后再执行派单逻辑。我在代码里抽象了一个LockService接口本地开发用 JVM 锁生产环境可以替换成 Redisson 实现这就是留好的扩展点。4.3 状态机别把状态写散落一地任务状态字段是0 草稿、1 待派、2 已派、3 进行中、4 待验收、5 完成、6 退回、7 取消。如果每个接口里都写一堆 if 判断状态能不能流转后期维护就是灾难。我用的方式是定义一张状态机表当前状态触发操作允许目标状态1 待派派单2 已派2 已派开始执行3 进行中3 进行中提交完成4 待验收4 待验收验收通过5 完成4 待验收验收驳回3 进行中 / 6 退回任意取消7 取消后端我写一个TaskStateMachine里面是一个Map状态, Map操作, 可去状态所有接口统一调transition(current, event)方法。不合法就直接抛业务异常“当前状态不允许该操作”。这样我不用为每个接口检查状态合法性的重复代码而且业务方如果要新增“挂起”状态只改这一处配置就可以。4.4 消息通知的轻量实现调度系统有一个隐藏的尖叫点被派单的人必须第一时间知道有新任务不然所谓智能调度就是空谈。基层人员不一定随时盯着后台所以我做了两套通知第一套是站内信H5 页面右上角小红点列表页每隔 30 秒轮询一次未读消息第二套是接口预留把企业微信群机器人 Webhook、阿里云短信的 URL 做成配置项业务方接上就能发通知。这里我不建议一上来就做 WebSocket因为服务端需要维护心跳连接复杂度提升很明显。轮询 30 秒对这类管理系统来说完全够用户也不在意这点延迟。5. 前端落地工作台结构、动态菜单和权限控制5.1 前端工程目录和动态路由前端项目结构我会刻意保持规整方便团队接手src/ ├── api/ # 接口请求模块按业务域拆文件 ├── assets/ # 静态资源和全局样式 ├── components/ # 公用组件比如上传、日期选择 ├── layouts/ # 主框架侧边栏顶栏 ├── router/ # 路由定义和守卫 ├── stores/ # Pinia 存储 ├── utils/ # request 封装、日期处理、权限指令 └── views/ ├── dashboard/ # 数据看板 ├── dispatch/ # 任务管理、派单工作台 ├── schedule/ # 排班管理 └── system/ # 用户、部门、字典权限控制上我采用“动态路由”方案登录成功后后端根据角色返回菜单和权限码前端stores/user存下来通过router.addRoute动态添加路由。这样做的好处是不同角色看到的菜单完全不一样执行人员打开系统只有“我的任务”和“消息中心”调度员才有派单权限。路由守卫代码大概长这样逻辑很简单但很关键router.beforeEach(async (to) { const token localStorage.getItem(token) if (!token to.path ! /login) { return { path: /login, query: { redirect: to.fullPath } } } if (token !useUserStore().roles.length) { const store useUserStore() await store.fetchUserInfo() store.menus.forEach(route router.addRoute(route)) return { ...to, replace: true } } })5.2 调度工作台的关键交互调度员每天盯着的是调度工作台所以交互设计必须贴近他的习惯。我做的是三栏布局左侧是任务列表按紧急程度排序待派单的颜色标红即将超时的显示倒计时中间是排班日历展示人员一周的班次和已有任务右侧是人员面板点击候选人能看到技能标签、当前负荷、未来几天的任务数量。派单的最后一个动作是拖拽调度员从人员面板里拖一个名字到任务详情区系统会自动进行冲突检测不通过时给出原因“该人员此时段已有维修任务”通过则弹出确认框。这个交互看起来高级但实现并不难用原生 HTML5 拖拽 API 或者sortablejs插件都能搞定。不过移动端不适用所以我还保留了一个普通“单选人员确认派单”的表格模式手机浏览器可用。5.3 报表和实时刷新基层单位上线这类系统后最关心的是能不能向上级交代。所以首页我做了一个数据看板用 ECharts 展示今日任务量、已完成量、超时量人员负荷对比柱状图最近七天响应时长折线图班次统计饼图。前端刷新策略我用的是setInterval每 60 秒拉一次接口加一个“手动刷新”按钮。这个策略简单可靠不会因为 WebSocket 断连产生用户感知问题。如果后期要实时性更强再平滑升级到 WebSocket。6. 联调与部署绕不开的坑我一条条说6.1 时间格式的一致性一天内吃掉两小时前后端联调时最大的坑就是时间。典型现象是前端传了一个“2025-05-10 08:00:00”后端的LocalDateTime接收没问题但存储到 MySQL 后查看变成“2025-05-10 00:00:00”或者反过来多出 8 小时。根本原因是数据库连接参数、Jackson 序列化、前端时区三者没对齐。我的固定配置是JDBC 连接串加serverTimezoneAsia/Shanghai和useSSLfalseSpring Boot 的spring.jackson.time-zoneGMT8统一日期格式为yyyy-MM-dd HH:mm:ss后端全部使用LocalDateTime不用Date避免老 API 的时间混乱前端用 dayjs 统一格式化展示接口传参时用2025-05-10 08:00:00字符串不做任何隐式时间戳转换。这套组合在我手上跑了几个项目基本没有再出现时区问题。如果你接手现成项目发现时间不对优先查这三处八成能解。6.2 跨域问题本地开发用代理生产尽量同源本地开发时Vite 默认跑在 5173后端跑在 8080直接发请求必然跨域。我一般不开后端的全局 CORS而是在 Vite 里配置代理// vite.config.ts export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })所有接口都挂在/api前缀下实现方式是在 axios 里设置baseURL /api后端 Controller 的类上统一加RequestMapping(/api/...)。生产环境我更倾向于 Nginx 把 /api 反向代理到后端服务实现同源访问完全不用开 CORS省得被人扫描出安全漏洞。6.3 前端打包进 Jar 还是独立 Nginx 部署交付的时候会面临一个选择前端dist是打进 Spring Boot 静态资源目录还是用 Nginx 单独部署打成一个 jar 的优势是部署极简java -jar一条命令适合只有一个服务器的场合。做法是npm run build后把dist目录复制到src/main/resources/static前后端一体。但要注意前端是 history 模式时刷新某个子路由会 404因为 Spring Boot 不知道该把它转发给 index.html。这时要么后端加一个 controller 处理未知页面要么让前端用 hash 模式路由。我个人的建议是如果已经用 Nginx就别打 jar如果没有 Nginx 环境前端用 hash 模式路由最省心。Nginx 部署的参考配置location / { root /opt/dispatch-ui; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }6.4 交付“源码数据库文档”的工程化规范最后说说交付件本身。拿到源码不是目的能自己部署、二次开发才是关键。我平时验收自己项目时会按这个清单整理README.md写清 JDK、Node、MySQL 的版本要求启动步骤数据库脚本位置默认账号密码sql 目录放初始化脚本按顺序编号比如V1__init_schema.sql、V2__init_dict_data.sql、V3__init_mock_data.sql接口文档集成 knife4jSwagger 的增强版启动后/doc.html直接看接口定义和在线调试部署手册包含 jar 部署、Nginx 静态部署两个方案以及改端口、改数据库密码的具体位置反向排查清单常见问题表比如“登录后路由空白”“时间少了 8 小时”“图片上传 404”。文档不用追求大而全但要保证一个新同学照着做能在 30 分钟内把系统跑起来。源码、数据库、文档这三样是同一个交付物缺了任何一样项目都算不上完整。做完这个项目我最大的体会是基层系统上线之后才是真正开始。所谓智能化调度核心不是炫技而是把单位里的隐性规则显性化让每个人心服口服。如果你准备复刻这套方案我的建议是先把排班规则和任务状态机定清楚再动手写代码表和接口可以多花两天慢慢想写逻辑反而很快。做完以上这些还可以往移动端小程序、消息推送、数据大屏这几个方向继续扩展那都是下一步的乐趣了。
返回列表