說明
最近需要弄一個通用的工作流引擎(前後端分離,前端繪製流程),選用Activiti6技術(6文檔較多 7目前沒有正式版,原理都差不多,7刪除了幾張表和service),在此記錄一下Activiti6在Springboot下的使用(僅介紹後端,前端繪製略過)。主要使用到的activiti service如下:
RepositoryService:對流程定義進行管理。
RuntimeService:對流程實例的管理。
TaskService:對流程任務進行管理。
IdentityService:管理用戶和用戶組。
ManagementService:提供對activiti數據庫的直接訪問【一般不用】。
HistoryService:對流程的歷史數據進行操作。
FormService:動態表單。
快速開始
Activiti6是有Springboot版本的可以在maven倉庫搜索->activiti6-Springboot,想要研究Activiti7的戳->activiti7-Springboot
首先更新pom.xml引入Activiti6,注意:Activiti6-starter支持到Springboot1.X的版本,不能使用2.X版本
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.21.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.yunlingfy</groupId>
<artifactId>springboot-activiti</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-activiti</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 使用undertow做服務器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 配置阿里druid連接池,通過改變spring.datasource.type設置數據源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-spring-boot-starter -->
<!--<dependency>-->
<!--<groupId>org.activiti</groupId>-->
<!--<artifactId>activiti-spring-boot-starter</artifactId>-->
<!--<version>7.1.0.M2</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>
<!-- 轉換模型需要使用,可以剔除 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
更新application.yml,如果需要項目啓動的時候自動部署bpmn流程,請在resource目錄下新建processes文件夾,在裏面放.bpmn文件,並將spring.check-process-definitions設置爲true
server:
port: 8082
# 下面是配置undertow作爲服務器的參數
undertow:
# 設置IO線程數, 它主要執行非阻塞的任務,它們會負責多個連接, 默認設置每個CPU核心一個線程
io-threads: 4
# 阻塞任務線程池, 當執行類似servlet請求阻塞操作, undertow會從這個線程池中取得線程,它的值設置取決於系統的負載
worker-threads: 20
# 以下的配置會影響buffer,這些buffer會用於服務器連接的IO操作,有點類似netty的池化內存管理
# 每塊buffer的空間大小,越小的空間被利用越充分
buffer-size: 1024
# 是否分配的直接內存
direct-buffers: true
spring:
application:
name: springboot-activiti
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://tencentyun:3506/activiti6?useSSL=false&characterEncoding=utf8
username: root
password: root
druid:
# 連接池的配置信息
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
maxActive: 20
# 配置獲取連接等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連接在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打開PSCache,並且指定每個連接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
filters: stat,wall,log4j
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# 配置DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
# 配置DruidStatViewServlet
stat-view-servlet:
url-pattern: "/druid/*"
# IP白名單(沒有配置或者爲空,則允許所有訪問)
# allow: 127.0.0.1,192.168.163.1
# IP黑名單 (存在共同時,deny優先於allow)
# deny: 192.168.1.73
# 禁用HTML頁面上的“Reset All”功能
reset-enable: false
# 登錄名
login-username: admin
# 登錄密碼
login-password: 123456
activiti:
# 設置activiti數據庫執行的策略類似hibernate的數據庫update設置,一般初次運行時設置爲true
# database-schema-update: true
# 關閉驗證自動部署/processes下的文件
check-process-definitions: false
# 保存歷史數據的最高級別
history-level: full
# 表示使用歷史表
db-history-used: true
info:
name: activiti-springboot
version: 0.1
編寫啓動類(不需要配置Activiti的配置,這裏只是多添加了一個Mybatis的通用mapper,不用mybatis的可以去除):
注意:如果不使用activiti自帶的Spring Security,需要在這裏去除Security的配置
package cn.yunlingfy.springbootactiviti;
import org.activiti.spring.boot.SecurityAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class SpringbootActivitiApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootActivitiApplication.class, args);
}
}
下面開始使用Activiti6啦~
Activiti6基本service的使用
package cn.yunlingfy.springbootactiviti.api.controller;
import cn.yunlingfy.springbootactiviti.infra.util.UploadFileMgr;
import org.activiti.engine.IdentityService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.identity.Group;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.Task;
import org.activiti.engine.HistoryService;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/")
public class LoginController {
private Logger logger = Logger.getLogger(this.getClass()); //log4j日誌
// 倉庫服務類
@Autowired
private RepositoryService repositoryService;
// 運行服務類
@Autowired
private RuntimeService runtimeService;
// 用戶任務服務類
@Autowired
private TaskService taskService;
// 身份管理和認證(建議自建表)
@Autowired
private IdentityService identityService;
// 歷史表
@Autowired
private HistoryService historyService;
@RequestMapping(value = "/getData", method = {RequestMethod.POST, RequestMethod.GET})
public String getData() {
return "dsa";
}
@RequestMapping(value = "/testDenied", method = {RequestMethod.POST, RequestMethod.GET})
public String testDenied() {
return "";
}
@RequestMapping(value = "/init", method = {RequestMethod.POST, RequestMethod.GET})
public String initUser(){
Group group1 = identityService.newGroup("dev");
group1.setName("dev");
group1.setType("devassignment");
identityService.saveGroup(group1);//建立HR組
Group group2 = identityService.newGroup("create");
group2.setName("create");
group2.setType("createassignment");
identityService.saveGroup(group2);//建立ZJ組
Group group3 = identityService.newGroup("admin");
group3.setName("admin");
group3.setType("adminassignment");
identityService.saveGroup(group3);//建立員工組
//newUser傳的是key【不是名字】
identityService.saveUser(identityService.newUser("aaa"));// 程序員
identityService.saveUser(identityService.newUser("bbb"));// 部長
identityService.saveUser(identityService.newUser("ccc"));// 經理
identityService.saveUser(identityService.newUser("ddd"));// 經理
identityService.createMembership("aaa", "dev");
identityService.createMembership("bbb", "dev");
identityService.createMembership("bbb", "create");
identityService.createMembership("ccc", "admin");
identityService.createMembership("ddd", "admin");
return "success";
}
/**
* 部署流程定義
*/
@RequestMapping(value = "/deployment", method = {RequestMethod.POST, RequestMethod.GET})
public String deployment() {
Deployment deployment = repositoryService.createDeployment()//創建一個部署對象
.name("申請流程_1")
.addClasspathResource("processes/c877c4b9-b07e-4a00-a17a-e8c7407c5b42.bpmn")
.deploy();
System.out.println("部署ID:" + deployment.getId());
System.out.println("部署名稱:" + deployment.getName());
return "success";
}
/**
* 刪除流程定義
*/
@RequestMapping(value = "/deleteProcess", method = {RequestMethod.POST, RequestMethod.GET})
public void deleteProcess(String deploymentId) {
/**不帶級聯的刪除:只能刪除沒有啓動的流程,如果流程啓動,就會拋出異常*/
// repositoryService.deleteDeployment(deploymentId);
/**級聯刪除:不管流程是否啓動,都能可以刪除(emmm大概是一鍋端)*/
repositoryService.deleteDeployment(deploymentId, true);
System.out.println("刪除成功!");
}
/**
* 啓動流程實例分配任務給個人
*/
@RequestMapping(value = "/start", method = {RequestMethod.POST, RequestMethod.GET})
public void start(String processDefinitionKey, String userKey) {
// String processDefinitionKey = "myProcess";//每一個流程有對應的一個key這個是某一個流程內固定的寫在bpmn內的
// 如果流程啓動需要參數
HashMap<String, Object> variables = new HashMap<>();
variables.put("userKey", userKey);//userKey在上文的流程變量中指定了
ProcessInstance instance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
// ProcessInstance instance = runtimeService.startProcessInstanceById(processDefinitionId);
System.out.println("流程實例ID:" + instance.getId());
System.out.println("流程定義ID:" + instance.getProcessDefinitionId());
}
/**
* 查詢當前人的個人任務
*/
@RequestMapping(value = "/findTask", method = {RequestMethod.POST, RequestMethod.GET})
public void findTask(String assignee) {
// String assignee = "aaa";
List<Task> list = taskService.createTaskQuery()//創建任務查詢對象
.taskAssignee(assignee)//指定個人任務查詢
.list();
if (list != null && list.size() > 0) {
for (Task task : list) {
System.out.println("任務ID:" + task.getId());
System.out.println("任務名稱:" + task.getName());
System.out.println("任務的創建時間:" + task.getCreateTime());
System.out.println("任務的辦理人:" + task.getAssignee());
System.out.println("流程實例ID:" + task.getProcessInstanceId());
System.out.println("執行對象ID:" + task.getExecutionId());
System.out.println("流程定義ID:" + task.getProcessDefinitionId());
}
}
}
/**
* aaa完成任務
*/
@RequestMapping(value = "/aaaCompleteTask", method = {RequestMethod.POST, RequestMethod.GET})
public void aaaCompleteTask(String taskId, String days) {
//任務ID
// String taskId = "47506";
// String days="4";
HashMap<String, Object> variables = new HashMap<>();
variables.put("days", days);//userKey在上文的流程變量中指定了
// taskService.claim(taskid,"ZJ2");//指定辦理人
// taskService.setAssignee(taskid, null);//回退爲組任務狀態
taskService.complete(taskId, variables);
System.out.println("完成任務:任務ID:" + taskId);
}
/**
* bbb完成任務
*/
@RequestMapping(value = "/bbbCompleteTask", method = {RequestMethod.POST, RequestMethod.GET})
public void bbbCompleteTask(String taskId) {
taskService.complete(taskId);
System.out.println("bbb完成任務:任務ID:" + taskId);
}
/**
* ccc完成任務
*/
@RequestMapping(value = "/cccCompleteTask", method = {RequestMethod.POST, RequestMethod.GET})
public void cccCompleteTask(String taskId) {
taskService.complete(taskId);
System.out.println("ccc完成任務:任務ID:" + taskId);
}
/**
* 查詢當前人的組任務
*/
@RequestMapping(value = "/findTaskGroup", method = {RequestMethod.POST, RequestMethod.GET})
public void findTaskGroup(String taskCandidateUser) {
//String assignee = "PTM";
// String taskCandidateUser="admin";
List<Task> list = taskService.createTaskQuery()//創建任務查詢對象
.taskCandidateUser(taskCandidateUser)//指定組任務查詢(也可以不指定)
// .taskAssignee(assignee)//指定個人任務查詢(如果完成任務時指定了辦理人)
.list();
String taskid ="";
String instanceId ="";
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任務ID:"+task.getId());
System.out.println("任務名稱:"+task.getName());
System.out.println("任務的創建時間:"+task.getCreateTime());
System.out.println("任務的辦理人:"+task.getAssignee());
System.out.println("流程實例ID:"+task.getProcessInstanceId());
System.out.println("執行對象ID:"+task.getExecutionId());
System.out.println("流程定義ID:"+task.getProcessDefinitionId());
taskid=task.getId();
instanceId = task.getProcessInstanceId();
}
}
//查詢組任務成員[兩種方式],runtime查詢沒有taskId,task查詢沒有InstanceId
//List<IdentityLink> listIdentity = taskService.getIdentityLinksForTask(taskid);
List<IdentityLink> listIdentity = runtimeService.getIdentityLinksForProcessInstance(instanceId);
for(IdentityLink identityLink:listIdentity ){
System.out.println("userId="+identityLink.getUserId());
System.out.println("taskId="+identityLink.getTaskId());
System.out.println("piId="+identityLink.getProcessInstanceId());
}
// String assignee = "a";
// List<Task> list = taskService.createTaskQuery()//創建任務查詢對象
//// .taskCandidateUser("ZJ")//指定組任務查詢
// .taskAssignee(assignee)
// .list();
// String taskid = "";
// String instanceId = "";
// if (list != null && list.size() > 0) {
// for (Task task : list) {
// System.out.println("任務ID:" + task.getId());
// System.out.println("任務名稱:" + task.getName());
// System.out.println("任務的創建時間:" + task.getCreateTime());
// System.out.println("任務的辦理人:" + task.getAssignee());
// System.out.println("流程實例ID:" + task.getProcessInstanceId());
// System.out.println("執行對象ID:" + task.getExecutionId());
// System.out.println("流程定義ID:" + task.getProcessDefinitionId());
// }
// }
}
@RequestMapping(value = "/findHistory", method = {RequestMethod.POST, RequestMethod.GET})
public void HistoryProcessInstance() {
List<HistoricProcessInstance> datas = historyService.createHistoricProcessInstanceQuery()
.finished().list();
for (HistoricProcessInstance historicProcessInstance : datas) {
System.out.println("流程實例id: " + historicProcessInstance.getId());
System.out.println("部署id: " + historicProcessInstance.getDeploymentId());
System.out.println("開始event: " + historicProcessInstance.getStartActivityId());
System.out.println("結束event: " + historicProcessInstance.getEndActivityId());
System.out.println("流程名稱: " + historicProcessInstance.getName());
System.out.println("PROC_DEF_ID: " + historicProcessInstance.getProcessDefinitionId());
System.out.println("流程定義Key: " + historicProcessInstance.getProcessDefinitionKey());
System.out.println("流程定義名稱: " + historicProcessInstance.getProcessDefinitionName());
}
}
/****************** 分割線 *****************************/
/**
* 開啓流程實例
*/
@RequestMapping(value = "/startProcessInstance", method = {RequestMethod.POST, RequestMethod.GET})
public void startProcessInstance(String processDefinitionKey) {
//流程定義的key
// String processDefinitionKey = "myProcess";
//key對應MyProcess.bpmn文件中id的屬性值,使用key值啓動,默認是按照最新版本的流程定義啓動
ProcessInstance pi = runtimeService.startProcessInstanceByKey(processDefinitionKey);
System.out.println("流程實例ID:" + pi.getId());//流程實例ID
System.out.println("流程定義ID:" + pi.getProcessDefinitionId());//流程定義ID
}
/**
* 查詢流程實例
*/
@RequestMapping(value = "/searchProcessInstance", method = {RequestMethod.POST, RequestMethod.GET})
public void searchProcessInstance(String processDefinitionKey) {
// String processDefinitionKey = "myProcess";
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.singleResult();
System.out.println("流程實例ID:" + pi.getId());
System.out.println("流程定義ID:" + pi.getProcessDefinitionId());
}
/**
* 流程實例的刪除
*/
@RequestMapping(value = "/deleteProcessInstanceTest", method = {RequestMethod.POST, RequestMethod.GET})
public void deleteProcessInstanceTest(String processDefinitionKey) {
// String processDefinitionKey = "myProcess";
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.singleResult();
String processInstanceId = pi.getProcessInstanceId();
System.out.println("流程實例ID:" + pi.getId());
runtimeService.deleteProcessInstance(processInstanceId, "刪除測試");
}
}
提示:taskService.createTaskQuery()可以添加很多的查詢參數,具體可以看代碼提示
上面的操作會使用到的重要的表如下:
act_id_user 用戶定義表(建議不用,功能不全)
act_id_group 分組表(同樣不建議使用)
act_re_deployment 部署信息表
act_re_procdef 流程定義表
act_de_model 流程模型表(模型信息)
act_ge_bytearray 模型二進制表(保存模型記錄)
act_ru_task 正在運行的任務(流程走完後這張表會清除信息,然後放入歷史表)
act_hi_* 歷史表
基本使用結束,但不夠動態,完成任務完全寫死,會造成每個流程的部署都需要改動代碼,下一節我們擴展一下~