编辑 | blame | 历史 | 原始文档

执行人员角色存储功能实现总结

一、功能需求

在急救转运任务创建时,需要区分存储执行人员的角色类型(司机、医生、护士),以便在同步调度单到旧系统时能准确对应到相应的随行人员参数:
- 司机 → Entourage_1
- 医生 → Entourage_3
- 护士 → Entourage_4
- 第一个执行人员自动成为领队(EntourageLeadID设为1/3/4)

二、实现方案

1. 数据库层

1.1 创建执行人员关联表

文件: sql/create_sys_task_assignee.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)自动识别用户类型:

// 根据用户的岗位或角色判断类型
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 人员筛选功能

前端提供了按角色筛选人员的功能:

<view class="staff-filter">
  <view class="filter-item" :class="{ active: staffFilterType === 'all' }" @click="filterStaff('all')">全部</view>
  <view class="filter-item" :class="{ active: staffFilterType === 'driver' }" @click="filterStaff('driver')">司机</view>
  <view class="filter-item" :class="{ active: staffFilterType === 'doctor' }" @click="filterStaff('doctor')">医生</view>
  <view class="filter-item" :class="{ active: staffFilterType === 'nurse' }" @click="filterStaff('nurse')">护士</view>
</view>

2.3 数据提交

buildSubmitData方法中,将执行人员的详细信息(包含角色类型)一起提交:

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实体类,对应数据库表:

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操作接口:

public interface SysTaskAssigneeMapper {
    // 查询任务的执行人员列表
    public List<SysTaskAssignee> selectSysTaskAssigneeByTaskId(Long taskId);
    
    // 批量新增任务执行人员关联
    public int batchInsertSysTaskAssignee(List<SysTaskAssignee> assignees);
    
    // 删除任务的所有执行人员关联
    public int deleteSysTaskAssigneeByTaskId(Long taskId);
    
    // ... 其他方法
}

3.3 Mapper XML

文件: ruoyi-system/src/main/resources/mapper/system/SysTaskAssigneeMapper.xml

实现了批量插入和查询(按排序顺序):

<insert id="batchInsertSysTaskAssignee" parameterType="java.util.List">
    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
    <foreach collection="list" item="item" separator=",">
        (#{item.taskId}, #{item.userId}, #{item.userName}, #{item.userType}, #{item.isPrimary}, #{item.sortOrder}, #{item.createTime}, #{item.createBy}, #{item.updateTime}, #{item.updateBy})
    </foreach>
</insert>

<select id="selectSysTaskAssigneeByTaskId" parameterType="Long" resultMap="SysTaskAssigneeResult">
    select id, task_id, user_id, user_name, user_type, is_primary, sort_order, create_time, create_by, update_time, update_by
    from sys_task_assignee
    where task_id = #{taskId}
    order by sort_order asc
</select>

3.4 服务层 - 保存执行人员信息

文件: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java

insertSysTask方法中添加了保存执行人员信息的逻辑:

@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<TaskCreateVO.AssigneeInfo> assignees) {
    if (assignees == null || assignees.isEmpty()) {
        return;
    }
    
    List<SysTaskAssignee> 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方法,优先从数据库读取执行人员信息:

/**
 * 获取任务的执行人员信息列表(包含角色类型)
 */
private List<TaskCreateVO.AssigneeInfo> getTaskAssignees(Long taskId) {
    List<TaskCreateVO.AssigneeInfo> assignees = new ArrayList<>();
    
    try {
        // 从数据库查询执行人员信息(按排序顺序)
        List<SysTaskAssignee> 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返回的角色信息:

private void syncTaskAssignees(SysTask task, Map<String, String> params) {
    try {
        // 获取任务的执行人员信息列表(包含角色类型)
        List<TaskCreateVO.AssigneeInfo> 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<AssigneeInfo>(包含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 - 创建执行人员关联表
  1. Java实体类
  • ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAssignee.java - 执行人员实体类
  1. Mapper接口
  • ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskAssigneeMapper.java - 执行人员Mapper接口
  1. Mapper XML
  • ruoyi-system/src/main/resources/mapper/system/SysTaskAssigneeMapper.xml - 执行人员Mapper XML
  1. 文档
  • prd/执行人员角色存储功能实现总结.md - 本文档

5.2 修改文件

  1. 前端
  • app/pages/task/create-emergency.vue - 已在之前修改,本次无新修改
  1. 后端VO
  • ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskCreateVO.java - 已在之前添加AssigneeInfo内部类和assignees字段
  1. 服务层
  • ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
    • 添加SysTaskAssigneeMapper注入
    • 在insertSysTask方法中调用saveTaskAssignees
    • 新增saveTaskAssignees方法保存执行人员信息
  1. 同步服务
  • 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'的记录
  1. 角色识别准确性
  • 前端通过岗位名称或角色名称包含关键词来判断角色
  • 需要确保系统中的岗位和角色命名规范,包含"司机"、"医生"、"护士"等关键词
  • 对于无法识别的用户,默认为司机类型
  1. 同步顺序
  • 执行人员的顺序很重要,第一个执行人员自动成为领队
  • 通过sort_order字段保证顺序的正确性
  1. 向后兼容
  • 对于旧数据,getTaskAssignees方法会自动从任务主表获取执行人员
  • 建议对旧数据进行数据迁移,将执行人员信息写入sys_task_assignee
  1. 性能优化
  • 使用批量插入减少数据库交互次数
  • 查询时按sort_order排序,确保顺序正确

九、优势总结

  1. 数据完整性:执行人员及其角色信息完整存储在数据库中
  2. 前端明确:用户选择时就能看到人员类型(司机/医生/护士)
  3. 后端简化:不需要在同步时重复查询和判断角色
  4. 同步可靠:直接使用存储的角色类型,避免判断错误
  5. 易于扩展:独立的关联表设计,便于后续功能扩展
  6. 向后兼容:兼容旧数据,不影响已有功能

十、未来扩展建议

  1. 执行人员任务分工:在sys_task_assignee表中添加task_role字段,记录执行人员在任务中的具体职责

  2. 执行人员状态追踪:添加status字段,记录执行人员的任务状态(已分配、已确认、执行中、已完成等)

  3. 执行人员变更记录:添加历史记录表,记录执行人员的变更历史

  4. 执行人员绩效统计:基于执行人员关联表,统计每个人员的任务执行情况

  5. 智能推荐:基于历史数据,智能推荐合适的执行人员组合