- 一、相关概念
- 二、使用步骤
- 三、activiti配置生成表
- 四、简单画一个流程
- 五、activiti接口基础api示例
- 1. 代码创建流程
- 2. 查询流程的定义
- 3. 删除流程
- 4. 获取流程里面的资源文件
- 5. 创建流程实例
- 6. 查看流程底下存在哪些实例
- 7. 根据流程和负责人查询任务
- 8. 处理流程
- 9. 流程历史信息查看
- 10. 单个流程全部实例挂起
- 11. 单个实例挂起和激活
- 六、activiti接口进阶(变量启动)
- 七、activiti网关
- 1. 排他网关
- 2. 并行网关
- 3. 包含网关
- 八、组任务流程
-
工作流概念
工作流是指业务过程的部分或整体在计算机应用环境下的自动化。是对工作流程及其各操作步骤之间业务规则的抽象、概括描述。
-
activiti介绍
activiti是一个工作流引擎,可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN进行定义,业务流程按照预先定义的流程进行执行。实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作流量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
-
BPM(Business Process Management)
业务流程管理
-
BPM软件
通过BPM软件可以实现对业务流程的整个生命周期进行建模、自动化、管理监控和优化。
-
BPMN(Business Process MOdel And Notation 业务流程模型和符号)
业务流程模型和符号,是由BPMI开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。
-
ProcessDefinition:流程定义
-
ProcessInstance:流程实例
-
部署activiti
activiti是一个工作流程,业务系统访问activiti的接口,就可以方便操作流程间的相关数据,这样就可以把工作流环境和业务系统的环境集成在一起。
-
流程定义
使用activiti建模工具定义业务流程,生成.bpmn文件(通过xml定义业务流程)。
-
流程定义部署
使用activiti提供的api把流程定义的内容存储起来,再Activiti执行过中可以查询的内容。activiti执行把流程定内容存储在数据库中。
-
启动一个流程实例
流程实例也叫ProcessInstance
启动一个流程实例表示开始一次业务流程的执行
比如同一个流程,张三可以启动,李四也可以启动,但是两次流程实例是互不影响的。
-
用户查询待办任务
由于集成了activiti,所有的任务都可以直接通过activiti提供的api进行查询,不需要我们自己查库。
-
用户办理任务
流程办理等操作,也是可以直接使用activiti提供的api即可。
-
流程结束
当没有下一个需要办理的节点,说明任务就结束了。
-
idea软件安装actiBPM插件,若idea找不到插件,可去官网下载,导入即可,可参考
https://blog.csdn.net/weixin_40496191/article/details/125097860
-
pom配置
org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web org.activiti activiti-spring-boot-starter 7.1.0.M1 org.activiti activiti-json-converter 7.1.0.M1 org.activiti activiti-image-generator 7.1.0.M1 org.apache.xmlgraphics batik-all 1.10 mysql mysql-connector-java runtime com.alibaba druid 1.2.2 -
配置文件配置activiti相关参数和数据库信息
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: tiger url: jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf-8 &allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8&nullCatalogMeansCurrent=true type: com.alibaba.druid.pool.DruidDataSource servlet: multipart: max-file-size: 100MB max-request-size: 100MB activiti: # false:默认,数据库表不变,但是如果版本不对或者缺失表会抛出异常(生产使用) # true:表不存在,自动创建(开发使用) # create_drop: 启动时创建,关闭时删除表(测试使用) # drop_create: 启动时删除表,在创建表 (不需要手动关闭引擎) database-schema-update: true #监测历史表是否存在,activities7默认不开启历史表 db-history-used: true #none:不保存任何历史数据,流程中这是最高效的 #activity:只保存流程实例和流程行为 #audit:除了activity,还保存全部的流程任务以及其属性,audit为history默认值 #full:除了audit、还保存其他全部流程相关的细节数据,包括一些流程参数 history-level: full #校验流程文件,默认校验resources下的process 文件夹的流程文件 check-process-definitions: true
-
启动项目,即可生成相关表
-
相关表关系
re表:流程定义和流程相关属性
ru表:运行时产生的数据
hi表:历史信息
ge表:通用信息
-
画图
创建demo.bpmn文件,简单实现流程图,id为“myLeave“,流程名称为“员工请假审批流程”,责任人从上往下依次为worker、manager、financer。
-
画完图后,可以将流程图导出来
1)复制一份新的文件,以xml结尾
2)右键显示图标
3)导出png图片
@RequestMapping("createProcesses") @ResponseBody public void createProcesses() { //使用获取RepositoryService进行部署 Deployment deployment = repositoryService.createDeployment() .addClasspathResource("static/bpmn/demo.bpmn")//添加bpmn资源 .addClasspathResource("static/bpmn/demo.png")//添加png资源 .name("员工请假审批流程") .deploy();//部署流程 //输出流程部署的信息 System.out.println("流程部署id:" + deployment.getId()); System.out.println("流程部署名称:" + deployment.getName()); } //结果: //流程部署id:1fb480d0-0773-11ed-8dc3-005056c00001 //流程部署名称:员工请假审批流程
执行后可以去数据库查看相关的表数据
act_re_deployment:查看相关的流程的创建
act_re_procdef:查看流程的定义
act_ge_bytearray:流程文件存储
2. 查询流程的定义@RequestMapping("searchProcess") @ResponseBody public void searchProcess() { String key= "myLeave"; //获取ProcessDefinitionQuery对象,用来查询操作 ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); List3. 删除流程list = processDefinitionQuery.processDefinitionKey(key) .orderByProcessDefinitionVersion()//安装版本信息 .desc()//倒序 .list(); //输出流程定义的信息 for (ProcessDefinition processDefinition : list) { System.out.println("流程定义的id:" + processDefinition.getId()); System.out.println("流程定义的name:" + processDefinition.getName()); System.out.println("流程定义的key:" + processDefinition.getKey()); System.out.println("流程定义的version:" + processDefinition.getVersion()); System.out.println("流程部署的id:" + processDefinition.getDeploymentId()); System.out.println("--------------------------------------------------"); } } //结果 //相同key,则自动提升版本 //流程定义的id:myLeave:2:f6844e42-0774-11ed-96b3-005056c00001 //流程定义的name:员工请假审批流程 //流程定义的key:myLeave //流程定义的version:2 //流程部署的id:f66888df-0774-11ed-96b3-005056c00001 //-------------------------------------------------- //流程定义的id:myLeave:1:fc307169-0773-11ed-9b87-005056c00001 //流程定义的name:员工请假审批流程 //流程定义的key:myLeave //流程定义的version:1 //流程部署的id:fc1a2a46-0773-11ed-9b87-005056c00001 //--------------------------------------------------
@RequestMapping("deleteProcess") @ResponseBody public void deleteProcess() { String id = "f66888df-0774-11ed-96b3-005056c00001"; //设置true,择优级联删除的效果。false如果已有实例,则会删除错误 repositoryService.deleteDeployment(id, true); }4. 获取流程里面的资源文件
@RequestMapping("searchProcesslFile") @ResponseBody public void searchProcesslFile() throws IOException { String id = "myLeave"; //查询器 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey(id) .singleResult(); //获取流程部署id String deploymentId = processDefinition.getDeploymentId(); //通过repositoryService对象的相关方法来获取图片信息和bpmn信息 //png图片 InputStream pngInput = repositoryService .getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName()); //bpmn 文件的流 InputStream bpmnInput = repositoryService .getResourceAsStream(deploymentId, processDefinition.getResourceName()); //文件的保存 File filePng = new File("E:/学习/activiti/evection.png"); File fileBpmn = new File("E:/学习/activiti/evection.bpmn"); OutputStream pngOut = new FileOutputStream(filePng); OutputStream bpmnOut = new FileOutputStream(fileBpmn); //输入流和输出流的转化 IOUtils.copy(pngInput, pngOut); IOUtils.copy(bpmnInput, bpmnOut); pngInput.close(); pngOut.close(); bpmnInput.close(); bpmnOut.close(); }5. 创建流程实例
@RequestMapping("addApplication") @ResponseBody public void addApplication() { String businessKey = "myLeave"; //启动一个流程实例 //ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(businessKey); //启动一个流程实例,设置业务id,长度最大255,通过processInstance.getBusinessKey()获取 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(businessKey,"businessKey"); System.out.println("流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例id:" + processInstance.getId()); System.out.println("当前活动id:" + processInstance.getActivityId()); } //结果: //流程定义id:myLeave:2:a07c05cb-076f-11ed-afa5-005056c00001 //流程实例id:f6f5bf5a-0772-11ed-8dc3-005056c00001 //当前活动id:null6. 查看流程底下存在哪些实例
@RequestMapping("searchProcessRunInstance") @ResponseBody public void searchProcessRunInstance() { String key = "myLeave"; List7. 根据流程和负责人查询任务list = runtimeService.createProcessInstanceQuery().processDefinitionKey(key).list(); //输出流程定义的信息 for (ProcessInstance processInstance : list) { System.out.println("流程实例id:" + processInstance.getProcessInstanceId()); System.out.println("所属流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("是否完成:" + processInstance.isEnded()); System.out.println("是否暂停:" + processInstance.isSuspended()); System.out.println("当前活动标识:" + processInstance.getActivityId()); System.out.println("业务关键字:" + processInstance.getBusinessKey()); System.out.println("------------" ); } } //结果: //流程实例id:906f1e33-0776-11ed-96b3-005056c00001 //所属流程定义id:myLeave:2:f6844e42-0774-11ed-96b3-005056c00001 //是否完成:false //是否暂停:false //当前活动标识:null //业务关键字:null //------------ //流程实例id:fece74da-0773-11ed-9b87-005056c00001 //所属流程定义id:myLeave:1:fc307169-0773-11ed-9b87-005056c00001 //是否完成:false //是否暂停:false //当前活动标识:null //业务关键字:null //------------
@RequestMapping("searchTask") @ResponseBody public void searchTask() { String assignee = "worker"; String key = "myLeave"; //根据流程的key和任务负责人 查询任务 List8. 处理流程list = taskService.createTaskQuery() .processDefinitionKey(key) .taskAssignee(assignee) .list(); //输出当前用户具有的任务 for (Task task : list) { System.out.println("流程实例id:" + task.getProcessDefinitionId()); System.out.println("任务id:" + task.getId()); System.out.println("任务负责人:" + task.getAssignee()); System.out.println("任务名称:" + task.getName()); } } //结果: //流程实例id:myLeave:2:a07c05cb-076f-11ed-afa5-005056c00001 //任务id:2da138fa-0772-11ed-a901-005056c00001 //任务负责人:worker //任务名称:创建请假流程
@RequestMapping("solveTask") @ResponseBody public void solveTask() { String assignee = "worker"; String key = "myLeave"; //根据流程的key和任务负责人 查询任务 Task task = taskService.createTaskQuery() .processDefinitionKey(key) .taskAssignee(assignee) .singleResult(); //完成任务,跳到下一个流程 taskService.complete(task.getId()); }
act_ru_execution:任务操作
act_ru_task:目前需要处理的任务表
9. 流程历史信息查看@RequestMapping("searchHistoricActivityInstance") @ResponseBody public void searchHistoricActivityInstance() { String id = "myLeave:1:fc307169-0773-11ed-9b87-005056c00001"; //获取actinst表的查询对象 HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService.createHistoricActivityInstanceQuery(); //查询actinst表,根据流程id查询 HistoricActivityInstanceQuery historicActivityInstanceQuery1 = historicActivityInstanceQuery.processDefinitionId(id); //查询actinst表,根据流程实例id查询 //historicActivityInstanceQuery.processInstanceId(流程实例id); //排序 historicActivityInstanceQuery1.orderByHistoricActivityInstanceStartTime().desc(); //获取查询结果 List10. 单个流程全部实例挂起list = historicActivityInstanceQuery1.list(); //输出查询结果 for (HistoricActivityInstance hi : list) { System.out.println(hi.getActivityId()); System.out.println(hi.getActivityName()); System.out.println(hi.getActivityType()); System.out.println(hi.getAssignee()); System.out.println(hi.getProcessDefinitionId()); System.out.println(hi.getProcessInstanceId()); } } //结果: //_4 //部门经理审批 //userTask //manager //myLeave:1:fc307169-0773-11ed-9b87-005056c00001 //fece74da-0773-11ed-9b87-005056c00001 //_3 //创建请假流程 //userTask //worker //myLeave:1:fc307169-0773-11ed-9b87-005056c00001 //fece74da-0773-11ed-9b87-005056c00001 //_2 //StartEvent //startEvent //null //myLeave:1:fc307169-0773-11ed-9b87-005056c00001 //fece74da-0773-11ed-9b87-005056c00001
@RequestMapping("allLockOrOpenPeocess") @ResponseBody public void allLockOrOpenPeocess() { String id = "myLeave"; ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey(id) .singleResult(); //获取流程的状态 boolean suspended = processDefinition.isSuspended(); String pdId = processDefinition.getId(); //如果激活就挂起,挂起就激活 if (suspended) { //表示当前是挂起的,需要激活。 //参数:流程定义id、是否激活、激活时间 repositoryService.activateProcessDefinitionById(pdId, true, null); System.out.println("流程定义:" + pdId + ",已激活"); } else { //激活状态,则需要挂起 //参数:流程定义id、是否挂起、挂起时间 repositoryService.suspendProcessDefinitionById(pdId, true, null); System.out.println("流程定义:" + pdId + ",已挂起"); } }11. 单个实例挂起和激活
@RequestMapping("singleLockOrOpenPeocess") @ResponseBody public void singleLockOrOpenPeocess() { //task表的proc_inst_id String id = "dc609b81-082e-11ed-a65d-005056c00001"; //获取实例对象 ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(id) .singleResult(); //获取状态 boolean suspended = processInstance.isSuspended(); String pdId = processInstance.getId(); if (suspended) { //表示当前是挂起的,需要激活 runtimeService.activateProcessInstanceById(pdId); System.out.println("流程定义:" + pdId + ",已激活"); } else { //激活状态,则需要挂起 runtimeService.suspendProcessInstanceById(pdId); System.out.println("流程定义:" + pdId + ",已挂起"); } }
-
流程变量
Global变量:这个是流程变量的默认作用域,表示是一个完整的流程实例。Global变量名不能重复,如果重复则会被后面的覆盖。
Local变量:Local变量只针对一个任务或者一个执行实例,变量名可相同,互不影响。
ps1:如果设置了流程变量,就必须复制,否则将会报错导致流程结束。
ps2:如果连线分支不设置条件,默认走sequenceFlow id(可在xml查看)小的那条线。
-
画分支图,并且设置流程审批人变量,依次设置为 a s s i g n e e 0 、 {assignee0}、 assignee0、{assignee1}、 a s s i g n e e 2 、 {assignee2}、 assignee2、{assignee3}
-
启动流程,并且创建实例(带变量)
@RequestMapping("addApplication") @ResponseBody public void addApplication() { Map
variables=new HashMap<>(); Map evention=new HashMap<>(); evention.put("num",2); variables.put("evention",evention); variables.put("assignee0","申请人"); variables.put("assignee1","部门经理"); variables.put("assignee2","总经理"); variables.put("assignee3","财务人事"); String key = "myLeave1"; ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,variables); } -
依次调用solveTask方法,可以发现最终走的分支是num小于3的分支,即不经过总经理审批。
-
也可以在每次执行时,重新设置本地变量
ps:这里需要留意,7.1.0.M1版本的activiti无法直接在complete时覆盖全局和本地变量,只能通过设置本地变量覆盖。
@RequestMapping("addApplication") @ResponseBody public void addApplication() { Map
variables=new HashMap<>(); Map evention=new HashMap<>(); evention.put("num",2); variables.put("evention",evention); variables.put("assignee0","申请人"); variables.put("assignee1","部门经理"); variables.put("assignee2","总经理"); variables.put("assignee3","财务人事"); String key = "myLeave1"; ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,variables); } @RequestMapping("solveTask") @ResponseBody public void solveTask() { String assignee = "申请人"; String id = "myLeave1"; //根据流程的key和任务负责人 查询任务 Task task = taskService.createTaskQuery() .processDefinitionKey(id) .taskAssignee(assignee) .singleResult(); //完成任务,跳到下一个流程 Map
variables=new HashMap<>(); Map evention=new HashMap<>(); evention.put("num",2); variables.put("evention",evention); variables.put("assignee0","申请人1"); variables.put("assignee1","部门经理1"); variables.put("assignee2","总经理1"); variables.put("assignee3","财务人事1"); taskService.setVariablesLocal(task.getId(),variables); taskService.complete(task.getId()); } @RequestMapping("solveTaskForVariables") @ResponseBody public void solveTaskForVariables() { String assignee = "部门经理1"; String id = "myLeave1"; //根据流程的key和任务负责人 查询任务 Task task = taskService.createTaskQuery() .processDefinitionKey(id) .taskAssignee(assignee) .singleResult(); //完成任务,跳到下一个流程 Map
variables=new HashMap<>(); Map evention=new HashMap<>(); evention.put("num",2); variables.put("evention",evention); variables.put("assignee0","申请人2"); variables.put("assignee1","部门经理2"); variables.put("assignee2","总经理2"); variables.put("assignee3","财务人事2"); taskService.setVariablesLocal(task.getId(),variables); taskService.complete(task.getId()); } 然后查询当前任务
@RequestMapping("searchTaskByKey") @ResponseBody public void searchTaskByKey() { String key = "myLeave1"; //根据流程的key和任务负责人 查询任务 List
tasks = taskService.createTaskQuery() .processDefinitionKey(key) .list(); for (Task task : tasks) { System.out.println(task.getId()); System.out.println(task.getName()); System.out.println(task.getAssignee()); System.out.println("--------------"); } } //结果 //3c18d52e-0841-11ed-b1d5-005056c00001 //财务部审批 //财务人事2 //--------------
-
概念:用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断是否为true,如果为ture则执行。如果多个为true,则执行id值小的。如果没 有位true的,则抛出异常
-
流程图
-
概念:并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起。并行网关的线上面,写上条件也没有用,在并行网关的后面分支都要执行完成之后,才会走下一个任务,不然,只要有一个分支没有完成,后面的任务就不会走
-
流程图
-
概念:包含网关可以看做是排他网关和并行网关的结合体。和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们,不定义则默认放行。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。这也是用的最多的网关。
-
流程图
-
概念
即任务的下一个处理人不再是一个具体的人员,而是一组候选人。候选人可以主动拾取任务,然后办理。
-
流程图,即再流程的第二步,经理或总经理需要拾取任务才能进行处理
-
代码实现
由于activiti需要整合Security框架,所以需要做好权限配置,这里是简单做了个lisi用户的配置
package activiti.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.ComponentScan; 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 @Qualifier("myUserDetailsService") 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); } }
package activiti.config; 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; @Configuration public class SpringSecurityConfiguration { private Logger logger = LoggerFactory.getLogger(SpringSecurityConfiguration.class); @Bean public UserDetailsService myUserDetailsService() { InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); //这里添加用户,后面处理流程时用到的任务负责人,需要添加在这里 String[][] usersGroupsAndRoles = { {"lisi", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"}, {"system", "password", "ROLE_ACTIVITI_USER"}, {"admin", "password", "ROLE_ACTIVITI_ADMIN"}, }; for (String[] user : usersGroupsAndRoles) { List
authoritiesStrings = Arrays.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(); } } 查找并且拾取任务
@RequestMapping("claimTask") @ResponseBody public void claimTask() { String taskCandidateUser = "lisi"; String id = "myLeave5"; //根据流程的key和任务负责人 查询任务 Task task = taskService.createTaskQuery() .processDefinitionKey(id) .taskCandidateUser(taskCandidateUser) .singleResult(); if(task!=null){ taskService.claim(task.getId(),taskCandidateUser); } }
lisi处理任务
@RequestMapping("solveTask") @ResponseBody public void solveTask() { String assignee = "lisi"; String id = "myLeave5"; //根据流程的key和任务负责人 查询任务 Task task = taskService.createTaskQuery() .processDefinitionKey(id) .taskAssignee(assignee) .singleResult(); //完成任务,跳到下一个流程 taskService.complete(task.getId()); }