赵宇博的技术博客 赵宇博的技术博客
首页
前端
后端
数据库专栏
k8s专栏
分布式专栏
Linux网络专栏
手写系列专栏
随笔
关于
GitHub (opens new window)
首页
前端
后端
数据库专栏
k8s专栏
分布式专栏
Linux网络专栏
手写系列专栏
随笔
关于
GitHub (opens new window)
  • JVM专题

  • 源码专题

  • Activi6专题

    • Activiti6-28张表关系梳理
    • SpringBoot集成Activiti和UI
    • Activiti6-API详解
    • Activiti6-业务实现
    • 杂谈

    • 后端
    • Activi6专题
    zhaoyb
    2024-12-06
    目录

    Activiti6-业务实现

    # Activiti6-业务实现

    针对于Activiti,如何实现复杂的业务,这里做一些本地化的实现

    # 1、资产申请流程

    image-20241209165516592

    整个流程结束之后,执行任务列表如下:

    image-20241209165112229

    # 2、错误事件流程

    image-20241209164952973

    整个流程结束之后,执行任务列表如下:

    image-20241209165041261

    # 3、动态分配办理人

    # 3.1、UEL表达式动态分配

    1、在任务节点的Assignee属性中配置表达式 ${taskUserManager.getUser()}

    2、在代码中定义Bean如下

    @Component
    public class TaskUserManager implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        public String getUser() {
            return "zhaoyubo";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    执行到当前节点就可以通过代码动态查询出当节点的办理人

    image-20241212145459981

    # 3.2、监听器动态分配

    1、每个节点都配置任务监听器,当节点创建时会执行监听器的逻辑

    image-20241212151305046

    2、监听器代码中,根据当前节点的ID判断应该把节点任务给某个办理人

    /**
     * 用户任务监听器 -监听器会在任务创建时执行
     */
    @Component
    public class UserTaskListener implements TaskListener {
    
        @Override
        public void notify(DelegateTask delegateTask) {
            String taskDefinitionKey = delegateTask.getTaskDefinitionKey();
            if ("audit1".equals(taskDefinitionKey)) {
                delegateTask.setAssignee("lisi");
            } else if ("audit2".equals(taskDefinitionKey)) {
                delegateTask.setAssignee("zhangsan");
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    执行结果如下:

    image-20241212150358514

    image-20241212151208808

    # 4、并行网关验证

    主要验证一个问题:并行网关不关注外出的顺序流,即使外出顺序流上面设置的条件为false,也会顺利到达下一个节点

    新建一个模型,在并行网关的第一个顺序流上设置一个false的条件,验证是否可以进入【审核3】节点

    image-20241212162640103

    验证结果是可以的:

    image-20241212162741963

    # 5、动态跳转实现

    使用场景:当前流程在【节点1】,需要后台自动跳转到【节点3】,使用代码实现:

    主要逻辑就是先获取当前流程实例

    然后获取流程模型,根据参数中的两个节点ID,从流程模型中获取两个节点

    然后删除当前节点的相关数据

    然后创建新的流程实例,把流程实例的当前节点设置为要跳转的节点

    然后把这个新的流程实例压入Operation等待Activiti调用,之后就成功跳转到目标节点

    import java.util.List;
    
    import org.activiti.bpmn.model.BpmnModel;
    import org.activiti.bpmn.model.FlowElement;
    import org.activiti.engine.ActivitiException;
    import org.activiti.engine.ActivitiIllegalArgumentException;
    import org.activiti.engine.impl.context.Context;
    import org.activiti.engine.impl.interceptor.Command;
    import org.activiti.engine.impl.interceptor.CommandContext;
    import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
    import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
    import org.activiti.engine.impl.util.ProcessDefinitionUtil;
    
    /**
     * 动态跳转
     */
    public class DynamicJumpCmd implements Command<String> {
        /**
         * 流程实例ID
         */
        protected String processInstanceId;
    
        /**
         * 跳转起始节点
         */
        protected String fromActivityId;
    
        /**
         * 跳转目标节点
         */
        protected String toActivityId;
    
        public DynamicJumpCmd(String processInstanceId, String fromActivityId, String toActivityId) {
            this.processInstanceId = processInstanceId;
            this.fromActivityId = fromActivityId;
            this.toActivityId = toActivityId;
        }
    
        @Override
        public String execute(CommandContext commandContext) {
            // processInstanceId参数不能为空
            if (this.processInstanceId == null) {
                throw new ActivitiIllegalArgumentException("Process instance id is required");
            }
            // 获取执行实例管理类
            ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
            // 获取执行实例
            ExecutionEntity execution = (ExecutionEntity)executionEntityManager.findById(this.processInstanceId);
            if (execution == null) {
                throw new ActivitiException("Execution could not be found with id " + this.processInstanceId);
            }
            if (!execution.isProcessInstanceType()) {
                throw new ActivitiException(
                    "Execution is not a process instance type execution for id " + this.processInstanceId);
            }
            ExecutionEntity activeExecutionEntity = null;
            // 获取所有子执行实例
            List<ExecutionEntity> childExecutions =
                executionEntityManager.findChildExecutionsByProcessInstanceId(execution.getId());
            for (ExecutionEntity childExecution : childExecutions) {
                if (childExecution.getCurrentActivityId().equals(this.fromActivityId)) {
                    activeExecutionEntity = childExecution;
                }
            }
            if (activeExecutionEntity == null) {
                throw new ActivitiException("Active execution could not be found with activity id " + this.fromActivityId);
            }
            // 获取流程模型
            BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId());
            // 获取当前节点
            FlowElement fromActivityElement = bpmnModel.getFlowElement(this.fromActivityId);
            // 获取目标节点
            FlowElement toActivityElement = bpmnModel.getFlowElement(this.toActivityId);
            // 校验id为fromActivityId的节点是否存在
            if (fromActivityElement == null) {
                throw new ActivitiException(
                    "Activity could not be found in process definition for id " + this.fromActivityId);
            }
            // 校验id为toActivityId的节点是否存在
            if (toActivityElement == null) {
                throw new ActivitiException(
                    "Activity could not be found in process definition for id " + this.toActivityId);
            }
            boolean deleteParentExecution = false;
            ExecutionEntity parentExecution = activeExecutionEntity.getParent();
            // 兼容子流程节点的场景
            if ((fromActivityElement.getSubProcess() != null) && ((toActivityElement.getSubProcess() == null)
                || (!toActivityElement.getSubProcess().getId().equals(parentExecution.getActivityId())))) {
                deleteParentExecution = true;
            }
            // 删除当前节点所在的执行实例及相关数据
            executionEntityManager.deleteExecutionAndRelatedData(activeExecutionEntity,
                "Change activity to " + this.toActivityId, false);
            // 如果是子流程节点,删除其所在的执行实例及相关数据
            if (deleteParentExecution) {
                executionEntityManager.deleteExecutionAndRelatedData(parentExecution,
                    "Change activity to " + this.toActivityId, false);
            }
            // 创建当前流程实例的子执行实例
            ExecutionEntity newChildExecution = executionEntityManager.createChildExecution(execution);
            // 设置执行实例的当前活动节点为目标节点
            newChildExecution.setCurrentFlowElement(toActivityElement);
            // 向operations中压入继续流程的操作类
            Context.getAgenda().planContinueProcessOperation(newChildExecution);
    
            return "";
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    import org.activiti.engine.ManagementService;
    
    public class DynamicJumpService {
    
        public DynamicJumpService(ManagementService managementService) {
            this.managementService = managementService;
        }
    
        protected ManagementService managementService;
    
        public void executeJump(String processInstanceId, String fromActivityId, String toActivityId) {
            // 实例化自定义跳转Command类
            DynamicJumpCmd dynamicJumpCmd = new DynamicJumpCmd(processInstanceId, fromActivityId, toActivityId);
            // 通过ManagementService管理服务执行自定义跳转Command类
            managementService.executeCommand(dynamicJumpCmd);
        }
    }
    
    
        @Test
        public void testDynamic() {
            // 验证动态跳转
            DynamicJumpService dynamicJumpService = new DynamicJumpService(managementService);
            // 从节点1 跳转到节点3
            dynamicJumpService.executeJump("195001", "sid-7B87D2B7-147B-4119-9C61-5CECEEE40699",
                "sid-D1F7B581-CA2F-4868-879E-6E5D178DD097");
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    image-20241216150426456

    # 6、任务撤回实现

    任务撤回的场景是:任务提交之后,下一节点的审核人尚未进行审核,则提交人可以撤回当前任务,实现如下:

    先获取一节点所有的任务列表

    然后判断后续任务有没有结束

    然后把当前节点设置为要撤回的目标节点,

    最后把新的执行实例压入Operation等待Activiti调用,之后就成功跳转到目标节点

    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    
    import org.activiti.bpmn.model.*;
    import org.activiti.engine.HistoryService;
    import org.activiti.engine.RuntimeService;
    import org.activiti.engine.history.HistoricTaskInstance;
    import org.activiti.engine.impl.interceptor.Command;
    import org.activiti.engine.impl.interceptor.CommandContext;
    import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
    import org.activiti.engine.impl.util.ProcessDefinitionUtil;
    import org.activiti.engine.runtime.Execution;
    import org.activiti.engine.runtime.ProcessInstance;
    
    /**
     * 撤回任务
     */
    public class TaskRecallCmd implements Command<String> {
    
        public String taskId;
    
        public TaskRecallCmd(String taskId) {
            this.taskId = taskId;
        }
    
        @Override
        public String execute(CommandContext commandContext) {
    
            // 获取历史服务
            HistoryService historyService = commandContext.getProcessEngineConfiguration().getHistoryService();
            // 根据当前任务id查询历史任务
            HistoricTaskInstance historicTaskInstance =
                historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
            // 进行一系列任务和流程校验
            basicCheck(commandContext, historicTaskInstance);
            // 获取流程模型
            BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(historicTaskInstance.getProcessDefinitionId());
            FlowElement flowElement = bpmnModel.getFlowElement(historicTaskInstance.getTaskDefinitionKey());
            List<String> nextElementIdList = new ArrayList();
            List<UserTask> nextUserTaskList = new ArrayList();
            // 获取后续节点信息
            getNextElementInfo(bpmnModel, flowElement, nextElementIdList, nextUserTaskList);
            // 再校验是否后续节点任务是否已经办理完成
            existNextFinishedTaskCheck(commandContext, historicTaskInstance, nextUserTaskList);
            // 流程相关数据准备
            DynamicStateManager dynamicStateManager = new DynamicStateManager();
            // 获取可撤回的节点列表
            List<String> recallElementIdList =
                getRecallElementIdList(commandContext, historicTaskInstance, nextElementIdList);
            List<ExecutionEntity> executions = new ArrayList<>();
            for (String activityId : recallElementIdList) {
                // 查询后续节点对应的执行实例
                ExecutionEntity execution = dynamicStateManager
                    .resolveActiveExecution(historicTaskInstance.getProcessInstanceId(), activityId, commandContext);
                executions.add(execution);
            }
            // 执行撤回操作
            dynamicStateManager.moveExecutionState(executions, historicTaskInstance.getTaskDefinitionKey(), commandContext);
            return "";
        }
    
        /**
         * 任务校验
         * 
         * @param commandContext
         * @param taskInstance
         */
        private void basicCheck(CommandContext commandContext, HistoricTaskInstance taskInstance) {
            if (taskInstance == null) {
                String msg = "任务不存在";
                throw new RuntimeException(msg);
            }
            if (taskInstance.getEndTime() == null) {
                String msg = "任务正在执行,不需要回退";
                throw new RuntimeException(msg);
            }
            // 判断当前流程实例是否已经结束
            RuntimeService runtimeService = commandContext.getProcessEngineConfiguration().getRuntimeService();
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(taskInstance.getProcessInstanceId()).singleResult();
            if (processInstance == null) {
                String msg = "该流程已经完成,无法进行任务回退。";
                throw new RuntimeException(msg);
            }
        }
    
        /**
         * 获取后续节点信息
         * 
         * @param bpmnModel
         *            流程模型
         * @param currentFlowElement
         *            当前节点
         * @param nextElementIdList
         *            后续节点Id列表
         * @param nextUserTaskList
         *            后续用户任务节点列表
         */
        private void getNextElementInfo(BpmnModel bpmnModel, FlowElement currentFlowElement, List<String> nextElementIdList,
            List<UserTask> nextUserTaskList) {
            // 查询当前节点所有流出顺序流
            List<SequenceFlow> outgoingFlows = ((FlowNode)currentFlowElement).getOutgoingFlows();
            for (SequenceFlow flow : outgoingFlows) {
                // 后续节点
                FlowElement targetFlowElement = bpmnModel.getFlowElement(flow.getTargetRef());
                nextElementIdList.add(targetFlowElement.getId());
                if (targetFlowElement instanceof UserTask) {
                    nextUserTaskList.add((UserTask)targetFlowElement);
                } else if (targetFlowElement instanceof Gateway) {
                    Gateway gateway = ((Gateway)targetFlowElement);
                    // 网关节点执行递归操作
                    getNextElementInfo(bpmnModel, gateway, nextElementIdList, nextUserTaskList);
                } else {
                    // 其它类型节点暂未实现
                }
            }
        }
    
        /**
         * 校验是否后续节点任务是否已经办理完成
         * 
         * @param commandContext
         *            上下文CommandContext
         * @param currentTaskInstance
         *            当前任务实例
         * @param nextUserTaskList
         *            后续用户
         */
        private void existNextFinishedTaskCheck(CommandContext commandContext, HistoricTaskInstance currentTaskInstance,
            List<UserTask> nextUserTaskList) {
            List<HistoricTaskInstance> hisTaskList = commandContext.getProcessEngineConfiguration().getHistoryService()
                .createHistoricTaskInstanceQuery().processInstanceId(currentTaskInstance.getProcessInstanceId())
                .taskCompletedAfter(currentTaskInstance.getEndTime()).list();
            List<String> nextUserTaskIdList = nextUserTaskList.stream().map(UserTask::getId).collect(Collectors.toList());
            if (!hisTaskList.isEmpty()) {
                hisTaskList.forEach(obj -> {
                    if (nextUserTaskIdList.contains(obj.getTaskDefinitionKey())) {
                        String msg = "存在已完成下一节点任务";
                        throw new RuntimeException(msg);
                    }
                });
            }
        }
    
        /**
         * 获取可撤回的节点列表
         * 
         * @param commandContext
         *            上下文CommandContext
         * @param currentTaskInstance
         *            任务实例
         * @param nextElementIdList
         *            后续节点列表
         * @return
         */
        private List<String> getRecallElementIdList(CommandContext commandContext, HistoricTaskInstance currentTaskInstance,
            List<String> nextElementIdList) {
            List<String> recallElementIdList = new ArrayList();
            List<Execution> executions =
                commandContext.getProcessEngineConfiguration().getRuntimeService().createExecutionQuery()
                    .processInstanceId(currentTaskInstance.getProcessInstanceId()).onlyChildExecutions().list();
            if (!executions.isEmpty()) {
                executions.forEach(obj -> {
                    if (nextElementIdList.contains(obj.getActivityId())) {
                        recallElementIdList.add(obj.getActivityId());
                    }
                });
            }
            return recallElementIdList;
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173

    # 7、任务撤销/收回实现

    任务撤销(或者是收回)的场景是:

    • 只能由流程发起人操作
    • 清楚审批记录
    • 恢复到流程发起时的状态

    代码核心逻辑如下:

    1、自定义CMD继承启动流程命令StartProcessInstanceCmd

    2、判断当前是否有流程实例ID,如果没有则正常启动,如果有则代表是撤销场景,则走自定义实现

    3、在自定义实现中去启动流程,初始化流程实例属性等

    processInstanceHelper = commandContext.getProcessEngineConfiguration().getProcessInstanceHelper();
    ProcessInstance processInstance = null;
    if (StringUtils.isNoneBlank(processInstanceId)) {
        processInstance = this.createAndStartProcessInstance(processDefinition, processInstanceId, businessKey,
                                                             processInstanceName, variables, transientVariables);
    } else {
        processInstance = super.createAndStartProcessInstance(processDefinition, businessKey, processInstanceName,
                                                              variables, transientVariables);
    }
    
    ...
    ...
    // 创建流程实例
    String initiatorVariableName = null;
    if (initialFlowElement instanceof StartEvent) {
        initiatorVariableName = ((StartEvent)initialFlowElement).getInitiator();
    }
    // 调用自定义Execution实体管理器创建流程实例
    ExecutionEntity processInstance = ((CustomExecutionEntityManager)commandContext.getExecutionEntityManager())
        .createProcessInstanceExecution(processDefinition, processInstanceId, businessKey,
                                        processDefinition.getTenantId(), initiatorVariableName);
    ...
    ...
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    image-20241216200954298

    # 7、代码创建流程模型

    # 8、会签加签

    业务场景: 审核节点临时增加一个或者多个共同审批人,同时审批结束后,当前节点才算是通过

    代码实现:

    1、新建加签命令AddMultiInstanceUserTaskCmd继承Command

    2、任务需要设置为多任务实例

    3、获取当前流程实例,为加签的并行会签创建子执行实例,同时去设置加签任务的局部变量

    4、最后把执行实例压入Operation中

    //查询当前任务实例
    TaskEntity taskEntity = taskEntityManager.findById(this.taskId);
    if (taskEntity == null) {
        throw new ActivitiException("id为" + this.taskId + "的任务不存在");
    }
    ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
    //查询当前任务实例所对应的执行实例
    ExecutionEntity executionEntity = executionEntityManager.findById(taskEntity.getExecutionId());
    //查询多实例的根执行实例
    ExecutionEntity miExecution = executionEntityManager.findFirstMultiInstanceRoot(executionEntity);
    if (miExecution == null) {
        throw new ActivitiException("节点 " + taskEntity.getTaskDefinitionKey() + "不是多实例任务");
    }
    ...
    ... 
    //查询多实例activiti:collection配置的表达式中的变量的值
    List<String> collectionValue =  (List)miExecution.getVariable(collectionKey);
    if (collectionValue.contains(assignee)) {
        throw new ActivitiException("加签用户 " + assignee + "已经在审批名单中");
    }
    //往变量中加入加签用户
    collectionValue.add(assignee);
    miExecution.setVariable(collectionKey, collectionValue);
    ...
    ...    
    //如果是并行多实例还需要做额外操作
    if (!multiInstanceLoopCharacteristics.isSequential()) {
        //更新nrOfActiveInstances变量
        Integer nrOfActiveInstances = (Integer) miExecution.getVariable(NUMBER_OF_ACTIVE_INSTANCES);
        miExecution.setVariableLocal(NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances + 1);
    
        //创建加签任务的执行实例
        ExecutionEntity childExecution = executionEntityManager.createChildExecution(miExecution);
        childExecution.setCurrentFlowElement(miActivityElement);
        //设置加签任务执行实例的局部变量
        Map executionVariables = new HashMap();
        executionVariables.put(multiInstanceLoopCharacteristics.getElementVariable(), assignee);
        ParallelMultiInstanceBehavior miBehavior = (ParallelMultiInstanceBehavior) miActivityElement.getBehavior();
        executionVariables.put(miBehavior.getCollectionElementIndexVariable(),currentNumberOfInstances);
        childExecution.setVariablesLocal(executionVariables);
        //向operations中压入继续流程的操作类
        commandContext.getAgenda().planContinueMultiInstanceOperation(childExecution);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43

    image-20241216205123252

    #Activiti
    Activiti6-API详解
    jar启动和IDE里启动Sprintboot的区别

    ← Activiti6-API详解 jar启动和IDE里启动Sprintboot的区别→

    最近更新
    01
    Activiti6-API详解
    11-28
    02
    SpringBoot集成Activiti和UI
    11-21
    03
    Activiti6-28张表关系梳理
    11-21
    更多文章>
    Theme by Vdoing | Copyright © 2022-2024 赵宇博 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式