Administrator
发布于 2025-09-27 / 8 阅读
0

Flowable02: 动态创建流程节点

项目背景

我们公司接的客户是做资产评估的,所以对于审批&过程中的文件 要求都会非常严格,且需要留痕,那么 流程系统就是非常适合他们,客户之前也是用的 买断制产品(一个单机windows软件,只能固定操作,没有拓展性)。

ps:第一次做这方面深入分享,可能表达 以及设计上有瑕疵🤨

创建流程节点思路

知识概要

每个流程节点 都是可以配置 审批人, 可以为流程设置 默认审批人 也可以 动态设置审批人(即在流程发起时为节点选择)

需求分析

但是如果 发布的流程 存在多层嵌套,A流程执行过程会进入子流程B,这个时候B流程中的节点需要动态设置审批人, 那么问题来了,如果是 一个流程直接发布配置即可,但是他是子流程,他是不会发布的,而且有父流程也就是A流程发布,这就导致不能为他配置 审批人

解决办法

1.一次设置

既然发布时才能 配置,那我们可以通过 迭代发布流程下 所有子流程 集中处理自动审批人

2.动态设置

进入子流程前,先读取是否需要 设置审批人,然后创建一个 临时审批人 节点,为子流程配置

实现思路

1. 如何判断需要自定义审批人?

通过查看BPMN发现,在UserTask用户任务选择为 审批人自选时, -> flowable:candidateStrategy="35"

2. 临时节点应该在哪个节点创建?

经过讨论,最优解应该是在 主流程 进入 子流程 时进行创建,即使多层嵌套子流程,你总要进入必定触发。

代码实现

  1. 重写CallActivityBehavior中的 execute 方法(进入子流程时会启动)

/**
 * 自定义的 CallActivity 行为类:
 * - 在进入子流程前,如果子流程存在 START_USER_SELECT 节点,要求已提供对应的候选人;
 * - 若未提供,则自动创建“选择子流程审批人”任务,等待补全后再继续执行。
 */
@Slf4j
@Setter
public class BpmCallActivityBehavior extends CallActivityBehavior {

    private BpmProcessDefinitionService processDefinitionService;

    public BpmCallActivityBehavior(CallActivity callActivity) {
        super(callActivity);
    }

    @Override
    public void execute(DelegateExecution execution) {
        // 1) 获取子流程定义 Key
        String subProcessKey = getSubProcessKey(execution);
    
        // 2) 扫描子流程中的 START_USER_SELECT 任务(运行时通过 RepositoryService 获取)
        List<UserTask> startUserSelectTasks = null;
        try {
            RepositoryService repositoryService = CommandContextUtil.getProcessEngineConfiguration().getRepositoryService();
            ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionKey(subProcessKey)
                    .active()
                    .processDefinitionTenantId(FlowableUtils.getTenantId());
            org.flowable.engine.repository.ProcessDefinition def = query.singleResult();
            if (def != null) {
                BpmnModel bpmnModel = repositoryService.getBpmnModel(def.getId());
                startUserSelectTasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
            }
        } catch (Exception e) {
            log.warn("[execute][检查子流程({})的 START_USER_SELECT 任务时出错,忽略校验,直接进入子流程]", subProcessKey, e);
        }

        // 子流程没有 START_USER_SELECT 节点,直接进入
        if (CollUtil.isEmpty(startUserSelectTasks)) {
            super.execute(execution);
            return;
        }
    
        // 3) 若已在等待补全,检查变量是否就绪;未就绪则继续等待
        Boolean waiting = (Boolean) execution.getVariable("waitingForStartUserSelectAssignees");
        if (Boolean.TRUE.equals(waiting)) {
            if (isSelectionComplete(startUserSelectTasks, getStartUserSelectAssignees(execution))) {
                clearWaitingFlags(execution);
                ensureStartUserId(execution);
                super.execute(execution);
            }
            // 未就绪则继续等待
            return;
        }
    
        // 4) 未在等待,检查当前变量是否已经完整;完整则直接进入子流程
        if (isSelectionComplete(startUserSelectTasks, getStartUserSelectAssignees(execution))) {
            ensureStartUserId(execution);
            super.execute(execution);
            return;
        }
    
        // 5) 变量不完整,创建一个“选择子流程审批人”任务,等待补全
        createStartUserSelectAssigneesTask(execution, subProcessKey, startUserSelectTasks);
    }

    private String getSubProcessKey(DelegateExecution execution) {
        CallActivity callActivity = (CallActivity) execution.getCurrentFlowElement();
        return callActivity.getCalledElement();
    }

    @SuppressWarnings("unchecked")
    private Map<String, List<Long>> getStartUserSelectAssignees(DelegateExecution execution) {
        return (Map<String, List<Long>>) execution.getVariable(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
    }

    private boolean isSelectionComplete(List<UserTask> tasks, Map<String, List<Long>> selects) {
        if (CollUtil.isEmpty(tasks)) {
            return true;
        }
        if (selects == null) {
            return false;
        }
        for (UserTask task : tasks) {
            List<Long> ids = selects.get(task.getId());
            if (CollUtil.isEmpty(ids)) {
                return false;
            }
        }
        return true;
    }

    private void createStartUserSelectAssigneesTask(DelegateExecution execution, String subProcessKey, List<UserTask> tasks) {
        TaskService taskService = CommandContextUtil.getProcessEngineConfiguration().getTaskService();

        TaskEntityImpl task = (TaskEntityImpl) taskService.newTask(IdUtil.fastSimpleUUID());
        task.setName("选择子流程审批人");
        task.setDescription("请为即将启动的子流程选择审批人");
        task.setAssignee(getCurrentUserId(execution));
        task.setProcessInstanceId(execution.getProcessInstanceId());
        // 注意:不再绑定 executionId,避免 complete 时触发引擎导致 “this activity isn't waiting for a trigger”
        // task.setExecutionId(execution.getId());
        task.setTaskDefinitionKey("selectStartUserAssignees_" + execution.getCurrentActivityId());

        // 先保存任务,再写变量,避免 Flowable 统计计数时的 NPE
        taskService.saveTask(task);

        // 设置辅助变量,便于前端渲染 + 后端恢复(统一用 TaskService 设置)
        Map<String, Object> vars = new HashMap<>();
        vars.put("formType", 10);              // 让前端展示“选择节点审批人”按钮
        vars.put("subProcessKey", subProcessKey);
        vars.put("callActivityId", execution.getCurrentActivityId());
        // 记录当前 callActivity 的 executionId,审批完成时用于恢复继续执行(仅信息保存)
        vars.put("callActivityExecutionId", execution.getId());
        vars.put("requiredStartUserSelectTaskIds",
                tasks.stream().map(UserTask::getId).collect(Collectors.toList()));
        taskService.setVariables(task.getId(), vars);

        // 标记为等待状态
        execution.setVariable("waitingForStartUserSelectAssignees", true);
        execution.setVariable("startUserSelectTaskId", task.getId());

        log.info("[createStartUserSelectAssigneesTask][流程实例({}) 创建选择子流程审批人任务({}) 子流程({}) 必填任务IDs({})]",
                execution.getProcessInstanceId(), task.getId(), subProcessKey,
                tasks.stream().map(UserTask::getId).collect(Collectors.toList()));
    }

    private void clearWaitingFlags(DelegateExecution execution) {
        // 清理流程变量
        execution.removeVariable("waitingForStartUserSelectAssignees");
        execution.removeVariable("startUserSelectTaskId");
    }

    private void ensureStartUserId(DelegateExecution execution) {
        if (execution.getVariable("startUserId") == null) {
            String uid = getCurrentUserId(execution);
            if (uid != null) {
                execution.setVariable("startUserId", uid);
            }
        }
    }

    private String getCurrentUserId(DelegateExecution execution) {
        // 优先用 Flowable Authentication 上下文,其次用流程变量 startUserId
        String userId = Authentication.getAuthenticatedUserId();
        if (userId != null) {
            return userId;
        }
        Object startUserId = execution.getVariable("startUserId");
        return startUserId != null ? Objects.toString(startUserId, null) : null;
    }
    
}