# activiti7-util **Repository Path**: chongyahhh/activiti7-util ## Basic Information - **Project Name**: activiti7-util - **Description**: activiti7封装 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 1 - **Created**: 2021-01-06 - **Last Updated**: 2024-07-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # activiti7-util #### 介绍 activiti7封装 # 什么是工作流以及工作流有什么用 先来贴张图: ![](https://img-blog.csdnimg.cn/20200408074413248.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) (该图来自百度百科,如有侵权,望及时告知) 是不是很抽象,不是很好理解?用简单的话说一下就是按照一定的规则绘制流程图,将这个流程图交给特定的引擎,由这个引擎来帮助我们推进流程的进行,而在这里我们这个引擎便是activiti。 此时你可能还没有理解工作流有什么用,因为第一时间想到的流程可能是请假或报销流程,并没有那么复杂,我们直接看张图,体会一下: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408075924870.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) (上图来自百度百科,如有侵权,望及时告知) 现在你们可以想想要如何实现这个业务流程。如果用常规的方法来实现的话,可能会有许多if和同种不同类的sql,在完成一个任务之前还得及时监控之前要完成的任务是否都已完成,流程控制的代码会隐式地存在于业务代码中,当有一个地方要修改,可能会连带起很多地方都要修改。而工作流的引用,在一定程度上可以将流程的推进和业务逻辑解耦,你在绘制流程图的时候设定你的流程和部分跳转条件,然后将其交给activiti管理,之后只需在代码中及时推进流程的进行就可以了。 # 如何画流程图 在画流程图之前,首先要安装插件,idea是actiBPM,eclipse我不知道,安装的方法可以到网上去找,这里就不说了。 接下来新建一个bpmn文件demo2.bpmn,我们来大体看一下结构: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513075808388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 我们首先来了解一下,一个.bpmn文件到底意味着什么。一个.bpmn文件在某种意义上可以看成一个流程定义(ProcessDefinition),而activiti对流程的控制完全是按照这个来走的。后面会有具体说明,这里先继续介绍怎么画流程图。 ## 开始与结束事件 开始事件: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513081045225.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 结束事件: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513081310581.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) ## 任务 **UserTask**:这个任务是交给用户完成的,需要在业务逻辑代码中手动推进任务进行 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513084252320.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 设置用户与用户组: 注意idea的actiBPM最后一次更新是在14年,所以现在的idea可能不是很支持这个插件,可能出现各种各样的问题,比如修改了用户组,但没有保存也不会显示,这里没关系,只要保证.xml文件没有问题就可以了,所以下面用户组的设置我会从xml这向大家展示,首先将后缀名改为.xml,然后找到相应的userTask,添加如图的内容就行了: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408083055757.png) **ServiceTask**:交给系统自动执行的任务,不需要我们手动推进 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408084027793.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 指定的类: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408083915707.png) 剩下的任务类型在这里就不讲了,需要的话可以百度一下 ## 网关 首先我们要知道两个任务之间的连线上是可以设定条件的: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513084945294.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 哪个条件满足了就往哪走,但是在不使用网关的情况下,要是满足了多个条件会怎么样你们可以自己试试,我印象中会报错,这个时候就要用网关了。 这里主要介绍三个网关,最后的事件网关用得少,就没必要讲了: 1、 互斥网关(ExclusiveGateway): ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408084317615.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 互斥网关的分支只能找一个符合条件的走,要是有多个分支符合条件,找id小的走。(但既然用了互斥网关,应该就会保证条件没有重合的部分了) 2、 并行网关(ParallelGateway): ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408084409837.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 简单来说就是到这会分叉,当这并行的多个任务都完成后才会继续推进。但要注意,并行网关不会识别条件,只要是并行网关的分支,都会执行。 3、 包容网关(InclusiveGateway): ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408084548617.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 可以看做是并行网关和互斥网关的结合体,满足条件的就走,当所有满足条件的任务都完成后,流程才会继续推进。 这里只是简单介绍一下,想要深入了解可以百度一下。 # 引入maven依赖以及设置配置文件 maven: ``` org.activiti activiti-engine 7.0.0.Beta2 ``` 创建activiti.cfg.xml: ``` ``` 运行下面的代码,也就是获取activiti的引擎后,activiti会自动帮我们创建25张表,而这25张表是activiti帮我们管理流程的基础: ``` @Test public void test1() { ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration .createProcessEngineConfigurationFromResource("activiti.cfg.xml"); ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine(); } ``` 这里简单介绍下比较重要的几张表: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408085929725.png) 这里不详细介绍了,要是想具体了解可以自行百度。 但要注意的是,不同版本的activiti创建表的数量和意义也不是完全相同的,就像activiti6是创建28张表,因为activiti6比7多了identityService,而springboot对activiti7的starter去除了用户表,强耦合了spring security,所以少了几张用户表。(当然别的表也不完全相同) # 流程的部署、查询和推进 ## 流程的部署 首先,我们可以通过JBoss jBPM来通过.xml文件导出个.png文件,在部署.bpmn文件的时候可以顺便也部署上.png文件,之后可以导出.png文件来看看流程图是什么样的,当然,这个.png文件不是必须的。 先将.bpmn文件后缀改为.xml,然后选中右键: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408092122479.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020040809220966.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 然后就可以导出png文件到指定文件夹了 我们来了解下activiti提供的几个接口: RepositoryService -> 操作流程定义、部署流程定义 RuntimeService -> 开启流程,操作流程实例 TaskService -> 操作任务 HistoryService -> 操作历史记录 当然是不止这几个的,但是知道这几个入门还是够的。 言归正传,回到部署: ``` ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); RepositoryService repositoryService = processEngine.getRepositoryService(); // 直接部署 Deployment deployment = repositoryService.createDeployment() .addClasspathResource("processes/xxx.bpmn") .addClasspathResource("processes/xxx.png") .deploy(); ``` 这个ProcessEngines.getDefaultProcessEngine(),是当你按照一定的命名规则编写配置文件的时候,会默认加载该文件。默认格式比着上面的activiti.cfg.xml写就行,文件名和beanId不要变就行。 文件放在: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408093227523.png) processes中,当然,也可以自己找存放的路径 也可以通过zip文件部署: ``` ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); RepositoryService repositoryService = processEngine.getRepositoryService(); // zip压缩文件部署 InputStream is = ActivitiDeployment.class.getClassLoader().getResourceAsStream("processes/xxx.zip"); ZipInputStream zipInputStream = new ZipInputStream(is); Deployment deployment = repositoryService.createDeployment() .addZipInputStream(zipInputStream) .deploy(); ``` 然后看数据库中的三张表: 1.act_ge_bytearray ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408093926362.png) 2.act_re_procdef (存放流程定义(ProcessDefinition)) ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020040809412366.png) 3.act_re_deployment ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408094337784.png) 这里讲的比较细,在之后对数据库的讲解会少一些。 到此,流程的部署就完成了。 ## 开始一个流程实例 首先来看几个关键词: ProcessDefinition是流程定义,可以看做为java中的类,是流程的抽象; 每开始一个流程,都会通过流程定义来创建一个流程实例(ProcessInstance),可以看成对象。 **一个流程定义对应一个key和一个id,可以通过他们来开启一个流程实例**,而key就是我们画图时设置的id(这里有点混乱),而id是部署之后才有的。 先来看看我用来展示的流程图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408101022646.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NzUzNDUx,size_16,color_FFFFFF,t_70) 现在我们来开启一个流程实例: ``` ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); RuntimeService runtimeService = processEngine.getRuntimeService(); Map map = new HashMap<>(); map.put("amount",60F); map.put("memberStart","xxx"); // 这个key是画流程图时设置的id ProcessInstance processInstance = runtimeService .startProcessInstanceByKey("tradeDemo","businessKey",map) //.startProcessInstanceById("tradeDemo:1:2503","businessKey",map) ; ``` 在这里我们看到,可以通过key和id来开启一个流程实例。在这里有必要讲一下这里的businessKey和map。 首先我们得了解,数据库的25张表仅仅是管理流程进行的,而我们的数据还是得存在我们自己的表中的。我们通常可以通过两种方式关联,一是将我们的数据作为流程变量存到这25张表中,也就是参数中的map,k-v就是对应一个个的流程变量,但不推荐这么做,流程变量最好只放推动流程必须的变量,比如线上的条件,还记得之前设定的el表达式吗(在画流程图时设定的用户、线上的条件之类的),${xxx}对应的数据就是我们添加的k-v键值对中的数据,k对应的表达式中的xxx。另一个方法就是通过businessKey,我们可以把我们某条数据的id绑定到一个流程实例上(ProcessInstance),简单来说就是个外键。 当然,流程变量也分全局和局部的,全局的流程变量的作用域是一个流程实例,而局部的流程变量的作用域是一个任务(task)。这里的map设置的是全局的。之后会说怎么设置局部的流程变量,虽然这个用的不多。 开启一个流程实例后,我们来看看数据库的几张表: 1、act_ru_task ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408100913964.png) 现在就出现了第一个任务,看到NAME_是HouseBuyerApply,也就是流程图开始事件后的第一个用户任务。 2、act_ru_variable ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408101225758.png) 这张表存了我们的流程变量,但注意,序列化的数据会被放在act_ge_bytearray中: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408101408925.png) 部分类型的数据和对象会被序列化后存入到这张表中。**要注意,要想把一个对象存入流程变量,需将其序列化,所以对应的类要实现序列化的接口。** 3、act_ru_execution ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408101732185.png) 这个execution和流程实例的关系大家可以去百度一下,这里就不详细解释了。 其他的表其实也会有变化,但这里也不详细一一解释了,可以在测试的时候看看具体是有哪些表发生了变化,看一下表名和字段名也能大概了解是怎么回事。 ## 各种查询 首先是查询流程定义: ``` ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); RepositoryService repositoryService = processEngine.getRepositoryService(); List definitions = repositoryService .createProcessDefinitionQuery() .processDefinitionKeys(new HashSet<>()) .processDefinitionIds(new HashSet<>()) .list(); ``` 查询流程实例: ``` ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); RuntimeService runtimeService = processEngine.getRuntimeService(); List instances = runtimeService .createProcessInstanceQuery() .processInstanceBusinessKey("businessKey") .processInstanceId("") .processDefinitionKey("") .list(); ``` 查询任务: ``` ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); TaskService taskService = processEngine.getTaskService(); List tasks = taskService.createTaskQuery() .processDefinitionKey("tradeDemo") .taskAssignee("xxx") .taskCandidateGroup("manager") .list(); ``` 查询历史信息: ``` ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); HistoryService historyService = processEngine.getHistoryService(); List instances = historyService.createHistoricTaskInstanceQuery() .processDefinitionKey("") .processDefinitionId("") .list(); ``` 到此就不再多举例了,大家应该也看出规律了,都是采用的调用链的方式设置条件查询,实际上可用的条件非常多,大家可以自己看一下。甚至可以通过businessKey来查询一个流程实例。 而返回的数据的属性我也就不展示了,大家可以自己看看,也是非常多的。 历史查询也不是只能查任务,还可以查很多别的不同的数据。 ## 完成任务 直接上代码: ``` ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); TaskService taskService = processEngine.getTaskService(); //完成任务 taskService.complete("taskId"); //在任务执行过程中添加全局流程变量 taskService.setVariables("taskId",new HashMap<>()); //在任务执行过程中添加局部流程变量 taskService.setVariablesLocal("taskId",new HashMap<>()); //绑定执行人 taskService.claim("taskId","用户的id或姓名"); ``` 要注意,activiti这几个service不会在完成任务时判断权限,你只要用了complete,不管你现在操作的用户是谁,这个任务都会被完成,所以要是想管理权限,要自己再封装一层,比如在执行某任务前,先判断当前用户是不是该任务绑定的执行人,如果不是,就不让执行。 不用管上面taskService调用方法的顺序,只是展示一下taskService除了完成任务还能干些什么(比如绑定执行人、设定流程变量等),完成任务肯定不是按照上面的顺序来的。 到此,基本的操作就结束了 # Spring Boot整合 整合的时候你的springboot的版本要和activiti版本相对应,不然可能会出一些奇怪的问题。具体是怎么对应的这里我就不说了。 不过如果你要整合activiti7的springboot starter包,要注意这个包是耦合了Spring Security的,如果你没有配置Spring Security,在没登录的情况下会调用不了接口。 但这个starter也提供了几个runtime接口,耦合了security,简化了操作,这里我就不解释怎么使用了,都是大同小异的。 **注意!!!** ***所以如果你不想整合security,那就可以不用starter,直接用引擎,我暂时就是这样使用的,也就是我上面给出的坐标那种类型的。要是用了starter,可以在.yml文件中配置activiti的一些属性,但没用starter的话就不行,得自己引入bean配置。但注意,在springboot项目中不使用starter而直接使用activiti引擎的包,可能会出什么问题也说不定,我这里只是提供一种绕过security的思路,具体在之后会遇到什么问题我是不敢保证的,要是你们要整合到实际的项目,还想使用这种方法来绕过security的话,一定要做充分的测试来预防出现别的问题。要是出现什么问题,可能是不好解决甚至是不能解决的,所以使用这个方法要慎重,这里代码仅供学习使用。*** 这里贴个配置类: ``` @Configuration public class ActivitiConfig { @Autowired private DataSource dataSource; @Bean public StandaloneProcessEngineConfiguration processEngineConfiguration(){ StandaloneProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration(); configuration.setDataSource(dataSource); configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); configuration.setAsyncExecutorActivate(false); return configuration; } @Bean public ProcessEngine processEngine(){ return processEngineConfiguration().buildProcessEngine(); } @Bean public RepositoryService repositoryService(ProcessEngine processEngine){ return processEngine.getRepositoryService(); } @Bean public RuntimeService runtimeService(ProcessEngine processEngine){ return processEngine.getRuntimeService(); } @Bean public TaskService taskService(ProcessEngine processEngine){ return processEngine.getTaskService(); } } ``` # 总结 **到这里就结束了,这里说的只是比较简单的问题,只看这个肯定是不够的,我写这篇博客的目的本来就是复习一下,然后分享下经验,给大家提供一点思路,这篇博客可以当一个学习目录用,你们可以自行百度来加深深度和纠正错误。因为我学习activiti也没几天,不敢保证所有的内容都是完全正确的,部分理解也可能不正确,部分解释也可能不合理,这里就靠你们自己在广泛搜索的过程中纠正了,而我之前在图片中说什么什么不重要并不是说真的不重要,只是说我觉得在初步学习的时候不是那么用得到。所以这里只推荐用作学习目录和大体了解,要是想深入学习,还是得找别的资料。要是有什么错误,可以评论一下,我再修改,谢谢观看。