由于Activiti项目团队自身的一些问题,新版本迭代很快,但功能基本上还是基于原来的Activiti 5.x/6.x,加上项目质量管控多少问题,新版本的bug多修复慢。
国内很多activiti的用户还停留在5.x/6.x的阶段,故网上各种教材文章的内容也如此,对Activiti 7.x的资料有点无面,基本上都是就一两个点的记录。
本测试将以尽量最新的环境版本来搭建,记录下备查也供有需之人参考。
activiti的源自jBPM,分支版本有flowable、camunda等
主要经历了三个大版本,分别是 5.x 6.x 7.x。目前(2021-05-21)的最新版本如下:
在maven中央仓库中有的最新版本是7.1.0.M6
https://mvnrepository.com/artifact/org.activiti/activiti-spring-boot-starter/7.1.0.M6
官网最新版本是7.1.0-M12
https://activiti.gitbook.io/activiti-7-developers-guide/releases/7.1.0-m12
github最新版本是7.1.0-M13
https://github.com/Activiti/Activiti/releases/tag/7.1.0-M13 要在项目中用的话clone下来make install,本测试就尝试最新的。
注意后两个版本M前面是短杆不是点,别眼残
bpmn 2.0 概念问度娘;
流程定义文件 ProcessDefinition File
就是在bpmn2.0规范下用工具建立的流程模板文件,一般是".bpmn"或者".bpmn20.xml",本质上就是个xml文件。
流程定义文件常见编辑工具:
a. Eclipse+插件,插件地址 http://www.activiti.org/designer/update ,现在可用没细测;
b. idea早期版本(2020.1之前?)+插件(actibpm.jar),插件很久没维护,有bug新版的idea不可用;
c. bpmnjs.io,https://bpmn.io/toolkit/bpmn-js/ 可在线编辑,但默认生成的camunda格式流程定义文件与activiti不兼容;
d.基于bpmnjs修改后的工具之一:https://github.com/activiti/activiti-modeling-app ,没细研究
e.基于bpmnjs修改后的工具之二:activiti cloud modeler,下面详细介绍。
部署 Deploy
简单讲就是把流程定义文件加载的数据库中,将xml文件存入数据库中,并解析到相关的数据表中。一次部署操作会在activiti的数据库中保留一条部署记录。
流程定义 ProcessDefinition
流程定义的xml文件加载到activiti解析后插入table中的流程定义内容,一般一次一个流程定义文件部署会生成一条流程定义记录,一次部署一个zip若有多个定义文件就是多条记录。
部署与流程定义中的一条记录是逻辑概念,实际上设计到一系列Table记录。
流程实例 ProcessInstance
就是从特定流程定义记录复制出来的一组记录,可以类比理解为java的类创建的对象。
任务 Task
通俗点说一个流程中的一个需要处理的节点成为一个任务。
网关与事件暂不展开
参考:https://activiti.gitbook.io/activiti-7-developers-guide/getting-started/getting-started-activiti-cloud/getting-started-docker-compose
cd ~
git clone https://github.com/Activiti/activiti-cloud-examples
cd activiti-cloud-examples/docker-compose
无git就安装git或者到 https://github.com/Activiti/activiti-cloud-examples 下载zip放到一个地方解压
修改隐藏文件 .evn
把 DOCKER_IP 设成Docker本机IP,不能用localhost或者127.0.0.1
拉取并启动 activiti cloud modeler
make modeler
make logs
make ps
或
docker-compose ps
注意state应该都是 up 就没问题了,注意别跟自己之前的docker镜像混淆,实在不行就找台干净的机器(虚拟机4C8G也行)安装一个docker
http://$DOCKER_IP/modeling
DOCKER_IP就是刚才.env 中设置的ip
账号/密码:modeler / password
git clone https://github.com/Activiti/Activiti.git
cd Activiti
git checkout 7.1.0-M13
mvn -T 1C clean install -Dmaven.test.skip=true
上个厕所喝喝水撩撩妹…好了
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>M13-demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>M13-demoname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>11java.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-dependenciesartifactId>
<version>7.1.0-M13version>
<scope>importscope>
<type>pomtype>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
spring:
activiti:
database-schema-update: true
history-level: full
db-history-used: true
Flow_0tygt5u
Flow_0kpr5l9
Flow_0tygt5u
Flow_0ignfmw
Flow_0ignfmw
Flow_18fmv1a
Flow_0bhmsuw
Flow_0o85xnk
Flow_0kpr5l9
Flow_0hgmw2q
Flow_0bhmsuw
Flow_18fmv1a
Flow_0hgmw2q
Flow_0o85xnk
${days>3}
${days<=3}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
@Configuration
public class DemoSecurityConfiguration {
private Logger logger = LoggerFactory.getLogger(DemoSecurityConfiguration.class);
@Bean
public UserDetailsService myUserDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = {
{"user1", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"user2", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"user3", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"user4", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"user5", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"user6", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"user7", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"system", "password", "ROLE_ACTIVITI_USER"},
{"admin", "password", "ROLE_ACTIVITI_ADMIN"},
};
for (String[] user : usersGroupsAndRoles) {
List<String> authoritiesStrings = asList(Arrays.copyOfRange(user, 2, user.length));
logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");
inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]),
authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
}
return inMemoryUserDetailsManager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
SecurityUtil.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import java.util.Collection;
@Component
public class SecurityUtil {
private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
@Autowired
private UserDetailsService userDetailsService;
public void logInAs(String username) {
UserDetails user = userDetailsService.loadUserByUsername(username);
if (user == null) {
throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
}
logger.info("> Logged in as: " + username);
SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
@Override
public Object getCredentials() {
return user.getPassword();
}
@Override
public Object getDetails() {
return user;
}
@Override
public Object getPrincipal() {
return user;
}
@Override
public boolean isAuthenticated() {
return true;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
@Override
public String getName() {
return user.getUsername();
}
}));
org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
}
}
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultTaskListener implements TaskListener {
private Logger logger = LoggerFactory.getLogger(DefaultTaskListener.class);
@Override
public void notify(DelegateTask delegateTask) {
logger.info("任务监听器-流程实例ID: " + delegateTask.getProcessInstanceId()
+ " 执行人: " + delegateTask.getAssignee());
}
}
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest
public class AskLeaveTest_6x_full {
private Logger logger = LoggerFactory.getLogger(AskLeaveTest_6x_full.class);
@Autowired
TaskService taskService;
@Autowired
HistoryService historyService;
@Autowired
RepositoryService repositoryService;
@Autowired
RuntimeService runtimeService;
@Autowired
ManagementService managementService;
@Autowired
SecurityUtil securityUtil;
@Test
@Tag("test")
public void askLeaveTest() {
// 外系统的关联单号
String businessKey = "ASK00001";
//流程定义的key
String processDefinitionKey = "askleave_0521";
String days = "4";
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("days", days);
//启动一个流程实例
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(processDefinitionKey, businessKey,variables);
logger.info(processInstance + " 流程启动成功");
//user1 查找任务
days = "2";
String assignee = "user1";
securityUtil.logInAs(assignee);
Task task = taskService
.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.taskAssignee(assignee)
.list().get(0);
//user1 处理任务,即填写请假单,把天数变量赋值 >3 天总监要加签
variables.put("days", days);
taskService.complete(task.getId(), variables); //set variables有效 !!
logger.info(task.getId() + " 请假单填写完成,请教天数:" + days);
//主管 查找并处理任务
assignee = "user2";
securityUtil.logInAs(assignee);
List<Task> taskList = taskService
.createTaskQuery()
.processInstanceId(processInstance.getId()) //指定实例ID
.taskAssignee(assignee) //指定用户
.list();
if (taskList.size() > 0) {
task = taskList.get(0);
taskService.complete(task.getId(), variables);
logger.info(task.getId() + " 主管审批完成");
} else {
logger.info("主管无任务可审核");
}
//总监 查找并处理任务 判断是否需要加签
assignee = "user4";
securityUtil.logInAs(assignee);
taskList = taskService
.createTaskQuery()
.processInstanceId(processInstance.getId()) //指定实例ID
.taskAssignee(assignee) //指定用户
.list();
if (taskList.size() > 0) {
task = taskList.get(0);
taskService.complete(task.getId(), variables);
logger.info(task.getId() + " 总监加签完成");
} else {
logger.info("总监无任务可加签");
}
//人事 查找并处理任务 归档
assignee = "user3";
securityUtil.logInAs(assignee);
taskList = taskService
.createTaskQuery()
.processInstanceId(processInstance.getId()) //指定实例ID
.taskAssignee(assignee) //指定用户
.list();
if (taskList.size() > 0) {
task = taskList.get(0);
taskService.complete(task.getId(), variables);
logger.info(task.getId() + " 人事归档完成");
} else {
logger.info("人事无任务可归档");
}
//查看历史记录
String processInstanceId = processInstance.getId();
List<HistoricTaskInstance> HisList = historyService
.createHistoricTaskInstanceQuery()
.orderByHistoricTaskInstanceEndTime().desc()
.processInstanceId(processInstanceId) //流程实例ID条件
.list();
logger.info("");
for (HistoricTaskInstance hi : HisList) {
logger.info("=============================================================");
logger.info("getId :" + hi.getId());
logger.info("getProcessDefinitionId:" + hi.getProcessDefinitionId());
logger.info("getProcessInstanceId :" + hi.getProcessInstanceId());
logger.info("getName :" + hi.getName());
logger.info("getStartTime :" + hi.getStartTime());
logger.info("getEndTime :" + hi.getEndTime());
}
}
}
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest
public class AskLeaveTest_7x_full {
private Logger logger = LoggerFactory.getLogger(AskLeaveTest_7x_full.class);
@Autowired
private SecurityUtil securityUtil;
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
HistoryService historyService;
@Test
@Tag("test")
public void askLeaveTest() {
// 外系统的关联单号
String businessKey = "ASK00001";
String days = "5";
//流程定义的key
String processDefinitionKey = "askleave_0521";
//启动一个流程实例
ProcessInstance processInstance = processRuntime.start(
ProcessPayloadBuilder.start()
.withProcessDefinitionKey(processDefinitionKey) //key多次部署可能重复,需要加限定条件
.withName(processDefinitionKey + "流程实例名称")
.withVariable("days", 0) //初始化参数0,若流程定义中没有默认值会出错
.withBusinessKey(businessKey)
.build()
);
//user1 查找任务
String assignee = "user1";
securityUtil.logInAs(assignee);
Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 100));
if (tasks.getContent().size() > 0) {
Task task = tasks.getContent().get(0);
//user1 处理任务,即填写请假单,把天数变量赋值 >3 天总监要加签
//null 则为 候选任务,需要现claim
if (task.getAssignee() == null) {
taskRuntime.claim(TaskPayloadBuilder.claim()
.withTaskId(task.getId())
.build());
}
//处理任务并设置变量
taskRuntime.complete(TaskPayloadBuilder
.complete()
.withTaskId(task.getId())
.build());
} else {
logger.info(assignee + " 无任务可审核");
}
//user2 主管查找任务并处理任务
assignee = "user2";
securityUtil.logInAs(assignee);
tasks = taskRuntime.tasks(Pageable.of(0, 100));
if (tasks.getContent().size() > 0) {
Task task = tasks.getContent().get(0);
//null 则为 候选任务,需要现claim
if (task.getAssignee() == null) {
taskRuntime.claim(TaskPayloadBuilder.claim()
.withTaskId(task.getId())
.build());
}
//处理任务
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("days", days);
/**
* bug 怀疑是 TaskRuntimeImpl.java 164行 那个 true应该是false
*/
taskRuntime.complete(TaskPayloadBuilder
.complete()
.withTaskId(task.getId())
.withVariable("days", days) //bug?无效?
//.withVariables(variables) //bug?无效?
.build());
} else {
logger.info(assignee + " 无任务可审核");
}
//user4 总监查找任务并处理任务
assignee = "user4";
securityUtil.logInAs(assignee);
tasks = taskRuntime.tasks(Pageable.of(0, 100));
if (tasks.getContent().size() > 0) {
Task task = tasks.getContent().get(0);
//null 则为 候选任务,需要现claim
if (task.getAssignee() == null) {
taskRuntime.claim(TaskPayloadBuilder.claim()
.withTaskId(task.getId())
.build());
}
//处理任务
taskRuntime.complete(TaskPayloadBuilder
.complete()
.withTaskId(task.getId())
.build());
} else {
logger.info(assignee + " 无任务可审核");
}
//user3 人事查找任务并处理任务
assignee = "user3";
securityUtil.logInAs(assignee);
tasks = taskRuntime.tasks(Pageable.of(0, 100));
if (tasks.getContent().size() > 0) {
Task task = tasks.getContent().get(0);
//null 则为 候选任务,需要现claim
if (task.getAssignee() == null) {
taskRuntime.claim(TaskPayloadBuilder.claim()
.withTaskId(task.getId())
.build());
}
//处理任务
taskRuntime.complete(TaskPayloadBuilder
.complete()
.withTaskId(task.getId())
.build());
} else {
logger.info(assignee + " 无任务可审核");
}
//查看历史记录
String processInstanceId = processInstance.getId();
List<HistoricTaskInstance> HisList = historyService
.createHistoricTaskInstanceQuery()
.orderByHistoricTaskInstanceEndTime().desc()
.processInstanceId(processInstanceId) //流程实例ID条件
.list();
logger.info("");
for (HistoricTaskInstance hi : HisList) {
logger.info("=============================================================");
logger.info("getId :" + hi.getId());
logger.info("getProcessDefinitionId:" + hi.getProcessDefinitionId());
logger.info("getProcessInstanceId :" + hi.getProcessInstanceId());
logger.info("getName :" + hi.getName());
logger.info("getStartTime :" + hi.getStartTime());
logger.info("getEndTime :" + hi.getEndTime());
}
}
}
https://github.com/dgatiger/activiti_m13_springboot_2.5_demo.git
大家愉快地去玩吧~~