Activiti7中并没有实现从一个UserTask跳转到另一个UserTask,要实现节点之间自由跳转,需要通过自定义命令来实现。

Activiti7实现主要使用了命令模式(Command)和责任链模式(Intercepter)。

  • 命令模式:主要是将每个操作封装成一个命令。如:
  • DeployCmd:部署操作。
  • CompleteTaskCmd:审批操作。
  • DeleteTaskCmd:删除操作。
  • AddCommentCmd:添加审批意见操作。
  • 责任链模式:每个拦截器AbstractCommandInterceptor有一个next属性指向下一个拦截器。
  • LogIntercepter:第一个拦截器 first
  • SpringTransactionIntercepter:Spring事务拦截器。
  • CommandContextIntercepter:命令上下文拦截器。
  • CommandInvoker:最后一个拦截器,用于执行前面的各种Cmd命令。

一:application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/user?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
    username: root
    password: root123
    driver-class-name: com.mysql.cj.jdbc.Driver
## Mybatis 配置
mybatis:
  typeAliasesPackage: com.example.demo.entity
  mapperLocations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

# 打印mybatis中的sql语句
logging:
    level:
      org.activiti.engine.impl.persistence.entity: debug


activiti:
  #1.flase: 默认值。activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
  #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
  #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
  #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
  database-schema-update: true
  #检测历史表是否存在 activiti7默认没有开启数据库历史记录 启动数据库历史记录
  db-history-used: true
  #记录历史等级 可配置的历史级别有none, activity, audit, full
  history-level: full
  #校验流程文件,默认校验resources下的processes文件夹里的流程文件
  check-process-definitions: false

二:bpmn

Activiti7工作流引擎:节点动态跳转_拦截器

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1692856575168" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
  <process id="leave" isClosed="false" isExecutable="true" name="请假流程" processType="None">
    <startEvent id="start" name="StartEvent"/>
    <userTask activiti:exclusive="true" id="leads" name="组长审批"/>
    <userTask activiti:exclusive="true" id="manager" name="经理审批"/>
    <sequenceFlow id="_5" sourceRef="start" targetRef="leads"/>
    <sequenceFlow id="_6" sourceRef="leads" targetRef="manager"/>
    <exclusiveGateway gatewayDirection="Unspecified" id="_7" name="ExclusiveGateway"/>
    <sequenceFlow id="_8" sourceRef="manager" targetRef="_7"/>
    <userTask activiti:exclusive="true" id="gm" name="总经理审批"/>
    <userTask activiti:exclusive="true" id="dgm" name="副总经理审批"/>
    <sequenceFlow id="_11" sourceRef="_7" targetRef="gm">
      <conditionExpression xsi:type="tFormalExpression">
        <![CDATA[
        ]]>
      </conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="_12" sourceRef="_7" targetRef="dgm">
      <conditionExpression xsi:type="tFormalExpression">
        <![CDATA[
        ]]>
      </conditionExpression>
    </sequenceFlow>
    <userTask activiti:exclusive="true" id="hr" name="人事审批"/>
    <sequenceFlow id="_2" sourceRef="gm" targetRef="hr"/>
    <sequenceFlow id="_3" sourceRef="dgm" targetRef="hr"/>
    <endEvent id="end" name="EndEvent"/>
    <sequenceFlow id="_9" sourceRef="hr" targetRef="end"/>
  </process>
  <bpmndi:BPMNDiagram documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
    <bpmndi:BPMNPlane bpmnElement="leave">
      <bpmndi:BPMNShape bpmnElement="start" id="Shape-start">
        <omgdc:Bounds height="32.0" width="32.0" x="60.0" y="160.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="leads" id="Shape-leads">
        <omgdc:Bounds height="55.0" width="85.0" x="170.0" y="150.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="manager" id="Shape-manager">
        <omgdc:Bounds height="55.0" width="85.0" x="315.0" y="150.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_7" id="Shape-_7" isMarkerVisible="false">
        <omgdc:Bounds height="32.0" width="32.0" x="480.0" y="160.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="gm" id="Shape-gm">
        <omgdc:Bounds height="55.0" width="85.0" x="620.0" y="95.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="dgm" id="Shape-dgm">
        <omgdc:Bounds height="55.0" width="85.0" x="620.0" y="220.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="hr" id="Shape-hr">
        <omgdc:Bounds height="55.0" width="85.0" x="815.0" y="170.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="end" id="Shape-end">
        <omgdc:Bounds height="32.0" width="32.0" x="960.0" y="180.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="_12" id="BPMNEdge__12" sourceElement="_7" targetElement="dgm">
        <omgdi:waypoint x="512.0" y="176.0"/>
        <omgdi:waypoint x="620.0" y="247.5"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_2" id="BPMNEdge__2" sourceElement="gm" targetElement="hr">
        <omgdi:waypoint x="705.0" y="122.5"/>
        <omgdi:waypoint x="815.0" y="197.5"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_3" id="BPMNEdge__3" sourceElement="dgm" targetElement="hr">
        <omgdi:waypoint x="705.0" y="247.5"/>
        <omgdi:waypoint x="815.0" y="197.5"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_5" id="BPMNEdge__5" sourceElement="start" targetElement="leads">
        <omgdi:waypoint x="92.0" y="176.0"/>
        <omgdi:waypoint x="170.0" y="177.5"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="leads" targetElement="manager">
        <omgdi:waypoint x="255.0" y="177.5"/>
        <omgdi:waypoint x="315.0" y="177.5"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="manager" targetElement="_7">
        <omgdi:waypoint x="400.0" y="177.5"/>
        <omgdi:waypoint x="480.0" y="176.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_9" id="BPMNEdge__9" sourceElement="hr" targetElement="end">
        <omgdi:waypoint x="900.0" y="197.5"/>
        <omgdi:waypoint x="960.0" y="196.0"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_11" id="BPMNEdge__11" sourceElement="_7" targetElement="gm">
        <omgdi:waypoint x="512.0" y="176.0"/>
        <omgdi:waypoint x="620.0" y="122.5"/>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

三:自定义跳转命令

DynamicJumpCmd

/**
 * 节点动态跳转.
 */
@Slf4j
@AllArgsConstructor
public class DynamicJumpCmd implements Command<Void> {

    /** 流程实例id */
    private String processInstanceId;

    /** 从哪个节点 */
    private String fromActivityId;

    /** 跳转到那个节点 */
    private String toActivityId;


    @Override
    public Void execute(CommandContext commandContext) {
        log.info("node jump: processInstanceId={}, fromActivityId={}, toActivityId={}", processInstanceId, fromActivityId, toActivityId);
        // 0.参数合法性检查
        ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
        ExecutionEntity executionEntity = executionEntityManager.findById(processInstanceId);
        Assert.isTrue(executionEntity != null, "processInstanceId is not exist");
        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(executionEntity.getProcessDefinitionId());
        FlowElement fromFlowElement = bpmnModel.getFlowElement(fromActivityId);
        Assert.isTrue(fromFlowElement != null, "fromActivityId is not exist");
        FlowElement toFlowElement = bpmnModel.getFlowElement(toActivityId);

        // 1.找出子执行实例fromActivityId,并删除该执行实例
        List<ExecutionEntity> childList = executionEntityManager.findChildExecutionsByProcessInstanceId(processInstanceId);
        ExecutionEntity currentChildExecutionEntity = null;
        for (ExecutionEntity child : childList) {
            if (child.getCurrentActivityId().equals(fromActivityId)) {
                currentChildExecutionEntity = child;
                break;
            }
        }
        executionEntityManager.deleteExecutionAndRelatedData(currentChildExecutionEntity, "任务动态跳转");

        // 2.创建新的执行实例,并绑定到toActivityId
        ExecutionEntity childExecution = executionEntityManager.createChildExecution(executionEntity);
        childExecution.setCurrentFlowElement(toFlowElement);

        // 执行流程操作
        Context.getAgenda().planContinueProcessOperation(childExecution);
        return null;
    }
}

DynamicJumpService

@Service
public class DynamicJumpService {

    @Autowired
    protected ManagementService managementService;

    public void jumpTask(String processInstanceId, String fromActivityId, String toActivityId) {
        DynamicJumpCmd dynamicJumpCmd = new DynamicJumpCmd(processInstanceId, fromActivityId, toActivityId);
        managementService.executeCommand(dynamicJumpCmd);
    }
}
@RequestMapping("/jump")
public void jump(String processInstanceId, String from, String to) {
    dynamicJumpService.jumpTask(processInstanceId, from, to);
}

@RequestMapping("/complete")
public void complete(String processInstanceId, String assignee) {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    Task task = taskService.createTaskQuery().processDefinitionKey("leave")
            .processInstanceId(processInstanceId)
            .singleResult();

    taskService.setAssignee(task.getId(), assignee);
    taskService.complete(task.getId());
}

四:Test

Activiti7工作流引擎:节点动态跳转_工作流节点跳转_02

Activiti7工作流引擎:节点动态跳转_数据库_03

Activiti7工作流引擎:节点动态跳转_拦截器_04

  • insert into ACT_RU_EXECUTION
  • insert into ACT_RU_TASK
  • update ACT_RU_EXECUTION:更新子执行实例
  • delete from ACT_RU_TASK
  • delete from ACT_RU_EXECUTION

Activiti7工作流引擎:节点动态跳转_数据库_05

insert into ACT_RU_EXECUTION (ID_, REV_, PROC_INST_ID_, BUSINESS_KEY_, PROC_DEF_ID_, ACT_ID_, IS_ACTIVE_, IS_CONCURRENT_, IS_SCOPE_,IS_EVENT_SCOPE_, IS_MI_ROOT_, PARENT_ID_, SUPER_EXEC_, ROOT_PROC_INST_ID_, SUSPENSION_STATE_, TENANT_ID_, NAME_, START_TIME_, START_USER_ID_, IS_COUNT_ENABLED_, EVT_SUBSCR_COUNT_, TASK_COUNT_, JOB_COUNT_, TIMER_JOB_COUNT_, SUSP_JOB_COUNT_, DEADLETTER_JOB_COUNT_, VAR_COUNT_, ID_LINK_COUNT_, APP_VERSION_) values ( ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
2d360fca-4c7b-11ee-88f4-7221aa3b96b2(String), 0930c9a5-4c79-11ee-88f4-7221aa3b96b2(String), null, leave:1:37059261-4246-11ee-b072-8251588a22ea(String), hr(String), true(Boolean), false(Boolean), false(Boolean), false(Boolean), false(Boolean), 0930c9a5-4c79-11ee-88f4-7221aa3b96b2(String), null, 0930c9a5-4c79-11ee-88f4-7221aa3b96b2(String), 1(Integer), (String), null, 2023-09-06 14:04:01.751(Timestamp), null, false(Boolean), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer), 0(Integer), null



insert into ACT_RU_TASK (ID_, REV_, NAME_, BUSINESS_KEY_, PARENT_TASK_ID_, DESCRIPTION_, PRIORITY_, CREATE_TIME_, OWNER_, ASSIGNEE_, DELEGATION_, EXECUTION_ID_, PROC_INST_ID_, PROC_DEF_ID_, TASK_DEF_KEY_, DUE_DATE_, CATEGORY_, SUSPENSION_STATE_, TENANT_ID_, FORM_KEY_, CLAIM_TIME_, APP_VERSION_) values (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
53d7968b-4c7b-11ee-88f4-7221aa3b96b2(String), 人事审批(String), null, null, null, 50(Integer), 2023-09-06 14:05:06.638(Timestamp), null, null, null, 2d360fca-4c7b-11ee-88f4-7221aa3b96b2(String), 0930c9a5-4c79-11ee-88f4-7221aa3b96b2(String), leave:1:37059261-4246-11ee-b072-8251588a22ea(String), hr(String), null, null, 1(Integer), (String), null, null, null

update ACT_RU_EXECUTION set REV_ = 3, ACT_ID_ = ‘manager’  where ID_ = '0932ec86-4c79-11ee-88f4-7221aa3b96b2' and REV_ = 2;

delete from ACT_RU_TASK where ID_ = '0970dfe9-4c79-11ee-88f4-7221aa3b96b2' and REV_ = 1;

delete from ACT_RU_EXECUTION where ID_ = '0932ec86-4c79-11ee-88f4-7221aa3b96b2' and REV_ = 3

Activiti7工作流引擎:节点动态跳转_工作流节点跳转_06

五:继续审批

节点跳转之后继续审批一下,验证节点跳转是否有问题。

Activiti7工作流引擎:节点动态跳转_数据库_07