# 执行人员角色存储功能实现总结 ## 一、功能需求 在急救转运任务创建时,需要区分存储执行人员的角色类型(司机、医生、护士),以便在同步调度单到旧系统时能准确对应到相应的随行人员参数: - 司机 → Entourage_1 - 医生 → Entourage_3 - 护士 → Entourage_4 - 第一个执行人员自动成为领队(EntourageLeadID设为1/3/4) ## 二、实现方案 ### 1. 数据库层 #### 1.1 创建执行人员关联表 **文件**: `sql/create_sys_task_assignee.sql` ```sql CREATE TABLE `sys_task_assignee` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `task_id` bigint(20) NOT NULL COMMENT '任务ID', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `user_name` varchar(50) DEFAULT NULL COMMENT '用户姓名', `user_type` varchar(20) NOT NULL COMMENT '用户类型:driver-司机,doctor-医生,nurse-护士', `is_primary` char(1) DEFAULT '0' COMMENT '是否为主要执行人:0-否,1-是', `sort_order` int(11) DEFAULT 0 COMMENT '排序顺序(用于确定领队)', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_by` varchar(64) DEFAULT '' COMMENT '创建者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `update_by` varchar(64) DEFAULT '' COMMENT '更新者', PRIMARY KEY (`id`), KEY `idx_task_id` (`task_id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务执行人员关联表'; ``` **字段说明**: - `task_id`: 关联的任务ID - `user_id`: 执行人员的用户ID - `user_name`: 用户姓名(冗余字段,方便查询) - `user_type`: 用户角色类型(driver/doctor/nurse),由前端识别并提交 - `is_primary`: 是否为主要执行人(第一个执行人员) - `sort_order`: 排序顺序,用于确定领队(第一个执行人员为领队) ### 2. 前端实现 #### 2.1 用户角色识别 **文件**: `app/pages/task/create-emergency.vue` 前端已经实现了根据用户的岗位名称(postName)或角色名称(roleName)自动识别用户类型: ```javascript // 根据用户的岗位或角色判断类型 getUserType(user) { const postName = user.posts && user.posts.length > 0 ? user.posts[0].postName : '' const roleName = user.roles && user.roles.length > 0 ? user.roles[0].roleName : '' // 判断是否为司机 if (postName.includes('司机') || roleName.includes('司机')) { return 'driver' } // 判断是否为医生 if (postName.includes('医生') || roleName.includes('医生')) { return 'doctor' } // 判断是否为护士 if (postName.includes('护士') || roleName.includes('护士')) { return 'nurse' } // 其他类型,默认为司机 return 'driver' } ``` #### 2.2 人员筛选功能 前端提供了按角色筛选人员的功能: ```vue 全部 司机 医生 护士 ``` #### 2.3 数据提交 在`buildSubmitData`方法中,将执行人员的详细信息(包含角色类型)一起提交: ```javascript const submitData = { taskType: 'EMERGENCY_TRANSFER', // ...其他字段 assigneeIds: this.selectedStaff.map(staff => staff.userId), assigneeId: this.selectedStaff.length > 0 ? this.selectedStaff[0].userId : null, // 执行人员详细信息(包含角色类型) assignees: this.selectedStaff.map(staff => ({ userId: staff.userId, userName: staff.nickName, userType: staff.type // driver/doctor/nurse })) } ``` ### 3. 后端实现 #### 3.1 实体类 **文件**: `ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAssignee.java` 创建了`SysTaskAssignee`实体类,对应数据库表: ```java public class SysTaskAssignee extends BaseEntity { private Long id; private Long taskId; private Long userId; private String userName; private String userType; // driver/doctor/nurse private String isPrimary; // 是否为主要执行人 private Integer sortOrder; // 排序顺序 // ... getters and setters } ``` #### 3.2 Mapper接口 **文件**: `ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAssigneeMapper.java` 提供了CRUD操作接口: ```java public interface SysTaskAssigneeMapper { // 查询任务的执行人员列表 public List selectSysTaskAssigneeByTaskId(Long taskId); // 批量新增任务执行人员关联 public int batchInsertSysTaskAssignee(List assignees); // 删除任务的所有执行人员关联 public int deleteSysTaskAssigneeByTaskId(Long taskId); // ... 其他方法 } ``` #### 3.3 Mapper XML **文件**: `ruoyi-system/src/main/resources/mapper/system/SysTaskAssigneeMapper.xml` 实现了批量插入和查询(按排序顺序): ```xml insert into sys_task_assignee(task_id, user_id, user_name, user_type, is_primary, sort_order, create_time, create_by, update_time, update_by) values (#{item.taskId}, #{item.userId}, #{item.userName}, #{item.userType}, #{item.isPrimary}, #{item.sortOrder}, #{item.createTime}, #{item.createBy}, #{item.updateTime}, #{item.updateBy}) ``` #### 3.4 服务层 - 保存执行人员信息 **文件**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java` 在`insertSysTask`方法中添加了保存执行人员信息的逻辑: ```java @Override @Transactional public int insertSysTask(TaskCreateVO createVO) { // ... 保存任务主信息 int result = sysTaskMapper.insertSysTask(task); // 保存车辆关联信息 // ... // 保存执行人员信息(包含角色类型) if (result > 0 && createVO.getAssignees() != null && !createVO.getAssignees().isEmpty()) { saveTaskAssignees(task.getTaskId(), createVO.getAssignees()); } // ... 其他逻辑 } /** * 保存任务执行人员信息(包含角色类型) */ private void saveTaskAssignees(Long taskId, List assignees) { if (assignees == null || assignees.isEmpty()) { return; } List taskAssignees = new ArrayList<>(); Date now = DateUtils.getNowDate(); String currentUser = SecurityUtils.getUsername(); for (int i = 0; i < assignees.size(); i++) { TaskCreateVO.AssigneeInfo assigneeInfo = assignees.get(i); SysTaskAssignee taskAssignee = new SysTaskAssignee(); taskAssignee.setTaskId(taskId); taskAssignee.setUserId(assigneeInfo.getUserId()); taskAssignee.setUserName(assigneeInfo.getUserName()); taskAssignee.setUserType(assigneeInfo.getUserType()); // 第一个执行人员为主要执行人 taskAssignee.setIsPrimary(i == 0 ? "1" : "0"); taskAssignee.setSortOrder(i); taskAssignee.setCreateTime(now); taskAssignee.setCreateBy(currentUser); taskAssignee.setUpdateTime(now); taskAssignee.setUpdateBy(currentUser); taskAssignees.add(taskAssignee); } // 批量保存 if (!taskAssignees.isEmpty()) { sysTaskAssigneeMapper.batchInsertSysTaskAssignee(taskAssignees); } } ``` #### 3.5 旧系统同步 - 读取执行人员信息 **文件**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java` 修改了`getTaskAssignees`方法,优先从数据库读取执行人员信息: ```java /** * 获取任务的执行人员信息列表(包含角色类型) */ private List getTaskAssignees(Long taskId) { List assignees = new ArrayList<>(); try { // 从数据库查询执行人员信息(按排序顺序) List taskAssignees = sysTaskAssigneeMapper.selectSysTaskAssigneeByTaskId(taskId); if (taskAssignees != null && !taskAssignees.isEmpty()) { // 将数据库中的执行人员转换为AssigneeInfo对象 for (SysTaskAssignee taskAssignee : taskAssignees) { TaskCreateVO.AssigneeInfo assignee = new TaskCreateVO.AssigneeInfo(); assignee.setUserId(taskAssignee.getUserId()); assignee.setUserName(taskAssignee.getUserName()); assignee.setUserType(taskAssignee.getUserType()); assignees.add(assignee); } log.info("从数据库获取执行人员信息成功,任务ID: {}, 人员数量: {}", taskId, assignees.size()); return assignees; } // 如果数据库中没有执行人员信息,尝试从任务的主要执行人获取 log.warn("数据库中未找到执行人员信息,尝试从任务主要执行人获取,任务ID: {}", taskId); SysTask task = sysTaskMapper.selectSysTaskByTaskId(taskId); if (task != null && task.getAssigneeId() != null) { SysUser user = sysUserMapper.selectUserById(task.getAssigneeId()); if (user != null) { TaskCreateVO.AssigneeInfo assignee = new TaskCreateVO.AssigneeInfo(); assignee.setUserId(user.getUserId()); assignee.setUserName(user.getNickName()); assignee.setUserType(getUserType(user)); // 通过角色判断类型 assignees.add(assignee); } } } catch (Exception e) { log.error("获取任务执行人员信息异常,任务ID: {}", taskId, e); } return assignees; } ``` 同步逻辑`syncTaskAssignees`方法保持不变,直接使用`getTaskAssignees`返回的角色信息: ```java private void syncTaskAssignees(SysTask task, Map params) { try { // 获取任务的执行人员信息列表(包含角色类型) List assignees = getTaskAssignees(task.getTaskId()); // ... 其他逻辑 // 遍历执行人员,根据角色类型分配到对应的Entourage参数 for (int i = 0; i < assignees.size(); i++) { TaskCreateVO.AssigneeInfo assignee = assignees.get(i); Long userId = assignee.getUserId(); String userType = assignee.getUserType(); // 直接使用前端传递的角色类型 // ... 根据userType分配到Entourage_1/3/4 } params.put("EntourageLeadID", leadEntourageId); params.put("Entourage_1", driverOaId); // 司机 params.put("Entourage_3", doctorOaId); // 医生 params.put("Entourage_4", nurseOaId); // 护士 } catch (Exception e) { log.error("同步任务执行人员异常,任务ID: {}", task.getTaskId(), e); } } ``` ## 三、数据流程 ### 3.1 创建任务时的数据流程 ``` 前端选择人员 ↓ getUserType识别角色(根据岗位名称或角色名称) ↓ 添加到selectedStaff(包含type属性:driver/doctor/nurse) ↓ buildSubmitData生成assignees数组 [{userId, userName, userType}, ...] ↓ 后端接收TaskCreateVO(包含assignees字段) ↓ insertSysTask保存任务主信息 ↓ saveTaskAssignees保存执行人员信息到sys_task_assignee表 ↓ 同步到旧系统(异步执行) ``` ### 3.2 同步到旧系统时的数据流程 ``` syncEmergencyTaskToLegacy ↓ buildDispatchOrderParams ↓ syncTaskAssignees获取执行人员信息 ↓ getTaskAssignees从数据库查询(按sort_order排序) ↓ 返回List(包含userId, userName, userType) ↓ 遍历执行人员,根据userType分配 - driver → Entourage_1 - doctor → Entourage_3 - nurse → Entourage_4 - 第一个人员 → EntourageLeadID设为1/3/4 ↓ 发送HTTP请求到旧系统 ``` ## 四、关键技术点 ### 4.1 前端角色识别 通过判断岗位名称或角色名称中是否包含关键词: - "司机" → driver - "医生" → doctor - "护士" → nurse - 其他 → 默认为driver ### 4.2 数据存储设计 创建独立的关联表`sys_task_assignee`: - 优点:支持多个执行人员,每个人员都有明确的角色类型 - 优点:通过sort_order字段确定领队顺序 - 优点:is_primary字段标识主要执行人 - 优点:便于后续扩展(如添加分工、任务分配等) ### 4.3 批量插入优化 使用MyBatis的批量插入,一次性保存所有执行人员信息,提高性能。 ### 4.4 向后兼容 `getTaskAssignees`方法具有向后兼容性: 1. 优先从新表`sys_task_assignee`查询执行人员信息 2. 如果查询不到,则从任务主表的`assignee_id`字段获取主要执行人 3. 对于旧数据,通过`getUserType`方法判断角色类型 ## 五、文件清单 ### 5.1 新建文件 1. **数据库脚本** - `sql/create_sys_task_assignee.sql` - 创建执行人员关联表 2. **Java实体类** - `ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAssignee.java` - 执行人员实体类 3. **Mapper接口** - `ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAssigneeMapper.java` - 执行人员Mapper接口 4. **Mapper XML** - `ruoyi-system/src/main/resources/mapper/system/SysTaskAssigneeMapper.xml` - 执行人员Mapper XML 5. **文档** - `prd/执行人员角色存储功能实现总结.md` - 本文档 ### 5.2 修改文件 1. **前端** - `app/pages/task/create-emergency.vue` - 已在之前修改,本次无新修改 2. **后端VO** - `ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskCreateVO.java` - 已在之前添加AssigneeInfo内部类和assignees字段 3. **服务层** - `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java` - 添加SysTaskAssigneeMapper注入 - 在insertSysTask方法中调用saveTaskAssignees - 新增saveTaskAssignees方法保存执行人员信息 4. **同步服务** - `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java` - 添加SysTaskAssigneeMapper注入 - 修改getTaskAssignees方法,优先从数据库读取执行人员信息 ## 六、部署步骤 1. **执行数据库脚本** ```bash # 在MySQL数据库中执行 mysql -u root -p < sql/create_sys_task_assignee.sql ``` 2. **编译后端代码** ```bash mvn clean install ``` 3. **重启后端服务** ```bash java -jar ruoyi-admin.jar ``` 4. **前端代码已在之前部署,无需重新部署** ## 七、测试验证 ### 7.1 创建任务测试 1. 打开APP,进入"创建急救转运任务"页面 2. 选择多个执行人员(包含司机、医生、护士) 3. 观察人员筛选功能是否正常工作 4. 提交任务,检查数据库`sys_task_assignee`表中是否正确保存了执行人员信息 5. 验证字段值: - `user_type`是否为driver/doctor/nurse - `is_primary`第一个是否为"1",其他为"0" - `sort_order`是否从0开始递增 ### 7.2 同步测试 1. 创建任务后,等待2秒让同步服务异步执行 2. 查看日志,确认是否从数据库正确读取了执行人员信息 3. 检查旧系统的调度单表,验证: - `EntourageLeadID`是否正确设置为1/3/4 - `Entourage_1`是否为司机的OA_UserID - `Entourage_3`是否为医生的OA_UserID - `Entourage_4`是否为护士的OA_UserID ### 7.3 向后兼容测试 1. 查询一个旧任务(没有执行人员关联记录) 2. 尝试同步该任务到旧系统 3. 验证是否能从任务主表的`assignee_id`字段获取执行人员 4. 验证是否能正确判断该执行人员的角色类型 ## 八、注意事项 1. **数据一致性**: - 执行人员信息存储在两个地方:任务主表的`assignee_id`(主要执行人)和`sys_task_assignee`表(所有执行人员) - 需要保持两者的一致性,`assignee_id`应该等于`sys_task_assignee`表中`is_primary='1'`的记录 2. **角色识别准确性**: - 前端通过岗位名称或角色名称包含关键词来判断角色 - 需要确保系统中的岗位和角色命名规范,包含"司机"、"医生"、"护士"等关键词 - 对于无法识别的用户,默认为司机类型 3. **同步顺序**: - 执行人员的顺序很重要,第一个执行人员自动成为领队 - 通过`sort_order`字段保证顺序的正确性 4. **向后兼容**: - 对于旧数据,`getTaskAssignees`方法会自动从任务主表获取执行人员 - 建议对旧数据进行数据迁移,将执行人员信息写入`sys_task_assignee`表 5. **性能优化**: - 使用批量插入减少数据库交互次数 - 查询时按`sort_order`排序,确保顺序正确 ## 九、优势总结 1. **数据完整性**:执行人员及其角色信息完整存储在数据库中 2. **前端明确**:用户选择时就能看到人员类型(司机/医生/护士) 3. **后端简化**:不需要在同步时重复查询和判断角色 4. **同步可靠**:直接使用存储的角色类型,避免判断错误 5. **易于扩展**:独立的关联表设计,便于后续功能扩展 6. **向后兼容**:兼容旧数据,不影响已有功能 ## 十、未来扩展建议 1. **执行人员任务分工**:在`sys_task_assignee`表中添加`task_role`字段,记录执行人员在任务中的具体职责 2. **执行人员状态追踪**:添加`status`字段,记录执行人员的任务状态(已分配、已确认、执行中、已完成等) 3. **执行人员变更记录**:添加历史记录表,记录执行人员的变更历史 4. **执行人员绩效统计**:基于执行人员关联表,统计每个人员的任务执行情况 5. **智能推荐**:基于历史数据,智能推荐合适的执行人员组合