# 执行人员角色存储功能实现总结
## 一、功能需求
在急救转运任务创建时,需要区分存储执行人员的角色类型(司机、医生、护士),以便在同步调度单到旧系统时能准确对应到相应的随行人员参数:
- 司机 → 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. **智能推荐**:基于历史数据,智能推荐合适的执行人员组合