说明
最近需要弄一个通用的工作流引擎(前后端分离,前端绘制流程),选用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_* 历史表
基本使用结束,但不够动态,完成任务完全写死,会造成每个流程的部署都需要改动代码,下一节我们扩展一下~