Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

工作流中支持任务认领 #696

Open
eric-lxm opened this issue Nov 13, 2024 · 0 comments
Open

工作流中支持任务认领 #696

eric-lxm opened this issue Nov 13, 2024 · 0 comments

Comments

@eric-lxm
Copy link

eric-lxm commented Nov 13, 2024

碰到问题,请在 https://github.com/YunaiV/ruoyi-vue-pro/issues 搜索是否存在相似的 issue。

不按照模板提交的 issue,会被系统自动删除。

基本信息

  • ruoyi-vue-pro 版本:2.3.0-SNAPSHOT
  • 操作系统:WIN10
  • 数据库:MYSQL8.0

你猜测可能的原因

(必填)我花费了 2-4 小时自查,发现可能的原因是:当一个任务需要多个人处理时,此时增加认领功能,谁认领此任务则可继续处理任务。

复现步骤

第一步,BpmUserTaskActivityBehavior中增加处理多个候选人的逻辑。

1)计算任务的候选人集合,而非之前单个
private Set<Long> calculateTaskCandidateUsers0(DelegateExecution execution) {
        // 情况一,如果是多实例的任务,例如说会签、或签等情况,则从 Variable 中获取。
        // 顺序审批可见 BpmSequentialMultiInstanceBehavior,并发审批可见 BpmSequentialMultiInstanceBehavior
        if (super.multiInstanceActivityBehavior != null) {
            HashSet<Long> set = new HashSet<>();
            set.add(execution.getVariable(super.multiInstanceActivityBehavior.getCollectionElementVariable(), Long.class));
            return set;
        }

        // 情况二,如果非多实例的任务,则计算任务处理人
        // 第一步,先计算可处理该任务的处理人们
        // 第二步,后随机选择一个任务的处理人
        // 疑问:为什么一定要选择一个任务处理人?
        // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。
        //      如果希望一个任务可以同时被多个人处理,可以考虑使用 BpmParallelMultiInstanceBehavior 实现的会签 or 或签。
        return taskCandidateInvoker.calculateUsers(execution);
    }

(2)为任务设置负责人,支持设置多个负责人
    protected void handleAssignments(TaskService taskService, String assignee, String owner,
                                     List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
                                     DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
        // 第一步,获得任务的候选用户, 这里可能会有多个候选用户???todo:
        //Long assigneeUserId = calculateTaskCandidateUsers(execution);
        Set<Long> assigneeUserIds = calculateTaskCandidateUsers0(execution);
        Assert.notEmpty(assigneeUserIds, "任务处理人不能为空");

        if (1 < assigneeUserIds.size()) {
            // 第二步,设置作为负责人 todo: 这里随机选择了一个,不合适,需要认领该任务。
            BpmTaskService bpmTaskService = SpringUtil.getBean(BpmTaskService.class);
            bpmTaskService.addCandidateUsers(task.getId(), assigneeUserIds);
        } else if (1 == assigneeUserIds.size()) {
            // 第二步,设置作为负责人
            TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserIds.stream().findFirst().get()));
        }
    }

第二步,获取待办任务时要获取候选和已经分配给的任务。BpmTaskServiceImpl。
(1) 将多个候选人添加到指定任务

/**
     * 添加候选用户 到指定任务
     *
     * @param taskId         任务Id
     * @param candidateUsers 候选用户
     */
    @Override
    public void addCandidateUsers(String taskId, Collection<Long> candidateUsers) {
        candidateUsers.forEach(e -> taskService.addCandidateUser(taskId, String.valueOf(e)));
    }

(2)更改获取待办任务接口。

    public PageResult<Task> getTaskTodoPage0(Long userId, BpmTaskPageReqVO pageVO) {
        TaskQuery taskQuery = taskService.createTaskQuery()
                .taskCandidateOrAssigned(String.valueOf(userId))
                //.taskAssignee(String.valueOf(userId)) // 分配给自己
                .active()
                .includeProcessVariables()
                .orderByTaskCreateTime().desc(); // 创建时间倒序
        if (StrUtil.isNotBlank(pageVO.getName())) {
            taskQuery.taskNameLike("%" + pageVO.getName() + "%");
        }
        if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
        }

        List<Task> taskList = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
        long count = taskList.size();
        if (count == 0) {
            return PageResult.empty();
        }

        return new PageResult<>(CollUtil.sub(taskList, 0, (int) count), count);
    }

(2TaskConvert转换器中。将Task转成BpmTaskRespVO, 将任务认领状态返回前端
default PageResult<BpmTaskRespVO> buildTodoTaskPage0(PageResult<Task> pageResult,
                                                         Map<String, ProcessInstance> processInstanceMap,
                                                         Map<Long, AdminUserRespDTO> userMap) {
        List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(pageResult.getList(), task -> {
            BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
            taskVO.setType(FlowableUtils.getTaskType(task));
            ProcessInstance processInstance = processInstanceMap.get(taskVO.getProcessInstanceId());
            if (processInstance == null) {
                return null;
            }
            taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
            AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
            taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
            return taskVO;
        });
        return new PageResult<>(taskVOList, pageResult.getTotal());
    }

(3)增加认领和归还的实现。

/**
     * 认领流程任务
     *
     * @param userId 用户编号
     * @param reqVO  分配请求
     */
    public void claimTask(Long userId, BpmTaskClaimReqVO reqVO) {
        String taskId = reqVO.getId();
        // 1.1 校验任务
        Task task = validateTaskExist(taskId);
        if (StrUtil.isNotBlank(task.getAssignee()) && !task.getAssignee().equals(reqVO.getAssigneeUserId().toString())) { // 校验当前审批人和被转派人不是同一人
            throw exception(TASK_CLAIM_FAIL_TASK_CLAIMED, adminUserApi.getUser(reqVO.getAssigneeUserId()).getNickname());
        }
        // 1.2 校验目标用户存在
        AdminUserRespDTO assigneeUser = adminUserApi.getUser(reqVO.getAssigneeUserId());
        if (assigneeUser == null) {
            throw exception(TASK_CLAIM_FAIL_USER_NOT_EXISTS);
        }

        // 2. 添加认领意见
        taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.CLAIM.getType(),
                BpmCommentTypeEnum.CLAIM.formatComment(assigneeUser.getNickname(), reqVO.getReason()));

        // 3. 认领任务
        taskService.claim(taskId, reqVO.getAssigneeUserId().toString());
    }

    /**
     * 归还流程任务
     *
     * @param userId 用户编号
     * @param reqVO  分配请求
     */
    public void unclaimTask(Long userId, BpmTaskUnclaimReqVO reqVO) {
        String taskId = reqVO.getId();
        // 1.1 校验任务
        Task task = validateTask(userId, reqVO.getId());
        if (!task.getAssignee().equals(reqVO.getAssigneeUserId().toString())) { // 校验当前归还人和任务处理人必须是同一人
            throw exception(TASK_UNCLAIM_FAIL_USER_REPEAT);
        }
        // 1.2 校验目标用户存在
        AdminUserRespDTO assigneeUser = adminUserApi.getUser(reqVO.getAssigneeUserId());
        if (assigneeUser == null) {
            throw exception(TASK_UNCLAIM_FAIL_USER_NOT_EXISTS);
        }

        // 2. 添加归还意见
        taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.CLAIM.getType(),
                BpmCommentTypeEnum.UNCLAIM.formatComment(assigneeUser.getNickname(), reqVO.getReason()));

        // 3. 归还任务
        taskService.unclaim(taskId);
    }

第三步,增加认领和归还接口。BpmTaskController中增加以下接口。

  @PutMapping("/claim")
    @Operation(summary = "认领任务", description = "认领候选组中的指定任务")
    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
    public CommonResult<Boolean> claimTask(@Valid @RequestBody BpmTaskClaimReqVO reqVO) {
        taskService.claimTask(getLoginUserId(), reqVO);
        return success(true);
    }

    @PutMapping("/unclaim")
    @Operation(summary = "归还任务", description = "归还指定任务,任务归还后变成未认领状态")
    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
    public CommonResult<Boolean> unclaimTask(@Valid @RequestBody BpmTaskUnclaimReqVO reqVO) {
        taskService.unclaimTask(getLoginUserId(), reqVO);
        return success(true);
    }

第四步, 判断任务的状态。

public enum TaskType {
    SINGLE_HANDLER(1), // 单个处理人状态
    UNCLAIMED(2),      // 任务处于未认领状态
    CLAIMED(3);        // 任务已经认领

    private final int value;

    TaskType(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

// FlowUtils, 根据任务实体类判断任务状态
public static TaskType getTaskType(Object taskInfo) {
    if (taskInfo instanceof TaskEntityImpl) {
        TaskEntityImpl task = (TaskEntityImpl) taskInfo;
        if (ObjUtil.isEmpty(task.getAssignee())) {
            if (task.getIdentityLinkCount() > 1) {
                return TaskType.UNCLAIMED;
            }
        } else {
            if (task.getIdentityLinkCount() > 1) {
                return TaskType.CLAIMED;
            }
        }
    } else if (taskInfo instanceof HistoricTaskInstanceEntityImpl) {
        HistoricTaskInstanceEntityImpl task = (HistoricTaskInstanceEntityImpl) taskInfo;
        if (ObjUtil.isEmpty(task.getAssignee())) {
            if (task.getIdentityLinks().size() > 1) {
                return TaskType.UNCLAIMED;
            }
        } else {
            if (task.getIdentityLinks().size() > 1) {
                return TaskType.CLAIMED;
            }
        }
    }
    return TaskType.SINGLE_HANDLER;
}

(3@Schema(description = "管理后台 - 流程任务 Response VO")
@Data
public class BpmTaskRespVO {

    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private String id;

    @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
    private String name;

    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;

    @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime endTime;

    @Schema(description = "持续时间", example = "1000")
    private Long durationInMillis;

    @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
    private Integer status; // 参见 BpmTaskStatusEnum 枚举
    // 任务实体中新增加的任务认领状态字段
    @Schema(description = "任务认领状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private TaskType type = SINGLE_HANDLER; // 默认SINGLE_HANDLER直接分配的任务
}

报错信息

前端获取任务时要携带任务状态,标明任务是处理于待认领状态还是已认领状态。
带上必要的截图

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant