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
<?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
- insert into ACT_RU_EXECUTION
- insert into ACT_RU_TASK
- update ACT_RU_EXECUTION:更新子执行实例
- delete from ACT_RU_TASK
- delete from ACT_RU_EXECUTION
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
五:继续审批
节点跳转之后继续审批一下,验证节点跳转是否有问题。