2023年7月24日发(作者:)
⼯作流引擎之-activiti6使⽤⼀、前⾔在很多项⽬中,我们都很有可能⽤到了⼯作流处理逻辑,⽐如类同钉钉的申请流程。所有的流程都具有相同的特点由⼀个起点发起中间分割为多个流最后汇聚到⼀个终点其中可以由处理节点和通知节点…等等,所有的业务流程处理逻辑⼤体相同,为什么不可以将相同的流程化的逻辑封装起来模块化组件化的⽅式⽤于其他项⽬当中呢?其实针对这个问题,⽹络上早有许多开源项⽬将其封装,⽽且都有不错的扩展性。其中⽐较热门的较activiti和flowable了,当然,本⽂的重点调研的是activiti的使⽤,所以其后所有内容重点介绍的是activiti,flowable在这⾥仅做⽐对,不会有任何体现,以下⽂章我们以这两个框架进⾏简单介绍。声明:(1)以下部分代码摘⾃⽹络,但是⽹络上的代码⼤部分是⽆法串联起来的,特别是相关的概念和使⽤流程上进⾏的很⼤部分说明,所以很⼤部分都是经过加⼯,使得各位童鞋在学习的时候能够在理解核⼼概念后从事开发,这样就能顺⼿拈来定制或改造⾃⼰的业务。(2)学习的时候我也是带着疑问去调研的,所以⽬录接⼝上就能体现我疑问点,可能排列顺序逻辑稍有问题,请学习的同学务必完所有⽂章在提问。1、历史记录关于流程处理的开源项⽬⽐较多,其中使⽤⽐较多的属activiti和flowable开源框架了。他们两个团队的历史如下Tijs Rademakers,算是activiti5以及6⽐较核⼼的leader了。现在是flowable框架的leaderJoram Barrez 算是activiti5以及6⽐较核⼼的leader了。⽬前从事flowable框架开发。Salaboy Activiti Cloud BPM leader(Activiti Cloud BPM 也就是⽬前的activiti7框架)Activiti7是 Salaboy团队开发的。activiti6以及activiti5代码⽬前有 Salaboy团队进⾏维护。因为Tijs Rademakers团队去开发flowable框架了,所以activiti6以及activiti5代码已经交接给了 Salaboy团队(可以理解为离职之前⼯作交接)。⽬前的activiti5以及activiti6代码还是原Tijs Rademakers原有团队开发的。Salaboy团队⽬前在开发activiti7框架。对于activiti6以及activiti5的代码官⽅已经宣称暂停维护了。activiti7就是噱头 内核使⽤的还是activiti6。并没有为引擎注⼊更多的新特性,只是在activiti之外的上层封装了⼀些应⽤ 注意:activiti6的很多框架bug在flowable框架中已经修复的差不多了activiti5以及ativiti6的核⼼开发团队是Tijs Rademakers团队。activiti6最终版本由Salaboy团队发布的。可能很多⼈有疑惑,activiti6核⼼代码是Tijs Rademakers团队开发的,为何是Salaboy团队发布的呢?很简单,因为这个时候Tijs Rademakers团队已经去开发flowable去了。flowable是基于4 分⽀开发的。下⾯我们截图⼀些flowable的发展。2、flowable特点⽬前Flowable已经修复了activiti6很多的bug,可以实现零成本从activiti迁移到flowable。flowable⽬前已经⽀持加签、动态增加实例中的节点、⽀持cmmn、dmn规范。这些都是activiti6⽬前版本没有的。它的特点如下:flowable已经⽀持所有的历史数据使⽤mongdb存储,activiti没有。flowable⽀持事务⼦流程,activiti没有。flowable⽀持多实例加签、减签,activiti没有。flowable⽀持httpTask等新的类型节点,activiti没有。flowable⽀持在流程中动态添加任务节点,activiti没有。flowable⽀持历史任务数据通过消息中间件发送,activiti没有。flowable⽀持java11,activiti没有。flowable⽀持动态脚本,,activiti没有。flowable⽀持条件表达式中⾃定义juel函数,activiti没有。flowable⽀持cmmn规范,activiti没有。flowable修复了dmn规范设计器,activit⽤的dmn设计器还是旧的框架,bug太多。flowable屏蔽了pvm,activiti6也屏蔽了pvm(因为6版本官⽅提供了加签功能,发现pvm设计的过于臃肿,索性直接移除,这样加签实现起来更简洁、事实确实如此,如果需要获取节点、连线等信息可以使⽤bpmnmodel替代)。flowable与activiti提供了新的事务监听器。activiti5版本只有事件监听器、任务监听器、执⾏监听器。flowable对activiti的代码⼤量的进⾏了重构。activiti以及flowable⽀持的数据库有h2、hsql、mysql、oracle、postgres、mssql、db2。其他数据库不⽀持的。使⽤国产数据库的可能有点失望了,需要修改源码了。flowable⽀持jms、rabbitmq、mongodb⽅式处理历史数据,activiti没有。3、框架前景⽐较2019年6⽉中旬 salboy已从alfresco公司离职。activiti7/8开发动向不明确。flowable以6.4.1版本为分⽔岭,⼤⼒发展其商业版产品。开源版本维护不及时。部分功能已经不再开源版发布,⽐如表单⽣成器(表单引擎)、历史数据同步⾄其他数据源、es等等。dmn⽬前是个半成品,没有camunda稳定和好⽤,对于dmn规范⽀持薄弱。部分商业版的组件被商业化,因此开源版不再维护。Mongdb⽬前也放到商业产品中了,开源版的⼏乎不能⽤。其他的后续再来总结、上述的新特性在我们的系列课程在基本都给⼤家讲解了。⼆、activiti介绍1、activiti介绍Activiti5是由Alfresco软件在2010年5⽉17⽇发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、⼯作流、服务协作等领域的⼀个开源的、灵活的、易扩展的可执⾏流程语⾔框架。Activiti基于Apache许可的开源BPM平台,创始⼈Tom Baeyens是JBoss jBPM的项⽬架构师,它特⾊是提供了eclipse插件,开发⼈员可以通过插件直接绘画出业务流程图。activiti5 软件环境1. JDK1.6或者更⾼版本2. ⽀持的数据库有:h2, mysql, oracle, postgres, mssql, db2等。3. ⽀持activiti5运⾏的jar包4. 开发环境为Eclipse3.7或者以上版本,myeclipse为8.6版本4.2:相关资源下载2、七⼤核⼼接⼝①管理流程部署和流程定义的API RepositoryService②流程运⾏时对流程实例进⾏管理与控制** RuntimeService**③对流程任务进⾏管理(任务提醒、创建任务等)TaskService④提供对流程⾓⾊数据管理的API(⽤户)** IdentityService**⑤提供对流程引擎进⾏管理和维护服务** ManagementService**⑥对流程历史数据进⾏操作(查询、删除)HistoryService⑦表单服务** FormService**3、最核⼼的类ACTIVITI 对象很多,有些对象命名很相近 ,对于理解其源码和具体实现逻辑会造成困扰 ,⽐如Activity 和 ActivityImplTask和TaskEntityExecutionEntity和ExecutionImpl命名类似,加上Activiti使⽤了⼤量的设计模式,代码引⽤层次⾮常深,让⼈阅读代码时容易混乱。 所以将ACTIVITI的对象按⽣命时期划分则容易区分,逐个时期理解,降低阅读难度。模型期流程设计完成后,会产⽣⼀个xml流程设计⽂件,Activiti通过流程配置对象实例化BpmnParseHandler集合,完成xml解析到模型期对象。部署期部署期主要对象为ProcessDefinitionEntity,通过RepositoryService来部署。运⾏期主要通过RuntimeService和TaskService来实现流程实例、分⽀、任务对象的创建和执⾏。历史期通过HistoryService查询历史信息。模型期对象:Activity和Task系统类图Activity的定义Activity是对应流程XML定义的所有元素的抽象⽗类,即中的各种元素(包括任务、⽹关、⼦流程等),流程定义基于Actitity的⼦类来完成整个流程定义的构造。Activity和ActivityImpl的区别Acitity对应的流程元素的定义,是定义期对象。ActitityImpl是流程实例运⾏期间具体活动的实例对象,是运⾏期对象。Task是⼀个Actitity的空的抽象⼦类,但是Activiti⽀持的所有任务类型均是它的⼦类或孙⼦类。在Activiti中有两个Task类,⼀个是model包下的抽象类,是模型期的;⼀个是task包下的Task接⼝,是运⾏期的。任务类图部署期对象:ProcessDefinitionEntityProcessDefinitionEntity的类结构图:图⽐较⼤,主要是要把关键的对象都纳⼊进来。ProcessDefinitionEntity的⽗类ProcessDefinitionImpl聚合了ActivityImpl初始化活动和集合活动。继承ScopeImpl:聚合了ExecutionListener,这是Activiti的事件扩展点。Pvm抽象基础类:PvmProcessDefinition --> ReadOnlyProcessDefinition -->PvmScope–>PvmProcessElement。PVM主要就是负责流程整个运⾏期的执⾏、流转等所有运⾏过程。关于PVM值得专门撰写,对于Activiti的深⼊个性化扩展(⽐如驳回、跳转、分发汇总等)都会直接处理Pvm对象。聚合了TaskDefinition:TaskDefinition是基于Task对象解析获得的,可以参考UserTaskParseHandler代码。实现了PersistentObject、HasRevision:表⽰它是持久化的,并且有版本控制。运⾏期对象:TaskEntity、ActivityImpl、ExecutionEntity和ExecutionImplTaskEntity:任务对象,实现Task接⼝。TaskService服务操作的对象。ActivityImpl:PvmActivity的实现类,对Acitivity进⾏扩展开发务必关键学习好的类。【重要】ExecutionEntity:流程实例接⼝的实现类ExecutionImpl:ExecutionEntity实例的实际实现,虽然ExecutionEntity是通过RuntimeServiceImpl实现的,但是经过⼀连串处理,实际是由ExecutionImpl实现的。-ActivityImpl类结构图ActivityImpl在整个Activiti对象中最为复杂,也最为核⼼,我们对Activiti进⾏的本⼟化扩展都是围绕该对象进⾏操作的,⽐如驳回、任意驳回、分发、汇总等复杂的业务处理,均是通过对它进⾏编程实现的。ExecutionEntity和ExecutionImpl类结构图:两者并没有关联关系,可以认为ExecutionEntity是实体类,ExecutionImpl是业务实现类。activiti中最核⼼的类ProcessEngine,作⽤是什么?【1地位】这是activiti最核⼼的类,其他的类都是衍⽣;【2创建⼯作流引擎】 ProcessEngine processEngine = aultProcessEngine();【3仓库服务RepositoryService】 RepositoryService repositoryService = ositoryService();【4运⾏时服务RuntimeService】 RuntimeService runtimeService = timeService();【5任务服务TaskService】 TaskService taskService = kService();【6任务服务TaskService】HistoryService historyService = toryService();ProcessDefinition流程定义类。可以从这⾥获得资源⽂件等。ProcessInstance代表流程定义的执⾏实例。如员⼯请了⼀天的假,他就必须发出⼀个流程实例的申请。⼀个流程实例包括了所有的运⾏节点。我们可以利⽤这个对象来了解当前流程实例的进度等信息。流程实例就表⽰⼀个流程从开始到结束的最⼤的流程分⽀,即⼀个流程中流程实例只有⼀个。ExecutionActiviti⽤这个对象去描述流程执⾏的每⼀个节点。在没有并发的情况下,Execution就是同ProcessInstance。流程按照流程定义的规则执⾏⼀次的过程,就可以表⽰执⾏对象Execution。ProcessInstance就是Execution。但在现实意义上有所区别在单线流程中,如上图的贷款流程,ProcessInstance与Execution是⼀致的。wire money(汇钱)和archive(存档)是并发执⾏的。 这个时候,总线路代表ProcessInstance,⽽分线路中每个活动代表Execution。总结如下:⼀个流程中,执⾏对象可以存在多个,但是流程实例只能有⼀个当流程按照规则只执⾏⼀次的时候,那么流程实例就是执⾏对象如下为各个服务的衍⽣代码 ProcessEngine processEngine = aultProcessEngine(); //管理流程定义 RepositoryService repositoryService = ositoryService(); //执⾏管理,包括启动、推进、删除流程实例等 RuntimeService runtimeService = timeService(); //任务管理 TaskService taskService = kService(); //历史管理(执⾏完的数据的管理 HistoryService historyService = toryService(); //组织机构管理 IdentityService identityService = ntityService(); //可选服务,任务表单管理 FormService formService = mService();4、相关概念说明我们以请假流程为例⼦,⾸先员⼯A和员⼯B向部门经理请假,对于员⼯A理由合理充分,经理批准请假,对于B请假不合理,经理拒绝请假。针对于请假流程,图中涉及到的⼏个组成部分:⼈物:员⼯A和B以及部门经理事件(动作):请假、批准、拒绝我们⾸先说明⼀下其中涉及到的⼏个概念⼯作流(WorkFlow)⼯作流(Workflow),就是“业务过程的部分或整体在计算机应⽤环境下的⾃动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递⽂档、信息或任务的过程⾃动进⾏,从⽽实现某个预期的业务⽬标,或者促使此⽬标的实现” 。⼯作流管理系统⼯作流管理系统(Workflow Management System, WfMS)是⼀个软件系统,它完成⼯作量的定义和管理,并按照在系统中预先定义好的⼯作流规则进⾏⼯作流实例的执⾏。⼯作流管理系统不是企业的业务系统,⽽是为企业的业务系统的运⾏提供了⼀个软件的⽀撑环境。⼯作流管理联盟给的定义:⼯作流管理系统是⼀个软件系统,它通过执⾏经过计算的流程定义去⽀持⼀批专门设定的业务流程。⼯作流管理系统被⽤来定义、管理、和执⾏⼯作流程。⼯作流管理系统⽬标管理⼯作的流程以确保⼯作在正确的时间被期望的⼈员所执⾏——在⾃动化进⾏的业务过程中插⼊⼈⼯的执⾏和⼲预⼯作流引擎ProcessEngine对象,这是Activiti⼯作的核⼼。负责⽣成流程运⾏时的各种实例及数据、监控和管理流程的运⾏。BPMN业务流程建模与标注(Business Process Model and Notation,BPMN) ,描述流程的基本符号,包括这些图元如何组合成⼀个业务流程图(Business Process Diagram)数据库表Activiti的后台是有数据库的⽀持,所有的表都以ACT_开头。 第⼆部分是表⽰表的⽤途的两个字母标识。 ⽤途也和服务的API对应。ACT_RE_*: ‘RE’表⽰repository。 这个前缀的表包含了流程定义和流程静态资源 (图⽚,规则,等等)。ACT_RU_*: ‘RU’表⽰runtime。 这些运⾏时的表,包含流程实例,任务,变量,异步任务,等运⾏中的数据。 Activiti只在流程实例执⾏过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运⾏时表可以⼀直很⼩速度很快。ACT_ID_*: ‘ID’表⽰identity。 这些表包含⾝份信息,⽐如⽤户,组等等。ACT_ID_*: ‘ID’表⽰identity。 这些表包含⾝份信息,⽐如⽤户,组等等。ACT_HI_*: ‘HI’表⽰history。 这些表包含历史数据,⽐如历史流程实例, 变量,任务等等。ACT_GE_*: 通⽤数据, ⽤于不同场景下,如存放资源⽂件。相关表作⽤说明资源库流程规则表1. act_re_deployment 部署信息表2. act_re_model 流程设计模型部署表3. act_re_procdef 流程定义数据表运⾏时数据库表1. act_ru_execution 运⾏时流程执⾏实例表2. act_ru_identitylink 运⾏时流程⼈员表,主要存储任务节点与参与者的相关信息3. act_ru_task 运⾏时任务节点表4. act_ru_variable 运⾏时流程变量数据表历史数据库表1. act_hi_actinst 历史节点表2. act_hi_attachment 历史附件表3. act_ih_comment 历史意见表4. act_hi_identitylink 历史流程⼈员表5. act_hi_detail 历史详情表,提供历史变量的查询6. act_hi_procinst 历史流程实例表7. act_hi_taskinst 历史任务实例表8. act_hi_varinst 历史变量表组织机构表1. act_id_group ⽤户组信息表2. act_id_info ⽤户扩展信息表3. act_id_membership ⽤户与⽤户组对应信息表4. act_id_user ⽤户信息表这四张表很常见,基本的组织机构管理,关于⽤户认证⽅⾯建议还是⾃⼰开发⼀套,组件⾃带的功能太简单,使⽤中有很多需求难以满⾜通⽤数据表1. act_ge_bytearray ⼆进制数据表2. act_ge_property 属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插⼊三条记录活动BPMN图中,每⼀个元素都有各⾃的名称,常见的元素包括UserTask、SequenceFlow(Flow)、StartEvent、EndEvent、排他⽹关(并⾏⽹关、包含⽹关、事件⽹关),在程序中我们常见activitiId或者name表⽰该元素实例化的后的taskId或者flowId,也就是说activiti就是当前运⾏元素的实例。如当前活动为Flow(sequenceFlow)对应id为flow线条的id,flow名称为线条线上的名称。流程定义与流程实例流程图就是我们通过在线编辑器或eclipse编辑器制作出来的xml格式的bpmn格式的流程定义⽂件,它定义了事件的所有流程相关的任务处理逻辑。流程被部署并启动之后就会被实例化,⼀个流程可以实例化多次,对应的实例就是流程实例。activiti配置⽂件说明是 Activiti核⼼配置⽂件,配置流程引擎创建⼯具的基本参数和数据库连接池参数。 定义数据库配置参数jdbcUrl: 数据库的JDBC URL。jdbcDriver: 对应不同数据库类型的驱动。jdbcUsername: 连接数据库的⽤户名。jdbcPassword: 连接数据库的密码。连接池配置基于JDBC参数配置的数据库连接 会使⽤默认的MyBatis连接池。 下⾯的参数可以⽤来配置连接池jdbcMaxActiveConnections: 连接池中处于被使⽤状态的连接的最⼤值。默认为10。jdbcMaxIdleConnections: 连接池中处于空闲状态的连接的最⼤值。jdbcMaxCheckoutTime: 连接被取出使⽤的最长时间,超过时间会被强制回收。 默认为20000(20秒)。jdbcMaxWaitTime: 这是⼀个底层配置,让连接池可以在长时间⽆法获得连接时, 打印⼀条⽇志,并重新尝试获取⼀个连接。(避免因为错误配置导致沉默的操作失败)。 默认为20000(20秒)。也可以使⽤urce⽹关说明排他⽹关排他⽹关显⽰成⼀个普通⽹关(⽐⽅。菱形图形), 内部是⼀个“X”图标,表⽰异或(XOR)语义排他⽹关(也叫异或(XOR)⽹关,或更技术性的叫法 基于数据的排他⽹关), ⽤来在流程中实现决策。 当流程运⾏到这个⽹关,全部外出顺序流都会被处理⼀遍。 当中条件解析为true的顺序流(或者没有设置条件,概念上在顺序流上定义了⼀个’true’) 会被选中,让流程继续执⾏。并⾏⽹关并⾏⽹关显⽰成⼀个普通⽹关(菱形)内部是⼀个“加号”图标, 表⽰“与(AND)”语义。⽹关也能够表⽰流程中的并⾏情况。最简单的并⾏⽹关是 并⾏⽹关,它同意将流程 分成多条分⽀。也能够把多条分⽀ 汇聚到⼀起。并⾏⽹关的功能是基于进⼊和外出的顺序流的分⽀: 并⾏后的全部外出顺序流,为每⼀个顺序流都创建⼀个并发分⽀。汇聚: 全部到达并⾏⽹关。在此等待的进⼊分⽀。 直到全部进⼊顺序流的分⽀都到达以后。 流程就会通过汇聚⽹关。假设同⼀个并⾏⽹关有多个进⼊和多个外出顺序流。 它就同⼀时候具有分⽀和汇聚功能。 这时。⽹关会先汇聚全部进⼊的顺序流,然后再切分成多个并⾏分⽀,与其它⽹关的主要差别是,并⾏⽹关不会解析条件。即使顺序流中定义了条件。也会被忽略。注意:并⾏ ⽹关 要有2个,⼀个是⽤于分⽀,⼀个⽤于聚合测试:启动项⽬后,有两个任务,我们先 提交⼀个 功能模块1的任务后发现只剩下⼀个任务了,再次提交剩下任务就进⼊了下⼀个阶段包括⽹关并⾏⽹关显⽰为⼀个普通⽹关(菱形),内部包括⼀个圆圈图标。包括⽹关能够看做是排他⽹关和并⾏⽹关的结合体。 和排他⽹关⼀样,你能够在外出顺序流上定义条件。包括⽹关会解析它们。 可是基本的差别是包括⽹关能够选择多于⼀条顺序流。这和并⾏⽹关⼀样。当 main config 中的 表达式 条件返回的结果为真时 运⾏ 并⾏⽹关。结果为假时 运⾏ 排他任务事件⽹关事件⽹关和其它BPMN⽹关⼀样显⽰成⼀个菱形, 内部包括指定图标。基于事件⽹关同意依据事件推断流向,⽹关的每⼀个外出顺序流都要连接到⼀个中间捕获事件。 当流程到达⼀个基于事件⽹关,⽹关会进⼊等待状态:会暂停运⾏。 与此同⼀时候,会为每⼀个外出顺序流创建相对的事件订阅。注意基于事件⽹关的外出顺序流和普通顺序流不同。这些顺序流不会真的"运⾏"。相反。它们让流程引擎去决定运⾏到基于事件⽹关的流程须要订阅哪些事件。要考虑下⾯条件基于事件⽹关必须有两条或以上外出顺序流基于事件⽹关后,仅仅能使⽤intermediateCatchEvent类型。 (activiti不⽀持基于事件⽹关后连接ReceiveTask。)连接到基于事件⽹关的intermediateCatchEvent仅仅能有⼀条进⼊顺序流。5、activiti使⽤(1) 使⽤流程使⽤activiti的基本流程是流程定义(流程图设计)–>流程定义部署–>启动流程实例–>处理任务(查询任务+处理任务)–>完成任务–>结束注意:只有流程定义被部署之后才可以启动流程实例,否则会报找不到对应部署错误。流程定义(BPMN设计图)可以使⽤eclipse的activiti插件进⾏设计或者使⽤activiti⾃带的在线编辑器进⾏创建和设计,两者区别在于activiti在线编辑器使⽤activiti在线编辑器设计必须提供对象的接⼝创建对应模块,然后携带model的id重定向到设计页⾯,该模式设计会直接保存到数据库中流程定义表中。我们可以借助model的id直接部署该模型。eclipse的activiti插件它是⼀个独⽴的插件,我们可以借助该插件完成BPMN流程定义图的设计,然后将BPMN流程定义导⼊到项⽬resources的processes⽬录中,启动activiti或者通过接⼝加载部署资源⽬录下的流程定义。流程部署在启动流程实例之前,我们必须将流程定义部署到activiti中,否则⽆法启动流程实例启动流程实例流程需要运⾏起来,必须要启动已经部署的流程定义。查询我的任务流程实例启动之后,就可以查询我的(⽤户设计的节点)处理任务。也可以查询流程实例资源和相关变量处理我的任务查询到我的任务之后,就是处理该任务,可以设置处理结果和相关变量。流程结束当最后的流程任务处理完成之后,就标识流程处理完成。(2) activiti配置在Activiti中,在创建核⼼的流程引擎对象时会⾃动建表。如果程序正常执⾏,mysql会⾃动建库,然后创建23张表。 基于SpringBoot框架的activiti配置如下package
import
import
import
import
import
import
import
import
import
import
import
import
import
import
/** * @⽂件名称: * @功能描述: activiti启动配置 * @版权信息: * @技术交流: 961179337(QQ群) * @编写作者: lixx2048@ * @联系⽅式: 941415509(QQ) * @开发⽇期: 2021年7⽉24⽇ * @历史版本: V1.0 */@Configurationpublic class ActivitiConfig extends AbstractProcessEngineAutoConfiguration {
/** * @功能描述:
创建启动配置类 * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉24⽇ * @历史版本: V1.0
* @参数说明:
⽣成启动配置之后会创建所有表 * @返
回
值: */ @Bean public SpringProcessEngineConfiguration springProcessEngineConfiguration(DataSource dataSource,PlatformTransactionManager platformTransactionManager){ //
创建配置对象 SpringProcessEngineConfiguration spec = new SpringProcessEngineConfiguration(); //
设置数据源 aSource(dataSource); //
设置事务管理器 nsactionManager(platformTransactionManager); //
创建或更新表结构 abaseSchemaUpdate("true"); //
获取所有资源 Resource[] resources = null; try { resources = new PathMatchingResourcePatternResolver().getResources("classpath*:processes/*.bpmn"); } catch (IOException e) { tackTrace(); } //
启动⾃动部署流程 loymentResources(resources); return spec; }
/** * @功能描述:
⽣成核⼼处理引擎对象 * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉24⽇Resource;PathMatchingResourcePatternResolver;PlatformTransactionManager;IOException;DataSource;HistoryService;RepositoryService;RuntimeService;TaskService;ProcessEngineFactoryBean;SpringProcessEngineConfiguration;AbstractProcessEngineAutoConfiguration;Bean;Configuration;; * @开发⽇期: 2021年7⽉24⽇ * @历史版本: V1.0
* @参数说明: * @返
回
值: */ @Bean public ProcessEngineFactoryBean processEngine(SpringProcessEngineConfiguration engineConfiguration){ ProcessEngineFactoryBean processEngineFactoryBean = new ProcessEngineFactoryBean(); cessEngineConfiguration(engineConfiguration); return processEngineFactoryBean; }
/** * @功能描述:
⽣成仓库管理对象 * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉24⽇ * @历史版本: V1.0
* @参数说明:管理流程定义、资源等 * @返
回
值: */ @Bean public RepositoryService repositoryService(ProcessEngineFactoryBean engineFactoryBean) throws Exception{ return ect().getRepositoryService(); }
/** * @功能描述:
⽣成运⾏时服务对象 * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉24⽇ * @历史版本: V1.0
* @参数说明: * @返
回
值: */ @Bean public RuntimeService runtimeService(ProcessEngineFactoryBean engineFactoryBean) throws Exception{ return ect().getRuntimeService(); }
/** * @功能描述:
⽣成任务对象 * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉24⽇ * @历史版本: V1.0
* @参数说明:个⼈任务查询等 * @返
回
值: */ @Bean public TaskService taskService(ProcessEngineFactoryBean engineFactoryBean) throws Exception{ return ect().getTaskService(); }
/** * @功能描述:
⽣成历史管理对象 * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉24⽇ * @历史版本: V1.0
* @参数说明: * @返
回
值: */ @Bean public HistoryService historyService(ProcessEngineFactoryBean engineFactoryBean) throws Exception{ return ect().getHistoryService(); }}(3) activiti使⽤接下来就是⼏个核⼼类的使⽤,围绕(1)中介绍的使⽤流程调⽤相关接⼝部署流程定义、启动流程实例、查询流程任务和处理流程任务即可,下⾯我们会针对相关的接⼝进⾏详细介绍。最核⼼的接⼝类为ProcessEngine,他可以通过ProcessEngines获取,⽽其他的仓库服务、运⾏时服务、历史服务够可以通过ProcessEngine衍⽣获取,这⾥如前所述不再介绍!6、接⼝调⽤说明(1) 流程部署定义/** *
部署流程定义 */@Testpublic void deploymentProcessDefinition(){ //获取流程定义与部署相关Service Deployment deployment = ositoryService() //创建⼀个部署对象 .createDeployment() //指定部署名称 .name("helloworld⼊门程序") //加载资源⽂件 .addClasspathResource("diagrams/") //完成部署 .deploy(); n(()); n(e());}(2) 启动流程实例部署流程定义之后,可以通过流程定义的id或者流程定义的key来启动,这⾥需要注意,流程定义id的⽣成规则key:版本:⽣成ID其中key为流程定义bpmn⽂件⽂件中的process节点的id值如(ask4Leave),key属性被⽤来区别不同的流程定义.带有特定key的流程定义第⼀次部署时,version为1。之后每次部署都会在当前最⾼版本号上加1,如流程定义id如下apply4Leave:1:15006vacationProcess:1:4apply4Leave:2:15011vacationProcess:2:5004apply4Leave:3:17504vacationProcess:3:15007查询流程定义可以通过如下接⼝获取@RequestMapping(value = "findProcessDefinition") public void findProcessDefinition() { //
查询所有流程定义 List
打印所有流程定义 if (list != null && () > 0) { for (ProcessDefinition pd : list) { //
流程定义的key+版本+随机⽣成数 n("流程定义ID:" + ()); //
对应⽂件中的name属性值 n("流程定义的名称:" + e()); //
对应⽂件中的id属性值 n("流程定义的key:" + ()); //
当流程定义的key值相同的相同下,版本升级,默认1 n("流程定义的版本:" + sion()); n("资源名称bpmn⽂件:" + ourceName()); n("资源名称png⽂件:" + gramResourceName()); n("部署对象ID:" + loymentId()); n("--------------------------"); } } }获取到流程定义id之后,我们即可使⽤流程定义id来启动流程实例 /** * @功能描述:
通过流程定义id启动流程实例 * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉25⽇ * @历史版本: V1.0
* @参数说明:该流程定义必须是已经被部署后才能启动,否则跑出异常 * @返
回
值:
流程定义id(规则key:version:随机数)可以通过查询流程定义接⼝获取 *
由于不知道随机数id⽣成值,如果想不需要查询的情况下启动流程可以使⽤ *
本⽰例提供的startProcessInstanceByKey接⼝通过流程定义key启动流程实例 */ @RequestMapping(value = "startProcessInstanceById") public String startProcessInstanceById(@PathVariable("processDefinitionId") String processDefinitionId) { //
获取运⾏时服务 RuntimeService runtimeService = ProcessEngines .getDefaultProcessEngine() .getRuntimeService();
//
通过部署id启动流程实例 ProcessInstance processInstance = rocessInstanceById(processDefinitionId); return (); }注意:(1)如果资源⽬录process⼦⽬录下存在流程定义bpmn⽂件,则系统启动的时候回被⾃动加载到流程定义表中(act_re_procdef),但是不会⾃动部署!(2)存储流程定义相关的部署信息。即流程定义⽂档的存放地。每部署⼀次就会增加两条记录,⼀条是关于bpmn规则⽂件的,⼀条是图⽚的(如果部署时只指定了bpmn⼀个⽂件,activiti会在部署时解析bpmn⽂件内容⾃动⽣成流程图)。两个⽂件不是很⼤,都是以⼆进制形式存储在数据库中。当然也可以通过key来部署/** *
启动流程实例 */@Testpublic void startProcessInstance(){ //获取与正在执⾏的流程⽰例和执⾏对象相关的Service ProcessInstance processInstance = processEngine //获取运⾏时服务 .getRuntimeService() //使⽤流程定义的key启动实例,key对应bpmn⽂件中id的属性值,默认按照最新版本流程启动 .startProcessInstanceByKey("helloworld");
n(()); n(cessDefinitionId());}(3) 查看我的任务/** *
查询当前的个⼈任务 */@Testpublic void findPersonalTask(){ //
与正在执⾏的任务相关的Service List
if(list != null && () > 0){ for(Task task : list){ n(()); n(e()); n(ateTime()); n(ignee()); n(cessInstanceId()); n(cutionId()); n(cessDefinitionId()); } }}(4) 完成我的任务/** *
完成我的任务 */@Testpublic void completePersonalTask(){ kService() .complete("7502");}(5) 部署流程定义通过模型ID部署流程定义如果我们已经通过在线编辑器保存了模型,那么我们就可以直接使⽤模型id部署对应的流程@RequestMapping(value = "deploy/{modelId}")public String deploy(@PathVariable("modelId") String modelId) { try { //
获取数据仓库服务 //
获取数据仓库服务 ProcessEngine processEngine = aultProcessEngine(); RepositoryService repositoryService = ositoryService();
//
根据模型id获取模型编辑源信息 Model modelData = el(modelId); ObjectMapper objectMapper = new ObjectMapper(); ObjectNode modelNode = (ObjectNode) objectMapper .readTree(elEditorSource(())); //
转化成XML BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode); byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model); //
创建并部署流程 String processName = e() + "."; Deployment deployment = repositoryService .createDeployment() .name(e()) .addString(processName, new String(bpmnBytes,"UTF-8")) .deploy();
//
返回流程部署ID return "部署成功,部署ID=" + (); } catch (Exception e) { ("根据模型部署流程失败:modelId="+ modelId, e); return "部署流程失败!流程图错误"; }}以上通过addString⽅法添加xml模型定义描述来实现流程的部署。通过classpath部署流程定义/**部署流程定义(从classpath)*/@Testpublic void deploymentProcessDefinition_classpath(){ Deployment deployment = processEngine //与流程定义和部署对象相关的Service .getRepositoryService()
//创建⼀个部署对象 .createDeployment()
//添加部署的名称 .name("流程定义")
//从classpath的资源中加载,⼀次只能加载⼀个⽂件 .addClasspathResource("diagrams/") //从classpath的资源中加载,⼀次只能加载⼀个⽂件 .addClasspathResource("diagrams/") //完成部署 .deploy();
n("部署ID:"+()); n("部署名称:"+e());}以上通过addClasspathResource⽅法添加流程资源⽂件来实现流程的部署通过zip⽂件部署流程定义/**部署流程定义(从zip)*/@Testpublic void deploymentProcessDefinition_zip(){ InputStream in = ss().getClassLoader() .getResourceAsStream("diagrams/"); ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deployment = processEngine //与流程定义和部署对象相关的Service .getRepositoryService()
//创建⼀个部署对象 .createDeployment()
//添加部署的名称 .name("流程定义")
//指定zip格式的⽂件完成部署 .addZipInputStream(zipInputStream) //完成部署 .deploy();
n("部署ID:"+()); n("部署名称:"+e());}以上通过addZipInputStream添加流程定义zip⽂件流来实现流动的部署。这⼀步在数据库中将操作三张表:a) act_re_deployment(部署对象表)存放流程定义的显⽰名和部署时间,每部署⼀次增加⼀条记录b) act_re_procdef(流程定义表)存放流程定义的属性信息,部署每个新的流程定义都会在这张表中增加⼀条记录。注意:当流程定义的key相同的情况下,使⽤的是版本升级c) act_ge_bytearray(资源⽂件表)存储流程定义相关的部署信息。即流程定义⽂档的存放地。每部署⼀次就会增加两条记录,⼀条是关于bpmn规则⽂件的,⼀条是图⽚的(如果部署时只指定了bpmn⼀个⽂件,activiti会在部署时解析bpmn⽂件内容⾃动⽣成流程图)。两个⽂件不是很⼤,都是以⼆进制形式存储在数据库中。(6) 查看流程定义/**查询流程定义*/@Testpublic void findProcessDefinition(){ //与流程定义和部署对象相关的Service List
//返回结果集数量 //.count(); //分页查询 //.listPage(firstResult, maxResults);
if(list!=null && ()>0){ for(ProcessDefinition pd:list){ //流程定义的key+版本+随机⽣成数 n("流程定义ID:"+()); //对应⽂件中的name属性值 n("流程定义的名称:"+e()); //对应⽂件中的id属性值 n("流程定义的key:"+()); //当流程定义的key值相同的相同下,版本升级,默认1 n("流程定义的版本:"+sion()); n("资源名称bpmn⽂件:"+ourceName()); n("资源名称png⽂件:"+gramResourceName()); n("部署对象ID:"+loymentId()); n("#########################################################"); } }}可以看到流程定义的key值相同的情况下,版本是从1开始逐次升级的,流程定义的Id是【key:版本:⽣成ID】。由运⾏结果可以看出:Key和Name的值为:bpmn⽂件process节点的id和name的属性值。key属性被⽤来区别不同的流程定义,带有特定key的流程定义第⼀次部署时,version为1。之后每次部署都会在当前最⾼版本号上加1,Id的值的⽣成规则为:{processDefinitionKey}:{processDefinitionVersion}:{generated-id}, 这⾥的generated-id是⼀个⾃动⽣成的唯⼀的数字,重复部署⼀次,deploymentId的值以⼀定的形式变化,规则act_ge_property表⽣成。说明:流程定义和部署对象相关的Service都是RepositoryService。创建流程定义查询对象,可以在ProcessDefinitionQuery上设置查询的相关参数调⽤ProcessDefinitionQuery对象的list⽅法,执⾏查询,获得符合条件的流程定义列表由运⾏结果可以看出:Key和Name的值为:bpmn⽂件process节点的id和name的属性值key属性被⽤来区别不同的流程定义带有特定key的流程定义第⼀次部署时,version为1。之后每次部署都会在当前最⾼版本号上加1Id的值的⽣成规则为:{processDefinitionKey}:{processDefinitionVersion}:{generated-id}, 这⾥的generated-id是⼀个⾃动⽣成的唯⼀的数字重复部署⼀次,deploymentId的值以⼀定的形式变化规则act_ge_property表⽣成(7) 删除流程定义/**删除流程定义*/@Testpublic void deleteProcessDefinition(){ //使⽤部署ID,完成删除 String deploymentId = "601";
//不带级联的删除:只能删除没有启动的流程,如果流程启动,就会抛出异常 //processEngine // .getRepositoryService() // .deleteDeployment(deploymentId); //级联删除:不管流程是否启动,都能可以删除 processEngine .getRepositoryService() .deleteDeployment(deploymentId, true); n("删除成功!");}如果该流程定义下没有正在运⾏的流程,则可以⽤普通删除。如果是有关联的信息,⽤级联删除。项⽬开发中使⽤级联删除的情况⽐较多,删除操作⼀般只开放给超级管理员使⽤。(8) 获取流程的资源/**查看流程图 * @throws IOException */@Testpublic void viewPic() throws IOException { /**将⽣成图⽚放到⽂件夹下*/ String deploymentId = "801"; //获取图⽚资源名称 List
获取资源仓库服务 .getRepositoryService() //
通过部署id获取部署资源 .getDeploymentResourceNames(deploymentId);
//定义图⽚资源的名称 String resourceName = ""; if(list!=null && ()>0){ for(String name:list){ if(f(".png")>=0){ resourceName = name; } } }
//获取图⽚的输⼊流 InputStream in = processEngine .getRepositoryService() .getResourceAsStream(deploymentId, resourceName);
//将图⽚⽣成到D盘的⽬录下 File file = new File("D:/"+resourceName); //将输⼊流的图⽚写到D盘下 putStreamToFile(in, file);}查询出流程定义⽂档。主要查的是图⽚,⽤于显⽰流程⽤。 deploymentId为流程部署ID,resourceName为act_ge_bytearray表中NAME_列的值,使⽤repositoryService的getDeploymentResourceNames⽅法可以获取指定部署下得所有⽂件的名称,使⽤repositoryService的getResourceAsStream⽅法传⼊部署ID和资源图⽚名称可以获取部署下指定名称⽂件的输⼊流,最后的有关IO流的操作,使⽤FileUtils⼯具的copyInputStreamToFile⽅法完成流程流程到⽂件的拷贝,将资源⽂件以流的形式输出到指定⽂件夹下。(9) 查询最新版流程定义/***附加功能:查询最新版本的流程定义*/@Testpublic void findLastVersionProcessDefinition(){ List
/** Map
List
//遍历,获取每个流程定义的部署ID if(list!=null && ()>0){ for(ProcessDefinition pd:list){ //获取部署ID String deploymentId = loymentId(); processEngine .getRepositoryService() .deleteDeployment(deploymentId, true); } }}流程实例、任务的执⾏:涉及到的表act_ru_execution: 正在执⾏的执⾏对象表act_hiJ_procinst: 流程实例的历史表act_ru_task:正在执⾏的任务表(只有节点是UserTask的时候,该表有数据)act_hi_taskinst:任务历史表(只有UserTask的时候,该表存在数据)act_hi_actinst:所有活动节点的历史表(11) 启动流程实例/** *
启动流程实例 */ @Test public void startProcessInstance(){ //获取与正在执⾏的流程⽰例和执⾏对象相关的Service ProcessInstance processInstance = processEngine //获取运⾏时服务 .getRuntimeService() //使⽤流程定义的key启动实例,key对应bpmn⽂件中id的属性值,默认按照最新版本流程启动 .startProcessInstanceByKey("helloworld");
n(()); n(cessDefinitionId()); }通过流程定义的key启动流程实例,这时打开数据库act_ru_execution表,ID_表⽰执⾏对象ID,PROC_INST_ID_表⽰流程实例ID,如果是单例流程(没有分⽀和聚合),那么流程实例ID和执⾏对象ID是相同的。⼀个流程流程实例只有⼀个,执⾏对象可以存在多个。(12) 查询我的个⼈任务/**查询当前⼈的个⼈任务*/ @Test public void findMyPersonalTask(){ String assignee = "张三"; List
if(list!=null && ()>0){ for(Task task:list){ n("任务ID:"+()); n("任务名称:"+e()); n("任务的创建时间:"+ateTime()); n("任务的办理⼈:"+ignee()); n("流程实例ID:"+cessInstanceId()); n("执⾏对象ID:"+cutionId()); n("流程定义ID:"+cessDefinitionId()); n("########################################################"); } } }因为是任务查询,所以从processEngine中应该得到TaskService,使⽤TaskService获取到任务查询对象TaskQuery,为查询对象添加查询过滤条件,使⽤taskAssignee指定任务的办理者(即查询指定⽤户的代办任务),同时可以添加分页排序等过滤条件,调⽤list⽅法执⾏查询,返回办理者为指定⽤户的任务列表,任务ID、名称、办理⼈、创建时间可以从act_ru_task表中查到。在这种情况下,ProcessInstance相当于Execution, 如果assignee属性为部门经理,结果为空。因为现在流程只到了”填写请假申请”阶段,后⾯的任务还没有执⾏,即在数据库中没有部门经理可以办理的任务,所以查询不到。 ⼀个Task节点和Execution节点是1对1的情况,在task对象中使⽤Execution_来表⽰他们之间的关系任务ID在数据库表act_ru_task中对应“ID_”列。在activiti任务中,主要分为两⼤类查询任务(个⼈任务和组任务):确切指定了办理者的任务,这个任务将成为指定者的私有任务,即个⼈任务。⽆法指定具体的某⼀个⼈来办理的任务,可以把任务分配给⼏个⼈或者⼀到 多个⼩组,让这个范围内的⽤户可以选择性(如有空余时间时)来办理这类任务,即组任务。(13) 办理任务 /**完成我的任务*/ @Test public void completeMyPersonalTask(){ //任务ID String taskId = "1202"; processEngine //与正在执⾏的任务管理相关的Service .getTaskService() //完成任务 .complete(taskId); n("完成任务:任务ID:"+taskId); }是办理任务,所以从ProcessEngine得到的是TaskService。当执⾏完这段代码,再以员⼯的⾝份去执⾏查询的时候,会发现这个时候已经没有数据了,因为正在执⾏的任务中没有数据。对于执⾏完的任务,activiti将从act_ru_task表中删除该任务,下⼀个任务会被插⼊进来。以”部门经理”的⾝份进⾏查询,可以查到结果。因为流程执⾏到部门经理审批这个节点了。再执⾏办理任务代码,执⾏完以后以”部门经理”⾝份进⾏查询,没有结果。重复这个步骤直到流程执⾏完。(14) 查询流程状态判断流程是正在执⾏还是结束 /**查询流程状态(判断流程正在执⾏,还是结束)*/ @Test public void isProcessEnd(){ String processInstanceId = "1001"; ProcessInstance pi = processEngine //表⽰正在执⾏的流程实例和执⾏对象 .getRuntimeService() //创建流程实例查询 .createProcessInstanceQuery() //使⽤流程实例ID查询 .processInstanceId(processInstanceId) .singleResult(); if(pi==null){ n("流程已经结束"); }else{ n("流程没有结束"); } }在流程执⾏的过程中,创建的流程实例ID在整个过程中都不会变,当流程结束后,流程实例将会在正在执⾏的执⾏对象表中(act_ru_execution)被删除。因为是查询流程实例,所以先获取runtimeService,创建流程实例查询对象,设置实例ID过滤参数,由于⼀个流程实例ID只对应⼀个实例,使⽤singleResult执⾏查询返回⼀个唯⼀的结果,如果结果数量⼤于1,则抛出异常,判断指定ID的实例是否存在,如果结果为空,则代表流程结束,实例在正在执⾏的执⾏对象表中已被删除,转换成历史数据。(15) 查询历史任务/**查询历史任务*/@Testpublic void findHistoryTask(){ String taskAssignee = "张三"; List
n(()+" "+ cessDefinitionId()+" "+ rtTime()+" "+ Time()+" "+ ationInMillis());}流程变量:act_ru_variable 正在执⾏的流程变量表act_hi_varinst 历史的流程变量表如下为对应实例模拟获取流程变量的场景/**模拟设置和获取流程变量的场景 */@Testpublic void setAndGetVariables(){ RuntimeService runtimeService = timeService(); TaskService taskService = kService(); //使⽤执⾏对象ID设置 iable(executionId, variableName, value);(设置⼀个) iables(executionId, variables); //使⽤任务ID设置 iable(taskId, variableName, value);(设置⼀个) iables(taskId, variables); //启动流程实例的同时设置 rocessInstanceByKey(processDefinitionKey, variables); //完成任务的同时设置 te(taskId, variables); /**获取流程变量*/ //使⽤执⾏对象ID和流程变量的名称,获取流程变量的值 iable(executionId, variableName); //使⽤执⾏对象ID,获取所有的流程变量,将流程变量放置到Map集合中, //map集合的key就是流程变量的名称,map集合的value就是流程变量的值 iables(executionId); //使⽤执⾏对象ID,获取流程变量的值,通过设置流程变量的名称存放到集合中, //获取指定流程变量名称的流程变量的值,值存放到Map集合中 iables(executionId, variableNames); //使⽤任务ID和流程变量的名称,获取流程变量的值 iable(taskId, variableName); //使⽤任务ID,获取所有的流程变量,将流程变量放置到Map集合中, //map集合的key就是流程变量的名称,map集合的value就是流程变量的值 iables(taskId); //使⽤任务ID,获取流程变量的值,通过设置流程变量的名称存放到集合中, //获取指定流程变量名称的流程变量的值,值存放到Map集合中 iables(taskId, variableNames);}(17) 设置流程变量/**设置流程变量 */@Testpublic void setVariables(){ TaskService taskService = kService(); //任务ID String taskId = "50004";
//⼀、设置流程变量,使⽤基本数据类型 //local与当前task绑定,下⼀个task不可见 iableLocal(taskId,"请假天数",3); iable(taskId,"请假⽇期",new Date()); iable(taskId,"请假原因","回家探亲");
//⼆:设置流程变量,使⽤javabean类型 /** *
当⼀个javabean(实现序列号)放置到流程变量中,要求javabean的属性不能再发⽣变化 *
如果发⽣变化,再获取的时候,抛出异常 *
解决⽅案:在Person对象中添加: * private static final long serialVersionUID = 6757393795687480331L; *
同时实现Serializable
* */ Person p = new Person(); (20); e("翠花"); iable(taskId, "⼈员信息(添加固定版本)", p); n("流程变量设置成功");}流程变量⽀持的类型从图中可以看出包括了⼤部分封装类型和Date、String和实现了Serializable接⼝的类的类型。流程变量的获取针对流程实例(即1个流程),每个流程实例获取的流程变量时不同的,使⽤基本类型获取流程变量,在taskService中使⽤任务ID,流程变量的名称,获取流程变量的值。Javabean类型设置获取流程变量,除了需要这个javabean实现了Serializable接⼝外,还要求流程变量对象的属性不能发⽣变化,否则抛出异常。解决⽅案,固定序列化ID。setVariable和setVariableLocal的区别setVariable设置流程变量的时候,流程变量名称相同的时候,后⼀次的值替换前⼀次的值,⽽且可以看到TASK_ID的字段不会存放任务ID的值setVariableLocal设置流程变量的时候,针对当前活动的节点设置流程变量,如果⼀个流程中存在2个活动节点,对每个活动节点都设置流程变量,即使流程变量的名称相同,后⼀次的版本的值也不会替换前⼀次版本的值,它会使⽤不同的任务ID作为标识,存放2个流程变量值,⽽且可以看到TASK_ID的字段会存放任务ID的值。例如act_hi_varinst 表的数据:不同的任务节点,即使流程变量名称相同,存放的值也是不同的。如图:使⽤setVariableLocal说明流程变量绑定了当前的任务,当流程继续执⾏时,下个任务获取不到这个流程变量(因为正在执⾏的流程变量中没有这个数据),所有查询正在执⾏的任务时不能查询到我们需要的数据,此时需要查询历史的流程变量。(18) 获取流程变量/**获取流程变量 */@Testpublic void getVariables(){ //任务ID String taskId = "55002"; //获取任务服务 TaskService taskService = kService();
/**⼀:获取流程变量,使⽤基本数据类型*/ Integer days = (Integer) iable(taskId, "请假天数"); Date date = (Date) iable(taskId, "请假⽇期"); String resean = (String) iable(taskId, "请假原因"); n("请假天数:"+days); n("请假⽇期:"+date); n("请假原因:"+resean);
/**⼆:获取流程变量,使⽤javabean类型*/ Person p = (Person)iable(taskId, "⼈员信息(添加固定版本)"); n(()+" "+e());}(19) 查询历史流程变量/**查询流程变量的历史表*/@Testpublic void findHistoryProcessVariables(){ List
if(list!=null && ()>0){ for(HistoricVariableInstance hvi:list){ n(()+" "+ cessInstanceId()+" "+ iableName()+" "+ iableTypeName()+" "+ ue()); n("###############################################"); } }}历史的流程变量查询,指定流程变量的名称,查询act_hi_varinst表(也可以针对,流程实例ID,执⾏对象ID,任务ID查询)(20) 查已执⾏任务 public DynamicFormConf getRunNodes(String processId) { DynamicFormConf dynamicFormConf = new DynamicFormConf(); //
获取流程历史中已执⾏节点,并按照节点在流程中执⾏先后顺序排序 List
已执⾏的节点ID集合 if(mpty(historicActivityInstanceList)){ Map
驳回到指定节点 * @param approvalOpinionVO //申请流程
审批信息 * @param task //任务信息 * @param map * @return */ @Override public boolean runNodes(ApprovalOpinionVO approvalOpinionVO, Task task, Map
//获取当前节点 String currActivityId = kDefinitionKey(); String processDefinitionId = cessDefinitionId(); BpmnModel bpmnModel = nModel(processDefinitionId); FlowNode currFlow = (FlowNode) nProcess().getFlowElement(currActivityId); if (null == currFlow) { List
//获取⽬标节点 FlowNode targetFlow = (FlowNode) wElement(NodeId()); //如果不是同⼀个流程(⼦流程)不能驳回 if (!(entContainer().equals(entContainer()))) { throw new CustomException("此处⽆法进⾏驳回操作"); } //记录原活动⽅向 List
审批意见 act_ru_variable
变量表
Map
驳回指定节点 List
实体类转换器(没有 vo dto
等要求的可以不⽤转换,直接⽤⼀个类就可以了)
ApprovalOpinionDTO applyOpinionDTO = 2dto(approvalOpinionVO); ApprovalOpinionDTO applyOpinionDTO = 2dto(approvalOpinionVO); gStr(kNodeName()+"撤回到"+e()); (applyOpinionDTO); (_APPLY_OPINION_LIST, String(approvalOpinionDTOs)); //完成节点任务 te((), map); //恢复原⽅向 goingFlows(oriSequenceFlows); return true; }针对与activiti6则⽤法如下 /** * @功能描述:
回退节点 * @版权信息: * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉30⽇ * @备注信息: TODO */ public void jump(String taskId){ //
任务服务 TaskService taskService = ProcessEngines .getDefaultProcessEngine() .getTaskService(); //
仓库服务 RepositoryService repositoryService = ProcessEngines .getDefaultProcessEngine() .getRepositoryService(); //
查询个⼈任务 Task currentTask = taskService .createTaskQuery() .taskId(taskId) .singleResult();
//
获取流程定义 Process process = repositoryService .getBpmnModel(cessDefinitionId()) .getMainProcess(); //
获取⽬标节点定义 FlowNode targetNode = (FlowNode)wElement("startTask");
//
管理服务 ManagementService managementService = ProcessEngines .getDefaultProcessEngine() .getManagementService();
//
删除当前运⾏任务-返回任务的连线 String executionEntityId = eCommand(new DeleteTaskCmd(()));
//
流程执⾏到来源节点 eCommand(new SetFLowNodeAndGoCmd(targetNode, executionEntityId)); }删除命令package
import
import
import
import
import
;NeedsActiveTaskCmd;CommandContext;ExecutionEntity;TaskEntity;TaskEntityManagerImpl;/** * @⽂件名称: * @功能描述:
删除任务命令 * @版权信息: * @技术交流: 961179337(QQ群) * @编写作者: lixx2048@ * @联系⽅式: 941415509(QQ) * @开发⽇期: 2021年7⽉30⽇ * @历史版本: V1.0
* @备注信息: TODO */public class DeleteTaskCmd extends NeedsActiveTaskCmd
public DeleteTaskCmd(String taskId){ super(taskId); }
public String execute(CommandContext commandContext, TaskEntity currentTask){ /** //
流程定义id String procDefId = cessDefinitionId(); //
获取服务 RepositoryService repositoryService = ProcessEngines .getDefaultProcessEngine() .getRepositoryService(); //
获取流程定义 s process = nModel(procDefId).getMainProcess();
//
获取需要提交的节点 FlowNode targetNode = (FlowNode)wElement(Key); */ //
获取所需服务 TaskEntityManagerImpl taskEntityManager = (TaskEntityManagerImpl)kEntityManager(); //
获取当前任务的来源任务及来源节点信息[任务的前⼀连线] ExecutionEntity executionEntity = cution(); //
删除当前任务,来源任务 Task(currentTask, "跳转删除", false, false); //
返回来源任务ID return (); }
public String getSuspendedTaskException() { return "挂起的任务不能跳转"; }}跳转命令package
import
import
import
import
import
import
List;;FlowNode;SequenceFlow;ActivitiException;Command;CommandContext;import ExecutionEntity;/** * @⽂件名称: * @功能描述:
根据提供节点和执⾏对象id,进⾏跳转命令 * @版权信息: * @技术交流: 961179337(QQ群) * @编写作者: lixx2048@ * @联系⽅式: 941415509(QQ) * @开发⽇期: 2021年7⽉30⽇ * @历史版本: V1.0
* @备注信息: TODO */public class SetFLowNodeAndGoCmd implements Command
⽬标节点 private FlowNode flowElement; private String executionId;
public SetFLowNodeAndGoCmd(FlowNode flowElement, String executionId){ ement = flowElement; ionId = executionId; }
public Void execute(CommandContext commandContext){ //
获取⽬标节点的来源连线 List
//
查询到执⾏的执⾏流[⽬标流连线] ExecutionEntity executionEntity = commandContext .getExecutionEntityManager() .findById(executionId); //
随便选⼀条连线来执⾏时 rentFlowElement((0));
//
随便选⼀条连线来执⾏时,当前执⾏计划为从连线流转到⽬标节点,实现跳转 commandContext .getAgenda() .planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
return null; }}(21) 导出流程定义⽂件 /** * @功能描述:
根据数据库中读取的流程模板⽣成bpmn * @编写作者: lixx2048@ * @开发⽇期: 2021年7⽉24⽇ * @历史版本: V1.0
* @参数说明:
导出model对象为指定类型 * @返
回
值: modelId
模型ID,导出⽂件类型(bpmnjson) */ @RequestMapping(value = "export/{modelId}") public void export(@PathVariable("modelId") String modelId, HttpServletResponse response) { try { //
获取仓库对象 ProcessEngine processEngine = aultProcessEngine(); RepositoryService repositoryService = ositoryService();
//
获取模型数据 Model modelData = el(modelId);
//
获取编辑器源 byte[] bytes = elEditorSource(()); String jsonModel = new String(bytes,"UTF-8");
//
转换为Bpmn模型 JsonNode editorNode = new ObjectMapper().readTree(elEditorSource(())); BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(editorNode); String filename = nProcess().getId() + ".bpmn";
//
转换为XML⽣成输⼊流 byte[] exportBytes = new BpmnXMLConverter().convertToXML(bpmnModel,"UTF-8"); String XML = new String(exportBytes,"UTF-8"); ByteArrayInputStream in = new ByteArrayInputStream(es("UTF-8"));
//
设置响应头为附件形式并返回xml⼆进制数据 der("Content-Disposition", "attachment; filename=" + filename); (in, putStream());
uffer(); //File(response, in, filename); (); } catch (Exception e) { ("导出model的xml⽂件失败!", e); } }7、activiti⽤户相关表介绍Activiti中内置了⼀套⽤户、⽤户组关系,以及对它们的操作API。关于⽤户和⽤户组表⼯业四张,如下图(1) ACT_ID_USER(⽤户信息表)(2)ACT_ID_INFO(⽤户扩展信息表)(3) ACT_ID_GROUP(⽤户组信息表)(4)ACT_ID_MEMBERSHIP(⽤户与⽤户组关系信息表)⽤户整合通常来说在项⽬中都已经是有了⽤户和⾓⾊权限功能。⽐如我创建了⼀个springboot项⽬,已经创建了⽤户表和⾓⾊表,那么如何将项⽬本⾝的⽤户和⾓⾊与activiti的⽤户、⽤户组整合在⼀起 .解决思路:在项⽬中创建了⽤户时,同时也需要将⽤户与Activiti的⽤户关联起来,直接通过id关联即可(1) 创建activiti⽤户package
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
@Controller@Slf4j@RequestMapping("/leave")public class LeaveController { @Autowired LeaveMapper leaveMapper;
Slf4j;Controller;RequestMapping;LeaveMapper;LeaveService;Test;Autowired;FileInputStream;FileNotFoundException;InputStream;ZipInputStream;HistoryService;IdentityService;ProcessEngine;RepositoryService;RuntimeService;TaskService;Group;User;Deployment;; @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Autowired private IdentityService identityService; @Autowired private RepositoryService repositoryService; @Autowired private ProcessEngine processEngine; @Autowired private HistoryService historyService;
@Autowired private LeaveService leaveService;
// 1、部署流程资源【第⼀种⽅式:classpath】 @RequestMapping("/deploy1") public void deploy1( ){
Deployment deployment = processEngine .getRepositoryService() //获取流程定义和部署对象相关的Service .createDeployment() //创建部署对象 .name("请假申请审核流程") //声明流程的名称 //加载资源⽂件,⼀次只能加载⼀个⽂件 .addClasspathResource("processes/") .addClasspathResource("processes/") .deploy();//完成部署
n("部署ID:"+());//1 n("部署时间:"+loymentTime());
}
// 2、创建Activiti⽤户 @RequestMapping("/addUser") public void addUser( ){
//项⽬中每创建⼀个新⽤户,对应的要创建⼀个Activiti⽤户,两者的userId和userName⼀致
//添加⽤户 User user1 = r("user1"); stName("张三"); tName("张"); sword("123456"); il("zhangsan@"); er(user1);
User user2 = r("user2"); stName("李四"); tName("李"); sword("123456"); il("lisi@"); er(user2);
User user3 = r("user3"); stName("王五"); tName("王"); sword("123456"); il("wangwu@"); er(user3);
User user4 = r("user4"); stName("吴六"); tName("吴"); sword("123456"); il("wuliu@"); er(user4); }}启⽤应⽤,发现 act_id_user表 新增了四条⽤户信息(2)查询activiti⽤户 //3、根据id查询Activiti⽤户 @RequestMapping("/queryUser") public void queryUser( ){
User user = identityService .createUserQuery() .userId("user1") .singleResult();
n(()); n(stName()); n(tName()); n(sword()); n(il()); }(3)创建Activiti⽤户组Activiti中的⽤户组信息相当于权限系统当中的⾓⾊,⽤户可以属于多个⽤户组,⽤户组也可以包含多个⽤户,同⼀个⽤户组当中的⽤户具有相同的权限。 //4、创建Activiti⽤户组 @RequestMapping("/addGroup") public void addGroup( ){
Group group1 = up("group1"); e("员⼯组"); e("员⼯组"); oup(group1);
Group group2 = up("group2"); e("总监组"); e("总监阻"); oup(group2);
Group group3 = up("group3"); e("经理组"); e("经理组"); oup(group3);
Group group4 = up("group4"); e("⼈⼒资源组"); e("⼈⼒资源组"); oup(group4); }(4)查询Activiti⽤户组 //5、通过⽤户组id查询Activiti⽤户组 @RequestMapping("/queryGroup") public void queryGroup( ){ Group group = identityService .createGroupQuery() .groupId("group1") .singleResult();
n(()); n(e()); n(e()); }(5) 创建Activiti(⽤户-⽤户组)关系 //6、创建Activiti(⽤户-⽤户组)关系 @RequestMapping("/addMembership") public void addMembership( ){ Membership("user1", "group1");//user1
在员⼯阻 Membership("user2", "group2");//user2在总监组 Membership("user3", "group3");//user3在经理组 Membership("user4", "group4");//user4在⼈⼒资源组 }(6)查询属于⽤户组group1的⽤户 //7、查询属于组group1的⽤户 @RequestMapping("/queryUserListByGroup") public void queryUserListByGroup( ){
//查询属于组group1的⽤户 List
for (User user : usersInGroup) { n(stName()); } }(7)查询⽤户user1所属于的⽤户组 //8、查询user1所属于的组 @RequestMapping("/queryGroupListByUser") public void queryGroupListByUser( ){
//查询user1所属于的组 List
for (Group group : groupsForUser) { n(e()); } }⽤户组任务(候选⼈)(1)相关概念任务负责⼈:每⼀个任务都有⼀个任务负责⼈assignee任务候选⼈:在流程定义中在任务结点的 assignee 可以固定设置任务负责⼈,在流程定义时将参与者固定设置在.bpmn ⽂件中,如果临时任务负责⼈变更则需要修改流程定义,系统可扩展性差。 针对这种情况可以给任务设置多个候选⼈,可以从候选⼈中选择参与者来完成任务。(2)候选⼈设置在流程定义中设置任务候选⼈:在流程图中任务节点的配置中设置 candidate-users(候选⼈),多个候选⼈之间⽤逗号分开在线编辑器设置如下设置成功后可看到bpmn⽂件配置 assignee="xiaozhang" assignee="xiaowang" exclusive="true" id="_3" name="申请请单"/>exclusive="true" id="_4" name="部门经理审批"/>exclusive="true" id="_5" name="总经理批"/>candidateUsers="zhangsan,lisi" 我们可以看到部门经理的审核⼈已经设置为 zhangsan,lisi 这样的⼀组候选⼈,可以使activiti:candiateUsers=”⽤户1,⽤户2,⽤户3”的这种⽅式来实现设置⼀组候选⼈。(3)⽤户流程如果⼀个任务指定了候选⼈列表,那么⽤户处理流程⼜是如何的?第⼀步:查询组任务 指定候选⼈,查询该候选⼈当前的待办任务。 候选⼈不能办理任务。第⼆步:拾取(claim)任务拾取任务:该组任务的所有候选⼈都能拾取该任务。 ⽤户被拾取之后将候选⼈的组任务变成了个⼈任务。原来候选⼈就变成了该任务的负责⼈。归还任务:如果拾取后不想办理该任务了, 需要将已经拾取的个⼈任务归还到组⾥边,将个⼈任务变成了组任务。第三步:查询个⼈任务查询⽅式同个⼈任务部分,根据 assignee 查询⽤户负责的个⼈任务。第四步:办理个⼈任务⽤户变为任务负责⼈之后,查询到个⼈任务就可以完成⾃⼰的任务了。设计到的api#根据⽤户负责⼈查询任务TaskQuery().taskAssignee(param);#根据⽤户候选⼈查询任务TaskQuery().taskCandidateUser(param); #根据后选择查询任务TaskQuery().taskCandidateGroup(param);#设置任务负责⼈(班⾥⼈)TaskService().setAssignee(taskId,userId);#设置后选择taskService().addCandidateGroup(taskId, groupid);#签收(拾取)任务(taskId, currentUserId);根据以上⽤户候选⼈处理任务流程,我们的实现逻辑实例如下所⽰部署流程定义 public static void main(String[] args) { //1.创建ProcessEngine对象 ProcessEngine processEngine = aultProcessEngine(); //2.得到RepositoryService实例 RepositoryService repositoryService = ositoryService(); //3.进⾏部署 Deployment deployment = Deployment() .addClasspathResource("diagram/") //添加bpmn资源 .name("请假申请单流程") .deploy(); //4.输出部署的⼀些信息 n(e()); n(()); }启动流程实例 public static void main(String[] args) { //1.得到ProcessEngine对象 ProcessEngine processEngine = aultProcessEngine(); //2.得到RunService对象 RuntimeService runtimeService = timeService(); //3.创建流程实例 流程定义的key需要知道 holiday ProcessInstance processInstance = rocessInstanceByKey("holiday5"); //4.输出实例的相关信息 n("流程定义ID" + cessDefinitionId());//holiday:1:4 n("流程实例ID" + ());//2501 }执⾏任务先由之前固定写好的xiaozhang去申请请假单 public static void main(String[] args) { //1.得到ProcessEngine对象 ProcessEngine processEngine = aultProcessEngine(); //2.得到TaskService对象 TaskService taskService = kService(); //3.查询当前⽤户的任务 Task task = TaskQuery() Task task = TaskQuery() .processDefinitionKey("holiday5") .taskAssignee("xiaozhang") .singleResult(); //4.处理任务,结合当前⽤户任务列表的查询操作的话,任务ID:() if (task != null) { te(()); n("⽤户任务执⾏完毕..."); } //5.输出任务的id n(()); }查询⽤户组任务 public static void main(String[] args) { //1.得到ProcessEngine对象 ProcessEngine processEngine = aultProcessEngine(); //2.得到TaskService对象 TaskService taskService = kService(); //3.设置⼀些参数,流程定义的key,候选⽤户 String key = "holiday5"; String candidateUsers = "zhangsan"; //4.执⾏查询 List //5.输出 for (Task task : list) { n(cessInstanceId()); n(()); n(e()); n(ignee());//为null,说明当前的zhangsan只是⼀个候选⼈,并不是任务的执⾏⼈ } }拾取任务public static void main(String[] args) { //1.得到ProcessEngine对象 ProcessEngine processEngine = aultProcessEngine(); //2.得到TaskService对象 TaskService taskService = kService(); //3.设置⼀些参数,流程定义的key,候选⽤户 String key = "holiday5"; String candidateUsers = "zhangsan"; //4.执⾏查询 Task task = TaskQuery() .processDefinitionKey(key) .taskCandidateUser(candidateUsers)//设置候选⽤户 .singleResult(); if (task != null) { ((), candidateUsers);//第⼀个参数任务ID,第⼆个参数为具体的候选⽤户名 n("任务拾取完毕!"); } }查询⽤户当前任务 public static void main(String[] args) { //1.得到ProcessEngine对象 ProcessEngine processEngine = aultProcessEngine(); //2.得到TaskService对象 TaskService taskService = kService(); //3.设置⼀些参数,流程定义的key,⽤户 String key = "holiday5"; String assignee = "zhangsan"; //4.执⾏查询 List //5.输出 for (Task task : list) { n(cessInstanceId()); n(()); n(e()); n(ignee());//任务的执⾏⼈ } }执⾏任务 public static void main(String[] args) { //1.得到ProcessEngine对象 ProcessEngine processEngine = aultProcessEngine(); //2.得到TaskService对象 TaskService taskService = kService(); //3.设置⼀些参数,流程定义的key,⽤户 String key = "holiday5"; String assignee = "zhangsan"; //4.执⾏查询 Task task = TaskQuery() .processDefinitionKey(key) .taskAssignee(assignee) //设置任务的负责⼈ .singleResult(); //5.执⾏当前的任务 if (task != null) { te(()); n("任务执⾏完毕!"); } }归还组任务/任务交接归还:如果个⼈不想办理该组任务,可以归还组任务,归还后该⽤户不再是该任务的负责⼈归还任务后,回到拾取任务之前的状态,zhangsan和lisi都可以去重新拾取这⼀⼯单。交接:任务负责⼈将任务交给其它候选⼈办理该任务
发布者:admin,转转请注明出处:http://www.yc00.com/news/1690213175a315759.html
评论列表(0条)