环境准备
win10
eclipse 2023-03
eclipse Activiti插件
Mysql 5.x
Activiti的作用等不再赘叙,直接上代码和细节
POM
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>5.18.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
启动类
import org.activiti.spring.boot.SecurityAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
配置文件 application.yml
首次启动时,会自动创建activiti的表
spring:
datasource:
driveClassName: com.mysql.cj.jdbc.Driver
#&nullCatalogMeansCurrent=true
url: jdbc:mysql://127.0.0.1:3306/act?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
username: root
password: root
hikari:
mininum-idle: 5
idle-timeout: 30000
connection-timeout: 30000
maxinum-pool-size: 10
max-lifetime: 60000
connect-test-query: select 1
BPMN
囊括了 系统任务、用户任务、执行监听器、任务监听器、排他网关,常用的都有了
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 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" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="testP" name="测试一把" isExecutable="true">
<startEvent id="startevent1" name="Start">
<extensionElements>
<activiti:executionListener event="start" delegateExpression="${startListener}"></activiti:executionListener>
</extensionElements>
</startEvent>
<endEvent id="endevent1" name="End">
<extensionElements>
<activiti:executionListener event="end" delegateExpression="${endListener}"></activiti:executionListener>
</extensionElements>
</endEvent>
<serviceTask id="servicetask1" name="Service Task1" activiti:delegateExpression="${myServiceTask1}"></serviceTask>
<serviceTask id="servicetask2" name="Service Task2" activiti:delegateExpression="${myServiceTask2}"></serviceTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="servicetask1" targetRef="servicetask2"></sequenceFlow>
<userTask id="usertask1" name="User Task">
<extensionElements>
<activiti:taskListener event="create" delegateExpression="${userTaskListener}"></activiti:taskListener>
</extensionElements>
</userTask>
<sequenceFlow id="flow3" sourceRef="servicetask2" targetRef="usertask1"></sequenceFlow>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
<serviceTask id="servicetask3" name="Service Task3" activiti:delegateExpression="${myServiceTask3}"></serviceTask>
<serviceTask id="servicetask4" name="Service Task4" activiti:delegateExpression="${myServiceTask4}"></serviceTask>
<sequenceFlow id="flow4" sourceRef="usertask1" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow5" sourceRef="exclusivegateway1" targetRef="servicetask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${age > 18}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" sourceRef="exclusivegateway1" targetRef="servicetask4">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${age <= 18}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" sourceRef="servicetask3" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow8" sourceRef="servicetask4" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:... 这些不展示了/>
</definitions>
部署流程
import org.activiti.engine.RepositoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 部署流程
*
* @author wusc
* @since 2023年8月8日16:16:40
*/
@Component
public class ProcessDeployConfig {
private final Logger logger = LoggerFactory.getLogger(ProcessDeployConfig.class);
@Autowired
private RepositoryService repositoryService;
@PostConstruct
public void deploy() {
logger.info("部署流程");
repositoryService.createDeployment()
.name("我今天来测试一把")
.addClasspathResource("testP.bpmn")// 我这个就放在src/main/resources目录下
.deploy();
}
}
启动流程
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ActivitiController {
private final Logger logger = LoggerFactory.getLogger(ActivitiController.class);
@Autowired
private RuntimeService runtimeService;// 直接引用
@RequestMapping("/act/start")
public String start(
@RequestParam(value = "name") String name,
@RequestParam(value = "phone") String phone) {
// 全局变量,可以放订单信息、用户信息、流程状态、流程步骤等
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("name", name);
paramMap.put("phone", phone);
// 任务唯一KEY,这里一般用orderId/orderNo 等唯一键来代替,后面处理任务时有用到
String uuid = UUID.randomUUID().toString();
logger.info("uuid:{}", uuid);
// 流程KEY,BPMN XML的ID
String processKey = "testP";
runtimeService.startProcessInstanceByKey(processKey, uuid, paramMap);
return "success";
}
}
Start 节点
start/end 不设置监听器也可以
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 执行监听器,流程启动时触发
*
* @author wusc
* @since 2023年8月8日16:01:48
*/
@Component("startListener")
public class StartListener implements ExecutionListener {
private static final long serialVersionUID = 422573009417731884L;
private final Logger logger = LoggerFactory.getLogger(StartListener.class);
@Override
public void notify(DelegateExecution execution) throws Exception {
logger.info("[{}]流程开始", execution.getEngineServices().getRepositoryService()
.getProcessDefinition(execution.getProcessDefinitionId()).getKey());
}
}
系统任务
系统任务是自动化处理的,需要指定哪个类来执行逻辑
Service Task1、Task2处理方式相同,execute()方法里面写自己的逻辑即可
import java.util.Map;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 系统自动任务,需要实现JavaDelegate,execute()执行完毕,当前任务完成,自动进入下一任务
*
* myServiceTask1,实例名 = 上图Main config里面的变量
*
* @author wusc
* @since 2023年8月8日16:28:55
*
*/
@Component("myServiceTask1")
public class MyServiceTask1 implements JavaDelegate {
private final Logger logger = LoggerFactory.getLogger(MyServiceTask1.class);
@Override
public void execute(DelegateExecution execution) throws Exception {
logger.info("activitiId:{}", execution.getCurrentActivityId());
logger.info("activitiName:{}", execution.getCurrentActivityName());
Map<String, Object> paramMap = execution.getVariables();
logger.info("获取全局变量,name={}", paramMap.get("name"));
logger.info("获取全局变量,phone={}", paramMap.get("phone"));
// 一套增删改查
}
}
用户任务
与系统任务不同,用户任务不指定哪个类来执行逻辑;
1. 设置监听器,给任务绑定用户;
2. 通过controller接收http请求触发,经唯一键和用户标识查到当前任务,手动Complete;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 用户任务就是具体某个人的任务,所以需要先分配给具体用户,然后该用户来处理
*
* @author wusc
* @since 2023年8月8日16:13:58
*
*/
@Component("userTaskListener")
public class UserTaskListener implements TaskListener {
private static final long serialVersionUID = -3300154910592367754L;
private final Logger logger = LoggerFactory.getLogger(UserTaskListener.class);
@Override
public void notify(DelegateTask delegateTask) {
logger.info("分配用户任务到具体用户身上");
delegateTask.setOwner("Pony");// 这个任务属于Pony(这里可以用 用户ID来填充)
delegateTask.setAssignee("wusc");// 这个任务Pony没空处理,wusc可以帮他处理,也就是二人都可以处理
}
}
处理用户任务,taskService.complete
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ActivitiController {
private final Logger logger = LoggerFactory.getLogger(ActivitiController.class);
@Autowired
private TaskService taskService;
@RequestMapping("/complete")
public String complete(
@RequestParam(value = "uuid") String uuid,
@RequestParam(value = "age") int age) {
logger.info("age={}", age);
Task task = taskService.createTaskQuery()
.processInstanceBusinessKey(uuid)// 通过唯一键定位
.taskAssignee("wusc")// 查自己的任务
.list()
.get(0);
logger.info("完成任务,taskId={}", task.getId());
Map<String, Object> userMap = new HashMap<>();
userMap.put("age", age);
taskService.complete(task.getId(), userMap);
return "success";
}
}
排他网关
排他网关通过age变量确定走哪一条路
系统任务
同Service Task1一样,在execute()方法内些自己的逻辑即可,方法执行完自动到下一个节点
import java.util.Map;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component("myServiceTask3")
public class MyServiceTask3 implements JavaDelegate {
private final Logger logger = LoggerFactory.getLogger(MyServiceTask3.class);
@Override
public void execute(DelegateExecution execution) throws Exception {
logger.info("activitiId:{}", execution.getCurrentActivityId());
logger.info("activitiName:{}", execution.getCurrentActivityName());
Map<String, Object> paramMap = execution.getVariables();
logger.info("获取全局变量,age={}", paramMap.get("age"));
}
}
End 节点
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component("endListener")
public class EndListener implements ExecutionListener {
private static final long serialVersionUID = 422573009417731884L;
private final Logger logger = LoggerFactory.getLogger(EndListener.class);
@Override
public void notify(DelegateExecution execution) throws Exception {
logger.info("[{}]流程结束", execution.getEngineServices().getRepositoryService()
.getProcessDefinition(execution.getProcessDefinitionId()).getKey());
}
}
执行日志
通过日志可以看到,启动流程后,Service Task1、Service Task2自动执行了,执行到User Task时,触发了任务监听器,给用户任务分配了具体用户
业务KEY=78bc0087-79a9-4205-bdc5-7144d936ffa9,调用完成任务接口
因为age=18,经过排他网关判断,进入Service Task4节点,最后流程结束
思考题
看日志,调用完成任务接口,Service Task4的线程ID与接口处理业务时的线程ID是相同的,这是为啥?