说明:
Activiti是工作流引擎,用于请假等各种流程实现自动化控制,和Jbpm有点类似。
自动生成的数据库act_*表说明:
act_ge_*:包含通用数据。
act_hi_*:包含历史数据,历史流程实例、变量、任务等。
act_re_*:包含流程定义和静态资源。
act_ru_*:包含流程实例、任务、变量、异步任务等运行时数据,流程结束时会删除这些表的数据。
SpringBoot配置:https://blog.csdn.net/a526001650a/article/details/106687559
一、IDEA安装actiBPM插件:
1.IntelliJ IDEA安装actiBPM插件,从以下地址下载插件,从本地选择安装:
https://plugins.jetbrains.com/plugin/7429-actibpm/versions
2.解决流程节点中文名称乱码,IDEA\bin目录下,在idea.exe.vmoptions和idea64.exe.vmoptions末尾增加:
-Dfile.encoding=UTF-8
二、流程文件创建:
1.创建流程文件:
(1)新建一个流程文件,右击工程resources/processes目录->New->BpmnFile->New BpmnFile对话框中输入文件名leave,点OK创建:
(2)双击打开leave.bpmn文件,将右边流程节点拖入中间空白处,设计流程图(中间空白处为流程设计处,右侧为流程节点):
StartEvent:流程开始
UserTask:任务节点
EndEvent:流程结束
(3)面板图标说明:
StartEvent:任务开始,必须有
UserTask:每个任务节点,必须有
EndEvent:任务结束,必须有
ExclusiveGateway:排他网关
ParallelGateway:并行网关,会忽略流程线中设置的条件
InclusiveGateway:包含网关,排他网关和并行网关结合体
(4)拖动一个StartEvent和EndEvent作为开始与结束,拖动6个UserTask作为任务节点,光标放在节点中心处按住拖出线,将每个节点连接起来:
(5)点击空白处,在左侧设置流程图的Id和Name值:
(6)依次选中每个任务节点,在左侧Assignee栏输入操作人:
(7)将bpmn转换为png格式,bpmn重命名为xml,打开导出为png格式。
2..网关:
(1)不使用网关:
多条线同时满足条件时,多条线同时执行;
多条线同时不满足条件时,抛出ActivitiException异常。
(2)排他网关ExclusiveGateway:
多条线同时满足时,选id值最小的执行;
多条线同时不满足条件时,抛出ActivitiException异常。
打开leave.bpmn文件,右侧控件面板,拖出ExclusiveGateway,放在总监审批节点后面,后面2条线都从ExclusiveGateway延伸,如图:
(3)并行网关ParallelGateway(会忽略流程线中设置的条件):
1>第1个ParallelGateway节点,将流程线从一条线拆分成多条线;
2>执行并行节点的任务,如人事存档和财务存档;
3>第2个ParallelGateway节点,将流程线从多条线合并成一条线,待多条线汇聚完成后,才会执行下一结节。
打开leave.bpmn文件,右侧控件面板,拖出第1个ParallelGateway,放在总监节点后面 -> 将ParallelGateway拖出2条线接到人事存档和财务存档任务节点 -> 拖出第2个ParallelGateway,将人事存档和财务存档流程线合并到此ParallelGateway,如图:
(4)包含网关InclusiveGateway(排他网关和并行网关结合体):
1>第1个InclusiveGateway节点,将流程线从一条线拆分成多条线,多条线可以设条件,只有满足条件的线才会执行;
2>执行并行节点的任务,如人事存档或财务存档;
3>第2个InclusiveGateway节点,将流程线从满足条件的多条线合并成一条线,待多条线汇聚完成后,才会执行下一结节。
打开leave.bpmn文件,右侧控件面板,拖出第1个InclusiveGateway,放在总监节点后面 -> 将InclusiveGateway拖出2条线接到人事存档和财务存档任务节点,对2条件设置不同条件 -> 拖出第2个InclusiveGateway,将人事存档和财务存档流程线合并到此InclusiveGateway,如图:
三、配置使用流程的工程:
1.加入activiti依赖和mysql驱动依赖,在工程/pom中:
<dependencies>
<!-- 导入activiti依赖包 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
<!-- 导入spring-security依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
<!-- 导入mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!-- 必须导入jdbc依赖包,解决找不到database的问题 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
<!-- 添加私服仓库地址 -->
<repositories>
<repository>
<id>alfresco</id>
<name>Activiti Releases</name>
<url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
2.配置流程文件路径和mysql数据库连接,在resources/application.yml中:
server:
port: 10000
spring:
datasource: #mysql数据库连接信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC
username: root
password: root
activiti: #activiti配置
database-schema-update: true #是否生成表结构
#process-definition-location-prefix: "classpath*:/processes/*.bpmn" #配置bpmn流程路径
history-level: full #full表示使用全部记录历史
db-history-used: true #配置生成history表,默认false,只会生成17张表
3.配置SecurityAutoConfiguration,在启动类Application中:
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class MyApplication {
public static void main(String[] args) {//应用入口
SpringApplication.run(MyApplication.class, args); //运行MyApplication
}
}
4.相关的类创建(找个使用的地方定义):
(1)传统方式:
//流程的资源管理
@Autowired
private RepositoryService repositoryService;
//流程的运行管理
@Autowired
private RuntimeService runtimeService;
//流程的历史管理
@Autowired
private HistoryService historyService;
//流程的任务管理
@Autowired
private TaskService taskService;
(2)Activiti7新API方式:
//新API方式:流程的资源管理
@Autowired
private ProcessRuntime processRuntime;
//此SecurityUti为自定义类
@Autowired
SecurityUtil securityUtil;
@Autowired //新API方式:任务管理
private TaskRuntime taskRuntime;
四、流程-部署/启动:
1.部署:
//1.1.流程-部署,此微服务启动时就执行
public void deploy() {
// ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// repositoryService = engine.getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("leave.bpmn")
.addClasspathResource("leave.png")
.name("请假流程")
.deploy();
}
//1.2.流程-部署,此微服务启动时就执行,Zip包方式
public void deployZip() {
ZipInputStream zin = new ZipInputStream(getClass().getClassLoader().getResourceAsStream("leave.zip")); //leave.zip包含了leave.bpmn和leave.png
repositoryService.createDeployment()
.addZipInputStream(zin)
.name("请假流程")
.deploy();
}
2.启动:
(1)传统方式:
//2.流程-启动,页面上点提交请假流程时执行
public void start() {
//1.1 启动流程实例,leave为整个流程图上设置的id值
//runtimeService.startProcessInstanceByKey("leave");
//1.2 启动流程实例,第2个参数为业务标识businessKey,存在act_ru_execution表BUSINESS_KEY_列
//ProcessInstance pi = runtimeService.startProcessInstanceByKey("leave", "businessKey");
//String bkey = pi.getBusinessKey(); //获取businessKey
//1.3 启动流程实例,第2个参数map为流程变量,控制流程走向
Map<String, Object> map = new HashMap<>();
map.put("day", 3); //传入day值,控制流程走向
ProcessInstance pi = runtimeService.startProcessInstanceByKey("leave", map);
//通过流程实例ID传入参数
//runtimeService.setVariables("流程实例ID", map);
}
(2)Activiti7新API方式:
//启动流程实例
public void startBy7() {
securityUtil.logInAs("yyh0"); //认证用户信息,用户名要和权限组中的一样
processRuntime.start(ProcessPayloadBuilder
.start()
.withProcessDefinitionKey("leave") //设置用哪张流程图,leave为流程图中设置的id
.withName("请假流程") //流程名称
// .withVariable("day", 3) //传入流程变量
// .withBusinessKey("businessKey")
.build());
}
五、流程定义相关的操作(leave为请假流程设置的id值):
1.查询流程信息:
(1)传统方式:
public void queryProcess() {
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("leave") //leave为请假流程设置的id值
.orderByProcessDefinitionVersion().desc() //对结果按版本号降序
.list();
for (ProcessDefinition item : list) {
item.getId(); //流程id
item.getName(); //流程名称
item.getKey(); //流程key
item.getVersion(); //流程版本
}
}
(2)Activiti7新API方式:
public void queryProcessBy7() {
securityUtil.logInAs("yyh0"); //认证用户信息,用户名要和权限组中的一样
Page page = processRuntime.processDefinitions(Pageable.of(0, 20));
List<ProcessDefinition> list = page.getContent();
for (ProcessDefinition item : list) {
item.getId(); //流程id
item.getName(); //流程名称
item.getKey(); //流程key
item.getVersion(); //流程版本
}
}
2.删除流程:
public void deleteProcess(String deploymentId) { //deployment通过queryProcess查出来
repositoryService.deleteDeployment(deploymentId, true); //设为true时即使启动了也能删除。反之不行
}
3.获取流程文件名与文件流:
public void queryProcessFile() {
ProcessDefinition item = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave").singleResult();
//获取流程文件名称
String bpmnName = item.getResourceName();
String pngName = item.getDiagramResourceName();
//获取流程文件的流
InputStream bpmnIn = repositoryService.getResourceAsStream(item.getDeploymentId(), bpmnName);
InputStream pngIn = repositoryService.getResourceAsStream(item.getDeploymentId(), pngName);
}
六、流程实例相关的操作(leave为请假流程设置的id值):
1.暂停XX流程的所有实例:
public void pauseAllInstance() {
ProcessDefinition item = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave").singleResult();
if (!item.isSuspended()) { //流程实例没有暂停
repositoryService.suspendProcessDefinitionById(item.getId(), true, null); //暂停此流程下的所有流程实例
}
}
2.激活XX流程的所有实例:
public void resumeAllInstance() {
ProcessDefinition item = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave").singleResult();
if (item.isSuspended()) { //流程实例已经暂停
repositoryService.activateProcessDefinitionById(item.getId(), true, null); //激活此流程下的所有流程实例
}
}
3.暂停XX流程的某个实例:
public void pauseInstance(String instanceId) {//instanceId为流程实例id
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
if (!pi.isSuspended()) { //流程实例没有暂停
runtimeService.suspendProcessInstanceById(pi.getId()); //暂停单个流程实例
}
}
4.激活XX流程的某个实例:
public void resumeInstance(String instanceId) {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId("leave").singleResult();
if (pi.isSuspended()) { //流程实例已经暂停
runtimeService.activateProcessInstanceById(pi.getId()); //激活单个流程实例
}
}
5.查询XX流程实例的历史信息:
public void queryHistory() {
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processInstanceId("leave")
.orderByHistoricActivityInstanceStartTime().desc() //排序
.list();
for (HistoricActivityInstance item : list) {//获取每一个流程信息
item.getActivityId(); //流程-节点id,ACT_ID_
item.getActivityName(); //流程-节点名称,ACT_NAME_
item.getAssignee(); //流程-任务节点执行人,ASSIGNEE_
item.getProcessDefinitionId(); //流程id,例:1.4
item.getProcessInstanceId(); //流程实例id,EXECUTION_ID_
}
}
七、流程任务相关的操作:
1.查询名下的个人任务:
(1)传统方式:
public Task findTask(String username) {
Task task = taskService.createTaskQuery()
.processDefinitionKey("leave") //leave为整个流程id值
.taskAssignee(username) //查个人任务
.singleResult();
//通过任务ID传入参数
// Map<String, Object> map = new HashMap<>();
// map.put("day", 3); //传入day值,控制流程走向
// taskService.setVariables("任务ID", map);
// taskService.setVariablesLocal("任务ID", map); //local作用域,只对当前任务节点有效果
return task;
}
(2)Activiti7新API方式:
public List<Task> findTaskBy7(String username) {
securityUtil.logInAs("yyh0"); //认证用户信息,用户名要和权限组中的一样
return taskRuntime.tasks(Pageable.of(0, 20)).getContent();
}
2.查询名下的组任务:
(1)设置组任务(设置多个候选人):
打开bpmn流程图 -> 需要候选人的任务节点 -> 在左侧Candidate Users栏,输入多个角色以逗号隔开,如图:
(2)查询:
public Task findGroupTask(String username) {
return taskService.createTaskQuery()
.processDefinitionKey("leave") //leave为整个流程id值
.taskCandidateUser(username) //候选人查组任务
.singleResult();
}
3.拾取组任务为个人任务:
(1)传统方式:
public void claim(String username) {
Task task = findGroupTask(username);
if (task == null) {
return;
}
taskService.claim(task.getId(), username);
}
(2)Activiti7新API方式:
public void claimBy7(String username) {
securityUtil.logInAs("yyh0"); //认证用户信息,用户名要和权限组中的一样
List<Task> tasks = findTaskBy7(username);
if (tasks == null || tasks.isEmpty()) {
return;
}
for (Task task : tasks) {
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
}
}
4.取消拾取,重置为组任务:
public void cancelClaim(String username) {
Task task = findTask(username);
if (task == null) {
return;
}
taskService.setAssignee(task.getId(), null); //执行人设为null,则重新变为组任务
}
5.修改任务处理人:
public void changeAssignee(String oldName, String newName) {
Task task = findTask(oldName);
if (task == null) {
return;
}
taskService.setAssignee(task.getId(), newName); //执行人设为新用户
}
6.处理个人任务:
(1)传统方式:
public void complete(String username) {
Task task = findTask(username);
if (task == null) {
return;
}
//1.1 完成任务
//taskService.complete(task.getId());
//1.2 完成任务,第2个参数map为流程变量,控制流程走向
Map<String, Object> map = new HashMap<>();
map.put("day", 4); //传入day值,控制流程走向
taskService.complete(task.getId(), map);
}
(2)Activiti7新API方式:
public void completeBy7(String username) {
securityUtil.logInAs("yyh0"); //认证用户信息,用户名要和权限组中的一样
List<Task> tasks = findTaskBy7(username);
if (tasks == null || tasks.isEmpty()) {
return;
}
for (Task task : tasks) {
Map<String, Object> map = new HashMap<>();
map.put("day", 4); //传入day值,控制流程走向
taskRuntime.complete(TaskPayloadBuilder.complete().withVariables(map).withTaskId(task.getId()).build());
}
}
八、设置处理任务的角色:
1.固定方式,打开bpmn流程图,点击每个任务节点,在左侧Assignee栏输入固定的用户名。
2.UEL表达式方式:
说明:打开bpmn流程图 -> 点击每个任务节点左侧Assignee栏,将写死的用户名替换为UEL表达式。
(1)UEL-value表达式:
定义流程变量:
格式:${自定义键名}
例:${key1}
流程实例启动时传入参数:
Map<String, Object> map = new HashMap<>();
map.put("key1", "yyh0"); //将bpmn流程图左侧Assignee栏的${key1},设值为yyh1
runtimeService.startProcessInstanceByKey("leave", map);
(2)UEL-method表达式:
格式:${javaBean对象名.变量名},此javaBean对象名必须是spring容器中的一个bean,实现Serializable,生成serialVersionUID。
例:${user.name}
流程实例启动时传入参数:
Map<String, Object> map = new HashMap<>();
User user = new User();
user.setName("yyh0");
map.put("user", user); //将bpmn流程图左侧Assignee栏的${user.name},设值为yyh0
runtimeService.startProcessInstanceByKey("leave", map);
3.监听器方式:
(1)实现TaskListener接口,设置Assignee栏的值:
public class TaskLinstener1 implements org.activiti.engine.delegate.TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setAssignee("yyh0"); //设置Assignee栏的值
}
}
(2)打开bpmn流程图,点击每个任务节点,在左侧Task Listeners -> 新增一个Listener:
Event=Create
Type=Class
Class=选择TaskLinstener1类文件
九、增加条件控制流程走向:
globa变量:作用域为整个流程实例
local变量:作用域为当前任务节点或者当前连接线
1.打开bpmn流程图 -> 选中2个任务节点之间的线 -> 在左侧Condition栏,输入UEL表达式控制流程走向,如图:
(1)选中指向总经理的线,输入:
${day>3}
(2)选中指向结束的线,输入:
${day<=3}
2.传入参数值,赋值给上一步的day,控制流程走向:
(1)启动流程实例时传入参数:
Map<String, Object> map = new HashMap<>();
map.put("day", 3);
runtimeService.startProcessInstanceByKey("leave", map);
(2)在任务完成时传入参数:
taskService.complete(taskId, map);
(3)通过流程实例ID传入参数(流程实例未结束为前提):
runtimeService.setVariables(流程实例ID, map);
(4)通过当前节点的任务ID传入参数(当前节点任务未结束为前提):
taskService.setVariables("任务ID", map);
taskService.setVariablesLocal("任务ID", map); //local作用域
十、SecurityUtil和MySecurityConfig配置类:
1.SecurityUtil(用于认证登录):
官方例子:
https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-process-example/src/main/java/org/activiti/examples/SecurityUtil.java
代码:
@Component
public class SecurityUtil { //此类直接下载
@Autowired
private UserDetailsService userDetailsService;
public void logInAs(String username) {
UserDetails details = userDetailsService.loadUserByUsername(username);
if (details == null) {
return;
}
SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return details.getAuthorities();
}
@Override
public Object getCredentials() {
return details.getPassword();
}
@Override
public Object getDetails() {
return details;
}
@Override
public Object getPrincipal() {
return details;
}
@Override
public boolean isAuthenticated() {
return true;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
@Override
public String getName() {
return details.getUsername();
}
}));
org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
}
}
2.MySecurityConfig(配置权限组):
官方例子:
https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-process-example/src/main/java/org/activiti/examples/DemoApplicationConfiguration.java
代码:
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Autowired
public void configure(AuthenticationManagerBuilder am) throws Exception {
am.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity hs) throws Exception {
hs.csrf().disable().authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}
@Bean
public UserDetailsService userDetailsService() {//自定义方法
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//权限组,用于认证登录
String[][] roles = {
{"yyh0", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"yyh1", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"yyh2", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"yyh3", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"yyh4", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"yyh5", "123456", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"other", "123456", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"admin", "123456", "ROLE_ACTIVITI_ADMIN"},
};
for (String[] role : roles) {
List<String> list = Arrays.asList(Arrays.copyOfRange(role, 2, role.length));
manager.createUser(new User(role[0], passwordEncoder().encode(role[1]),
list.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
}
return manager;
}
@Bean
public PasswordEncoder passwordEncoder() {//自定义方法
return new BCryptPasswordEncoder();
}
}