工作流引擎之-activiti6使用

一、前言

在很多项目中,我们都很有可能用到了工作流处理逻辑,比如类同钉钉的申请流程。所有的流程都具有相同的特点

  • 由一个起点发起
  • 中间分割为多个流
  • 最后汇聚到一个终点
  • 其中可以由处理节点和通知节点

等等,所有的业务流程处理逻辑大体相同,为什么不可以将相同的流程化的逻辑封装起来模块化组件化的方式用于其他项目当中呢?其实针对这个问题,网络上早有许多开源项目将其封装,而且都有不错的扩展性。其中比较热门的较activiti和flowable了,当然,本文的重点调研的是activiti的使用,所以其后所有内容重点介绍的是activiti,flowable在这里仅做比对,不会有任何体现,以下文章我们以这两个框架进行简单介绍。

声明:

(1)以下部分代码摘自网络,但是网络上的代码大部分是无法串联起来的,特别是相关的概念和使用流程上进行的很大部分说明,所以很大部分都是经过加工,使得各位童鞋在学习的时候能够在理解核心概念后从事开发,这样就能顺手拈来定制或改造自己的业务。

(2)学习的时候我也是带着疑问去调研的,所以目录接口上就能体现我疑问点,可能排列顺序逻辑稍有问题,请学习的同学务必完所有文章在提问。

1、历史记录

关于流程处理的开源项目比较多,其中使用比较多的属activiti和flowable开源框架了。他们两个团队的历史如下

  • Tijs Rademakers,算是activiti5以及6比较核心的leader了。现在是flowable框架的leader

  • Joram 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框架中已经修复的差不多了

扫描二维码关注公众号,回复: 14606416 查看本文章
  • activiti的github地址:https://github.com/Activiti/Activiti

  • activiti5以及ativiti6的核心开发团队是Tijs Rademakers团队。activiti6最终版本由Salaboy团队发布的。可能很多人有疑惑,activiti6核心代码是Tijs Rademakers团队开发的,为何是Salaboy团队发布的呢?很简单,因为这个时候Tijs Rademakers团队已经去开发flowable去了。flowable是基于activiti-6.0.0.Beta4 分支开发的。下面我们截图一些flowable的发展。

  • flowable的github地址:https://github.com/flowable/flowable-engine

关于更多的Activiti6视频地址:https://ke.qq.com/course/package/11402?tuin=84de321b

关于更多的Flowable视频地址:https://ke.qq.com/course/package/11431?tuin=84de321b

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 和 ActivityImpl
  • Task和TaskEntity
  • ExecutionEntity和ExecutionImpl

命名类似,加上Activiti使用了大量的设计模式,代码引用层次非常深,让人阅读代码时容易混乱。 所以将ACTIVITI的对象按生命时期划分则容易区分,逐个时期理解,降低阅读难度。

  • 模型期
    流程设计完成后,会产生一个xml流程设计文件,Activiti通过流程配置对象实例化BpmnParseHandler集合,完成xml解析到模型期对象。

  • 部署期

    部署期主要对象为ProcessDefinitionEntity,通过RepositoryService来部署。

  • 运行期
    主要通过RuntimeService和TaskService来实现流程实例、分支、任务对象的创建和执行。

  • 历史期
    通过HistoryService查询历史信息。

模型期对象:Activity和Task

系统类图请添加图片描述

  • Activity的定义

    Activity是对应流程XML定义的所有元素的抽象父类,即Bpmn.xml中的各种元素(包括任务、网关、子流程等),流程定义基于Actitity的子类来完成整个流程定义的构造。

    Activity和ActivityImpl的区别

    Acitity对应的流程元素的定义,是定义期对象。ActitityImpl是流程实例运行期间具体活动的实例对象,是运行期对象。

  • Task是一个Actitity的空的抽象子类,但是Activiti支持的所有任务类型均是它的子类或孙子类。

    在Activiti中有两个Task类,一个是model包下的抽象类,是模型期的;一个是task包下的Task接口,是运行期的。

    任务类图
    请添加图片描述

部署期对象:ProcessDefinitionEntity

ProcessDefinitionEntity的类结构图:
请添加图片描述

图比较大,主要是要把关键的对象都纳入进来。

  • ProcessDefinitionEntity的父类ProcessDefinitionImpl聚合了ActivityImpl初始化活动和集合活动。
  • 继承ScopeImpl:聚合了ExecutionListener,这是Activiti的事件扩展点。
  • Pvm抽象基础类:PvmProcessDefinition --> ReadOnlyProcessDefinition -->PvmScope–>PvmProcessElement。PVM主要就是负责流程整个运行期的执行、流转等所有运行过程。关于PVM值得专门撰写,对于Activiti的深入个性化扩展(比如驳回、跳转、分发汇总等)都会直接处理Pvm对象。
  • 聚合了TaskDefinition:TaskDefinition是基于Task对象解析获得的,可以参考UserTaskParseHandler代码。
  • 实现了PersistentObject、HasRevision:表示它是持久化的,并且有版本控制。

运行期对象:TaskEntity、ActivityImpl、ExecutionEntity和ExecutionImpl

  • TaskEntity:任务对象,实现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 = ProcessEngines.getDefaultProcessEngine();
  • 【3仓库服务RepositoryService】
  RepositoryService repositoryService = processEngine.getRepositoryService();
  • 【4运行时服务RuntimeService】
  RuntimeService runtimeService = processEngine.getRuntimeService();
  • 【5任务服务TaskService】
  TaskService taskService = processEngine.getTaskService();
  • 【6任务服务TaskService】
HistoryService historyService = processEngine.getHistoryService();
  • ProcessDefinition

    流程定义类。可以从这里获得资源文件等。

  • ProcessInstance

    代表流程定义的执行实例。如员工请了一天的假,他就必须发出一个流程实例的申请。

    一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。流程实例就表示一个流程从开始到结束的最大的流程分支,即一个流程中流程实例只有一个。

  • Execution

    Activiti用这个对象去描述流程执行的每一个节点。在没有并发的情况下,Execution就是同ProcessInstance。流程按照流程定义的规则执行一次的过程,就可以表示执行对象Execution。

    ProcessInstance就是Execution。但在现实意义上有所区别
    请添加图片描述

    在单线流程中,如上图的贷款流程,ProcessInstance与Execution是一致的。
    请添加图片描述

    wire money(汇钱)和archive(存档)是并发执行的。 这个时候,总线路代表ProcessInstance,而分线路中每个活动代表Execution。

    总结如下:

    • 一个流程中,执行对象可以存在多个,但是流程实例只能有一个
    • 当流程按照规则只执行一次的时候,那么流程实例就是执行对象

如下为各个服务的衍生代码

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    //管理流程定义
    RepositoryService repositoryService = processEngine.getRepositoryService();
    //执行管理,包括启动、推进、删除流程实例等
    RuntimeService runtimeService = processEngine.getRuntimeService();
    //任务管理
    TaskService taskService = processEngine.getTaskService();
    //历史管理(执行完的数据的管理
    HistoryService historyService = processEngine.getHistoryService();
    //组织机构管理
    IdentityService identityService = processEngine.getIdentityService();
    //可选服务,任务表单管理
    FormService formService = processEngine.getFormService();

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_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.cfg.xml是 Activiti核心配置文件,配置流程引擎创建工具的基本参数和数据库连接池参数。 定义数据库配置参数

  • jdbcUrl: 数据库的JDBC URL。
  • jdbcDriver: 对应不同数据库类型的驱动。
  • jdbcUsername: 连接数据库的用户名。
  • jdbcPassword: 连接数据库的密码。

连接池配置

基于JDBC参数配置的数据库连接 会使用默认的MyBatis连接池。 下面的参数可以用来配置连接池

  • jdbcMaxActiveConnections: 连接池中处于被使用状态的连接的最大值。默认为10。

  • jdbcMaxIdleConnections: 连接池中处于空闲状态的连接的最大值。

  • jdbcMaxCheckoutTime: 连接被取出使用的最长时间,超过时间会被强制回收。 默认为20000(20秒)。

  • jdbcMaxWaitTime: 这是一个底层配置,让连接池可以在长时间无法获得连接时, 打印一条日志,并重新尝试获取一个连接。(避免因为错误配置导致沉默的操作失败)。 默认为20000(20秒)。
    请添加图片描述

    也可以使用javax.sql.DataSource
    请添加图片描述

网关说明
  • 排他网关

    排他网关显示成一个普通网关(比方。菱形图形), 内部是一个“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 com.easystudy.conf;

import java.io.IOException;
import javax.sql.DataSource;

import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.spring.ProcessEngineFactoryBean;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.AbstractProcessEngineAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.PlatformTransactionManager;

/**
 * @文件名称: ActivitiConfig.java
 * @功能描述: activiti启动配置
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: [email protected]
 * @联系方式: 941415509(QQ)
 * @开发日期: 2021年7月24日
 * @历史版本: V1.0
 */
@Configuration
public class ActivitiConfig extends AbstractProcessEngineAutoConfiguration {
    
    
 
	/**
	 * @功能描述: 创建启动配置类
	 * @编写作者: [email protected]
	 * @开发日期: 2021年7月24日
	 * @历史版本: V1.0  
	 * @参数说明: 生成启动配置之后会创建所有表
	 * @返  回  值:
	 */
    @Bean
    public SpringProcessEngineConfiguration springProcessEngineConfiguration(DataSource dataSource,PlatformTransactionManager platformTransactionManager){
    
    
        // 创建配置对象
    	SpringProcessEngineConfiguration spec = new SpringProcessEngineConfiguration();
    	// 设置数据源
        spec.setDataSource(dataSource);
        // 设置事务管理器
        spec.setTransactionManager(platformTransactionManager);
        // 创建或更新表结构
        spec.setDatabaseSchemaUpdate("true");
        // 获取所有资源
        Resource[] resources = null;
        try {
    
    
            resources = new PathMatchingResourcePatternResolver().getResources("classpath*:processes/*.bpmn");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        // 启动自动部署流程
        spec.setDeploymentResources(resources);
        return spec;
    }
    
    /**
     * @功能描述: 生成核心处理引擎对象
     * @编写作者: [email protected]
     * @开发日期: 2021年7月24日
     * @历史版本: V1.0  
     * @参数说明:
     * @返  回  值:
     */
    @Bean
    public ProcessEngineFactoryBean processEngine(SpringProcessEngineConfiguration engineConfiguration){
    
    
        ProcessEngineFactoryBean processEngineFactoryBean = new ProcessEngineFactoryBean();
        processEngineFactoryBean.setProcessEngineConfiguration(engineConfiguration);
        return processEngineFactoryBean;
    }
 
    /**
     * @功能描述: 生成仓库管理对象
     * @编写作者: [email protected]
     * @开发日期: 2021年7月24日
     * @历史版本: V1.0  
     * @参数说明:管理流程定义、资源等
     * @返  回  值:
     */
    @Bean
    public RepositoryService repositoryService(ProcessEngineFactoryBean engineFactoryBean) throws Exception{
    
    
        return engineFactoryBean.getObject().getRepositoryService();
    }
    
    /**
     * @功能描述: 生成运行时服务对象
     * @编写作者: [email protected]
     * @开发日期: 2021年7月24日
     * @历史版本: V1.0  
     * @参数说明:
     * @返  回  值:
     */
    @Bean
    public RuntimeService runtimeService(ProcessEngineFactoryBean engineFactoryBean) throws Exception{
    
    
        return engineFactoryBean.getObject().getRuntimeService();
    }
    
    /**
     * @功能描述: 生成任务对象
     * @编写作者: [email protected]
     * @开发日期: 2021年7月24日
     * @历史版本: V1.0  
     * @参数说明:个人任务查询等
     * @返  回  值:
     */
    @Bean
    public TaskService taskService(ProcessEngineFactoryBean engineFactoryBean) throws Exception{
    
    
        return engineFactoryBean.getObject().getTaskService();
    }
    
    /**
     * @功能描述: 生成历史管理对象
     * @编写作者: [email protected]
     * @开发日期: 2021年7月24日
     * @历史版本: V1.0  
     * @参数说明:
     * @返  回  值:
     */
    @Bean
    public HistoryService historyService(ProcessEngineFactoryBean engineFactoryBean) throws Exception{
    
    
        return engineFactoryBean.getObject().getHistoryService();
    }
}
(3) activiti使用

接下来就是几个核心类的使用,围绕(1)中介绍的使用流程调用相关接口部署流程定义、启动流程实例、查询流程任务和处理流程任务即可,下面我们会针对相关的接口进行详细介绍。最核心的接口类为ProcessEngine,他可以通过ProcessEngines获取,而其他的仓库服务、运行时服务、历史服务够可以通过ProcessEngine衍生获取,这里如前所述不再介绍!

6、接口调用说明

(1) 流程部署定义
/**
 * 部署流程定义
 */
@Test
public void deploymentProcessDefinition(){
    
    
    //获取流程定义与部署相关Service
    Deployment deployment = processEngine.getRepositoryService()
        	//创建一个部署对象
            .createDeployment()
        	//指定部署名称
            .name("helloworld入门程序")
        	//加载资源文件
            .addClasspathResource("diagrams/helloworld.bpmn")
        	//完成部署
            .deploy();
    System.out.println(deployment.getId());
    System.out.println(deployment.getName());
}
(2) 启动流程实例

部署流程定义之后,可以通过流程定义的id或者流程定义的key来启动,这里需要注意,流程定义id的生成规则

key:版本:生成ID

其中key为流程定义bpmn文件文件中的process节点的id值如(ask4Leave),key属性被用来区别不同的流程定义.带有特定key的流程定义第一次部署时,version为1。之后每次部署都会在当前最高版本号上加1,如流程定义id如下

apply4Leave:1:15006
vacationProcess:1:4
apply4Leave:2:15011
vacationProcess:2:5004
apply4Leave:3:17504
vacationProcess:3:15007

查询流程定义可以通过如下接口获取

@RequestMapping(value = "findProcessDefinition")
    public void findProcessDefinition() {
    
    
    	// 查询所有流程定义
    	List<ProcessDefinition> list = ProcessEngines
    											.getDefaultProcessEngine()
    											.getRepositoryService()
    											.createProcessDefinitionQuery()
    											.orderByProcessDefinitionVersion()
    											.asc()
    											.list();
    	// 打印所有流程定义
    	if (list != null && list.size() > 0) {
    
    
            for (ProcessDefinition pd : list) {
    
    
            	// 流程定义的key+版本+随机生成数
                System.out.println("流程定义ID:" + pd.getId());
                // 对应helloworld.bpmn文件中的name属性值
                System.out.println("流程定义的名称:" + pd.getName());
                // 对应helloworld.bpmn文件中的id属性值
                System.out.println("流程定义的key:" + pd.getKey());
                // 当流程定义的key值相同的相同下,版本升级,默认1
                System.out.println("流程定义的版本:" + pd.getVersion());
                System.out.println("资源名称bpmn文件:" + pd.getResourceName());
                System.out.println("资源名称png文件:" + pd.getDiagramResourceName());
                System.out.println("部署对象ID:" + pd.getDeploymentId());
                System.out.println("--------------------------");
            }
        }
    }

获取到流程定义id之后,我们即可使用流程定义id来启动流程实例

    /**
     * @功能描述: 通过流程定义id启动流程实例
     * @编写作者: [email protected]
     * @开发日期: 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 = runtimeService.startProcessInstanceById(processDefinitionId);
    	return processInstance.getId();
    }

注意:

(1)如果资源目录process子目录下存在流程定义bpmn文件,则系统启动的时候回被自动加载到流程定义表中(act_re_procdef),但是不会自动部署!

(2)存储流程定义相关的部署信息。即流程定义文档的存放地。每部署一次就会增加两条记录,一条是关于bpmn规则文件的,一条是图片的(如果部署时只指定了bpmn一个文件,activiti会在部署时解析bpmn文件内容自动生成流程图)。两个文件不是很大,都是以二进制形式存储在数据库中。

当然也可以通过key来部署

/**
 * 启动流程实例
 */
@Test
public void startProcessInstance(){
    
    
    //获取与正在执行的流程示例和执行对象相关的Service
    ProcessInstance processInstance = processEngine
        	//获取运行时服务
        	.getRuntimeService()
            //使用流程定义的key启动实例,key对应bpmn文件中id的属性值,默认按照最新版本流程启动
            .startProcessInstanceByKey("helloworld");
    
    System.out.println(processInstance.getId());
    System.out.println(processInstance.getProcessDefinitionId());
}
(3) 查看我的任务
/**
 * 查询当前的个人任务
 */
@Test
public void findPersonalTask(){
    
    
    // 与正在执行的任务相关的Service
    List<Task> list = processEngine
        		//获取人物服务
                .getTaskService()
        		//创建查询任务对象
                .createTaskQuery()
        		//指定个人任务查询,指定办理人
                .taskAssignee("王五")
                .list();
    
    if(list != null && list.size() > 0){
    
    
        for(Task task : list){
    
    
            System.out.println(task.getId());
            System.out.println(task.getName());
            System.out.println(task.getCreateTime());
            System.out.println(task.getAssignee());
            System.out.println(task.getProcessInstanceId());
            System.out.println(task.getExecutionId());
            System.out.println(task.getProcessDefinitionId());
        }
    }
}
(4) 完成我的任务
/**
 * 完成我的任务
 */
@Test
public void completePersonalTask(){
    
    
    processEngine.getTaskService()
            	 .complete("7502");
}
(5) 部署流程定义
  • 通过模型ID部署流程定义

    如果我们已经通过在线编辑器保存了模型,那么我们就可以直接使用模型id部署对应的流程

    @RequestMapping(value = "deploy/{modelId}")
    public String deploy(@PathVariable("modelId") String modelId) {
          
          
    	try {
          
          
    		// 获取数据仓库服务
    		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    		RepositoryService repositoryService = processEngine.getRepositoryService();
    		
    		// 根据模型id获取模型编辑源信息
    		Model modelData = repositoryService.getModel(modelId);
            ObjectMapper objectMapper = new ObjectMapper();
    		ObjectNode modelNode = (ObjectNode) objectMapper
                .readTree(repositoryService.getModelEditorSource(modelData.getId()));
    
    		// 转化成XML
    		BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
    		byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
    
    		// 创建并部署流程
    		String processName = modelData.getName() + ".bpmn20.xml";
    		Deployment deployment = repositoryService
                            .createDeployment()
                            .name(modelData.getName())
                            .addString(processName, new String(bpmnBytes,"UTF-8"))
                            .deploy();
    		
    		// 返回流程部署ID
    		return "部署成功,部署ID=" + deployment.getId();
    	} catch (Exception e) {
          
          
    		log.error("根据模型部署流程失败:modelId="+ modelId, e);
    		return "部署流程失败!流程图错误";
    	}
    }
    

    以上通过addString方法添加xml模型定义描述来实现流程的部署。

  • 通过classpath部署流程定义

    /**部署流程定义(从classpath)*/
    @Test
    public void deploymentProcessDefinition_classpath(){
          
          
        Deployment deployment = processEngine
            	//与流程定义和部署对象相关的Service
            	.getRepositoryService()	
            	//创建一个部署对象
                .createDeployment()	
            	//添加部署的名称
                .name("流程定义")		 
            	//从classpath的资源中加载,一次只能加载一个文件
                .addClasspathResource("diagrams/helloworld.bpmn")
            	//从classpath的资源中加载,一次只能加载一个文件
                .addClasspathResource("diagrams/helloworld.png")
            	//完成部署
                .deploy();
        
        System.out.println("部署ID:"+deployment.getId());
        System.out.println("部署名称:"+deployment.getName());
    }
    

    以上通过addClasspathResource方法添加流程资源文件来实现流程的部署

  • 通过zip文件部署流程定义

    /**部署流程定义(从zip)*/
    @Test
    public void deploymentProcessDefinition_zip(){
          
          
        InputStream in = this.getClass().getClassLoader()
            			.getResourceAsStream("diagrams/helloworld.zip");
        ZipInputStream zipInputStream = new ZipInputStream(in);
        
        Deployment deployment = processEngine
            	//与流程定义和部署对象相关的Service
            	.getRepositoryService()	
            	//创建一个部署对象
                .createDeployment()		
            	//添加部署的名称
                .name("流程定义")		 
            	//指定zip格式的文件完成部署
                .addZipInputStream(zipInputStream)
            	//完成部署
                .deploy();
        
        System.out.println("部署ID:"+deployment.getId());
        System.out.println("部署名称:"+deployment.getName());
    }
    

    以上通过addZipInputStream添加流程定义zip文件流来实现流动的部署。

    这一步在数据库中将操作三张表:

    a) act_re_deployment(部署对象表)

    存放流程定义的显示名和部署时间,每部署一次增加一条记录

    b) act_re_procdef(流程定义表)

    存放流程定义的属性信息,部署每个新的流程定义都会在这张表中增加一条记录。

    注意:当流程定义的key相同的情况下,使用的是版本升级

    c) act_ge_bytearray(资源文件表)

    存储流程定义相关的部署信息。即流程定义文档的存放地。每部署一次就会增加两条记录,一条是关于bpmn规则文件的,一条是图片的(如果部署时只指定了bpmn一个文件,activiti会在部署时解析bpmn文件内容自动生成流程图)。两个文件不是很大,都是以二进制形式存储在数据库中。

(6) 查看流程定义
/**查询流程定义*/
@Test
public void findProcessDefinition(){
    
    
    //与流程定义和部署对象相关的Service
    List<ProcessDefinition> list = processEngine
        	.getRepositoryService()
        	//创建一个流程定义的查询
            .createProcessDefinitionQuery()
            /**指定查询条件,where条件*/
        	//使用部署对象ID查询
            //.deploymentId(deploymentId)
        	//使用流程定义ID查询
            //.processDefinitionId(processDefinitionId)
        	//使用流程定义的key查询
            //.processDefinitionKey(processDefinitionKey)
        	//使用流程定义的名称模糊查询
            //.processDefinitionNameLike(processDefinitionNameLike)
            /**排序*/
            .orderByProcessDefinitionVersion()
        	//按照版本的升序排列
        	.asc()
            //.orderByProcessDefinitionName()
        	//按照流程定义的名称降序排列
        	//.desc()
            /**返回的结果集*/
        	//返回一个集合列表,封装流程定义
            .list();
    		//返回惟一结果集
            //.singleResult();	
    		//返回结果集数量
            //.count();
    		//分页查询
            //.listPage(firstResult, maxResults);	
    
    if(list!=null && list.size()>0){
    
    
        for(ProcessDefinition pd:list){
    
    
            //流程定义的key+版本+随机生成数
            System.out.println("流程定义ID:"+pd.getId());
            //对应helloworld.bpmn文件中的name属性值
            System.out.println("流程定义的名称:"+pd.getName());
            //对应helloworld.bpmn文件中的id属性值
            System.out.println("流程定义的key:"+pd.getKey());
            //当流程定义的key值相同的相同下,版本升级,默认1
            System.out.println("流程定义的版本:"+pd.getVersion());
            System.out.println("资源名称bpmn文件:"+pd.getResourceName());
            System.out.println("资源名称png文件:"+pd.getDiagramResourceName());
            System.out.println("部署对象ID:"+pd.getDeploymentId());
         System.out.println("#########################################################");
        }
    }
}

可以看到流程定义的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。之后每次部署都会在当前最高版本号上加1

  • Id的值的生成规则为:{processDefinitionKey}:{processDefinitionVersion}:{generated-id}, 这里的generated-id是一个自动生成的唯一的数字

  • 重复部署一次,deploymentId的值以一定的形式变化

    规则act_ge_property表生成
    请添加图片描述

(7) 删除流程定义
/**删除流程定义*/
@Test
public void deleteProcessDefinition(){
    
    
    //使用部署ID,完成删除
    String deploymentId = "601";
    
    //不带级联的删除:只能删除没有启动的流程,如果流程启动,就会抛出异常
	//processEngine
    //		.getRepositoryService()
    //   	.deleteDeployment(deploymentId);

    //级联删除:不管流程是否启动,都能可以删除
    processEngine
        	.getRepositoryService()
            .deleteDeployment(deploymentId, true);
    System.out.println("删除成功!");
}

如果该流程定义下没有正在运行的流程,则可以用普通删除。如果是有关联的信息,用级联删除。项目开发中使用级联删除的情况比较多,删除操作一般只开放给超级管理员使用。

(8) 获取流程的资源
/**查看流程图
 * @throws IOException */
@Test
public void viewPic() throws IOException {
    
    
    /**将生成图片放到文件夹下*/
    String deploymentId = "801";
    //获取图片资源名称
    List<String> list = processEngine
        				// 获取资源仓库服务
        				.getRepositoryService()
        				// 通过部署id获取部署资源
            			.getDeploymentResourceNames(deploymentId);
    
    //定义图片资源的名称
    String resourceName = "";
    if(list!=null && list.size()>0){
    
    
        for(String name:list){
    
    
            if(name.indexOf(".png")>=0){
    
    
                resourceName = name;
            }
        }
    }
    
    //获取图片的输入流
    InputStream in = processEngine
        			.getRepositoryService()
            		.getResourceAsStream(deploymentId, resourceName);
    
    //将图片生成到D盘的目录下
    File file = new File("D:/"+resourceName);
    //将输入流的图片写到D盘下
    FileUtils.copyInputStreamToFile(in, file);
}

查询出流程定义文档。主要查的是图片,用于显示流程用。 deploymentId为流程部署ID,resourceName为act_ge_bytearray表中NAME_列的值,使用repositoryService的getDeploymentResourceNames方法可以获取指定部署下得所有文件的名称,使用repositoryService的getResourceAsStream方法传入部署ID和资源图片名称可以获取部署下指定名称文件的输入流,最后的有关IO流的操作,使用FileUtils工具的copyInputStreamToFile方法完成流程流程到文件的拷贝,将资源文件以流的形式输出到指定文件夹下。

(9) 查询最新版流程定义
/***附加功能:查询最新版本的流程定义*/
@Test
public void findLastVersionProcessDefinition(){
    
    
    List<ProcessDefinition> list = processEngine
        	.getRepositoryService()
            .createProcessDefinitionQuery()
        	//使用流程定义的版本升序排列
            .orderByProcessDefinitionVersion().asc()
            .list();
    
    /**
     Map<String,ProcessDefinition>
     map集合的key:流程定义的key
     map集合的value:流程定义的对象
     map集合的特点:当map集合key值相同的情况下,后一次的值将替换前一次的值
     */
    Map<String, ProcessDefinition> map = new LinkedHashMap<String, ProcessDefinition>();
    if(list!=null && list.size()>0){
    
    
        for(ProcessDefinition pd:list){
    
    
            map.put(pd.getKey(), pd);
        }
    }
    
    List<ProcessDefinition> pdList = new ArrayList<ProcessDefinition>(map.values());
    if(pdList!=null && pdList.size()>0){
    
    
        for(ProcessDefinition pd:pdList){
    
    
            //流程定义的key+版本+随机生成数
            System.out.println("流程定义ID:"+pd.getId());
            //对应helloworld.bpmn文件中的name属性值
            System.out.println("流程定义的名称:"+pd.getName());
            //对应helloworld.bpmn文件中的id属性值
            System.out.println("流程定义的key:"+pd.getKey());
            //当流程定义的key值相同的相同下,版本升级,默认1
            System.out.println("流程定义的版本:"+pd.getVersion());
            System.out.println("资源名称bpmn文件:"+pd.getResourceName());
            System.out.println("资源名称png文件:"+pd.getDiagramResourceName());
            System.out.println("部署对象ID:"+pd.getDeploymentId());
            System.out.println("#########################################################");
        }
    }
}
(10) 删除流程定义
/**附加功能:删除流程定义(删除key相同的所有不同版本的流程定义)*/
@Test
public void deleteProcessDefinitionByKey(){
    
    
    //流程定义的key
    String processDefinitionKey = "helloworld";
    //先使用流程定义的key查询流程定义,查询出所有的版本
    List<ProcessDefinition> list = processEngine
        	.getRepositoryService()
            .createProcessDefinitionQuery()
        	//使用流程定义的key查询
            .processDefinitionKey(processDefinitionKey)
            .list();
    
    //遍历,获取每个流程定义的部署ID
    if(list!=null && list.size()>0){
    
    
        for(ProcessDefinition pd:list){
    
    
            //获取部署ID
            String deploymentId = pd.getDeploymentId();
            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");
        
        System.out.println(processInstance.getId());
        System.out.println(processInstance.getProcessDefinitionId());
    }

通过流程定义的key启动流程实例,这时打开数据库act_ru_execution表,ID_表示执行对象ID,PROC_INST_ID_表示流程实例ID,如果是单例流程(没有分支和聚合),那么流程实例ID和执行对象ID是相同的。

一个流程流程实例只有一个,执行对象可以存在多个。

(12) 查询我的个人任务
/**查询当前人的个人任务*/
    @Test
    public void findMyPersonalTask(){
    
    
        String assignee = "张三";
        List<Task> list = processEngine
            	//与正在执行的任务管理相关的Service
            	.getTaskService()
            	//创建任务查询对象
                .createTaskQuery()
                /**查询条件(where部分)*/
            	//指定个人任务查询,指定办理人
                .taskAssignee(assignee)
            			//组任务的办理人查询
                        //.taskCandidateUser(candidateUser)
            			//使用流程定义ID查询
                        //.processDefinitionId(processDefinitionId)
            			//使用流程实例ID查询
                        //.processInstanceId(processInstanceId)
            			//使用执行对象ID查询
                        //.executionId(executionId)
                /**排序*/
                .orderByTaskCreateTime()
            	//使用创建时间的升序排列
            	.asc()
                /**返回结果集*/
            	//返回惟一结果集
                //.singleResult()
            	//返回结果集的数量
                //.count()
            	//分页查询
                //.listPage(firstResult, maxResults);
            	//返回列表
                .list();
        
        if(list!=null && list.size()>0){
    
    
            for(Task task:list){
    
    
                System.out.println("任务ID:"+task.getId());
                System.out.println("任务名称:"+task.getName());
                System.out.println("任务的创建时间:"+task.getCreateTime());
                System.out.println("任务的办理人:"+task.getAssignee());
                System.out.println("流程实例ID:"+task.getProcessInstanceId());
                System.out.println("执行对象ID:"+task.getExecutionId());
                System.out.println("流程定义ID:"+task.getProcessDefinitionId());
                System.out.println("########################################################");
            }
        }
    }

因为是任务查询,所以从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);
        System.out.println("完成任务:任务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){
    
    
            System.out.println("流程已经结束");
        }else{
    
    
            System.out.println("流程没有结束");
        }
    }

在流程执行的过程中,创建的流程实例ID在整个过程中都不会变,当流程结束后,流程实例将会在正在执行的执行对象表中(act_ru_execution)被删除。

因为是查询流程实例,所以先获取runtimeService,创建流程实例查询对象,设置实例ID过滤参数,由于一个流程实例ID只对应一个实例,使用singleResult执行查询返回一个唯一的结果,如果结果数量大于1,则抛出异常,判断指定ID的实例是否存在,如果结果为空,则代表流程结束,实例在正在执行的执行对象表中已被删除,转换成历史数据。

(15) 查询历史任务
/**查询历史任务*/
@Test
public void findHistoryTask(){
    
    
    String taskAssignee = "张三";
    List<HistoricTaskInstance> list = processEngine
        	//与历史数据(历史表)相关的Service
        	.getHistoryService()
        	//创建历史任务实例查询
            .createHistoricTaskInstanceQuery()
        	//指定历史任务的办理人
            .taskAssignee(taskAssignee)
            .list();
    if(list!=null && list.size()>0){
    
    
        for(HistoricTaskInstance hti:list){
    
    
            System.out.println(hti.getId()+" "+
                               hti.getName()+" "+
                               hti.getProcessInstanceId()+" "+
                               hti.getStartTime()+" "+
                               hti.getEndTime()+" "+
                               hti.getDurationInMillis());
            System.out.println("################################");
        }
    }
}
(16) 查询历史流程实例
/**查询历史流程实例*/
@Test
public void findHistoryProcessInstance(){
    
    
    String processInstanceId = "1001";
    HistoricProcessInstance hpi = processEngine
        	//与历史数据(历史表)相关的Service
        	.getHistoryService()
        	//创建历史流程实例查询
            .createHistoricProcessInstanceQuery()
        	//使用流程实例ID查询
            .processInstanceId(processInstanceId)
            .singleResult();
    
    System.out.println(hpi.getId()+" "+
                       hpi.getProcessDefinitionId()+" "+
                       hpi.getStartTime()+" "+
                       hpi.getEndTime()+" "+
                       hpi.getDurationInMillis());
}

流程变量:

  • act_ru_variable 正在执行的流程变量表
  • act_hi_varinst 历史的流程变量表

如下为对应实例

  • 模拟获取流程变量的场景

    /**模拟设置和获取流程变量的场景 */
    @Test
    public void setAndGetVariables(){
          
          
        RuntimeService runtimeService = processEngine.getRuntimeService();
        TaskService taskService = processEngine.getTaskService();
    
        //使用执行对象ID设置
        runtimeService.setVariable(executionId, variableName, value);(设置一个)
        runtimeService.setVariables(executionId, variables);
    
        //使用任务ID设置
        taskService.setVariable(taskId, variableName, value);(设置一个)
        taskService.setVariables(taskId, variables);
    
        //启动流程实例的同时设置
        runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
    
        //完成任务的同时设置
        taskService.complete(taskId, variables);
    
        /**获取流程变量*/
        //使用执行对象ID和流程变量的名称,获取流程变量的值
        runtimeService.getVariable(executionId, variableName);
        //使用执行对象ID,获取所有的流程变量,将流程变量放置到Map集合中,
        //map集合的key就是流程变量的名称,map集合的value就是流程变量的值
        runtimeService.getVariables(executionId);
        //使用执行对象ID,获取流程变量的值,通过设置流程变量的名称存放到集合中,
        //获取指定流程变量名称的流程变量的值,值存放到Map集合中
        runtimeService.getVariables(executionId, variableNames);
    
        //使用任务ID和流程变量的名称,获取流程变量的值
        taskService.getVariable(taskId, variableName);
        //使用任务ID,获取所有的流程变量,将流程变量放置到Map集合中,
        //map集合的key就是流程变量的名称,map集合的value就是流程变量的值
        taskService.getVariables(taskId);
        //使用任务ID,获取流程变量的值,通过设置流程变量的名称存放到集合中,
        //获取指定流程变量名称的流程变量的值,值存放到Map集合中
        taskService.getVariables(taskId, variableNames);
    }
    
(17) 设置流程变量
/**设置流程变量 */
@Test
public void setVariables(){
    
    
    TaskService taskService = processEngine.getTaskService();
    //任务ID
    String taskId = "50004";
    
    //一、设置流程变量,使用基本数据类型
    //local与当前task绑定,下一个task不可见
    taskService.setVariableLocal(taskId,"请假天数",3);
    taskService.setVariable(taskId,"请假日期",new Date());
    taskService.setVariable(taskId,"请假原因","回家探亲");
    
    //二:设置流程变量,使用javabean类型
    /**
     * 当一个javabean(实现序列号)放置到流程变量中,要求javabean的属性不能再发生变化
     * 如果发生变化,再获取的时候,抛出异常
     * 解决方案:在Person对象中添加:
     *         private static final long serialVersionUID = 6757393795687480331L;
     *      同时实现Serializable 
     * */
    Person p = new Person();
    p.setId(20);
    p.setName("翠花");
    taskService.setVariable(taskId, "人员信息(添加固定版本)", p);

    System.out.println("流程变量设置成功");
}

流程变量支持的类型
请添加图片描述

从图中可以看出包括了大部分封装类型和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) 获取流程变量
/**获取流程变量 */
@Test
public void getVariables(){
    
    
    //任务ID
    String taskId = "55002";
    //获取任务服务
    TaskService taskService = processEngine.getTaskService();
    
    /**一:获取流程变量,使用基本数据类型*/
    Integer days = (Integer) taskService.getVariable(taskId, "请假天数");
    Date date = (Date) taskService.getVariable(taskId, "请假日期");
    String resean = (String) taskService.getVariable(taskId, "请假原因");
    System.out.println("请假天数:"+days);
    System.out.println("请假日期:"+date);
    System.out.println("请假原因:"+resean);
    
    /**二:获取流程变量,使用javabean类型*/
    Person p = (Person)taskService.getVariable(taskId, "人员信息(添加固定版本)");
    System.out.println(p.getId()+" "+p.getName());
}
(19) 查询历史流程变量
/**查询流程变量的历史表*/
@Test
public void findHistoryProcessVariables(){
    
    
    List<HistoricVariableInstance> list = processEngine
        	//获取历史任务对象
        	.getHistoryService()
        	//创建一个历史的流程变量查询对象
            .createHistoricVariableInstanceQuery()
            .variableName("请假天数")
            .list();
    
    if(list!=null && list.size()>0){
    
    
        for(HistoricVariableInstance hvi:list){
    
    
            System.out.println(hvi.getId()+" "+
                               hvi.getProcessInstanceId()+" "+
                               hvi.getVariableName()+" "+
                               hvi.getVariableTypeName()+" "+
                               hvi.getValue());
            System.out.println("###############################################");
        }
    }
}

历史的流程变量查询,指定流程变量的名称,查询act_hi_varinst表(也可以针对,流程实例ID,执行对象ID,任务ID查询)

(20) 查已执行任务
    public DynamicFormConf getRunNodes(String processId) {
    
    
            DynamicFormConf dynamicFormConf = new DynamicFormConf();
            // 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstanceList = historyService
            				.createHistoricActivityInstanceQuery()
                            .processInstanceId(processId)
                            .activityType("userTask")   //用户任务
                            .finished()       			//已经执行的任务节点
                            .orderByHistoricActivityInstanceEndTime()
                            .asc()
                            .list();

            // 已执行的节点ID集合
         if(StringUtils.isNotEmpty(historicActivityInstanceList)){
    
    
             Map<String,String> map= new LinkedHashMap<String,String>();
            // map = historicActivityInstanceList.stream().collect(Collectors.toMap(HistoricActivityInstance::getActivityId,HistoricActivityInstance::getActivityName,(k1,k2)->k1));
           for (HistoricActivityInstance historicActivityInstance:historicActivityInstanceList){
    
    
               if(!map.containsKey(historicActivityInstance.getActivityId())){
    
    
                   map.put(historicActivityInstance.getActivityId(),historicActivityInstance.getActivityName());
               }
           }
             dynamicFormConf.setRunNodes(map);
         }
        return dynamicFormConf;
    }

(21) 驳回到指定节点
	/**
     * 驳回到指定节点
     * @param approvalOpinionVO    //申请流程 审批信息
     * @param task  //任务信息
     * @param map
     * @return
     */
    @Override
    public boolean runNodes(ApprovalOpinionVO approvalOpinionVO, Task task, Map<String, Object> map) {
    
    
        String myTaskId = null;
        //判断当前用户是否为该节点处理人
        if (UserUtils.getUserId().equals(task.getAssignee())) {
    
    
            myTaskId = task.getId();
        }
        //如果当前节点处理人不是该用户,就无法进行驳回操作
        if (null == myTaskId) {
    
    
            throw new CustomException("当前用户无法驳回");
        }
        
        //获取当前节点
        String currActivityId = task.getTaskDefinitionKey();
        String processDefinitionId = task.getProcessDefinitionId();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        FlowNode currFlow = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currActivityId);
        if (null == currFlow) {
    
    
            List<SubProcess> subProcessList = bpmnModel
                .getMainProcess()
                .findFlowElementsOfType(SubProcess.class, true);
            for (SubProcess subProcess : subProcessList) {
    
    
                FlowElement flowElement = subProcess.getFlowElement(currActivityId);
                if (flowElement != null) {
    
    
                    currFlow = (FlowNode) flowElement;
                    break;
                }
            }
        }
        
		//获取目标节点
        FlowNode targetFlow = (FlowNode) bpmnModel.getFlowElement(approvalOpinionVO.getRunNodeId());

        //如果不是同一个流程(子流程)不能驳回
        if (!(currFlow.getParentContainer().equals(targetFlow.getParentContainer()))) {
    
    
            throw new CustomException("此处无法进行驳回操作");
        }

        //记录原活动方向
        List<SequenceFlow> oriSequenceFlows = Lists.newArrayList();
        oriSequenceFlows.addAll(currFlow.getOutgoingFlows());

        //清理活动方向
        currFlow.getOutgoingFlows().clear();

        //建立新的方向
        List<SequenceFlow> newSequenceFlows = Lists.newArrayList();
        SequenceFlow newSequenceFlow = new SequenceFlow();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        newSequenceFlow.setId(uuid);
        newSequenceFlow.setSourceFlowElement(currFlow);  //原节点
        newSequenceFlow.setTargetFlowElement(targetFlow);  //目标节点
        newSequenceFlows.add(newSequenceFlow);
        currFlow.setOutgoingFlows(newSequenceFlows);

        //审批意见叠加
        //variables 审批意见     act_ru_variable  变量表 
        Map<String, Object> variables = task.getProcessVariables();
        //拒绝,通过,驳回 驳回指定节点
        List<ApprovalOpinionDTO> approvalOpinionDTOs = new ArrayList<>();
        //获取工作流审批记录
        Object options = variables.get(Constant.ACT_APPLY_OPINION_LIST);
        if (null != options) {
    
    
            approvalOpinionDTOs = JSONObject.parseArray(options.toString(), ApprovalOpinionDTO.class);
        }
        //添加审批过后的返回提升信息
        //ApprovalOpinionConverter 实体类转换器(没有 vo dto 等要求的可以不用转换,直接用一个类就可以了) 
        ApprovalOpinionDTO applyOpinionDTO = ApprovalOpinionConverter.INSTANCE.vo2dto(approvalOpinionVO);
        applyOpinionDTO.setFlagStr(applyOpinionDTO.getTaskNodeName()+"撤回到"+targetFlow.getName());
        approvalOpinionDTOs.add(applyOpinionDTO);
        map.put(Constant.ACT_APPLY_OPINION_LIST, JSONObject.toJSONString(approvalOpinionDTOs));

        //完成节点任务
        taskService.complete(task.getId(), map);
        //恢复原方向
        currFlow.setOutgoingFlows(oriSequenceFlows);
        return true;
    }

针对与activiti6则用法如下

	/**
     * @功能描述: 回退节点
     * @版权信息: www.easystudy.com
     * @编写作者: [email protected]
     * @开发日期: 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();
        
        // 获取流程定义
        org.activiti.bpmn.model.Process process = repositoryService
        											.getBpmnModel(currentTask.getProcessDefinitionId())
        											.getMainProcess();
        // 获取目标节点定义
        FlowNode targetNode = (FlowNode)process.getFlowElement("startTask");
        
        // 管理服务
        ManagementService managementService = ProcessEngines
												.getDefaultProcessEngine()
												.getManagementService();
        
        // 删除当前运行任务-返回任务的连线
        String executionEntityId = managementService.executeCommand(new DeleteTaskCmd(currentTask.getId()));
        
        // 流程执行到来源节点
        managementService.executeCommand(new SetFLowNodeAndGoCmd(targetNode, executionEntityId));
    }

删除命令

package com.easystudy.cmd;

import org.activiti.engine.impl.cmd.NeedsActiveTaskCmd;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManagerImpl;

/**
 * @文件名称: DeleteTaskCmd.java
 * @功能描述: 删除任务命令
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: [email protected]
 * @联系方式: 941415509(QQ)
 * @开发日期: 2021年7月30日
 * @历史版本: V1.0 
 * @备注信息: TODO
 */
public class DeleteTaskCmd extends NeedsActiveTaskCmd<String> {
    
    
	private static final long serialVersionUID = 1L;
	
	public DeleteTaskCmd(String taskId){
    
    
        super(taskId);
    }
	
    public String execute(CommandContext commandContext, TaskEntity currentTask){
    
    
    	/**
    	// 流程定义id
		String procDefId = execution.getProcessDefinitionId();
		// 获取服务
		RepositoryService repositoryService = ProcessEngines
													.getDefaultProcessEngine()
													.getRepositoryService();
		// 获取流程定义
        org.activiti.bpmn.model.Process process = repositoryService.getBpmnModel(procDefId).getMainProcess();
		
		// 获取需要提交的节点
        FlowNode targetNode = (FlowNode)process.getFlowElement(this.toTaskKey);
    	 */
        // 获取所需服务
        TaskEntityManagerImpl taskEntityManager = (TaskEntityManagerImpl)commandContext.getTaskEntityManager();
        // 获取当前任务的来源任务及来源节点信息[任务的前一连线]
        ExecutionEntity executionEntity = currentTask.getExecution();
        // 删除当前任务,来源任务
        taskEntityManager.deleteTask(currentTask, "跳转删除", false, false);
        // 返回来源任务ID
        return executionEntity.getId();
    }
    
    public String getSuspendedTaskException() {
    
    
        return "挂起的任务不能跳转";
    }
}

跳转命令

package com.easystudy.cmd;

import java.util.List;

import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
/**
 * @文件名称: SetFLowNodeAndGoCmd.java
 * @功能描述: 根据提供节点和执行对象id,进行跳转命令
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: [email protected]
 * @联系方式: 941415509(QQ)
 * @开发日期: 2021年7月30日
 * @历史版本: V1.0 
 * @备注信息: TODO
 */
public class SetFLowNodeAndGoCmd implements Command<Void> {
    
    
	// 目标节点
    private FlowNode flowElement;
    private String executionId;
    
    public SetFLowNodeAndGoCmd(FlowNode flowElement, String executionId){
    
    
        this.flowElement = flowElement;
        this.executionId = executionId;
    }
 
    public Void execute(CommandContext commandContext){
    
    
        // 获取目标节点的来源连线
        List<SequenceFlow> flows = flowElement.getIncomingFlows();
        if(flows==null || flows.size()<1){
    
    
            throw new ActivitiException("回退错误,目标节点没有来源连线");
        }
        
        // 查询到执行的执行流[目标流连线]
        ExecutionEntity executionEntity = commandContext
        										.getExecutionEntityManager()
        										.findById(executionId);
        // 随便选一条连线来执行时
        executionEntity.setCurrentFlowElement(flows.get(0));
        
        // 随便选一条连线来执行时,当前执行计划为从连线流转到目标节点,实现跳转
        commandContext
        	.getAgenda()
        	.planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
        
        return null;
    }
}
(21) 导出流程定义文件
    /**
     * @功能描述: 根据数据库中读取的流程模板生成bpmn
     * @编写作者: [email protected]
     * @开发日期: 2021年7月24日
     * @历史版本: V1.0  
     * @参数说明: 导出model对象为指定类型
     * @返  回  值: modelId 模型ID,导出文件类型(bpmn\json)
     */
    @RequestMapping(value = "export/{modelId}")
    public void export(@PathVariable("modelId") String modelId, HttpServletResponse response) {
    
    
        try {
    
    
        	// 获取仓库对象
        	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
			RepositoryService repositoryService = processEngine.getRepositoryService();
 
			// 获取模型数据
            Model modelData = repositoryService.getModel(modelId);
            
            // 获取编辑器源
            byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
            String jsonModel = new String(bytes,"UTF-8");
 
            // 转换为Bpmn模型
            JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
            BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(editorNode);
            String filename = bpmnModel.getMainProcess().getId() + ".bpmn";
 
            // 转换为XML生成输入流
            byte[] exportBytes = new BpmnXMLConverter().convertToXML(bpmnModel,"UTF-8");
            String XML =  new String(exportBytes,"UTF-8");
            ByteArrayInputStream in = new ByteArrayInputStream(XML.getBytes("UTF-8"));
            
            // 设置响应头为附件形式并返回xml二进制数据
            response.setHeader("Content-Disposition", "attachment; filename=" + filename);
            IOUtils.copy(in, response.getOutputStream());
 
            response.flushBuffer();
            //FileUtil.exportFile(response, in, filename);
            in.close();
        } catch (Exception e) {
    
    
            log.error("导出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 com.springboot.activiti.eimm.leave.controller;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.zip.ZipInputStream;

import org.activiti.engine.HistoryService;
import org.activiti.engine.IdentityService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.identity.Group;
import org.activiti.engine.identity.User;
import org.activiti.engine.repository.Deployment;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.springboot.activiti.eimm.leave.dao.LeaveMapper;
import com.springboot.activiti.eimm.leave.service.LeaveService;

import lombok.extern.slf4j.Slf4j;

@Controller
@Slf4j
@RequestMapping("/leave")
public class LeaveController {
    
    

	@Autowired
	LeaveMapper leaveMapper;
	
	@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/leave.bpmn")
		.addClasspathResource("processes/leave.png")
		.deploy();//完成部署
		
		System.out.println("部署ID:"+deployment.getId());//1
		System.out.println("部署时间:"+deployment.getDeploymentTime());
 
	}
	
	// 2、创建Activiti用户
  	@RequestMapping("/addUser")
  	public void addUser( ){
    
    	
  		//项目中每创建一个新用户,对应的要创建一个Activiti用户,两者的userId和userName一致
  		  
  		//添加用户
  		User user1 = identityService.newUser("user1");
  		user1.setFirstName("张三");
  		user1.setLastName("张");
  		user1.setPassword("123456");
  		user1.setEmail("[email protected]");
  	    identityService.saveUser(user1);
  	      
  	    User user2 = identityService.newUser("user2");
  	    user2.setFirstName("李四");
  	  	user2.setLastName("李");
  	  	user2.setPassword("123456");
  	  	user2.setEmail("[email protected]");
  	    identityService.saveUser(user2);
  		  
  	    User user3 = identityService.newUser("user3");
  	    user3.setFirstName("王五");
  	    user3.setLastName("王");
  		user3.setPassword("123456");
  		user3.setEmail("[email protected]");
  		identityService.saveUser(user3);
	  	
	  	 User user4 = identityService.newUser("user4");
	  	 user4.setFirstName("吴六");
	  	 user4.setLastName("吴");
	  	 user4.setPassword("123456");
	  	 user4.setEmail("[email protected]");
	  	 identityService.saveUser(user4);
  	}
}

启用应用,发现 act_id_user表 新增了四条用户信息

(2)查询activiti用户

    //3、根据id查询Activiti用户
  	@RequestMapping("/queryUser")
  	public void queryUser( ){
    
    	
  		User user = identityService
  			.createUserQuery()
  			.userId("user1")
  			.singleResult(); 
  			
  		System.out.println(user.getId());
  		System.out.println(user.getFirstName());
  		System.out.println(user.getLastName());
  		System.out.println(user.getPassword());
  		System.out.println(user.getEmail());
  	}

(3)创建Activiti用户组
Activiti中的用户组信息相当于权限系统当中的角色,用户可以属于多个用户组,用户组也可以包含多个用户,同一个用户组当中的用户具有相同的权限。

 	//4、创建Activiti用户组
  	@RequestMapping("/addGroup")
  	public void addGroup( ){
    
    	
  		Group group1 = identityService.newGroup("group1");
        group1.setName("员工组");
        group1.setType("员工组");
        identityService.saveGroup(group1);
        
        Group group2 = identityService.newGroup("group2");
        group2.setName("总监组");
        group2.setType("总监阻");
        identityService.saveGroup(group2);
        
        Group group3 = identityService.newGroup("group3");
        group3.setName("经理组");
        group3.setType("经理组");
        identityService.saveGroup(group3);
        
        Group group4 = identityService.newGroup("group4");
        group4.setName("人力资源组");
        group4.setType("人力资源组");
        identityService.saveGroup(group4);
  	}

(4)查询Activiti用户组

	//5、通过用户组id查询Activiti用户组
  	@RequestMapping("/queryGroup")
  	public void queryGroup( ){
    
    
  		Group group = identityService
  						.createGroupQuery()
  						.groupId("group1")
  						.singleResult();
  						
  		System.out.println(group.getId());
  		System.out.println(group.getName());
  		System.out.println(group.getType());
  	}

(5) 创建Activiti(用户-用户组)关系

   	//6、创建Activiti(用户-用户组)关系
  	@RequestMapping("/addMembership")
  	public void addMembership( ){
    
    
  		identityService.createMembership("user1", "group1");//user1 在员工阻
  		identityService.createMembership("user2", "group2");//user2在总监组
  		identityService.createMembership("user3", "group3");//user3在经理组
  		identityService.createMembership("user4", "group4");//user4在人力资源组
  	}

(6)查询属于用户组group1的用户

 	//7、查询属于组group1的用户
  	@RequestMapping("/queryUserListByGroup")
  	public void queryUserListByGroup( ){
    
    	
  	    //查询属于组group1的用户
  		List<User> usersInGroup = identityService
  									.createUserQuery()
  									.memberOfGroup("group1")
  									.list();
  									
  		for (User user : usersInGroup) {
    
    
			System.out.println(user.getFirstName());
		}
  	}

(7)查询用户user1所属于的用户组

 	//8、查询user1所属于的组
  	@RequestMapping("/queryGroupListByUser")
  	public void queryGroupListByUser( ){
    
    	
  	    //查询user1所属于的组
  		List<Group> groupsForUser = identityService
  										.createGroupQuery()
  										.groupMember("user1")
  										.list();
  										
  		for (Group group : groupsForUser) {
    
    
			System.out.println(group.getName());
		}
  	}
用户组任务(候选人)

(1)相关概念

  • 任务负责人:每一个任务都有一个任务负责人assignee
  • 任务候选人:在流程定义中在任务结点的 assignee 可以固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。 针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。

(2)候选人设置

在流程定义中设置任务候选人:

在流程图中任务节点的配置中设置 candidate-users(候选人),多个候选人之间用逗号分开

在线编辑器设置如下
请添加图片描述
请添加图片描述

设置成功后可看到bpmn文件配置

<userTask activiti:assignee="xiaozhang" activiti:exclusive="true" id="_3" name="申请请单"/>
<userTask activiti:candidateUsers="zhangsan,lisi" activiti:exclusive="true" id="_4" name="部门经理审批"/>
<userTask activiti:assignee="xiaowang" activiti:exclusive="true" id="_5" name="总经理批"/>

我们可以看到部门经理的审核人已经设置为 zhangsan,lisi 这样的一组候选人,可以使activiti:candiateUsers=”用户1,用户2,用户3”的这种方式来实现设置一组候选人。

(3)用户流程

如果一个任务指定了候选人列表,那么用户处理流程又是如何的?

  • 第一步:查询组任务 指定候选人,查询该候选人当前的待办任务。 候选人不能办理任务。

  • 第二步:拾取(claim)任务

    拾取任务:

    该组任务的所有候选人都能拾取该任务。 用户被拾取之后将候选人的组任务变成了个人任务。原来候选人就变成了该任务的负责人

    归还任务:

    如果拾取后不想办理该任务了, 需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。

  • 第三步:查询个人任务

    查询方式同个人任务部分,根据 assignee 查询用户负责的个人任务。

  • 第四步:办理个人任务

    用户变为任务负责人之后,查询到个人任务就可以完成自己的任务了。

设计到的api

#根据用户负责人查询任务
taskService.createTaskQuery().taskAssignee(param);
#根据用户候选人查询任务
taskService.createTaskQuery().taskCandidateUser(param); 
#根据后选择查询任务
taskService.createTaskQuery().taskCandidateGroup(param);
#设置任务负责人(班里人)
TaskService().setAssignee(taskId,userId);
#设置后选择
taskService().addCandidateGroup(taskId, groupid);
#签收(拾取)任务
taskService.claim(taskId, currentUserId);

根据以上用户候选人处理任务流程,我们的实现逻辑实例如下所示

  • 部署流程定义

        public static void main(String[] args) {
          
          
            //1.创建ProcessEngine对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2.得到RepositoryService实例
            RepositoryService repositoryService = processEngine.getRepositoryService();//3.进行部署
            Deployment deployment = repositoryService.createDeployment()
                    .addClasspathResource("diagram/holiday5.bpmn")  //添加bpmn资源
                    .name("请假申请单流程")
                    .deploy();//4.输出部署的一些信息
            System.out.println(deployment.getName());
            System.out.println(deployment.getId());
        }
    
  • 启动流程实例

        public static void main(String[] args) {
          
          
            //1.得到ProcessEngine对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2.得到RunService对象
            RuntimeService runtimeService = processEngine.getRuntimeService();//3.创建流程实例  流程定义的key需要知道 holiday
            ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday5");//4.输出实例的相关信息
            System.out.println("流程定义ID" + processInstance.getProcessDefinitionId());//holiday:1:4
            System.out.println("流程实例ID" + processInstance.getId());//2501
        }
    
  • 执行任务

    先由之前固定写好的xiaozhang去申请请假单

        public static void main(String[] args) {
          
          
            //1.得到ProcessEngine对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2.得到TaskService对象
            TaskService taskService = processEngine.getTaskService();//3.查询当前用户的任务
            Task task = taskService.createTaskQuery()
                    .processDefinitionKey("holiday5")
                    .taskAssignee("xiaozhang")
                    .singleResult();//4.处理任务,结合当前用户任务列表的查询操作的话,任务ID:task.getId()
            if (task != null) {
          
          
                taskService.complete(task.getId());
                System.out.println("用户任务执行完毕...");
            }//5.输出任务的id
            System.out.println(task.getId());
        }
    
  • 查询用户组任务

        public static void main(String[] args) {
          
          
            //1.得到ProcessEngine对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2.得到TaskService对象
            TaskService taskService = processEngine.getTaskService();//3.设置一些参数,流程定义的key,候选用户
            String key = "holiday5";
            String candidateUsers = "zhangsan";//4.执行查询
            List<Task> list = taskService.createTaskQuery()
                    .processDefinitionKey(key)
                    .taskCandidateUser(candidateUsers)//设置候选用户
                    .list();
            
            //5.输出
            for (Task task : list) {
          
          
                System.out.println(task.getProcessInstanceId());
                System.out.println(task.getId());
                System.out.println(task.getName());
                System.out.println(task.getAssignee());//为null,说明当前的zhangsan只是一个候选人,并不是任务的执行人
            }
        }
    
  • 拾取任务

    public static void main(String[] args) {
          
          
            //1.得到ProcessEngine对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2.得到TaskService对象
            TaskService taskService = processEngine.getTaskService();//3.设置一些参数,流程定义的key,候选用户
            String key = "holiday5";
            String candidateUsers = "zhangsan";//4.执行查询
            Task task = taskService.createTaskQuery()
                    .processDefinitionKey(key)
                    .taskCandidateUser(candidateUsers)//设置候选用户
                    .singleResult();
            if (task != null) {
          
          
                taskService.claim(task.getId(), candidateUsers);//第一个参数任务ID,第二个参数为具体的候选用户名
                System.out.println("任务拾取完毕!");
            }
        }
    
  • 查询用户当前任务

        public static void main(String[] args) {
          
          
            //1.得到ProcessEngine对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2.得到TaskService对象
            TaskService taskService = processEngine.getTaskService();//3.设置一些参数,流程定义的key,用户
            String key = "holiday5";
            String assignee = "zhangsan";//4.执行查询
            List<Task> list = taskService.createTaskQuery()
                    .processDefinitionKey(key)
                    .taskAssignee(assignee)  //设置任务的负责人
                    .list();
            
            //5.输出
            for (Task task : list) {
          
          
                System.out.println(task.getProcessInstanceId());
                System.out.println(task.getId());
                System.out.println(task.getName());
                System.out.println(task.getAssignee());//任务的执行人
            }
        }
    
  • 执行任务

        public static void main(String[] args) {
          
          
            //1.得到ProcessEngine对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2.得到TaskService对象
            TaskService taskService = processEngine.getTaskService();//3.设置一些参数,流程定义的key,用户
            String key = "holiday5";
            String assignee = "zhangsan";//4.执行查询
            Task task = taskService.createTaskQuery()
                    .processDefinitionKey(key)
                    .taskAssignee(assignee)  //设置任务的负责人
                    .singleResult();
            
            //5.执行当前的任务
            if (task != null) {
          
          
                taskService.complete(task.getId());
                System.out.println("任务执行完毕!");
            }}
    
  • 归还组任务/任务交接

    归还:如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人

    归还任务后,回到拾取任务之前的状态,zhangsan和lisi都可以去重新拾取这一工单。

    交接:任务负责人将任务交给其它候选人办理该任务

    交接可以交接给该流程的其它候选人,该实例中这一流程的候选人为zhangsan和lisi,所以可以选择交接给lisi。交接给lisi后,lisi可以去直接执行任务而不需要去拾取。

        public static void main(String[] args) {
          
          
            //1.得到ProcessEngine对象
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();//2.得到TaskService对象
            TaskService taskService = processEngine.getTaskService();//3.设置一些参数,流程定义的key,用户
            String key = "holiday5";
            String assignee = "zhangsan";//4.执行查询
            Task task = taskService.createTaskQuery()
                    .processDefinitionKey(key)
                    .taskAssignee(assignee)  //设置任务的负责人
                    .singleResult();
            
            //5.判断是否有这个任务
            if (task != null) {
          
          
                //如果设置为null,归还组任务,该任务没有负责人
    			//taskService.setAssignee(task.getId(), null);
                //交接任务为lisi  ,交接任务就是一个候选人拾取用户的过程
                taskService.setAssignee(task.getId(), "lisi");
                System.out.println("交接任务完成~!");
            }
        }
    
流程变量

我们可以给流程图中的候选用户设置流程变量,用法

${变量名}

请添加图片描述

然后我们在启动的时候设置变量对应值即可

    @RequestMapping(value = "startProcessInstanceByKey/{processDefinitionKey}")
    public String startProcessInstanceByKey(@PathVariable("processDefinitionKey") String processDefinitionKey) {
    
    
    	// 流程变量
    	Map<String,Object> variables = new HashMap<String,Object>();
        variables.put("userIds", "张三,李四,王五");
    	
    	// 通过流程定义key启动流程实例
    	ProcessInstance processInstance = ProcessEngines
							    			.getDefaultProcessEngine()
							    			.getRuntimeService()
							    			.startProcessInstanceByKey(processDefinitionKey/*, variables*/);
    	return processInstance.getId();
    }

或者通过任务监听器,然后将类名设置到监听器中(注意设置类的全名成,包含有包路径)

import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
public class MyTaskListener implements TaskListener{
    
    
    private static final long serialVersionUID = 1L;
    public void notify(DelegateTask delegateTask) {
    
    
        // TODO Auto-generated method stub
        delegateTask.addCandidateUser("张三");
        delegateTask.addCandidateUser("李四");
        delegateTask.addCandidateUser("王五");
    }
}
动态设置审批人
  • 任务监听器类需要实现

    org.flowable.engine.delegate.TaskListener

    TaskListener的事件:create、assigeneed、complete和delete事件。

    create事件是流程流转到该节点触发的事件。
    assigeneed事件是该任务节点设置了"assigenee"后才会触发的事件,若没有设置assigenee则不会触发此事件
    complete事件是完成时触发
    delete是删除任务时触发。
    

    实现代码

    public class UserTaskListner implements TaskListener {
          
          
        @Override
        public void notify(DelegateTask delegateTask) {
          
          
            String eventName=delegateTask.getEventName();
            delegateTask.setAssignee("user1");
        }
    }
    
    #查询用户任务
    List<Task> list=taskService.createTaskQuery().taskAssignee("user1").list();
    
  • 流程监听器需要实现

    org.flowable.engine.delegate.ExecutionListener;

    ExecutionListener有三个事件:start、take、end。start是流程开始节点的事件一般用在开始节点;end是流程结束时触发的事件,take是流程图中流转线条的事件。

自定义用户

一般情况下,应用系统都已经包含自己的用户管理系统,那么如何引入activiti后不使用activiti自带的用户和角色(组)管理,我们有如下方法:

  • 应用系统引入activiti自后将数据同步到activiti中

    也就是将所有的用户和角色管理的curd接口实现中添加activiti的用户组和用户功能(如应用层添加角色实现中添加activiti的用户组)

    注意:对已有系统中则必须添加用户同步接口,实现第一次嵌入activiti后数据同步。

  • 直接覆盖activiti的IdentifyService服务接口的实现

    通过以上我们知道,activiti的用户组和用户的管理是通过IdentifyService接口实现的存储到数据库或从activiti数据库删除的,我们直接覆盖其实现。

    我们需要在activiti配置中SpringProcessEngineConfiguration注入identifyService。SpringProcessEngineConfiguration.setIdentityService(identityService)

  • 用视图覆盖同名的ACT_ID_系列表

    因为activiti的用户分组等保存在act_id_*表中,所以,我们首先删除这些表,然后创建这些表的视图,对应视图如下

    ACT_ID_GROUP
    ACT_ID_INFO
    ACT_ID_MEMBERSHIP
    ACT_ID_USER
    

    然后修改默认引擎参数配置为false:SpringProcessEngineConfiguration.setDbIdentityUsed(false)

  • 自定义SessionFactory,非侵入式替换接口实现,对于公司内部有统一身份访问接口的推荐使用

    引擎内部与数据库交互使用的是MyBatis,Activiti的每一张表都有一个对应的XxxEntityManager(实体管理类,有接口和实现类)和XxxEntityManagerFactory(实体管理工厂类), 引擎的7个Service接口在需要CRUD实体时会根据接口获取注册的实体管理器实现类(初始化引擎时引擎会使用Map对象维护两者的映射关系),而引擎允许我们自己注册实体管理器实现类,查看源码后可以知道有关Identity操作的两个接口分别为:UserIdentityManager和GroupIdentityManager。

8、动态创建流程图

activiti-bpmn包含几个模块Model

  • activiti-bpmn-model:包含了BPMN2.0规范中部分对应的Java定义(也包括Activiti自己扩展的),描述了一些基本属性、结构
  • activiti-bpmn-converter:该模块负责对Model对象与XML进行互转;
  • activiti-bpmn-layout:可以根据流程定义文件中的XML定义生成BPMN DI信息(定义了流程中每一个活动的坐标、宽度、高度等)。

activiti-dynamic-process

Activiti团队核心成员frederikheremans创建了activiti-dynamic-process项目,该项目利用以上的几个模块演示了如何动态创建流程并部署运行,这几个步骤仅仅用了100行代码(还可以继续精简,但是这不是重点,重点在于体现Activiti的灵活性),动态创建流程图代码如下.

首先定义元素节点的用户任务实体信息

package com.easystudy.bpmn;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * @功能描述: 用户任务
 * @版权信息: www.easystudy.com
 * @编写作者: [email protected]
 * @开发日期: 2021年7月27日
 * @历史版本: V1.0
 */
@Data
@AllArgsConstructor
public class TaskInfo {
    
    
	// 任务ID
	private String id;
	// 任务名称
	private String name;
	// 任务负责人
	private String assignee;
}

然后在定义BPMN流程定义描述信息

package com.easystudy.bpmn;

import java.util.ArrayList;
import java.util.List;

import lombok.Data;

/**
 * @功能描述: BPMN流程定义信息
 * @版权信息: www.easystudy.com
 * @编写作者: [email protected]
 * @开发日期: 2021年7月27日
 * @历史版本: V1.0
 */
@Data
public class BpmnInfo {
    
    
	// 处理流程ID
	private String processId;
	// 流程按序排列的任务信息列表
	private List<TaskInfo> tasks;
	// 是否保存BPMN流程定义到文件
	private boolean save;
	// 保存BPMN图片文件全路径
	private String saveDirectory;
	
	public void addTask(TaskInfo task) {
    
    
		if(null == tasks) {
    
    
			tasks = new ArrayList<TaskInfo>();
		}
		tasks.add(task);
	}
}

一个BPMN信息描述了流程定义的所有任务信息,如下我们将添加启动事件、结束时间以及任务节点,连线起来

package com.easystudy.bpmn;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.activiti.bpmn.BpmnAutoLayout;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.EndEvent;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.io.FileUtils;

/**
 * @文件名称: BpmnCreator.java
 * @功能描述: 代码动态创建流程图
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: [email protected]
 * @联系方式: 941415509(QQ)
 * @开发日期: 2021年7月27日
 * @历史版本: V1.0
 * @注意事项:只能创建任务独立分支而非多分支
 */
public class BpmnCreator {
    
    
	/**
	 * @功能描述: 符合NCName规范的id
	 * @编写作者: [email protected]
	 * @开发日期: 2021年7月27日
	 * @历史版本: V1.0  
	 * @参数说明:字母或下划线打头
	 * @返  回  值:
	 */
	private String getUUID() {
    
    
		return "_" + UUID.randomUUID().toString().replace("-", "");
	}
	/**
	 * @功能描述: 创建BPMN流程定义
	 * @编写作者: [email protected]
	 * @开发日期: 2021年7月27日
	 * @历史版本: V1.0  
	 * @参数说明:
	 * @返  回  值:
	 */
	public boolean build(BpmnInfo info) throws Exception {
    
    
		// 必须有用户任务
		if (null == info.getTasks() || info.getTasks().size() <= 0) {
    
    
			return false;
		}
		
		// 1. 创建图形化工具模型
		BpmnModel model = new BpmnModel();
		Process process = new Process();
		process.setId(info.getProcessId());
		model.addProcess(process);

		// 节点id列表
		List<String> sequnceIds = new ArrayList<String>();
		
		// 开始事件节点(所有的事件、任务都被认为是Flow)
		String startEventId = getUUID();
		process.addFlowElement(createStartEvent(startEventId));
		sequnceIds.add(startEventId);
		
		// 添加用户任务节点
		List<TaskInfo> tasks = info.getTasks();
		for (TaskInfo task : tasks) {
    
    
			process.addFlowElement(createUserTask(task.getId(), task.getName(), task.getAssignee()));
			sequnceIds.add(task.getId());
		}
		
		// 结束事件节点
		String endEventId = getUUID();
		process.addFlowElement(createEndEvent(endEventId));
		sequnceIds.add(endEventId);

		// 连接各个节点
		for (int i = 0; i < sequnceIds.size() - 1; ++i) {
    
    
			process.addFlowElement(createSequenceFlow(sequnceIds.get(i), sequnceIds.get(i+1)));
		}

		// 2. 流程图自动布局(位于activiti-bpmn-layout模块)
		new BpmnAutoLayout(model).execute();

		// 3. 把BpmnModel对象部署到引擎
		Deployment deployment = ProcessEngines
									.getDefaultProcessEngine()
									.getRepositoryService()
									.createDeployment()
									.addBpmnModel(info.getProcessId() + ".bpmn", model)
									.name(info.getProcessId())
									.deploy();

		// 4. 启动流程实例
		ProcessInstance processInstance = ProcessEngines
											.getDefaultProcessEngine()
											.getRuntimeService()
											.startProcessInstanceByKey(info.getProcessId());

		// 5. 检查用户任务可用性-查询出流程实例的所有任务
		List<Task> userTasks = ProcessEngines
									.getDefaultProcessEngine()
									.getTaskService()
									.createTaskQuery()
									.processInstanceId(processInstance.getId())
									.list();

		// 当前可用任务为1【第一个任务被创建】
		if (1 != userTasks.size()) {
    
    
			return false;
		}

		// BPMN文件和流程定义图片文件保存
		if (info.isSave()) {
    
    
			// 存储当前目录
			if(null == info.getSaveDirectory()) {
    
    
				info.setSaveDirectory("./");
			}
			
			// 添加目录斜杠
			if ((!info.getSaveDirectory().endsWith("/")) && (!info.getSaveDirectory().endsWith("\\"))) {
    
    
				info.setSaveDirectory(info.getSaveDirectory() + "/");
			}
			
			// 6. 保存流程图定义到文件中:diagram.png
			InputStream processDiagram = ProcessEngines
											.getDefaultProcessEngine()
											.getRepositoryService()
											.getProcessDiagram(processInstance.getProcessDefinitionId());
			
			// 创建空目录
			new File(info.getSaveDirectory()).mkdirs();
			FileUtils.copyInputStreamToFile(processDiagram, new File(info.getSaveDirectory() + info.getProcessId() + ".png"));
			processDiagram.close();
			
			// 7. 保存流程定义BPMN的XML到文件:process.bpmn20.xml
			InputStream processBpmn = ProcessEngines
											.getDefaultProcessEngine()
											.getRepositoryService()
											.getResourceAsStream(deployment.getId(), info.getProcessId() + ".bpmn");
			FileUtils.copyInputStreamToFile(processBpmn, new File(info.getSaveDirectory() + info.getProcessId() + ".bpmn20.xml"));
			processBpmn.close();
		}

		return true;
	}

	/**
	 * @功能描述: 创建用户任务
	 * @编写作者: [email protected]
	 * @开发日期: 2021年7月27日
	 * @历史版本: V1.0  
	 * @参数说明:
	 * @返  回  值:
	 */
	protected UserTask createUserTask(String id, String name, String assignee) {
    
    
		UserTask userTask = new UserTask();
		userTask.setName(name);
		userTask.setId(id);
		userTask.setAssignee(assignee);
		return userTask;
	}

	/**
	 * @功能描述: 创建一个序列流
	 * @编写作者: [email protected]
	 * @开发日期: 2021年7月27日
	 * @历史版本: V1.0  
	 * @参数说明:
	 * @返  回  值:
	 */
	protected SequenceFlow createSequenceFlow(String from, String to) {
    
    
		SequenceFlow flow = new SequenceFlow();
		flow.setSourceRef(from);
		flow.setTargetRef(to);
		return flow;
	}

	/**
	 * @功能描述: 创建启动事件
	 * @编写作者: [email protected]
	 * @开发日期: 2021年7月27日
	 * @历史版本: V1.0  
	 * @参数说明:
	 * @返  回  值:
	 */
	protected StartEvent createStartEvent(String id) {
    
    
		StartEvent startEvent = new StartEvent();
		startEvent.setId(id);
		startEvent.setName("start");
		return startEvent;
	}

	/**
	 * @功能描述: 创建结束事件
	 * @编写作者: [email protected]
	 * @开发日期: 2021年7月27日
	 * @历史版本: V1.0  
	 * @参数说明:
	 * @返  回  值:
	 */
	protected EndEvent createEndEvent(String id) {
    
    
		EndEvent endEvent = new EndEvent();
		endEvent.setId(id);
		endEvent.setName("end");
		return endEvent;
	}
}

最后提供一个swagger接口供动态创建

    @RequestMapping(value = "createActiviti")
    public void createActiviti() throws Exception {
    
    
    	
    	BpmnInfo info = new BpmnInfo();
    	info.setProcessId("my-dynamic-model");
    	info.setSave(true);
    	info.setSaveDirectory("d:/bpmn");
    	info.addTask(new TaskInfo("task1", "task1-name", "user1"));
    	info.addTask(new TaskInfo("task2", "task2-name", "user2"));
    	
    	BpmnCreator creator = new BpmnCreator();
    	creator.build(info);
    }

如上所示,我建立了一个流程,并设置了中间的用户任务的负责人分别为user1和user2

开始 --> 任务1 --> 任务2 --> 结束

我们直接访问接口调用该流程

http://localhost:8000/demon/activiti/createActiviti

最后可以看到我们的流程定义图片和流程定义文件bpmn都生成在D:/bpmn目录中
请添加图片描述

我们打开png图片可以看到生成的流程定义图
请添加图片描述

我们打开bpmn的xml文件,可以看到结构信息

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="my-dynamic-model" isExecutable="true">
    <startEvent id="_04659a8695a34d5392a494e09decb431" name="start"></startEvent>
    <userTask id="task1" name="task1-name" activiti:assignee="user1"></userTask>
    <userTask id="task2" name="task2-name" activiti:assignee="user2"></userTask>
    <endEvent id="_0826a1bf85db49f28d112eda6887c08a" name="end"></endEvent>
    <sequenceFlow id="sequenceFlow-2a49c3c3-ac9c-4c45-b1e9-a6cbe394e107" sourceRef="_04659a8695a34d5392a494e09decb431" targetRef="task1"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-225d1463-3a15-436c-8c4e-835a6c653b99" sourceRef="task1" targetRef="task2"></sequenceFlow>
    <sequenceFlow id="sequenceFlow-23c99492-3210-49cc-ab0d-0bc7354e0c7b" sourceRef="task2" targetRef="_0826a1bf85db49f28d112eda6887c08a"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_my-dynamic-model">
    <bpmndi:BPMNPlane bpmnElement="my-dynamic-model" id="BPMNPlane_my-dynamic-model">
      <bpmndi:BPMNShape bpmnElement="_04659a8695a34d5392a494e09decb431" id="BPMNShape__04659a8695a34d5392a494e09decb431">
        <omgdc:Bounds height="30.0" width="30.0" x="0.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_0826a1bf85db49f28d112eda6887c08a" id="BPMNShape__0826a1bf85db49f28d112eda6887c08a">
        <omgdc:Bounds height="30.0" width="30.0" x="380.0" y="15.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="task1" id="BPMNShape_task1">
        <omgdc:Bounds height="60.0" width="100.0" x="80.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="task2" id="BPMNShape_task2">
        <omgdc:Bounds height="60.0" width="100.0" x="230.0" y="0.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-2a49c3c3-ac9c-4c45-b1e9-a6cbe394e107" id="BPMNEdge_sequenceFlow-2a49c3c3-ac9c-4c45-b1e9-a6cbe394e107">
        <omgdi:waypoint x="30.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="42.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="42.0" y="30.000000000000007"></omgdi:waypoint>
        <omgdi:waypoint x="80.0" y="30.000000000000007"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-23c99492-3210-49cc-ab0d-0bc7354e0c7b" id="BPMNEdge_sequenceFlow-23c99492-3210-49cc-ab0d-0bc7354e0c7b">
        <omgdi:waypoint x="330.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="342.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="342.0" y="30.000000000000004"></omgdi:waypoint>
        <omgdi:waypoint x="380.0" y="30.000000000000004"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sequenceFlow-225d1463-3a15-436c-8c4e-835a6c653b99" id="BPMNEdge_sequenceFlow-225d1463-3a15-436c-8c4e-835a6c653b99">
        <omgdi:waypoint x="180.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="192.0" y="30.0"></omgdi:waypoint>
        <omgdi:waypoint x="192.0" y="30.000000000000007"></omgdi:waypoint>
        <omgdi:waypoint x="230.0" y="30.000000000000007"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

可以看到与我们设计的实际相符合。

9、动态添加节点

10、驳回流程

项目设计到的流程示例如下所示
请添加图片描述
如果上所示,用户提交的请假单经过部门主管不同意则回退到提交请假任务,同意之后经过一个排他网关,如果是3天以上则需要总经理审批,总经理审批通过则经过人事,总经理不同意则继续回退到部门主管。如果少于3天则直接进入人事审批。这里面涉及到多个同意或者不同意等排他网关或者多个条件设置。那么如下我们就用代码来演示整个流程的操作。

这里涉及到两个问题

  • 在部门审批时判断是不同意,自动回到发起人那里,因为发起人就一个人。
  • 如果到部门主管(假设可以多个)一级审核完成后,到总经理审批,总经理审批后不同意回退到部门主管,无法确定是哪一个,因为candidate有可能多个。
实现方法一

解决方法:

可以在经理审批的时候根据经理的审批结果是否驳回,然后根据此task找到此前执行这个任务的人回退给他,就是审批的时候带上${inputuser}。但是这样写不通用,每个task都要写各自的提交。所以这里可以使用监听器!

(1)设置发起用户(用户负责人)变量

流程图上任务节点使用assigness(负责人)或者candidate users(候选人)动态的指定用户如使用变量${inputuser}
请添加图片描述

(2)设置提交监听器和驳回监听器
请添加图片描述
请添加图片描述
注意:这里的事件类型为take(执行监听器包括start、take和end三个事件),否则在完成任务的时候是不会触发调用这个监听器的,建停止的三个枚举值如下所示

ExecutionListener.EVENTNAME_START
ExecutionListener.EVENTNAME_END
ExecutionListener.EVENTNAME_TAKE

调用接口及执行结果

http://localhost:8000/demon/activiti/startProcessInstanceByKeyWithVariable/rejectProjcess
http://localhost:8000/demon/activiti/completeTask/16
http://localhost:8000/demon/activiti/findTasks
http://localhost:8000/demon/activiti/completeTaskWithVariable/19?yes=1&day=3
http://localhost:8000/demon/activiti/findTasks
http://localhost:8000/demon/activiti/completeTaskWithVariable/25?yes=0
http://localhost:8000/demon/activiti/findTasks
http://localhost:8000/demon/activiti/completeTaskWithVariable/27?yes=1&day=1
http://localhost:8000/demon/activiti/findTasks
http://localhost:8000/demon/activiti/completeTask/31
http://localhost:8000/demon/activiti/findTasks
http://localhost:8000/demon/activiti/findProcessInstanceStatus/11

Id:16
Name:提交请假单
Owner:null
Priority:50
TaskDefinitionKey:usertask1
CreateTime:Fri Jul 30 17:14:53 CST 2021
Assignee:lixx
Description:null
-------------------------
前一个用户任务节点id:usertask1, 名称:提交请假单
后一个用户任务节点id:usertask1, 名称:提交请假单
发起人:lixx
-----------------
完成任务:16
Id:19
Name:审批[部门主管]
Owner:null
Priority:50
TaskDefinitionKey:usertask2
CreateTime:Fri Jul 30 17:15:30 CST 2021
Assignee:lixx
Description:null
-------------------------
-----------------
完成任务:19
Id:25
Name:审批[总经理]
Owner:null
Priority:50
TaskDefinitionKey:usertask3
CreateTime:Fri Jul 30 17:17:26 CST 2021
Assignee:null
Description:null
-------------------------
-----------------
ExecutionEntity:null
发起人:lixx
完成任务:25
Id:27
Name:审批[部门主管]
Owner:null
Priority:50
TaskDefinitionKey:usertask2
CreateTime:Fri Jul 30 17:18:52 CST 2021
Assignee:null
Description:null
-------------------------
-----------------
完成任务:27
Id:31
Name:审批[人事]
Owner:null
Priority:50
TaskDefinitionKey:usertask4
CreateTime:Fri Jul 30 17:20:16 CST 2021
Assignee:null
Description:null
-------------------------
-----------------
完成任务:31
实现方法二

排他网管使用变量表达式,如下所示

${(day >= 3) && (leaveType == 1)}
${day >= 3 || department == 1}

请添加图片描述
我们只需要完成任务的时候设置变量即可

String taskId = "10032";
Map<String, Object> vars = new HashMap<>();
vars.put("day", 5);
vars.put("leaveType", 1);
processEngine.getTaskService().complete(taskId, vars);
实现方法三

自定义命令

package com.sn.cloud.activiti6.cmd;
 
import java.util.List;
 
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.cmd.NeedsActiveTaskCmd;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import org.activiti.engine.impl.util.ProcessDefinitionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
 
 
public class RollbackFirstTaskCmd extends NeedsActiveTaskCmd<Void> {
    
    
	private static Logger logger = LoggerFactory.getLogger(RollbackFirstTaskCmd.class);
 
    public RollbackFirstTaskCmd(String taskId){
    
    
        super(taskId);
    }
    public String deletereason;
    
	public Void execute(CommandContext commandContext, TaskEntity currentTask) {
    
    
		String processDefinitionId = currentTask.getProcessDefinitionId();
		String executionId = currentTask.getExecutionId();
		
        TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
        taskEntityManager.deleteTask(currentTask, deletereason, false, false);
 
		FlowNode firstUserTask = this.findFirstActivity(processDefinitionId);
		ExecutionEntity executionEntity = commandContext.getExecutionEntityManager().findById(executionId);
 
		// 获取目标节点的来源连线
        List<SequenceFlow> flows = firstUserTask.getIncomingFlows();
        if (flows == null || flows.isEmpty()) {
    
    
            throw new ActivitiException("回退错误,目标节点没有来源连线");
        }
        // 随便选一条连线来执行,时当前执行计划为,从连线流转到目标节点,实现跳转
		executionEntity.setCurrentFlowElement(flows.get(0));
        commandContext.getAgenda().planTakeOutgoingSequenceFlowsOperation(executionEntity, true);
 
		// executionEntity.setCurrentFlowElement(flowElement);
		// commandContext.getAgenda().planContinueProcessOperation(executionEntity);
 
		return null;
    }
 
    public String getSuspendedTaskException() {
    
    
        return "挂起的任务不能跳转";
    }
 
    /**
     * 获得第一个节点.
     */
    public FlowNode findFirstActivity(String processDefinitionId) {
    
    
		Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
		FlowElement flowElement = process.getInitialFlowElement();
		FlowNode startActivity = (FlowNode) flowElement;
 
        if (startActivity.getOutgoingFlows().size() != 1) {
    
    
            throw new IllegalStateException(
                    "start activity outgoing transitions cannot more than 1, now is : "
                            + startActivity.getOutgoingFlows().size());
        }
 
        SequenceFlow sequenceFlow = startActivity.getOutgoingFlows()
                .get(0);
        FlowNode targetActivity = (FlowNode) sequenceFlow.getTargetFlowElement();
 
        if (!(targetActivity instanceof UserTask)) {
    
    
            logger.info("first activity is not userTask, just skip");
 
            return null;
        }
 
        return targetActivity;
    }
}

执行命令

		RollbackFirstTaskCmd cmd = new RollbackFirstTaskCmd(taskId);
		cmd.deletereason = bill.get("note").toString();
		if (getWillRejectTask(bill, taskinfoTaskId) == null) {
    
    
			queryTaskinfoRule.updateTaskInfo();
		}
		processEngine.getManagementService().executeCommand(cmd);

其他命令

package com.edu.hart.web.manage.process;

import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.TransitionImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;

/**
 * 任务驳回方法支持
 *
 * @author create by 叶云轩 at 2018/1/15 09:32
 */
public class RejectTaskCMD implements Command<Object>, Serializable {
    
    
    /**
 * RejectTaskCMD 日志控制器
 * Create by 叶云轩 at 2018/1/19 09:43
 * Concat at [email protected]
 */
    private static final Logger LOGGER = LoggerFactory.getLogger(RejectTaskCMD.class);
    /**
 * 历史信息中的当前任务实例
 */
    private HistoricTaskInstance currentTaskInstance;
    /**
 * 历史信息中的目标任务实例
 */
    private HistoricTaskInstance destinationTaskInstance;
    /**
 * 目标任务节点
 */
    private ActivityImpl destinationActivity;

    /**
 * 构造方法
 *
 * @param currentTaskInstance  当前任务实例
 * @param destinationTaskInstance 目标任务实例
 * @param destinationActivity  目标节点
 */
    public RejectTaskCMD(HistoricTaskInstance currentTaskInstance
            , HistoricTaskInstance destinationTaskInstance
            , ActivityImpl destinationActivity) {
    
    
        this.currentTaskInstance = currentTaskInstance;
        this.destinationTaskInstance = destinationTaskInstance;
        this.destinationActivity = destinationActivity;
    }

    @Override
    public Object execute(CommandContext commandContext) {
    
    
        // 流程实例ID
        String processInstanceId = destinationTaskInstance.getProcessInstanceId();
        // 执行管理器
        ExecutionEntityManager executionEntityManager =
                commandContext.getExecutionEntityManager();
        // select * from ACT_RU_EXECUTION where ID_ = ? 查询当前流程实例中正在执行的唯一任务 --追源码时发现这个方法的作用,就记录了下来,省的自己遗忘掉
        ExecutionEntity executionEntity = executionEntityManager.findExecutionById(processInstanceId);
        // 当前活跃的节点信息
        ActivityImpl currentActivity = executionEntity.getActivity();
        // 创建一个出口转向
        TransitionImpl outgoingTransition = currentActivity.createOutgoingTransition();
        // 封装目标节点到转向实体
        outgoingTransition.setDestination(destinationActivity);
        // 流程转向
        executionEntity.setTransition(outgoingTransition);
        return null;
    }
}

流程转向示例

/**
     * 流程转向操作
     *
     * @param taskId     当前任务ID
     * @param activityId 目标节点任务ID
     * @param variables  流程变量
     * @throws Exception
     */

    private static void turnTransition(String taskId, String activityId,
                                       Map<String, Object> variables) throws Exception {
    
    

        // 当前节点
        ActivityImpl currActivity = findActivitiImpl(taskId, null);
        // 清空当前流向
        List<PvmTransition> oriPvmTransitionList = clearTransition(currActivity);

        // 创建新流向
        TransitionImpl newTransition = currActivity.createOutgoingTransition();
        // 目标节点
        ActivityImpl pointActivity = findActivitiImpl(taskId, activityId);

        // 设置新流向的目标节点
        newTransition.setDestination(pointActivity);
        // 执行转向任务
        taskService.complete(taskId, variables);

        // 删除目标节点新流入
        pointActivity.getIncomingTransitions().remove(newTransition);
        // 还原以前流向
        restoreTransition(currActivity, oriPvmTransitionList);
    }

	/**
     * 根据任务ID和节点ID获取活动节点 <br>
     * @param taskId     任务ID
     * @param activityId 活动节点ID <br>
     *                   <p>
     *                   如果为null或"",则默认查询当前活动节点 <br>
     *                   <p>
     *                   如果为"end",则查询结束节点 <br>
     * @return
     * @throws Exception
     */

    private static ActivityImpl findActivitiImpl(String taskId, String activityId)
            throws Exception {
    
    
        // 取得流程定义
        ProcessDefinitionEntity processDefinition = findProcessDefinitionEntityByTaskId(taskId);

        // 获取当前活动节点ID
        if (StringUtils.isEmpty(activityId)) {
    
    
            activityId = findTaskById(taskId).getTaskDefinitionKey();
        }

        // 根据流程定义,获取该流程实例的结束节点
        if (activityId.toUpperCase().equals("END")) {
    
    
            for (ActivityImpl activityImpl : processDefinition.getActivities()) {
    
    
                List<PvmTransition> pvmTransitionList = activityImpl
                        .getOutgoingTransitions();
                if (pvmTransitionList.isEmpty()) {
    
    
                    return activityImpl;
                }
            }
        }

        // 根据节点ID,获取对应的活动节点
        ActivityImpl activityImpl = ((ProcessDefinitionImpl) processDefinition)
                .findActivity(activityId);
        return activityImpl;
    }

	/**
     * 根据任务ID获得任务实例
     * @param taskId 任务ID
     * @return
     * @throws Exception
     */
    private static TaskEntity findTaskById(String taskId) throws Exception {
    
    
        TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
    
    
            throw new Exception("任务实例未找到!");
        }
        return task;
    }

11、多流程设计

https://blog.csdn.net/hichinamobile/article/details/79092926

三、在线设计器

网上找了很多资料都没有找到关于activiti6.0的在线流程设计器,大多数都是基于5.x的。因为6.0的源码包中没有在线设计器的资源包,因此本文使用的是activiti 5.22.0中web资源包来做的,只是依赖不一样ui一样,下面就能开始来实现一下吧。

第一步:在activiti官网下载5.22.0的源码包:https://www.activiti.org/get-started

第二步: 新建springboot项目在pom中配置如下

        <!-- activiti启动器 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter-basic</artifactId>
            <version>6.0.0</version>
        </dependency>
        <!--activiti在线设计器-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-modeler</artifactId>
            <version>5.22.0</version>
        </dependency>
        <!-- xml解析依赖 -->
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-transcoder</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-codec</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>6.0.0</version>
            <exclusions>
				 <exclusion>
					 <groupId>org.activiti</groupId>
					 <artifactId>activiti-bpmn-model</artifactId>
				 </exclusion>
			</exclusions>
        </dependency>

添加yaml配置

spring:
  #流程配置
  activiti:
    #自动检查、部署流程定义文件
    #false不自动检查resources/bpmn目录
    check-process-definitions: false
    #自动生成或更新数据库表结构
    database-schema-update: true
    #流程定义文件存放目录
    process-definition-location-prefix: classpath:/processes/
    #历史级别
    history-level: full
    #流程处理文件格式bpmn
    #process-definition-location-suffixes:

排除activiti的security安全配置

@SpringBootApplication(exclude = org.activiti.spring.boot.SecurityAutoConfiguration.class)
public class DemonApp {
    
    
	public static void main(String[] args) {
    
    
		SpringApplication.run(DemonApp.class, args);
	}
}

下载activiti5.22版本,解压

  • 下载完以后,解压activiti-5.22.0的包,解压activiti-explorer2,把webapp下面的diagram-viewer、editor-app、modeler.html复制到springboot项目下的static下,这是activiti的在线设计器,modeler.html就是设计的主界面
    请添加图片描述

  • 复制WEB-INF\classes下stencilset.json到自己的resources下

  • 将libs下的activiti-modeler-5.22.0-sources.jar解压出来,把org\activiti\rest\editor路径下的main、model文件夹复制到springboot项目的源码路径下,里面有三个类,主要用于读取stencilset.json

    - controller
    	- editor
    		ModeEditorJsonRestResource
    		ModelSaveRestResource
    		StencilsetRestResource
    

    提取activiti-modeler项目中的StencilsetRestResource.java,ModelEditorJsonRestResource.java,ModelSaveRestResource.java 放置到项目中,也可以根据需要自己定义

    /**
     * 模板编辑
     * @author HQ Zheng
     */
    @RestController
    @RequestMapping(value = "/service")
    public class ModelEditorJsonRestResource implements ModelDataJsonConstants {
          
          
      
      protected static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class);
      
      @Autowired
      private RepositoryService repositoryService;
      
      @Autowired
      private ObjectMapper objectMapper;
     
      /**
       * 获取模板编辑信息
       * @param modelId
       * @return
       */
      @RequestMapping(value="/model/{modelId}/json", method = RequestMethod.GET, produces = "application/json")
      public ObjectNode getEditorJson(@PathVariable String modelId) {
          
          
        ObjectNode modelNode = null;
        
        Model model = repositoryService.getModel(modelId);
          
        if (model != null) {
          
          
          try {
          
          
            if (StringUtils.isNotEmpty(model.getMetaInfo())) {
          
          
              modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
            } else {
          
          
              modelNode = objectMapper.createObjectNode();
              modelNode.put(MODEL_NAME, model.getName());
            }
            modelNode.put(MODEL_ID, model.getId());
            ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(
                new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));
            modelNode.put("model", editorJsonNode);
            
          } catch (Exception e) {
          
          
            LOGGER.error("Error creating model JSON", e);
            throw new ActivitiException("Error creating model JSON", e);
          }
        }
        return modelNode;
      }
    }
    
    /**模型保存
     * @author HQ Zheng
     */
    @RestController
    @RequestMapping(value = "/service")
    public class ModelSaveRestResource implements ModelDataJsonConstants {
          
          
      
      protected static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class);
     
      @Autowired
      private RepositoryService repositoryService;
      
      @Autowired
      private ObjectMapper objectMapper;
     
      /**
       * 保存模型
       * @param modelId
       * @param json_xml
       * @param svg_xml
       * @param name
       * @param description
       */
      @RequestMapping(value = "/model/{modelId}/save", method = RequestMethod.PUT)
      public void saveModel(@PathVariable String modelId, @RequestParam String json_xml,
                            @RequestParam  String svg_xml,@RequestParam String name, @RequestParam String description) {
          
          
        try {
          
          
     
          // 获取模型信息并更新模型信息
          ObjectMapper objectMapper = new ObjectMapper();
          Model model = repositoryService.getModel(modelId);
          ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
          modelJson.put(MODEL_NAME, name);
          modelJson.put(MODEL_DESCRIPTION, description);
          model.setMetaInfo(modelJson.toString());
          model.setName(name);
          repositoryService.saveModel(model);
          repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes("utf-8"));
     
          // 基于模型信息做流程部署
          InputStream svgStream = new ByteArrayInputStream(svg_xml.getBytes("utf-8"));
          TranscoderInput input = new TranscoderInput(svgStream);
          PNGTranscoder transcoder = new PNGTranscoder();
          ByteArrayOutputStream outStream = new ByteArrayOutputStream();
          TranscoderOutput output = new TranscoderOutput(outStream);
          transcoder.transcode(input, output);
          final byte[] result = outStream.toByteArray();
          repositoryService.addModelEditorSourceExtra(model.getId(), result);
          outStream.close();
          Model modelData = repositoryService.getModel(modelId);
          ObjectNode modelNode = (ObjectNode) objectMapper.readTree(repositoryService.getModelEditorSource(modelData.getId()));
          byte[] bpmnBytes = null;
          BpmnModel model2 = new BpmnJsonConverter().convertToBpmnModel(modelNode);
          bpmnBytes = new BpmnXMLConverter().convertToXML(model2);
          String processName = modelData.getName() + ".bpmn";
          Deployment deployment = repositoryService.createDeployment()
                  .name(modelData.getName())
                  .addString(processName, StringUtils.toEncodedString(bpmnBytes, Charset.forName("UTF-8")))
                  .deploy();
        } catch (Exception e) {
          
          
          LOGGER.error("模型保存失败", e);
          throw new ActivitiException("模型保存失败", e);
        }
     
      }
    }
    
    /**加载模板集
     * @author HQ Zheng
     */
    @RestController
    @RequestMapping(value = "/service")
    public class StencilsetRestResource {
          
          
      
      @RequestMapping(value="/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
      public @ResponseBody String getStencilset() {
          
          
        InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("static/stencilset.json");
        try {
          
          
          return IOUtils.toString(stencilsetStream, "utf-8");
        } catch (Exception e) {
          
          
          throw new ActivitiException("Error while loading stencil set", e);
        }
      }
    }
    
  • 修改editor-app下的app-cfg.js,把contextRoot后面改成 ‘’(这个和控制器的和controller里面加的requestMapping要一致

    ACTIVITI.CONFIG = {
          
          
        'contextRoot' : '/项目名/controller的根mapping名',
    };
    

    如果需要中文的编辑器,可以自己下载一个stencilset.json中文的

  • 运行项目访问: http://localhost:8002/editor 就会出现效果

    如果6.0的则访问: http://127.0.0.1:8080/xx/activiti-ui.html

在线编辑器下载地址:

  • 链接: https://pan.baidu.com/s/1aCl692Zkqq3F09THy0NF6w 提取码: 8bpi 复制这段内容后打开百度网盘手机App,操作更方便哦
  • 链接: https://pan.baidu.com/s/1ch-_8K_5OMHRhaOixz2MeQ 提取码: jqbw 复制这段内容后打开百度网盘手机App,操作更方便哦

安装Activiti插件
https://blog.csdn.net/qq_33547950/article/details/54926435

四、eclipse安装流程设计器

1、方式一

(1)打开 Help -> Install New Software.

(2)在Install界面板中,点击Add按钮

(3) 然后填入下列字段

Name: Activiti BPMN 2.0 designer 
Location: http://activiti.org/designer/update/ 

(4) 回到Install界面,在面板正中列表中把所有展示出来的项目都勾上

(5) 点击复选框
在Detail部分记得选中 “Contact all updates sites…” , 因为它会检查所有当前安装所需要的插件并可以被Eclipse下载

(6) 安装完以后,点击新建工程new->Other…打开面板

​ 可以看到activiti相关项目类型了, 说明安装成功了

2、方式二

在没有网络的情况下,安装流程设计器步骤如下:

  1. 解压老师发给大家的压缩包
  2. 把压缩包中的内容放入eclipse根目录的dropins文件夹下
  3. 重启eclipse,点击新建工程new->Other…打开面板,看到activiti插件

3、方式三

(1)下载 activiti-designer-5.18.0 (http://www.activiti.org/designer/archived/activiti-designer-5.18.0.zip )
这里提供github源码地址:https://github.com/Activiti/Activiti-Designer/releases

我的百度网盘地址:链接: https://pan.baidu.com/s/1nxoVxxZUgk2BrGZczcGIow 提取码: ad7p

(2)然后还是在上述安装软件对话框中,只是不再输入url,而是点击 Archive… 选择已经下来的zip离线包(.zip)
(3) 然后下一步下一步安装即可

4、生成图片

打开菜单Windows->Preferences->Activiti->Save下流程流程图片的生成方式
请添加图片描述

五、问题整理

1、权限校验

如果遇到集成后登陆跳出鉴权的输入框,有三种处理方式
(1)、在配置文件中加 (推荐使用)

security:
  basic:
    enabled: false

(2)、排除安全类

在启动类加 配置排除security的影响

@EnableAutoConfiguration(exclude = {
    
     org.activiti.spring.boot.SecurityAutoConfiguration.class}) 

(3)、在pom中排除security依赖

在使用到的activiti的jar中配置下排除security

<exclusions>
    <exclusion>
       <artifactId>spring-security-config</artifactId>
       <groupId>org.springframework.security</groupId>
   </exclusion>
   <exclusion>
       <artifactId>spring-security-core</artifactId>
       <groupId>org.springframework.security</groupId>
   </exclusion>
   <exclusion>
       <artifactId>spring-security-crypto</artifactId>
       <groupId>org.springframework.security</groupId>
   </exclusion>
   <exclusion>
       <artifactId>spring-security-web</artifactId>
       <groupId>org.springframework.security</groupId>
   </exclusion>

2、请假案例

package com.bean.activiti.service.impl;

import com.bean.activiti.entity.VacTask;
import com.bean.activiti.entity.Vacation;
import com.bean.activiti.util.ActivitiUtil;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.*;
import org.activiti.engine.history.*;
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.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.*;

/**
 * @author Gavin Lee
 * @version 1.0
 * @date 2020-09-18 12:26
 * @desc
 */
@Service
public class VacationService {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(VacationService.class);

    @Resource
    private RuntimeService runtimeService;
    @Resource
    private IdentityService identityService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;
    @Resource
    private RepositoryService repositoryService;


//    private static final String PROCESS_DEFINE_KEY = "vacationProcess";
    private static final String PROCESS_DEFINE_KEY = "studentVacationProcess";

   /* @PostConstruct
    public void initDeployment() {
        Deployment deployment2 = repositoryService.createDeployment().name("test")
                .addClasspathResource("./processes/vacation.bpmn")
                .deploy();
        System.out.println("Id:"+ deployment2.getId() + ";Name:" + deployment2.getName());

        Deployment deployment3 = repositoryService.createDeployment().name("test")
                .addClasspathResource("./processes/studentvaction.bpmn")
                .deploy();
        System.out.println("Id:"+ deployment3.getId() + ";Name:" + deployment3.getName());
    }*/


    public Object startVac(String userName, Vacation vac) {
    
    
		// 设置流程发起人(不是任务也不是负责人,在流程实例启动时候有)
        identityService.setAuthenticatedUserId(userName);
        // 开始流程
        ProcessInstance vacationInstance = runtimeService.startProcessInstanceByKey(PROCESS_DEFINE_KEY);
        // 查询当前任务
        Task currentTask = taskService.createTaskQuery().processInstanceId(vacationInstance.getId()).singleResult();
        // 申明任务
        taskService.claim(currentTask.getId(), userName);

        Map<String, Object> vars = new HashMap<>(4);
        vars.put("applyUser", userName);
        vars.put("days", vac.getDays());
        vars.put("reason", vac.getReason());

        taskService.complete(currentTask.getId(), vars);

        return true;
    }

    public Object myVac(String userName) {
    
    
        List<ProcessInstance> instanceList = runtimeService.createProcessInstanceQuery().startedBy(userName).list();
        List<Vacation> vacList = new ArrayList<>();
        for (ProcessInstance instance : instanceList) {
    
    
            Vacation vac = getVac(instance);
            vacList.add(vac);
        }
        return vacList;
    }

    private Vacation getVac(ProcessInstance instance) {
    
    
        Integer days = runtimeService.getVariable(instance.getId(), "days", Integer.class);
        String reason = runtimeService.getVariable(instance.getId(), "reason", String.class);
        Vacation vac = new Vacation();
        vac.setApplyUser(instance.getStartUserId());
        vac.setDays(days);
        vac.setReason(reason);
        Date startTime = instance.getStartTime(); // activiti 6 才有
        vac.setApplyTime(startTime);
        vac.setApplyStatus(instance.isEnded() ? "申请结束" : "等待审批");
        return vac;
    }


    public Object myAudit(String userName) {
    
    
        List<Task> taskList = taskService.createTaskQuery().taskCandidateUser(userName)
                .orderByTaskCreateTime().desc().list();
//        / 多此一举 taskList中包含了以下内容(用户的任务中包含了所在用户组的任务)
//        Group group = identityService.createGroupQuery().groupMember(userName).singleResult();
//        List<Task> list = taskService.createTaskQuery().taskCandidateGroup(group.getId()).list();
//        taskList.addAll(list);
        List<VacTask> vacTaskList = new ArrayList<>();
        for (Task task : taskList) {
    
    
            VacTask vacTask = new VacTask();
            vacTask.setId(task.getId());
            vacTask.setName(task.getName());
            vacTask.setCreateTime(task.getCreateTime());
            String instanceId = task.getProcessInstanceId();
            ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
            Vacation vac = getVac(instance);
            vacTask.setVac(vac);
            vacTaskList.add(vacTask);
        }
        return vacTaskList;
    }

    public Object passAudit(String userName, VacTask vacTask) {
    
    
        String taskId = vacTask.getId();
        String result = vacTask.getVac().getResult();
        Map<String, Object> vars = new HashMap<>();
        vars.put("result", result);
        vars.put("auditor", userName);
        vars.put("auditTime", new Date());
        taskService.claim(taskId, userName);
        taskService.complete(taskId, vars);
        return true;
    }

    public Object myVacRecord(String userName) {
    
    
        List<HistoricProcessInstance> hisProInstance = historyService.createHistoricProcessInstanceQuery()
                .processDefinitionKey(PROCESS_DEFINE_KEY).startedBy(userName).finished()
                .orderByProcessInstanceEndTime().desc().list();

        List<Vacation> vacList = new ArrayList<>();
        for (HistoricProcessInstance hisInstance : hisProInstance) {
    
    
            Vacation vacation = new Vacation();
            vacation.setApplyUser(hisInstance.getStartUserId());
            vacation.setApplyTime(hisInstance.getStartTime());
            vacation.setApplyStatus("申请结束");
            List<HistoricVariableInstance> varInstanceList = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(hisInstance.getId()).list();
            ActivitiUtil.setVars(vacation, varInstanceList);
            vacList.add(vacation);
        }
        return vacList;
    }


    public Object myAuditRecord(String userName) {
    
    
        List<HistoricProcessInstance> hisProInstance = historyService.createHistoricProcessInstanceQuery()
                .processDefinitionKey(PROCESS_DEFINE_KEY).involvedUser(userName).finished()
                .orderByProcessInstanceEndTime().desc().list();

        List<String> auditTaskNameList = new ArrayList<>();
        auditTaskNameList.add("学生填写申请");
        auditTaskNameList.add("教师审批");
        auditTaskNameList.add("教务处审批");
        auditTaskNameList.add("校长审批");

        List<Vacation> vacList = new ArrayList<>();
        for (HistoricProcessInstance hisInstance : hisProInstance) {
    
    
            List<HistoricTaskInstance> hisTaskInstanceList = historyService.createHistoricTaskInstanceQuery()
                    .processInstanceId(hisInstance.getId()).processFinished()
                    .taskAssignee(userName)
                    .taskNameIn(auditTaskNameList)
                    .orderByHistoricTaskInstanceEndTime().desc().list();
            boolean isMyAudit = false;
            for (HistoricTaskInstance taskInstance : hisTaskInstanceList) {
    
    
                if (taskInstance.getAssignee().equals(userName)) {
    
    
                    isMyAudit = true;
                }
            }
            if (!isMyAudit) {
    
    
                continue;
            }
            Vacation vacation = new Vacation();
            vacation.setApplyUser(hisInstance.getStartUserId());
            vacation.setApplyStatus("申请结束");
            vacation.setApplyTime(hisInstance.getStartTime());
            List<HistoricVariableInstance> varInstanceList = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(hisInstance.getId()).list();
            ActivitiUtil.setVars(vacation, varInstanceList);
            vacList.add(vacation);
        }
        return vacList;
    }
    /**
     * 删除流程实例
     * @param procinstId
     */
    public void deleteProcess(String procinstId) {
    
    
        deleteRuntimeProcess(procinstId);
        deleteHistoryRuntimeProcess(procinstId);
    }

    public String getProcessImage(String procinstId) {
    
    
        BufferedImage img = new BufferedImage(300, 150, BufferedImage.TYPE_INT_RGB);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        String imgBase64Str = "";
        try {
    
    
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(procinstId).singleResult();
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(historicProcessInstance.getProcessDefinitionId())//使用部署对象ID查询
                    .singleResult();
            List<Task> activeTasks=taskService.createTaskQuery().processDefinitionId(processDefinition.getId()).list();
            // 已执行的节点ID集合
            List<String> executedActivityIdList = new ArrayList<String>();
            List<String> highLines = new ArrayList<String>();
            List<String> highNodes = new ArrayList<String>();

            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
            for(Task tk:activeTasks){
    
    
                executedActivityIdList.add(tk.getTaskDefinitionKey());
                highLines.addAll(getHighLines(bpmnModel,tk.getTaskDefinitionKey()));
                highNodes.addAll(getHighNodes(bpmnModel,tk.getTaskDefinitionKey()));
            }
            // 获取流程图图像字符流
            InputStream imageStream = new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel, "png",
                    highNodes, highLines, "宋体", "宋体", "宋体", null, 1.0);
            // 输出资源内容到相应对象
            byte[] b = new byte[1024];
            int len;
            while ((len = imageStream.read(b, 0, 1024)) != -1) {
    
    
                outputStream.write(b, 0, len);
            }
            imgBase64Str = "data:image/png;base64," + Base64.getEncoder().encodeToString(Objects.requireNonNull(outputStream.toByteArray()));
            imageStream.close();
        } catch (Exception e) {
    
    
            LOGGER.error(e.getMessage(), e);
        }
        return imgBase64Str;
    }


    /**
     * 删除部署信息
     */
    public void deleteDeployment(String deploymentId){
    
    
        repositoryService.deleteDeployment(deploymentId, false);
    }

    /**
     * 在 ACT_HI_ACTINST 表中找到对应流程实例的userTask节点 不包括startEvent
     * @param processInstanceId
     * @return
     */
    private List<HistoricActivityInstance> getHisUserTaskActivityInstanceList(String processInstanceId) {
    
    
        List<HistoricActivityInstance> hisActivityInstanceList = ((HistoricActivityInstanceQuery) historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId).activityType("userTask")
                .finished().orderByHistoricActivityInstanceEndTime().desc())
                .list();
        return hisActivityInstanceList;
    }

    /**
     * 删除运行中流程实例
     * @param procinstId
     */
    private void deleteRuntimeProcess(String procinstId) {
    
    
        try {
    
    
            runtimeService.deleteProcessInstance(procinstId,"");
        } catch (Exception e) {
    
    
            LOGGER.error(e.getMessage(), e);
        }
    }

    /**
     * 删除历史中流程实例
     * @param procinstId
     */
    private void deleteHistoryRuntimeProcess(String procinstId) {
    
    
        try {
    
    
            historyService.deleteHistoricProcessInstance(procinstId);
        } catch (Exception e) {
    
    
            LOGGER.error(e.getMessage(), e);
        }
    }

    /**
     * 获取高亮的线
     * @param bpmnModel
     * @param key
     * @return
     */
    private Set<String> getHighLines(BpmnModel bpmnModel, String key){
    
    
        FlowNode fl=(FlowNode) bpmnModel.getFlowElement(key);
        List<SequenceFlow> pvmTransitions = fl.getIncomingFlows();
        Set<String> highLines=new HashSet<>();
        for(SequenceFlow sf:pvmTransitions){
    
    
            highLines.add(sf.getId());
            if(StringUtils.isNotBlank(sf.getSourceRef())){
    
    
                highLines.addAll(getHighLines(bpmnModel,sf.getSourceRef()));
            }
        }
        return highLines;
    }
    /**
     * 获取高亮的线
     * @param bpmnModel
     * @param key
     * @return
     */
    private Set<String> getHighNodes(BpmnModel bpmnModel, String key){
    
    
        FlowNode fl=(FlowNode) bpmnModel.getFlowElement(key);
        List<SequenceFlow> sequenceFlows = fl.getIncomingFlows();
        Set<String> highNodes=new HashSet<>();
        highNodes.add(key);
        for(SequenceFlow sf:sequenceFlows){
    
    
            if(StringUtils.isNotBlank(sf.getSourceRef())){
    
    
                highNodes.addAll(getHighNodes(bpmnModel,sf.getSourceRef()));
            }
        }
        return highNodes;
    }
}

六、参考文献

1、编辑器资源文件

通过在线编辑器创建一个模型1001,创建接口(注意该接口中已写死指定模型名称和模型id)

http://localhost:8000/demon/activiti/create

重定向到模型编辑页面

http://localhost:8000/demon/modeler.html?modelId=10001

然后通过拖拽编辑如下
请添加图片描述

以上案例点击保存按钮保存数据,然后可以调用部署流程接口进行数据部署

http://localhost:8000/demon/activiti/deploy/10001

部署后的编辑器源数据保存如下

{
	"resourceId": "10001",
	"properties": {
		"process_id": "process",
		"name": "",
		"documentation": "",
		"process_author": "",
		"process_version": "",
		"process_namespace": "http://www.activiti.org/processdef",
		"executionlisteners": "",
		"eventlisteners": "",
		"signaldefinitions": "",
		"messagedefinitions": ""
	},
	"stencil": {
		"id": "BPMNDiagram"
	},
	"childShapes": [{
		"resourceId": "sid-957BCB28-6934-44A0-BE05-73432A014398",
		"properties": {
			"overrideid": "",
			"name": "",
			"documentation": "",
			"executionlisteners": "",
			"initiator": "",
			"formkeydefinition": "",
			"formproperties": ""
		},
		"stencil": {
			"id": "StartNoneEvent"
		},
		"childShapes": [],
		"outgoing": [{
			"resourceId": "sid-52C74B61-E609-410F-B9BB-292314EB50B4"
		}],
		"bounds": {
			"lowerRight": {
				"x": 75,
				"y": 235
			},
			"upperLeft": {
				"x": 45,
				"y": 205
			}
		},
		"dockers": []
	},
	{
		"resourceId": "sid-37D5F680-A259-4005-BAA3-A9C8E279136B",
		"properties": {
			"overrideid": "",
			"name": "审批【部门经理】",
			"documentation": "",
			"asynchronousdefinition": "false",
			"exclusivedefinition": "false",
			"executionlisteners": "",
			"multiinstance_type": "None",
			"multiinstance_cardinality": "",
			"multiinstance_collection": "",
			"multiinstance_variable": "",
			"multiinstance_condition": "",
			"isforcompensation": "false",
			"usertaskassignment": "",
			"formkeydefinition": "",
			"duedatedefinition": "",
			"prioritydefinition": "",
			"formproperties": "",
			"tasklisteners": ""
		},
		"stencil": {
			"id": "UserTask"
		},
		"childShapes": [],
		"outgoing": [{
			"resourceId": "sid-97A0083A-42C1-487A-BFB6-7DBC380D2428"
		}],
		"bounds": {
			"lowerRight": {
				"x": 438.5,
				"y": 260
			},
			"upperLeft": {
				"x": 338.5,
				"y": 180
			}
		},
		"dockers": []
	},
	{
		"resourceId": "sid-6ADB3289-333C-4297-81F6-36B9E13F4061",
		"properties": {
			"overrideid": "",
			"name": "审批【总经理】",
			"documentation": "",
			"asynchronousdefinition": "false",
			"exclusivedefinition": "false",
			"executionlisteners": "",
			"multiinstance_type": "None",
			"multiinstance_cardinality": "",
			"multiinstance_collection": "",
			"multiinstance_variable": "",
			"multiinstance_condition": "",
			"isforcompensation": "false",
			"usertaskassignment": "",
			"formkeydefinition": "",
			"duedatedefinition": "",
			"prioritydefinition": "",
			"formproperties": "",
			"tasklisteners": ""
		},
		"stencil": {
			"id": "UserTask"
		},
		"childShapes": [],
		"outgoing": [{
			"resourceId": "sid-68C2971F-BA9E-46EA-A766-B8CB06CCBE71"
		}],
		"bounds": {
			"lowerRight": {
				"x": 618.5,
				"y": 260
			},
			"upperLeft": {
				"x": 518.5,
				"y": 180
			}
		},
		"dockers": []
	},
	{
		"resourceId": "sid-97A0083A-42C1-487A-BFB6-7DBC380D2428",
		"properties": {
			"overrideid": "",
			"name": "",
			"documentation": "",
			"conditionsequenceflow": "",
			"executionlisteners": "",
			"defaultflow": "false"
		},
		"stencil": {
			"id": "SequenceFlow"
		},
		"childShapes": [],
		"outgoing": [{
			"resourceId": "sid-6ADB3289-333C-4297-81F6-36B9E13F4061"
		}],
		"bounds": {
			"lowerRight": {
				"x": 518.28125,
				"y": 220
			},
			"upperLeft": {
				"x": 438.71875,
				"y": 220
			}
		},
		"dockers": [{
			"x": 50,
			"y": 40
		},
		{
			"x": 50,
			"y": 40
		}],
		"target": {
			"resourceId": "sid-6ADB3289-333C-4297-81F6-36B9E13F4061"
		}
	},
	{
		"resourceId": "sid-8E38823D-CB5B-4284-AA47-DC8C7F28662F",
		"properties": {
			"overrideid": "",
			"name": "",
			"documentation": "",
			"executionlisteners": ""
		},
		"stencil": {
			"id": "EndNoneEvent"
		},
		"childShapes": [],
		"outgoing": [],
		"bounds": {
			"lowerRight": {
				"x": 691.5,
				"y": 234
			},
			"upperLeft": {
				"x": 663.5,
				"y": 206
			}
		},
		"dockers": []
	},
	{
		"resourceId": "sid-68C2971F-BA9E-46EA-A766-B8CB06CCBE71",
		"properties": {
			"overrideid": "",
			"name": "",
			"documentation": "",
			"conditionsequenceflow": "",
			"executionlisteners": "",
			"defaultflow": "false"
		},
		"stencil": {
			"id": "SequenceFlow"
		},
		"childShapes": [],
		"outgoing": [{
			"resourceId": "sid-8E38823D-CB5B-4284-AA47-DC8C7F28662F"
		}],
		"bounds": {
			"lowerRight": {
				"x": 662.875,
				"y": 220
			},
			"upperLeft": {
				"x": 618.890625,
				"y": 220
			}
		},
		"dockers": [{
			"x": 50,
			"y": 40
		},
		{
			"x": 14,
			"y": 14
		}],
		"target": {
			"resourceId": "sid-8E38823D-CB5B-4284-AA47-DC8C7F28662F"
		}
	},
	{
		"resourceId": "sid-4497E91E-659E-487C-B0B3-F9E2D002899B",
		"properties": {
			"overrideid": "",
			"name": "提交申请",
			"documentation": "",
			"asynchronousdefinition": "false",
			"exclusivedefinition": "false",
			"executionlisteners": "",
			"multiinstance_type": "None",
			"multiinstance_cardinality": "",
			"multiinstance_collection": "",
			"multiinstance_variable": "",
			"multiinstance_condition": "",
			"isforcompensation": "false",
			"usertaskassignment": "",
			"formkeydefinition": "",
			"duedatedefinition": "",
			"prioritydefinition": "",
			"formproperties": "",
			"tasklisteners": ""
		},
		"stencil": {
			"id": "UserTask"
		},
		"childShapes": [],
		"outgoing": [{
			"resourceId": "sid-37D13494-261A-431A-B338-22745A8C11F4"
		}],
		"bounds": {
			"lowerRight": {
				"x": 250,
				"y": 260
			},
			"upperLeft": {
				"x": 150,
				"y": 180
			}
		},
		"dockers": []
	},
	{
		"resourceId": "sid-52C74B61-E609-410F-B9BB-292314EB50B4",
		"properties": {
			"overrideid": "",
			"name": "",
			"documentation": "",
			"conditionsequenceflow": "",
			"executionlisteners": "",
			"defaultflow": "false"
		},
		"stencil": {
			"id": "SequenceFlow"
		},
		"childShapes": [],
		"outgoing": [{
			"resourceId": "sid-4497E91E-659E-487C-B0B3-F9E2D002899B"
		}],
		"bounds": {
			"lowerRight": {
				"x": 149.78125,
				"y": 220
			},
			"upperLeft": {
				"x": 75.21875,
				"y": 220
			}
		},
		"dockers": [{
			"x": 15,
			"y": 15
		},
		{
			"x": 50,
			"y": 40
		}],
		"target": {
			"resourceId": "sid-4497E91E-659E-487C-B0B3-F9E2D002899B"
		}
	},
	{
		"resourceId": "sid-37D13494-261A-431A-B338-22745A8C11F4",
		"properties": {
			"overrideid": "",
			"name": "",
			"documentation": "",
			"conditionsequenceflow": "",
			"executionlisteners": "",
			"defaultflow": "false"
		},
		"stencil": {
			"id": "SequenceFlow"
		},
		"childShapes": [],
		"outgoing": [{
			"resourceId": "sid-37D5F680-A259-4005-BAA3-A9C8E279136B"
		}],
		"bounds": {
			"lowerRight": {
				"x": 338.166015625,
				"y": 220
			},
			"upperLeft": {
				"x": 250.333984375,
				"y": 220
			}
		},
		"dockers": [{
			"x": 50,
			"y": 40
		},
		{
			"x": 50,
			"y": 40
		}],
		"target": {
			"resourceId": "sid-37D5F680-A259-4005-BAA3-A9C8E279136B"
		}
	}],
	"bounds": {
		"lowerRight": {
			"x": 1200,
			"y": 1050
		},
		"upperLeft": {
			"x": 0,
			"y": 0
		}
	},
	"stencilset": {
		"url": "stencilsets/bpmn2.0/bpmn2.0.json",
		"namespace": "http://b3mn.org/stencilset/bpmn2.0#"
	},
	"ssextensions": []
}

创建的模型数据如下
请添加图片描述

部署成功后返回模型部署id
请添加图片描述

2、参考文献

参考文献:

https://blog.csdn.net/ssyujay/article/details/83896809

https://blog.csdn.net/puhaiyang/article/details/79845248

https://www.cnblogs.com/jpfss/p/11084050.html

https://www.jianshu.com/p/e5e3c381ef3f

activiti: https://www.activiti.org/userguide/

https://gitee.com/shenzhanwang/Spring-activiti#https://github.com/shenzhanwang/devoops

https://blog.51cto.com/u_13479739/2468362

源码获取、合作、技术交流请获取如下联系方式:
QQ交流群:961179337
在这里插入图片描述

微信账号:lixiang6153
公众号:IT技术快餐
电子邮箱:[email protected]

猜你喜欢

转载自blog.csdn.net/lixiang987654321/article/details/119275858