Springboot整合Activiti详解
作者:mmseoamin日期:2023-12-14

文章目录

  • 版本依赖
  • 配置文件
  • 需要注意的问题
  • 画流程图
  • activiti服务类进行编写
    • 流程部署
    • 流程定义
    • 启动流程
    • 流程实例
    • 测试流程
      • 启动流程
      • 完成任务
      • 受理任务

        版本依赖

        • 开发工具 IDEA
        • SpringBoot 2.4.5(这里我试过SpringBoot 3.1.1版本,Activiti没有启动,应该是依赖冲突了,后改成了2.4.5版本)
        • Activiti 7.1.0.M6

          父项目pom.xml

              
                  
                      
                      
                          org.springframework.boot
                          spring-boot-starter-parent
                          2.4.5
                          pom
                          import
                      
                      
                      
                          org.activiti.dependencies
                          activiti-dependencies
                          7.1.0.M6
                          pom
                          import
                      
                  
              
          

          子项目pom.xml

              
                  
                      org.springframework.boot
                      spring-boot-starter-web
                  
                  
                      org.springframework.boot
                      spring-boot-starter-test
                      test
                  
                  
                      org.activiti
                      activiti-spring-boot-starter
                  
                  
                      mysql
                      mysql-connector-java
                  
                  
                      org.springframework.boot
                      spring-boot-starter-jdbc
                  
              
          

          配置文件

          spring:
            datasource:
              driver-class-name: com.mysql.cj.jdbc.Driver
              type: com.zaxxer.hikari.HikariDataSource
              username: root
              password: 111111
              url: jdbc:mysql://127.0.0.1:3306/activiti?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&useInformationSchema=true
              hikari:
                #连接池做大连接数
                maximum-pool-size: 30
                #连接池空闲连接最小数量
                #minimum-idle: 10
                #允许连接在连接池中闲置最长时间
                #idle-timeout: 30000
                #池中连接最长生命周期
                max-lifetime: 120000
                #等待来自池的连接的最大毫秒数
                connection-timeout: 30000
            activiti:
              #自动更新数据库结构
              #1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
              #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
              #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
              #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
              database-schema-update: true
              #activiti7默认不生成历史信息表,开启历史表
              db-history-used: true
              #记录历史等级 可配置的历史级别有none, activity, audit, full
              #none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
              #activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
              #audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
              #full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
              history-level: full
              #自动检查、部署流程定义文件
              check-process-definitions: false
              # asyncExecutorActivate是指activiti在流程引擎启动就激活AsyncExecutor,异步:true-开启(默认)、false-关闭
              async-executor-activate: true
          

          配置文件这里注意要设置 database-sechema-update的属性为true,才会自动创建表。

          需要注意的问题

          在初次启动时可能会报错,报错如下:

          Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.core.userdetails.UserDetailsService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
          	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790)
          	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346)
          	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
          	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
          	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
          	... 142 more
          

          这里是需要一个UserDetailsService的Bean,需要创建一个类去实现UserDetailsService接口

          UserDetailsServiceImpl.class

          package org.example.config;
          import org.example.service.UserService;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.security.core.userdetails.UserDetails;
          import org.springframework.security.core.userdetails.UserDetailsService;
          import org.springframework.security.core.userdetails.UsernameNotFoundException;
          import org.springframework.stereotype.Component;
          @Component
          public class UserDetailsServiceImpl implements UserDetailsService {
              @Autowired
              UserService userService;
              @Override
              public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                  return userService.findOneUserByName(username);
              }
          }
          

          UserService.class

          package org.example.service;
          import org.springframework.security.core.GrantedAuthority;
          import org.springframework.security.core.authority.AuthorityUtils;
          import org.springframework.security.core.userdetails.User;
          import org.springframework.stereotype.Service;
          import java.util.List;
          @Service
          public class UserService {
              public User findOneUserByName(String username){
                  List authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
                  return new User(username,"",authorities);
              }
          }
          

          启动成功后,数据库会生成25张表

          Springboot整合Activiti详解,在这里插入图片描述,第1张

          画流程图

          在使用IDEA时,需要下载一个插件

          Springboot整合Activiti详解,在这里插入图片描述,第2张

          当这个插件安装好以后,新建文件会多一个选项

          Springboot整合Activiti详解,在这里插入图片描述,第3张

          新建的xml文件可以通过view BPMN Diagram,转化为图表操作

          Springboot整合Activiti详解,在这里插入图片描述,第4张

          Springboot整合Activiti详解,在这里插入图片描述,第5张

          如何通过图表操作画流程这里就不说了,可以自己摸索看看。

          activiti服务类进行编写

          package org.example.service;
          import org.activiti.engine.*;
          import org.activiti.engine.history.HistoricProcessInstance;
          import org.activiti.engine.repository.Deployment;
          import org.activiti.engine.repository.DeploymentQuery;
          import org.activiti.engine.repository.ProcessDefinition;
          import org.activiti.engine.repository.ProcessDefinitionQuery;
          import org.activiti.engine.runtime.ProcessInstance;
          import org.activiti.engine.task.Task;
          import org.apache.commons.io.IOUtils;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Service;
          import java.io.IOException;
          import java.io.InputStream;
          import java.nio.charset.StandardCharsets;
          import java.util.List;
          import java.util.Map;
          @Service
          public class ActivitiService {
              @Autowired
              RepositoryService repositoryService;
              @Autowired
              RuntimeService runtimeService;
              @Autowired
              TaskService taskService;
              @Autowired
              HistoryService historyService;
              /**
               * 部署流程服务
               */
              public void deployProcess(){
                  String file = "bpmn/test.bpmn20.xml";
                  Deployment deployment = repositoryService.createDeployment()
                          .addClasspathResource(file)
                          .name("流程部署测试")
                          .deploy();
                  System.out.println("流程部署名称:"+deployment.getName());
              }
              /**
               * 查询所有部署的流程定义
               * @return
               */
              public List getAllProcessDefinition(){
                  ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
                  return query.orderByProcessDefinitionVersion().desc().list();
              }
              /**
               * 查询所有的部署
               * @return
               */
              public List getAllDeployment(){
                  DeploymentQuery query = repositoryService.createDeploymentQuery();
                  return query.list();
              }
              /**
               * 查询所有流程实例
               * @return
               */
              public List getAllProcessInstance(){
                  List list = runtimeService.createProcessInstanceQuery().list();
                  return list;
              }
              public List getAllProcessTask(){
                  List list = taskService.createTaskQuery().list();
                  return list;
              }
              /**
               * 查询用户待完成和待认领的任务
               * @param username
               * @return
               */
              public List getAllProcessTaskByCandidateOrAssigned(String username){
                  List list = taskService.createTaskQuery().taskCandidateOrAssigned(username).list();
                  return list;
              }
              public List getAllProcessTaskByCandidate(String username){
                  List list = taskService.createTaskQuery().taskCandidateUser(username).list();
                  return list;
              }
              /**
               * 查询用户的待完成任务
               * @param username
               * @return
               */
              public List getAllProcessTaskByAssigned(String username){
                  List list = taskService.createTaskQuery().taskAssignee(username).list();
                  return list;
              }
              public void complateTask(Task task, Map map){
                  taskService.complete(task.getId(),map);
              }
              public void complateTask(Task task){
                  taskService.complete(task.getId());
              }
              public void claimTask(Task task,String username){
                  taskService.claim(task.getId(),username);
              }
              public ProcessInstance startProcess(String processDefinitionKey,String businessKey,Map map){
                  return runtimeService.startProcessInstanceById(processDefinitionKey,businessKey,map);
              }
              public String getProcessDefinitionXml(String processDefinitionId) {
                  ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);
                  String resourceName = processDefinition.getResourceName();
                  InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
                  try {
                      return IOUtils.toString(resourceAsStream, StandardCharsets.UTF_8);
                  } catch (IOException e) {
                      // handle exception
                  }
                  return null;
              }
              public List getHistoryByBusinessKey(String businessKey){
                  List list = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).list();
                  return list;
              }
          }
          

          Springboot整合Activiti详解,在这里插入图片描述,第6张

          上图是,如果需要使用activiti中提供的服务类,直接注入即可。即springboot整合activiti启动过程中,已经在容器中自动装配了对应的服务类,需要的时候,仅仅需要取来用即可。如果启动以后报错,容器没有自动装配对应的服务类,那很有可能是依赖冲突,activiti没有正常启动。

          流程部署

          流程部署有多种方式,这是通过资源路径部署的,也可以通过压缩包的形式部署流程。那就需要用到DeploymentBuilder的addZipInputStream方法,这里也是用到了设计模式中的建造者模式。

          Springboot整合Activiti详解,在这里插入图片描述,第7张

          Springboot整合Activiti详解,在这里插入图片描述,第8张

          流程定义

          这是一个查询所有部署的流程定义的方法

          Springboot整合Activiti详解,在这里插入图片描述,第9张

          当一个流程部署以后,就会生成一个流程定义,流程图的xml信息也会存入数据库。每次部署流程图都会生成一个新的流程定义。

          Springboot整合Activiti详解,在这里插入图片描述,第10张

          启动流程

          启动流程可以通过流程定义的id或者key,如果想通过流程定义的id启动一个流程可以使用startProcessInstanceById方法,如果想通过流程定义的key启动一个流程可以使用startProcessInstanceByKey方法。Springboot整合Activiti详解,在这里插入图片描述,第11张

          这里要明白流程定义id和key的区别,流程定义的key是在画流程的时候可以设置的,通过设置流程的id,这个就是流程的key。

          Springboot整合Activiti详解,在这里插入图片描述,第12张

          Springboot整合Activiti详解,在这里插入图片描述,第13张

          那流程的id呢,这是部署以后在数据库表的id。可以根据需要自行选择启动流程的方法。

          流程实例

          查询所有启动的流程实例

          Springboot整合Activiti详解,在这里插入图片描述,第14张

          每次开始一个流程以后,就会生成一个流程实例。通过这个方法可以查看所有的流程实例。

          测试流程

          package org.example;
          import org.activiti.engine.history.HistoricProcessInstance;
          import org.activiti.engine.repository.Deployment;
          import org.activiti.engine.repository.ProcessDefinition;
          import org.activiti.engine.runtime.ProcessInstance;
          import org.activiti.engine.task.Task;
          import org.assertj.core.util.DateUtil;
          import org.example.common.PrintUtil;
          import org.example.entity.User;
          import org.example.service.ActivitiService;
          import org.junit.jupiter.api.Test;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.test.context.SpringBootTest;
          import javax.annotation.Resource;
          import javax.sql.DataSource;
          import java.sql.SQLException;
          import java.util.*;
          @SpringBootTest
          public class SpringBootApplicationTest {
              @Resource
              DataSource dataSource;
              @Autowired
              ActivitiService activitiService;
              @Test
              void context() throws SQLException {
                  System.out.println(dataSource.getConnection());
              }
              @Test
              public void deployProcess(){
                  activitiService.deployProcess();
              }
              @Test
              public void TestStartProcess(){
                  ProcessDefinition processDefinition = activitiService.getAllProcessDefinition().get(0);
                  String bussinessKey = this.getClass().getSimpleName();
                  Map map = new HashMap();
                  map.put("creator","Echo");
                  activitiService.startProcess(processDefinition.getId(),bussinessKey,map);
              }
              @Test
              public void findAllDeployment(){
                  List list = activitiService.getAllDeployment();
                  PrintUtil.printTable(list);
              }
              @Test
              public void findAllProcessDefinition(){
                  List list = activitiService.getAllProcessDefinition();
                  PrintUtil.printTable(list);
              }
              @Test
              public void findAllProcessInstance(){
                  List list = activitiService.getAllProcessInstance();
                  PrintUtil.printTable(list);
              }
              @Test
              public void findAllProcessTask(){
                  List list = activitiService.getAllProcessTask();
                  PrintUtil.printTable(list);
              }
              @Test
              public void findTaskByUserId(){
                  String userId = "keaizp";
                  List list = activitiService.getAllProcessTaskByCandidateOrAssigned(userId);
                  PrintUtil.printTable(list);
              }
              @Test
              public void claimTask(){
                  String userId="keaizp";
                  List list = activitiService.getAllProcessTaskByCandidate(userId);
                  PrintUtil.printTable(list);
                  activitiService.claimTask(list.get(0),userId);
                  list = activitiService.getAllProcessTaskByCandidate("zengpei");
                  PrintUtil.printTable(list);
              }
              @Test
              public void complateUserTask(){
                  String userId = "Echo";
                  List list = activitiService.getAllProcessTaskByAssigned(userId);
                  List candidateUsers = new ArrayList();
                  candidateUsers.add("keaizp");
                  candidateUsers.add("zengpei");
                  Map map = new HashMap();
                  map.put("approver",candidateUsers);
                  activitiService.complateTask(list.get(0),map);
              }
              @Test
              public void processDefinitionXml(){
                  List list = activitiService.getAllProcessDefinition();
                  String xmlStr = activitiService.getProcessDefinitionXml(list.get(0).getId());
                  System.out.println(xmlStr);
              }
              @Test
              public void findHistoryByBusinessKey(){
                  List list = activitiService.getHistoryByBusinessKey("2023-07-02T22:45:45");
                  PrintUtil.printTable(list);
              }
              @Test
              public void printTest(){
                  List users = new ArrayList<>();
                  users.add(new User("keaizpeeeeeeeee",24,"male"));
                  users.add(new User("Echo",24,"male"));
                  users.add(new User("nick",24,"male"));
                  users.add(new User("amy",24,"male"));
                  PrintUtil.printTableFixed(users,16);
              }
          }
          

          启动流程

          Springboot整合Activiti详解,在这里插入图片描述,第15张

          这里我用的是一个格式化的日期做bussinessKey,实际业务中最好使用 流程作业类型 + 一个业务台账id,比如:请假申请要走审批流程, 离职申请也要走审批流程,如果只用id作为bussinessKey的话,bussinessKey就无法满足唯一性。所以最好 前面加上作业类型。这里的map传入的是流程图里面的参数。

          Springboot整合Activiti详解,在这里插入图片描述,第16张

          传入受理人,这里就会产生一个Task,就是用户的代办任务。

          完成任务

          Springboot整合Activiti详解,在这里插入图片描述,第17张

          当用户登录系统的时候,应该给用户提示,用户有未完成的代办事项,然后给出用户代办事项列表,做完一切以后,就是完成用户任务,这个时候,可以传入下一流程节点参与任务的人,当然你也可以传入几个候选人,candidate Users就是 候选人。可以传入一个用户id的列表,然后这几个人就会收到可以受理的任务,可以选择是否接受该任务。只要接受了任务,其他候选人就无法再接受此任务,同时这个任务的Assignee就会设置成受理人的id

          受理任务

          Springboot整合Activiti详解,在这里插入图片描述,第18张

          因为用户“keaizp”成为了候选者,他就可以看到自己可以受理的这个任务,用claim方法就可以受理该任务,当受理任务完成以后,再去看看另外一名候选者的受理任务,会发现已经没有待受理的任务了。