【Activity】从零开始到把握工作流开发业务代码
Activity #工作流
引言
本文算是本人从零边钻研边实际捣鼓到业务上线后全方位补充的一个笔记,对于技术人员举荐间接从 第七章技术选型 开始浏览,后面的实践局部根本是集体收集的一些网络材料和集体了解笔记,对于应用工作流实现性能没有任何影响。
再次强调,本文十分十分长,请读者按需观看。
一、工作流术语定义
1.1 根本定义
Georgakopoulos(乔治亚 * 科普洛斯)给出的工作流定义是:工作流是将一组工作组织起来以实现某个经营过程,定义了工作的触发程序和触发条件,每个工作能够由一个或多个软件系统实现,也能够由一个或一组人实现,还能够由一个或多集体与软件系统合作实现。
工作流实现了一整套流程外部主动实现的技术,能够屏蔽掉一些繁琐流程的业务进行简化。如果你的业务中没有工作流技术利用,那么齐全能够敞开本文而后去刷刷视频干点别的。
1.2 相干术语
上面工作流程的术语定义。
术语 | 含意 | 不失当比喻 |
---|---|---|
工作流 | 工作从开始到实现的过程。蕴含流程逻辑和路线规定,流程逻辑蕴含工作的执行程序,路线规定代表工作执行过程中必须恪守的路线, | 一项工作或者工作整体过程 |
流程定义 | 图形的流程定义。代表工作流的流程逻辑元素和它们元素关系。 | 相似快递的传输路线 |
流程实例 | 也叫工作。流程定义的 运行实例。 | 相似正在运行的汽车 |
工作流零碎 | 存储流程定义,通过 工作流引擎 组件驱动流程定义进行工作。 | 相似汽车发动机 |
流程定义工具 | 创立和更改流程定义的工具。能够是一个软件的组件,也能够是独立的应用程序。流程定义的工具具备复用性。 | 绘制汽车设计图的工具或者软件,能够批改产品设计 |
参与者 | 参与者能够是形象或者具体的,比方资源集、特定资源、组织单元、角色(一个人在组织外部的作用)、人或零碎(主动代理)。 | 路线上每一个节点的行为 |
流动 | 组成流程定义中的一个 逻辑步骤的工作。能够是主动的或人工的。常见的主动流动就是截止期限治理,如果到期未实现就主动发送揭示音讯。手动则是须要人力驱动零碎执行业务规定,比方咱们日常生活的报销申请审批。 | 形容某一项工作的解决细节 |
流动所有者 | 有权发表流动完结或者把工作流程推动到下一个流动参与者 | 快递运输过程中的每一个站点如何解决快递 |
工作所有者 | 有权参加实例执行过程的参与者。 | 相似会议的参加人员,有权加入然而不肯定有执行权 |
工作项 | 流程实例中流动的参与者将要执行的工作 | 比方送货的最初一步须要快递员派送快递到用户手里 |
1.3 为什么被叫做流?
各个节点通过内外部驱动触发引起节点的推动,造成一个流式的状态达到业务起点。比方一次用户查看淘宝商品的费用、一次领取胜利后的权利开明、一次用户注册、一次调度工作的运行等,都是能够是一个工作流。
1.4 艰深了解
艰深了解:工作流 抽取了流程(例如:销假、报销、岗位调整等流程)运行过程的共性,将 业务解决 和流程流转 剥离,缩小编码过程中的重复性(审批、驳回、转办、挂起、停止等管理工作)操作,并且实现流程流转去纸质化和可视化的技术。
1.5 生命周期
一个残缺的工作流生命周期次要有 5 步:
1、定义:即流程的定义,所有的流程总是从定义开始。次要工作是收集需要并将其转化为流程定义。
2、公布:开发人员将资源打包后在零碎平台中公布流程定义,次要工作流程定义文件 / 自定义表单 / 工作监听类等。
3、执行:具体的流程引擎依照下面定义的流程解决路线来执行业务。
4、监控:收集每个工作的后果,将依据不同后果来做解决。
5、优化:此时业务流程曾经实现,须要的就是优化流程或从新设计等。
二、为什么须要工作流
2.1 工作流管理系统劣势
1、疾速、高效、稳固的流程引擎,引擎反对大并发拜访。
2、兼具人工和主动流程,具备显著的“中国流程”特色的柔性。
3、灵便的部署形式,反对集中部署、分布式部署。
4、高效的流程集成、整合框架;同时反对流程开发。
5、国内数十个行业,领有近千个胜利的客户案例。
2.2 业务可视化
举个例子,退款自身非常复杂,经营、产品、技术、财务可能都无奈从繁多的角色来解释分明到底退款的整个链路和关键环节,然而通过工作流的形式来出现,则所有人能疾速看到退款到底是个什么样的业务。
通过流程图,咱们能够清晰的看到节点之间的变动。
2.3 业务可编排
业务流程的编排在理论的运作过程中可能随时变动,工作流流程具备编排性,能够通过节点疾速变动业务流程,能够灵便的增减节点,并且不会对于整个流程产生影响。
另一方面代码的可编排意味着代码复用性能够显著进步,比方下面的减少一个【敞开用户权利】的节点,或者删除【用户音讯】,咱们只须要批改流程而不须要调整业务代码。
2.4 主动重试
局部工作流反对长久化和主动重试能力。比方有时候须要在流程外面动静增删节点,然而动静增删节点可能会呈现失败的状况,呈现这种问题的时候能够通过局部节点重试解决问题。
三、常见开源工作流的比照(国外报告)
数据起源:Java Workflow Engines Comparison
Feature | Workflow Server | Activiti | jBPM | Camunda | Copper |
---|---|---|---|---|---|
Workflow types(工作流类型) | State machine | State machine | State machine | State machine | State machine |
Supported databases(反对数据库) | MS SQL Server, PostgreSQL, Oracle, MySQL, MongoDB | MS SQL, PostgreSQL, Oracle, MySQL, H2, DB2 | db2, derby, h2, hsqldb, mysql, oracle, postgresql, sqlserver | MS SQL, PostgreSQL, Oracle, MySQL, H2, DB2, MariaDB | PostgreSQL, Oracle, MySQL, H2, Apache Cassandra |
Long-running operations(是否反对长期运作) | Yes | Yes | Yes | Yes | Yes |
State persistence(状态长久化) | Serialization type is defined by Persistence Provider. You can easily control and change the settings saving process. | Activiti supports recovery in case of error, and will restart as a transactional state machine at the lowest level. | The runtime state of an executing process can be made persistent, for example, in a database. This allows to restore execution states of all running processes in case of unexpected failure. | Persistence Strategy based on the following concepts – Compact Tables, Deadlock Avoidance, Control Savepoints, Intelligent Caching, True Concurrency. | Not declared |
Versioning & upgrading(版本治理和降级) | The processes that were created before the schema change, work under the old scheme, whereas the scheme of the specific process is updated after calling the appropriate command. | Versioning only | Both | With the use of additional converters | Yes, you can dynamically modify workflows at runtime. As soon as you save the changed code, Copper compiles it automatically and loads it. |
Scheme format(底层存储格局) | The proprietary format based on XML. Import and export to BPMN2 starting with version 2.1. | BPMN2 | BPMN2 | BPMN2 | Process scheme is declared as Java-code |
Installing the process in an arbitrary state(是否反对任意状态兼容工作流) | Yes, by calling a single method | Yes | Yes | Yes | Yes |
Obtaining a list of available states for the current process(是否反对取得以后过程的可用状态列表) | Yes, by calling a single method | Yes, by calling a single method | No | No | Unknown |
Built-in authorization of access to external actions (commands) for the workflow(是否容许内部拜访外部流程) | Yes | No | No | No | No |
Timers and delays(是否反对计时和提早) | Yes | Yes | Yes | Yes | Yes |
Obtaining a list of available external actions for the current process(取得以后过程的可用内部口头的列表) | Yes, by calling a single method | No | No | No | No |
Simulated process execution(是否反对流程单元测试) | Yes, this mode is called Pre-Execution. | No | No | No | No |
Modifying schemes at runtime(是否反对在运行时扭转流程) | Yes, built in. | No | No | No | Yes |
Obtaining process lists for Inbox and Outbox folders(获取收件箱和发件箱文件夹的过程列表) | Yes | No | No | No | No |
四、工作流应用场景
4.1 畛域业务高复杂度
比方进销存、CRM、订单治理等具备肯定的畛域复杂度的业务,能够用工作流模式,来实现业务的可视化。
4.2 多节点、长链路
比方业务流转的过程要通过多集体,然而每个节点都须要独立,这种就比拟适宜无状态的内存工作流。
多节点比方一个审批工作须要同时转交给多集体审批并且同时通过能力进行下一步,长链路则是遇到审批过程须要多于 5 层以上的时候十分不便。
4.3 状态长久化和主动重试
例如订单领取胜利后,驱动上游业务零碎开明、发送用户揭示音讯、扣减库存等异步流程节点,须要长久化每个节点的执行状态。
五、工作流分类
那么工作流该如何分类,如何形象和归类本身的业务?
咱们能够把工作流大抵分类为:内存工作流、状态机工作流、人工工作流。
5.1 内存工作流
内存工作流是最简略的工作流,不须要长久化并且没有状态,相似代码的业务逻辑流转,因为所有的逻辑都是内存实现,业务自身是无状态的,所以不须要长久化。
内存工作流也能够用业务编码实现,能够使得业务可视化,视图能够看出整个业务是如何流转的,业务工作流使的每个节点可能最大水平的复用。
5.2 状态机工作流
当须要主动重试节点操作的时候须要用到状态机,状态机能够看作是内存工作流的降级。个别的工作流引擎提供了默认集成的调度框架能够低成本的实现主动调度。
当某个节点解决失败后,节点置为异样状态,工作流调度模块会拉取失败的节点,持续依照工作流预约义的流程重试,直到重试到指定的配置次数后,将整个流程置为执行失败,此时须要人工染指。
5.3 人工工作流
人工工作流应用场景最为宽泛和频繁也最贴合理论生存。人工工作流也叫做 内部触发驱动工作流,至多是存在一个或者多个节点是待内部确认能力推动整体业务流程。
人工工作流最大特点是状态流转的手动触发机制,这种拦挡触发到下一流程的机制能够实用于大部分业务。比方业务流程依赖审批的,比方退款流程,须要负责人审批通过后能力进行打款。
因为人工工作流须要手动触发能力进行下一步的流转,设计会略微简单一些,分了多个表。
- bpm_workflow_instance:工作流实例表,示意一个具体执行的业务流;
- bpm_task_instance:工作实例,将工作流的每个节点当做一个工作实例存储下来,形容一个工作流实例里 每个节点的具体状态;
- bpm_param_instance:参数实例,工作流或者工作实例的上下文入参快照;
- bpm_timer_task:解决定时工作表,比方人工节点未审批主动超期等 bpm_sequence:生成上述四个表的主键 ID 表;
六、工作流设计准则
6.1 工作流设计准则
6.1.1 角色
- 发起人:发起人是一个流程的次要关系者,是最关怀审批停顿的人,发起人实现的次要是事务性、操作性的工作。从发起人的角度来说,在创立完审批事项后,还须要欠缺相干信息、督促审批人及时处理、跟进驳回修改意见、从新提交。
- 审批人:审批人须要实现流程中的决策性工作,因而在审批人的视角,内容和操作都应该尽量精简:只展现出最重要的信息,防止过多信息影响判断,只进行必要操作,不能有过多抉择或操作,且须要保留审批历史,以便追溯。
6.1.2 内容:提炼最小汇合
内容设计要点在于尽可能精炼内容,要留神保留必要内容,但但凡零碎可主动获取的数据,就尽量避免发起人手动输出预置的罕用内容,用抉择的形式代替输出的,同时也能进步内容规范性。
6.1.3 依据业务设计流程
- 自主选定审批人流程:这是一种比拟轻量、灵便的审批流程模式,实用没有标准化工作流程的业务场景。当流程发起人发动审批事项时,须要主动抉择下一个环节的审批人,而下一个环节的审批人审批通过后,能够抉择持续流转到再下一人,也可完结这个流程。
- 串行流程:只有当每个环节的审批人通过流程后,才会进入到下一环节。每个环节的驳回,能够依据业务须要,设计成驳回到发起人、驳回到上一环节或回到指定环节从新审批,或兼而有之,作为选项供审批人抉择。
- 并行流程:并行流程是审批环节须要多人(角色)审批同意才算通过。任意一人审批通过即进入下一环节 & 必须所有人审批通过才进入下一环节。
- 条件触发流程:例如金额低于 1 万元由财务总监审批通过后即完结,金额在 1 万元以上则由副总裁审批通过后即完结。
- 混合流程:以上述金额审批为例,若金额低于 1 万元,由财务审批通过后即完结;金额在 1 万元到 10 万元的,须要先由财务审批,之后交由副总裁审批通过后即完结;金额高于 10 万元的,须要由董事长和总裁一起审批通过后才完结。
6.1.4 审批动作确定
- 通过。通过起因是否必填须要依据业务理论状况决定,通过之后流程会持续流转到下一个节点。
- 驳回批改。驳回起因个别须要设定成必填项,否则发起人或前一审批环节的参与者将无奈获取被驳回的理由、和批改倡议。驳回能够设计为三种:驳回到发起人 & 驳回上一环节 & 驳回到选定的之前的某个审批环节。
- 从新提交。通常审批人审批从新提交的内容时,须要附带上一次驳回的起因。
- 勾销。可选操作,个别由发起人而不是审批人勾销,起因是审批人只关怀是否通过还是驳回,勾销和驳回容易混同。
6.1.5 权限
权限体系的设计是一个大工程,在审批流程中,采纳基于角色的访问控制体系(RBAC)是一个不错的抉择。基于角色的访问控制体系,包含 用户、角色、指标、操作、许可权 五个根本数据元素,具体内容分为可辨别为性能权限和数据权限。
这里不过多扩大权限设计,在工作流设计中权限次要关注什么人能够发动审批,性能权限在领有对应权限的角色可见,数据权限则通常是发起人和审批人独特可见。
6.1.6 配置和扩展性
个别针对企业开发后盾零碎的灵活性绝对较少,而面向多个企业的商业化的零碎则灵活性要求高。配置和扩展性通常和业务复杂程度成正比。
配置的灵活性体现在以下方面:
- 审批流程的类型
- 可批改具体的审批环节
- 可增删改的各个环节审批人 / 角色
- 可配置的审批相干的权限。
6.1.7 效率
工作流的外围指标是进步企业和组织的运行效率,如果线上审批流程效率还不如原来的纸质操作,那这个流程的设计就是失败的,也失去了意义。所以利用工作流晋升审批流程效率,要留神尽可能精简审批的操作,做好流程操作的疏导。
6.2 流程编排
流程编码波及到各个节点的跳转细节。
流程编排的时候须要思考那些局部须要判断,哪些地方须要进行分支,须要留神失败节点的解决十分重要,同时也要思考工作流无奈往下进行的问题等。
6.3 异样解决
个别的工作流引擎工具中就设计了对异样的对立解决形式,上面的代码在也能够参考在日常开发过程中应用。
以上代码能够看出,工作流根本不解决异样,而是把异样收集到下层应用层进行对立解决。对立解决异样的长处是能够对立工作流节点错误处理形式,还能够打印谬误揭示日志,不便疾速定位工作流运行期间产生的异样问题。
6.4 工作流问题
- 罕用的 bmpn 格局就存在较多的问题,底层格局为难懂的 XML 元素。
- 图形拖拽不美观、上下文变量设置麻烦、容易出异样未知问题。
- 始终没有一个十分极致体验的工作流引擎。
- 引入工作流自身会减少工程难度。
- 减少学习和应用老本。
6.5 不实用场景
- 简略的业务逻辑,几段代码搞定的,也无需思考应用工作流。
- 比较简单的流程定义。
- 业务比拟固定的场景。
6.6 工作流业务特点
特点:
- 图形化、可视化设计流程图。
- 反对各种简单流程。
- 组织构造级解决者指定性能。
- B/ S 构造,纯浏览器利用。
- 弱小的安全性特色。
- 表单功能强大,扩大便捷。
- 灵便的外出、超时管理策略。
- 处理过程可跟踪、治理。
- 丰盛的统计、查问、报表性能。
- 与 MAIL 系统集成。
6.7 工作流思维
只有采纳流程模板 + 实例化思维都能够认为是工作流。
七、工作流技术选型
工作流如果是从零设计非常复杂,这里次要介绍介绍市面上常见的工作流引擎,市面上比拟支流的工作流引擎为osworkflow、jbpm、activiti、flowable、camunda……
其中 Jbpm4、Activiti、Flowable、camunda 四个框架同宗同源,先人都是Jbpm4,开发者只有用过其中一个框架,基本上就会用其它三个。
7.1 Osworkflow
Osworkflow 是一个轻量化的流程引擎,基于状态机机制,数据库表很少,Osworkflow 提供的工作流形成元素有:步骤(step)、条件(conditions)、循环(loops)、分支(spilts)、合并(joins)等,但不反对会签、跳转、退回、加签等这些操作,须要本人扩大开发,有肯定难度,如果流程比较简单,osworkflow 是不错的抉择。
文档地址:https://docs.huihoo.com/osworkflow/osworkflow-developer-guide.pdf
概括:轻量化流程引擎,基于状态机的机制进行工作,因为轻量级所以数据库内容比拟少,比拟适宜简略的业务。
Osworkflow 提供的工作流形成元素
- 步骤(step)
- 条件(conditions)
- 循环(loops)
- 分支(spilts)
- 合并
不反对的操作
- 回签
- 跳转
- 退回
- 加签
特点:因为轻量化,所以有些性能须要本人开发,针对一些简略流程的疾速开发是比拟好的抉择。
7.2 JBPM
官网:jBPM – Open Source Business Automation Toolkit – jBPM Business Automation Toolkit
JBPM 也是老牌软件,由 JBoss 公司开发,目前最高版本 JPBM7,须要留神从 JBPM5 开始就曾经和后面的版本断层了(5 开始基于 Drools Flow
,也叫做规定引擎),而大部分网络材料都是基于 JBPM 4 的,所以 强烈不倡议 抉择 jBPM5 当前版本,也不举荐用 JBPM,比拟容易踩坑。
JBPM 的规定引擎引入不仅没有升高工作流的了解和应用老本,反而导致结构设计凌乱并且臃肿不堪。
7.3 Activiti
Activiti 是自带 Spring 官网集成反对的工作流引擎,如果是 SpringBoot 开发能够作为备选计划。
须要留神 Activity 的背景比较复杂,最高版本 activiti 7,此外版本抉择上比较复杂,有 activiti5、activiti6、activiti7 几个支流版本,须要审慎考量。
咱们借着 Activity 扩大下相干历史。
activiti5 和 activiti6 的外围 leader 是 Tijs Rademakers(蒂亚斯 - 拉德梅克),因为团队外部一致,在 2017 年时 Tijs Rademakers 来到团队,创立了起初的 flowable, activiti6 以及 activiti5 代码曾经交接给了 Salaboy 团队, activiti6 以及 activiti5 的代码官网曾经暂停保护了, 目前支流版本为 Activiti7,然而 activiti7 内核应用的还是 activiti6,并没有为引擎注入更多的新个性,更多是对于云服务的一系列反对。
依据下面所述,咱们得悉 Activiti 有三个版本:activiti5、activiti6、activiti7。默认倡议抉择 Activiti7 的版本,Activiti 的独立起初是为了代替 JBPM 的,然而起初开发团队外部一致,主创因为意见不和,到职之后退出其余团队捣鼓出了 Flowable。由此可见 Flowable 必然把 Activity 以及 JBPM 带来的的历史遗留问题一一解决,所以也是比拟举荐的工作流引擎抉择。
说白了捣鼓来捣鼓去的都是同一帮人,和 Java 日志的历史有点像。
官网:Open Source Business Automation | Activiti
7.4 Flowable
Flowable 基于 activiti6 衍生进去的版本,目前最新版本是 v6.6.0,开发团队是从 activiti 中决裂进去的,修复了一众 activiti6 的 bug,并在其根底上研发了 DMN 反对,BPEL 反对等等,绝对开源版其商业版的性能会更弱小。
2016 年 10 月,Activiti 工作流引擎的次要开发者来到 Alfresco 公司,并在 Activiti 分支根底上开启了 Flowable 开源我的项目。基于 Activiti v6 beta4 公布的第一个 Flowable release 版本为 6.0。
Flowable 也是目前比拟支流的一种工作流引擎实现。
Flowable 在后续竭力倒退商业化,导致开源版本的更新比较落后。
7.5 Camunda
Camunda 基于 Activiti5,所以其保留了 PVM,最新版本 Camunda7.15,放弃每年公布 2 个小版本的节奏,开发团队又是从 activiti 中决裂进去的,倒退轨迹与 Flowable 类似,同时也提供了商业版,不过对于个别企业应用,开源版本也足够了。
黄埔军校:Activity
抉择 Camunda 的理由
(1)通过压力测试验证 Camunda BPMN 引擎性能和稳定性更好。
(2)性能比较完善,除了 BPMN,Camunda 还反对企业和社区版本中的 CMMN(案例治理)和 DMN(决策自动化)。Camunda 不仅带有引擎,还带有十分弱小的工具,用于建模,工作治理,操作监控和用户治理,所有这些都是开源的。
官方网站:https://docs.camunda.org/manual/7.15/
7.6 Easy-flow
码云上开源的流程设计器,没有社区和用户应用反馈反对,不过多介绍。
如果是深刻工作流引擎底层设计自身是一个不错的学习材料。
https://link.zhihu.com/?target=https%3A//gitee.com/xiaoka2017…
7.7 compileflow
淘宝开源的工作流引擎之一。算是国内最大的开源工作流引擎,然而存在一些“致命伤”,compileflow 原生只反对淘宝 BPM 标准 ,为兼容 BPMN 2.0 标准,做了肯定适配,但 仅反对局部 BPMN 2.0 元素,如需其余元素反对,可在原来根底上扩大。
github 地址:alibaba/compileflow: 🎨 core business process engine of Alibaba Halo platform, best process engine for trade scenes. | 一个高性能流程编排引擎 (github.com)
上面是官网的 README 介绍:
compileflow IDEA 设计插件: https://github.com/compileflow/compileflow-designer-upgrade
compileflow
是一个十分轻量、高性能、可集成、可扩大的流程引擎。
compileflow Process
引擎是淘宝工作流 TBBPM
引擎之一,是专一于纯内存执行,无状态的流程引擎,通过将流程文件转换生成 java
代码编译执行,简洁高效。以后是阿里业务中台交易等多个外围零碎的流程引擎。
compileflow
能让开发人员通过流程编辑器设计本人的业务流程,将简单的业务逻辑可视化,为业务设计人员与开发工程师架起了一座桥梁。
7.8 举荐组合
初学者集体比拟举荐 Activity7 + IDEA 的插件组合,最不便上手,Activity 人造反对 Spring 框架,主动生成的表数量仅为 28 张,相比 Flowable 的 70 多张表而言学习老本算是最低的,并且从下面的介绍能够看出,其余很多对标竞品的开发团队都是从 Activity黄埔军校 毕业的。
最为要害的是,这个组合网上还能够查到不少材料和实战 …..
八、流程设计器选型
8.1 bpmn-js
bpmn-js 是 BPMN 2.0 渲染工具包和 Web 模型,bpmn-js 正在致力成为 Camunda BPM 的一部分。
官方网站:https://bpmn.io/
8.2 mxGraph
mxGraph 是一个弱小的 JavaScript 流程图前端库,能够疾速创立交互式图表和图表应用程序,国内外驰名的 ProcessOne 和 draw.io 都是应用该库创立的弱小的在线流程图绘制网站。
8.3 Activiti-Modeler
Activiti 的旧版本中存在 web 版流程设计器,名字叫做 Activiti-explorer,长处是集成简略,开发工作量小,毛病是界面不美观,用户体验差。
8.4 Flowable-Modeler
Flowable 开源版本中带了 web 版流程设计器,展现格调和性能根本跟 Activiti-Modeler 一样,长处是集成简略,开发工作量小,毛病是界面不美观,用户体验差。
8.5 IDEA Activiti BPMN 插件
集体学习工作流模型图设计比拟举荐应用 Idea 的插件,最新版本的插件曾经被叫做activiti BPMN visualizer
。开发人员能够借助此插件相熟流程图的绘画过程。
PS:Activity 反对 BPMN2 标准,能够兼容大部分开源工作流框架应用。
8.6 logicFlow
滴滴开发的流程设计器:logicFlow,反对自定义优良,并能够将 bpmn-js 以插件模式集成进去,然而不举荐生产应用,比拟适宜作为国内优良学习材料的范本。
文档地址:https://github.com/didi/LogicFlow
九、Activity 详解
9.1 为什么要应用 Activity?
Activiti 是一个轻量级的工作流和业务流程治理(BPM)平台,面向业务人员,开发人员和系统管理员,外围是用于 Java 的的 BPMN 2 流程引擎。
- 开源可参考我的项目比拟多:github 和 gitee 上有十分多的成熟案例能够学习。
- Activiti 反对启动引擎后随时热部署。JBPM 存在一个软肋,RuntimeService 只能在启动的时候指定 bpmn 资源,一旦启动后便不再可能去更新或者减少 bpmn 了,这会导致咱们系统集成的艰难
- Activity 自身开源。开源意味着能够通过社区和其他人的实际案例解决应用问题上的疑难杂症。
- Activiti 依赖的第三方 jar 包绝对较少。
- Activiti 领有更敌对的用户体验。Activiti 则更贴近理论的利用场景,将流程设置为开始节点和完结节点,人工工作提供了表单设置,用户能够设置字段名称,字段类型。
- 自带 Spring 集成,同时也集成了 Spring Security 框架,也能够认为是 自带权限管制性能。
- API 简洁,开发人员能够疾速上手。
- Activity 7 没有显著的提高,并且可参考实战案例较少。
TaskQuery taskId(String taskId);
TaskQuery taskName(String name);
TaskQuery taskNameLike(String nameLike);
TaskQuery taskDescription(String description);
TaskQuery taskDescriptionLike(String descriptionLike);
TaskQuery taskPriority(Integer priority);
TaskQuery taskMinPriority(Integer minPriority);
TaskQuery taskMaxPriority(Integer maxPriority);
TaskQuery taskAssignee(String assignee);
TaskQuery taskAssigneeLike(String assigneeLike);
TaskQuery taskOwner(String owner);
TaskQuery taskOwnerLike(String ownerLike);
TaskQuery taskUnassigned();
TaskQuery taskUnnassigned();
Spring 集成 Activity 官网文档:Getting started with Activiti and Spring Boot
Activiti User Guide:Activiti User Guide
9.2 Activity 特点
1、数据长久化:Activiti 设计思维是简洁与疾速。
2、引擎 Service 接口:Activiti 引擎提供了七大 Service 接口,都是通过 ProcessEngine 获取,同时反对链式 API 编程格调。
Service 接口 | 作用 |
---|---|
RepositoryService | 流程仓库 Service,用于治理流程仓库,如:部署、删除、读取流程资源 |
IdentifyService | 身份 Service,可治理和查问用户、组之间的关系 |
RuntimeService | 运行时 Service,解决所有正在运行的工作和流程实例等 |
TaskService | 工作 Service,用于治理查问工作,如签收、办理、指派等 |
FormService | 表单 Service,用于读取和工作、流程相干的表单数据 |
HistoryService | 历史 Service,可查问所有历史数据 |
ManagementService | 引擎治理 Service,和具体业务无关,可用查问引擎配置、数据库、作业等 |
3、流程设计器:Activiti 团队设计了基于 BPMN2.0 标准的设计器 -Eclipse Designer,除此还有 Signavio 公司为 Activiti 定制的基于Web 的 Activiti Modeler 流程设计器。
4、原生反对 Spring:以后企业开发,基本上都会基于 Spring 去开发本人的零碎,因为 A ctiviti 原生反对 Spring,所以很轻松地进行 Spring 集成。
5、拆散运行时与历史数据:运行与历史数据的拆散,能够放慢运行时数据的性能,当须要历史数据时,咱们在去查问。
9.3 Activiti 利用
1、在系统集成方面:与 ESB 整合 / 与规定引擎整合 / 嵌入已有零碎平台(也是本我的项目的需要)
2、在其余产品中利用:Alfresco 公司的 ECM 产品在企业中利用,次要波及文档治理 / 合作 / 记录治理 / 知识库治理 / Web 内容治理等。
9.4 Activiti 框架与组件
Activiti 最重要的就是 引擎,除此之外就是内部的工具和组件。
Modeling | Runtion | Management |
---|---|---|
Activiti Modeler | Activiti Engine | Activiti Exproler |
Activiti Designer | Activiti REST | |
Activiti Kickstart |
上面对以上组件进行简略的阐明:
1、Activiti Engine:最外围的模块,提供针对 BPMN2.0 标准的解析 / 执行 / 创立 / 治理(工作 - 流程实例) / 查问历史记录并生成相应报表等。
2、Activiti Modeler:模型设计器,非 Activiti 公司开发。用于将需要转换为标准流程定义。
3、Activiti Designer:设计器,与 Activiti Modeler 性能相似。
4、Activiti Exproler:用来治理仓库 / 用户 / 组,启动流程 / 工作办理等。
5、Activiti REST:提供 REST 格调的服务,容许客户端以 JSON 的形式与引擎的 REST API 交互,协定具备跨平台 / 跨语言。
9.6 Activity 工作流要点
9.6.1 Idea 插件(学习)
IDEA 插件用于 Activity 画流程图应用,在 2023 年 IDEA 中这个我的项目叫做 Activiti BPMN visualizer
。能够间接在 ”Plugins” 当中查到。
应用 IDEA 插件的益处是在入门 Demo 的时候能够不依赖开源流程设计器组件即可绘制一些简略流程,并且因为遵循 bpmn2 的标准,能够通用在大部分工作流引擎上。
9.6.2 罕用 Service
Activiti 提供了几个 Service 类,用来管理工作流,罕用的有以下四项:
1) RepositoryService:提供 流程定义和部署 等性能。比如说,实现流程的的部署、删除,暂停和激活以及流程的查问等性能
2) RuntimeService:提供了解决 流程实例 不同步骤的构造和行为。包含启动流程实例、暂停和激活流程实例等性能
3) TaskService:提供无关 工作相干性能 的服务。包含工作的查问、删除以及实现等性能
4) HistoryService:提供 Activiti 引擎收集的 历史记录信息 服务。次要用于历史信息的查问性能
还有以下两项:
1)ManagementService:job 工作查问和数据库操作
2)DynamicBpmnService:无需重新部署就能批改流程定义内容
而 Spring Boot 集成 Activiti 实现工作流性能,也次要是采纳这些 Service 所提供的 相应的 API 来实现的。
9.6.3 ProcessEngine 对象
ProcessEngine 是 Activity 的工作引擎也是核心内容,作用是负责生成流程运行时的各种实例及数据、监控和治理流程的运行。理论应用过程个别会动态全局实例化这个对象,如果是 Spring 框架集成依赖则会由 Spring 帮咱们实现对象创立和托管。
ProcessEngine processEngine =ProcessEngines.getDefaultProcessEngine();
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
engine.getTaskService()
.complete("7502");
9.6.4 平安治理
后面介绍过 Activity 工作流 和 Spring Security 是能够进行绑定和兼容的,并且咱们应用的我的项目不想要 Security 也能够通过注解的形式禁用,比方理论我的项目中就通过上面的形式禁用 Spring 的 SecurityAutoConfiguration 和 Activity 的 SecurityAutoConfiguration:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
org.activiti.spring.boot.SecurityAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class
})
9.6.5 配置文件 activiti.cfg.xml
Activity 所需的配置文件也是 Activiti 外围配置文件,配置内容和咱们平时配置 JDBC 连贯相似。上面是参考的配置内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- 配置数据库相干 -->
<!-- 数据库驱动 -->
<property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"></property>
<!-- 数据库链接 -->
<property name="jdbcUrl" value="jdbc:mysql://localhost:12306/test?useSSL=false& useUnicode=true& characterEncoding=utf8& serverTimezone=Asia/Shanghai& nullCatalogMeansCurrent=true"></property>
<property name="jdbcUsername" value="xxxxx"></property>
<property name="jdbcPassword" value="xxxxx"></property>
<!-- 数据库表在生成时的依赖 -->
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
</beans>
9.6.6 application.yml 配置文件
spring:
datasource:
url: jdbc:mysql://localhost:12306/test?useUnicode=true&characterEncoding=utf8&nullCatalogMeansCurrent=true
username: xxx
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
activiti:
# flase:默认值。activiti 在启动时,会比照数据库表中保留的版本,如果没有表或者版本不匹配,将抛出异样。(生产环境罕用)# true:activiti 会对数据库中所有表进行更新操作。如果表不存在,则主动创立。(开发时罕用)# create_drop:在 activiti 启动时创立表,在敞开时删除表(必须手动敞开引擎,能力删除表)。(单元测试罕用)# drop-create:在 activiti 启动时删除原来的旧表,而后在创立新表(不须要手动敞开引擎)。database-schema-update: true
#默认不生成历史表,这里开启
db-history-used: true
#历史注销
# none: 不记录历史流程,性能高,流程完结后不可读取
# Activity: 归档流程实例和流动实例,流程变量不同步
# audit: 默认值,在 activiti 根底上同步变量值,保留表单属性
# full: 性能较差,记录所有实例和变量细节变动,最残缺的历史记录,如果须要日后跟踪具体能够开启 full(个别不倡议开启)history-level: full
deployment-mode: never-fail # 敞开 SpringAutoDeployment
- database-schema-update 配置为 true,即每次我的项目启动,都会对数据库进行更新操作,如果表不存在,则主动创立
- db-history-used 配置为 true 因为默认是不生成历史表的,配置为 true示意须要生成。
- history-level 配置 为 full,示意记录最残缺的历史记录。
-
deployment-mode 配置为 never-fail,即敞开掉 SpringAutoDeployment。如果不敞开,每次重新启动我的项目的时候,总是会在 ACT_RE_DEPLOYMENT 主动创立一个名为 SpringAutoDeployment 工作流记录。
- 影响:在开发阶段,须要常常重启我的项目,长此以往就会导致 ACT_RE_DEPLOYMENT 的记录越来越大了
9.7 表构造
如果配置了开启历史表的应用开关db-history-used: true。那么这里会额定创立历史表构造。我的项目启动之后会发现我的项目数据的会主动减少了 28 张 ACT 结尾的表。
9.7.1 5 种数据库表
迁徙 Activity 须要导入所需的数据库表,这些表的特点是所有的表都以ACT_ 结尾,第二局部示意对应的标识。
比方:
act_hi_*:’hi’示意 history,此前缀的表蕴含历史数据,如历史 (完结) 流程实例,变量,工作等等。
act_ge_*:’ge’示意 general,此前缀的表为通用数据,用于不同场景中。
act_evt_*:’evt’示意 event,此前缀的表为事件日志。
act_procdef_*:’procdef’示意 processdefine,此前缀的表为记录流程定义信息。
act_re_*:’re’示意 repository,此前缀的表蕴含了流程定义和流程动态资源(图片,规定等等)。
act_ru_*:’ru’示意 runtime,此前缀的表是记录运行时的数据,蕴含流程实例,工作,变量,异步工作等运行中的数据。Activiti 只在流程实例执行过程中保留这些数据,在流程完结时就会删除这些记录。
9.7.2 所有数据表简介
表名 | 表正文 |
---|---|
act_ge_bytearray | 二进制数据表,存储通用的流程定义和流程资源。 |
act_ge_property | 零碎相干属性,属性数据表存储整个流程引擎级别的数据,初始化表构造时,会默认插入三条记录。 |
act_re_deployment | 部署信息表 |
act_re_model | 流程设计模型部署表 |
act_re_procdef | 流程定义数据表 |
act_ru_deadletter_job | 作业死亡信息表,作业失败超过重试次数 |
act_ru_event_subscr | 运行时事件表 |
act_ru_execution | 运行时流程执行实例表 |
act_ru_identitylink | 运行时用户信息表 |
act_ru_integration | 运行时积分表 |
act_ru_job | 运行时作业信息表 |
act_ru_suspended_job | 运行时作业暂停表 |
act_ru_task | 运行时工作信息表 |
act_ru_timer_job | 运行时定时器作业表 |
act_ru_variable | 运行时变量信息表 |
act_hi_actinst | 历史节点表 |
act_hi_attachment | 历史附件表 |
act_hi_comment | 历史意见表 |
act_hi_detail | 历史详情表,提供历史变量的查问 |
act_hi_identitylink | 历史流程用户信息表 |
act_hi_procinst | 历史流程实例表 |
act_hi_taskinst | 历史工作实例表 |
act_hi_varinst | 历史变量表 |
act_evt_log | 流程引擎的通用事件日志记录表 |
act_procdef_info | 流程定义的动静变更信息 |
这里再分类理解一下:
全局通用数据(ACT_GE_*)
表名 | 解释 |
---|---|
ACT_GE_BYTEARRAY | 二进制数据表,存储通用的流程定义和流程资源。 |
ACT_GE_PROPERTY | 零碎相干属性,属性数据表存储整个流程引擎级别的数据。 |
动态信息表(ACT_RE_*)
表名 | 解释 |
---|---|
ACT_RE_DEPLOYMENT | 部署信息表 |
ACT_RE_MODEL | 流程设计模型部署表 |
ACT_RE_PROCDEF | 流程定义数据表 |
运行数据(ACT_RU_*)
表名 | 解释 |
---|---|
ACT_RU_DEADLETTER_JOB | 无奈执行工作表:如果一个工作执行了很屡次,都无奈执行,那么这个工作会写到此表 |
ACT_RU_EVENT_SUBSCR | 运行时事件 throwEvent、catchEvent 工夫监听信息表 |
ACT_RU_EXECUTION | 运行时流程执行实例 |
ACT_RU_IDENTITYLINK | 运行时流程人员表,次要存储工作节点与参与者的相干信息 |
ACT_RU_INTEGRATION | 运行时积分表 |
ACT_RU_JOB | 运行时定时工作数据表 |
ACT_RU_SUSPENDED_JOB | 暂停的工作,流程中有一个定时工作,如果把这个工作进行工作了,这个工作会在 act_ru_suspended_job 中写入数据 |
ACT_RU_TASK | 运行时工作节点表 |
ACT_RU_TIMER_JOB | 运行时定时器作业表 |
ACT_RU_VARIABLE | 运行时流程变量数据表 |
历史数据(ACT_HI_*)
表名 | 表正文 |
---|---|
act_hi_actinst | 历史节点表 |
act_hi_attachment | 历史附件表 |
act_hi_comment | 历史意见表 |
act_hi_detail | 历史详情表,提供历史变量的查问 |
act_hi_identitylink | 历史流程用户信息表 |
act_hi_procinst | 历史流程实例表 |
act_hi_taskinst | 历史工作实例表 |
act_hi_varinst | 历史变量表 |
其它表
表名 | 解释 |
---|---|
ACT_EVT_LOG | 事件日志 |
ACT_PROCDEF_INFO | 流程定义的动静变更信息 |
简略理解 Activity 的构造之后,上面介绍我的项目如何集成 Activity。
十、我的项目集成 Activity
万事开头难,集成 Activity 到我的项目可能是整个学习过程最难的一步,下文会将集体集成过程中遇到的问题一并列举。
10.1 版本抉择
能够参考网络材料抉择适合的版本。理论工作中因为可参考案例衡量、工夫起因和经验不足等种种原因,集体没有抉择材料和胜利案例比拟少的 Activity7 的版本,而是应用了网上可供参考材料比拟多的 6 的版本。
如果要集成 Activity 7,能够应用上面的依赖。
依据网络反馈和集体理论尝试,发现集成 Activity 7 同样会因为自带依赖版本抵触导致各种问题。
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
理论学习过程集体抉择了上面的依赖 <s> 抉择低版本的根本原因是网上有的代码抄 </s>:
- Activity 的相干组件依赖,比方将 JSON 数据转化为 Activity 的模型的编码依赖,bpmn 文件转为数据库以及逆向转化依赖,还有前前端 WEB 组件须要的一部分依赖。
<!-- Activity 依赖 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.9</version>
</dependency>
- 引入 Activity 依赖,留神 Activity6 应用的是还是 ORM 的查问形式,自带的 mybatis 依赖可能和 SpringBoot 不兼容,所以须要排除依赖附带的低版本 Mybatis 依赖。
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
增加以上依赖之后,启动我的项目查看是否能够失常启动。
10.2 集成 Activity web 编辑器
因为理论工作需要中要把 Activity 的 Web 版本组件(Activiti Explorer)集成到我的项目当中,工夫紧迫关系,集体抉择参考网上材料最终抉择从成熟的我的项目中拷贝前端依赖学习。
Activity 6.0 之后默认不再集成任何 Expolorer 的组件内容,然而能够从 Activity 5 当中找到相干依赖。参考链接:
- https://github.com/pcharoen/activiti-web/blob/master/wars/activiti-explorer.war
从官网的 war 包中进行集成比拟麻烦,更为快捷的形式是利用网络开源我的项目间接拷贝前端依赖,兼容到其余我的项目会略微好一些。
- RuoYi-activiti: 打造最好用的 OA 数字化解决方案,基于 Activiti6.0,集流程设计、流程部署、流程执行、工作办理、流程监控于一体的开源工作流开发平台。(gitee.com)
如果感觉成熟我的项目代码看起来比较复杂难以搜寻,能够参考上面这个我的项目,它基于 SpringBoot 实现非常简单易懂:
https://github.com/juzhizhang/springboot2-activiti-design
以 Ruoyi 的我的项目为例,咱们须要拷贝上面的内容:
- src / main / resources / static / diagram-viewer(倡议放到 static 目录)
- src / main / resources / static / editor-app(倡议放到 static 目录)
- src / main / resources / templates / modeler.html(倡议放到 templates 目录下)
- stencilset.json 文件(放到 resources 下)
留神这个组件应用的前端框架为国外比拟热门的 angular,比拟少见。
留神拷贝实现只是集成第一步,这时候前端是无奈失常应用,咱们还须要定义控制器,并且给前端提供必要 API 的接口。
10.3 提供 Activity web 编辑器必要 API 接口
要失常应用 Activity web 编辑器,我的项目中必须要提供几个必要的 API 接口。
10.3.1 editor 跳转集成
为了可能进入到编辑流程模型的页面,首先须要提供一个接口跳转到对应 html 文件。html 文件对应 modeler.html。
@GetMapping("/editor")
public String editor(){return "Activity/modeler";}
当咱们关上 WEB 编辑器进行流程模型编辑页面(modeler.html),咱们有可能会看到白板,这是因为页面还短少数据。简略来说就是外部须要 联合上下文 获取模型资源和组件 JSON 资源。
接口申请 URL 申请格局能够通过改前端脚本或者间接批改源码的形式调整。
下面次要蕴含了后缀别离是 sterncliset
和json
的接口。
10.3.2 sterncliset 接口提供
stencilset.json
的大抵内容如下,依据英文单词形容,很容易猜测出负责管理前端的组件展现。
案例:
activiti-explorer/editor/stencilset?version=1676012813897
。
提供 /editor/stencilset
管制层接口,通过 service 层调用获取 json 文件内容。
@RequestMapping(value = "/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
@ResponseBody
public String getStencilset() {
try {return ActivityModelService.getStencilset(STENCIL_SET);
} catch (Exception e) {log.error("Error while loading stencil set", e);
return "";
}
}
service 中利用了 Class 类加载器机制,因为 json 文件是放到 resources
上面的,能够间接从“根门路”获取到文件。
public String getStencilset(String resource) {InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream(resource);
try {return IOUtils.toString(stencilsetStream, "utf-8");
} catch (Exception e) {throw new ActivitiException("Error while loading stencil set", e);
}
}
10.3.3 加载指定模型数据
json
接口须要对应上面的控制器。
repositoryService:流程仓库 Service,用于治理流程仓库,如:部署、删除、读取流程资源。
@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(ModelDataJsonConstants.MODEL_NAME, model.getName());
}
modelNode.put(ModelDataJsonConstants.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;
}
10.3.4 保留模型接口
模型绘制实现之后,咱们须要保留数据接口,上面接口对应保留模型的操作。
@RequestMapping(value = "/model/{modelId}/save", method = RequestMethod.PUT)
@ResponseStatus(value = HttpStatus.OK)
public void saveModel(@PathVariable String modelId,
String name,
String description,
String json_xml,
String svg_xml) {
try {ActivityModelService.saveModel(modelId, name, description, json_xml, svg_xml);
}catch (Exception e){log.error("has error", e);
}
}
saveModel 办法代码如下:
@Override
public void saveModel(String modelId, String name, String description, String jsonXmlBytes, String svgXmlBytes) {
try {Model model = repositoryService.getModel(modelId);
ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
modelJson.put(ModelDataJsonConstants.MODEL_NAME, name);
modelJson.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
model.setMetaInfo(modelJson.toString());
model.setName(name);
model.setDeploymentId(null);
Integer version = model.getVersion();
version++;
model.setVersion(version);
repositoryService.saveModel(model);
repositoryService.addModelEditorSource(model.getId(), jsonXmlBytes.getBytes(StandardCharsets.UTF_8));
InputStream svgStream = new ByteArrayInputStream(svgXmlBytes.getBytes(StandardCharsets.UTF_8));
TranscoderInput input = new TranscoderInput(svgStream);
PNGTranscoder transcoder = new PNGTranscoder();
// Setup output
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput(outStream);
// Do the transformation
transcoder.transcode(input, output);
final byte[] result = outStream.toByteArray();
repositoryService.addModelEditorSourceExtra(model.getId(), result);
outStream.close();} catch (Exception e) {log.error("Error saving model", e);
throw new ActivitiException("Error saving model", e);
}
}
10.3.5 敞开页面
依据理论我的项目状况,有可能须要调整点击敞开申请地址。
closeEditor: function(services) {window.location.href = './';},
10.3.6 去除 Activity logo
搜寻.navbar-header
,并且批改为上面的内容。
.navbar-header {
padding-top: 0px;
line-height: 40px;
height: 40px;
color: #FFFFFF;
/*background: url(../images/logo.png) no-repeat 10px center;*/
/*width: 180px;*/ font-size: 20px;
margin-left: 30px;
font-weight: 660;
}
在 moderler.html
文件中,删除 logo 图片展现:
10.3.7 不必要组件流程组件
能够全局搜寻上面的正文,或者搜寻 stencil-controller.js
文件找到上面的内容。
// Add the stencil item to the correct group
批改上面的代码,代码外部保护一个 List,示意前端须要展现的内容,currentGroup
用于过滤出合乎的组件即可。
这部分性能是要去掉不须要的性能按钮,缩小误操作的可能性。
// 20230218 fixed
var sideList = ['StartNoneEvent', 'EndNoneEvent', 'UserTask'];
//
if (currentGroup) {
// Add the stencil item to the correct group
currentGroup.items.push(stencilItem);
if (ignoreForPaletteDefinition.indexOf(stencilItem.id) < 0) {if(sideList.include(stencilItem.id)){console.log(stencilItem);
currentGroup.paletteItems.push(stencilItem);
}
}
}
10.3.8 暗藏不必要的底部编辑窗口
底部编辑窗口也须要暗藏掉不须要的内容,暗藏思路和 7 点相似。
搜寻 selectedItem.properties.push(currentProperty);
,或者找到文件stencil-controller.js
找到上面的内容。
selectedItem.properties.push(currentProperty);
依葫芦画瓢,这里也增加一个数组:
var includeProperty = ['oryx-process_id', 'oryx-name', 'oryx-documentation', 'oryx-usertaskassignment'];
if(includeProperty.include(currentProperty.key)){selectedItem.properties.push(currentProperty);
}
这时候你可能会好奇,这些啥 stencilset.json
,实际上联合前端展现的数据标签以及 stencilset.json
能够找到相干内容,当然仔细阅读源代码也是种方法。
'oryx-process_id', 'oryx-name', 'oryx-documentation', 'oryx-usertaskassignment
10.3.9 调配负责人转为下拉框
理论利用中集体对于源码进行调整,把 Assignee 本来的流程变量表达式管制节点流转性能(下文会补充这个用法),转为抉择固定的下拉零碎角色,等于说是阉割灵活性使得非开发人员也能够失常上手(这部分齐全是业务须要)。
实现这样的成果要查找到 Activity 这部分的源码,查找须要花不少工夫这里就间接说答案了,找到 assignment-popup.html
文件,在结尾第十行左右的地位。
<div class="row row-no-gutter">
<div class="form-group">
<label for="assigneeField">{{'PROPERTY.ASSIGNMENT.ASSIGNEE' | translate}}</label>
<!-- <input type="text" id="assigneeField" class="form-control" ng-model="assignment.assignee" placeholder="{{'PROPERTY.ASSIGNMENT.ASSIGNEE_PLACEHOLDER'| translate}}" />-->
<select id="assigneeField" ng-model="assignment.assignee" class="form-control" placeholder="{{'PROPERTY.ASSIGNMENT.ASSIGNEE_PLACEHOLDER'| translate}}">
<option ng-repeat="item in myTestList" ng-value="{{item.roleName}}">{{item.roleName}}</option>
</select> </div>
</div>
被正文掉的局部就是本来的 Input text 输入框。这里批改为是用下拉框,并且后盾提供接口查问以后零碎的所有角色数据。
@RequestMapping(value = "/editor/systemRoleNames", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
@ResponseBody
public AjaxResult systemRoleNames() {List<Role> roles = service.listRole();
return AjaxResult.success(roles);
}
10.3.10 最终查看
最初拜访/editor
接口查看是否失常。
实现下面那一套之后,如果呈现如上页面,阐明 web 编辑器集成胜利。
毋庸置疑,这里截图为未暗藏不必要菜单版本。
10.4 可能遇到的问题
10.4.1 Factory method ‘springProcessEngineConfiguration’ threw exception; nested exception is java.io.FileNotFoundException: class path resource [processes/] cannot be resolved to URL because it does not exist
其实英文曾经说的很显著了,须要增加一个 processes 文件夹 在 resources 的目录。能够通过在 yml
配置当中将 check-process-definitions: false
去除强制查看。process-definition-location-prefix
能够批改模型图的存储地位。
# Spring 配置
spring:
# activiti 模块
# 解决启动报错:class path resource [processes/] cannot be resolved to URL because it does not exist
activiti:
check-process-definitions: false
10.4.2 application.yml 配置
Activity 6 的参考配置如下:
activiti:
# flase:默认值。activiti 在启动时,会比照数据库表中保留的版本,如果没有表或者版本不匹配,将抛出异样。(生产环境罕用)# true:activiti 会对数据库中所有表进行更新操作。如果表不存在,则主动创立。(开发时罕用)database-schema-update: true
# 解决启动报错:class path resource [processes/] cannot be resolved to URL because it does not exist
check-process-definitions: true
# 批改模型图的存储地位
process-definition-location-prefix: classpath:/processes/
# full: 性能较差,记录所有实例和变量细节变动,最残缺的历史记录,如果须要日后跟踪具体能够开启 full
history-level: full
10.4.3 java.lang.IllegalArgumentException: Could not find class [org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration]
这个报错很简略,意思是说找不到 Spring 的平安治理类。通常状况下只须要把 SpringBoot 的 Security 排除或者 Activity 的 Security 排除即可。
比方上面的形式排除 Activity 的依赖。(集体倡议)
@SpringBootApplication(exclude = {org.activiti.spring.boot.SecurityAutoConfiguration.class})
public class SaAdminWebApplication {// ....}
然而如果我的项目中没有应用 Spring Securrity,那么躲避此问题请持续看下一个解决方案。
10.4.4 Failed to process import candidates for configuration class [com.sa.SaAdminImplApplication]; nested exception is java.io.FileNotFoundException: class path resource [org/springframework/security/config/annotation/authentication/configurers/GlobalAuthenticationConfigurerAdapter.class] cannot be opened because it does not exist
这个报错是在退出 Activity 之后,Activity 旧版本的平安治理类和 SpringBoot 的平安治理产生的抵触。如果呈现相似报错,请查看是否存在上面的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
如果我的项目中并没有用 Security 的任何依赖,那么排除依赖的解决形式启动还是报错,因为集成的我的项目应用了 Shiro,于是决定在 SaAdminWebApplication
中进行如下设置:
@SpringBootApplication(exclude = {
org.activiti.spring.boot.SecurityAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class
})
public class Application {// ....}
如上配置之后最初我的项目胜利启动。
10.4.5 可视化编辑器的页面白板问题解决
集成在线可视化流程图编辑器的时候,首次跳转 modeler.html
页面可能会呈现上面的谬误,整个页面都是一块“白板”。
这是初学者集成比拟容易犯的谬误,遇到这样的画面,能够在 modeler.html
页面的浏览器中关上 F12,在“Network”当中能够看到有几个申请报错爆红。这几个接口的获取别离是 获取模型 JSON 信息 以及 获取模型本身 JSON 数据 。关键问题是集成 WEB 版本的流程编辑器之后, 拜访开源的 modeler.html 的编辑器页面,页面会主动发送申请。为了节俭读者工夫,这里省略了查找申请接口调用脚本的过程。
咱们能够浏览 ${我的项目门路下的 static 文件夹}$\model\manage\editor-app\app-cfg.js
这里的配置,文件内容如下,这里的 contextRoot
对应上下文信息 activiti-explorer
在申请中呈现了。
app-cfg.js
'use strict';
var ACTIVITI = ACTIVITI || {};
ACTIVITI.CONFIG = {'contextRoot' : 'activiti-explorer',};
这个 contextRoot 的设置又有什么含意?举个例子,这里拜访到编辑器的申请为 /activiti-explorer/model/7503/json
,7503 对应模型的惟一 ID 通过数据库能够查问进去,/activiti-explorer
则对应了上下文局部。
这里可能会问了,上下文是晓得了,然而怎么晓得脚本在哪里发送申请的。这里介绍一下 IDEA 搜寻技巧,咱们全局搜寻 contextRoot
,最终能够找到名为url-config.js
的文件的相干配置。
文件结尾有一大段正文,依据代码内容能够看到这里就是整个 Activity 前端须要的调用接口的全局配置。
getRoleNames 为业务需要增加。
url-config.js
文件内容如下:
var KISBPM = KISBPM || {};
KISBPM.URL = {getModel: function(modelId) {return ACTIVITI.CONFIG.contextRoot + '/model/' + modelId + '/json';},
getStencilSet: function() {return ACTIVITI.CONFIG.contextRoot + '/editor/stencilset?version=' + Date.now();
},
getRoleNames: function() {return ACTIVITI.CONFIG.contextRoot + '/editor/systemRoleNames?version=' + Date.now();
},
putModel: function(modelId) {return ACTIVITI.CONFIG.contextRoot + '/model/' + modelId + '/save';}
};
10.4.6 补充:集成到理论我的项目的注意事项
最为举荐的形式倡议找一个能失常跑的 SpringBoot 我的项目,对照我的项目一个个增加 Activity 的相干依赖,遇到报错依据谬误去搜寻解决。这种形式也能够节省下大量的试错而节约的工夫。
什么意思呢?这里回到依赖局部,比方上面的依赖咱们能够先只增加一个,打包依赖之后间接部署我的项目查看是否和已有的依赖存在抵触,也能够利用 IDEA 工具来解决依赖抵触问题。
<!-- Activity 依赖 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.9</version>
</dependency>
至此咱们把 Activity 胜利集成到我的项目当中,上面开始理解 Activity 的具体细节。
十一、Activity 设计流程图(各种组件,如连线、用户工作、网关)
咱们将 Activity 的 web 版本流程设计编辑器集成之后,前后端此时就能够实现根本的模型新增和编辑操,在介绍具体的代码增删改查之前,咱们先理解如何应用 Activity 流程设计器。
上面介绍最简略的根底的模型绘制和应用。
留神演示版本为最为简略的工作流版本,更为简单的工作流程设计能够将所有组件展现之后自行理解。
11.1 各种组件
留神上面的图中集体暗藏掉大部分按钮,篇幅无限,这里只介绍几个最简略和根底的性能。
11.2.0 主界面
主界面分为四个局部:左侧为性能按钮区域,顶部是一些针对流程图设计的快捷性能,比方剪切,粘贴,复制,还有放大和放大等。左边空白区域为绘制流程图的区域,咱们须要从左侧菜单拖拽节点到右侧绘制流程图,最初是底部的节点形容区域,其中能够设置节点的名称,节点的文档形容,对于连线或者用户流动,则能够设置流程变量或者调配责任人等操作。
回到左侧菜单,这里先介绍必备的开始节点和完结节点这两个组件。
11.2.1 Start Events
开始事件:开始事件并不是一个流动,它仅仅代表一个节点的开始。也是一个流程实例的终点开始节点,仅仅能够设置节点名称以及节点的备注和文档形容(对应底部局部)。开始事件少数状况下仅有一个。
11.2.2 EndEvents
完结事件:完结事件不肯定和开始事件一样一一对应的,在简单的工作流模型中,能够依据不同的分支进入呈现多个完结节点。
11.2.3 插曲:最简略的工作流
Activity 最简略的工作流实际上是像上面这样,间接把开始节点连贯到完结节点,尽管它没有任何实际意义,然而它的确能够正确部署(不会产生报错和异样),并且胜利部署为一个能够运作的工作流。
上面是保留操作,save 之后即可保留,批改过程能够编辑名称和惟一标识。
11.2.4 User Task
用户工作是最为简略也是最罕用的的一种流动节点,对应咱们日常工作的业务流转或者工作流转,用户工作同样能够设置节点名称和文档形容。
除此之外,用户流动作为流动节点,能够设置负责人:
这里 assignment 下拉抉择对应的负责人即可。
11.2 连线
连线目前我的项目仅仅应用箭头连线,箭头连线根本能够笼罩 99% 的应用场景,所以其余两种连线形式这里就疏忽了。
留神:连线实际上输出流程变量条件表达式,实现同一个“进口”进入不同下一个流动节点性能,通过连贯的表达式也能够实现各种细节判断。
后续此局部能够补充和欠缺。
11.3 用户工作
User Task 只是流动分类的一个细节分支,实际上除开用户工作之外很多用户工作。同样因为我的项目需要临时没有更多设计流程上更为简单的需要,所以一并疏忽了。
后续此局部能够补充和欠缺。
11.4 网关
尽管我的项目中并没有用到任何网关组件,然而网关是实现简单组件的前提这里一并补充介绍。
11.4.1 什么是排他网关?
排他网关,用来在流程中实现决策。当流程执行到这个网关,所有分支都会判断条件是否为 true,如果为 true 则执行该分支,否则执行另外的条件分支。
留神:排他网关只会抉择一个为 true 的分支执行。如果有两个分支条件都为 true,排他网关会抉择 id 值较小的一条分支去执行,当然不倡议理论应用过程中去“碰瓷”这个条件。
为什么要用排他网关?
不必排他网关也能够实现分支,然而在连线设置 condition 条件的毛病如果条件都不满足,流程就异样完结了(或者说看起来像是永远挂起)。
如:在 连线的 condition条件上设置分支条件。
在上面的案例当中,排他网关起到了分支 if/else 判断的作用。
须要留神,如果从网关进来的线所有条件都不满足则零碎抛出异样。
org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway 'exclusivegateway1' could be selected for continuing the process
at org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivityBehavior.java:85)
排他网关的图标通常是一个 X。
11.4.2 并行网关 ParallelGateway
并行网关容许将流程分成多条分支,也能够把多条分支汇聚到一起,并行网关的性能是基于进入和外出程序流的。
分支汇聚的术语叫做 fork/join,和 Java 的 fork / join 概念相似。
fork 分支:并行之后的所有外出程序流,为每个程序流都创立一个并发分支。
join 汇聚:所有达到并行网关,在此期待的进入分支,直到所有进入程序流的分支都达到当前,流程就会通过汇聚网关。
与其余网关的次要区别是,并行网关不会解析条件。即便程序流中定义了条件,也会被疏忽。
技术经理和项目经理是 两个 execution 分支,在 act_ru_execution 表有两条记录别离是技术经理和项目经理,act_ru_execution 还有一条记录示意该流程实例。
待技术经理和项目经理工作全副实现,在汇聚点汇聚,通过 parallelGateway 并行网关。
fork/join 的工作模式常常用在多集体共同完成一个工作的时候应用,比方一个计划要两个人都实现签字审批之后能力进入到下一步。
并行网关的图案是十字架款式。
11.4.3 什么是蕴含网关
蕴含网关能够看做是排他网关和并行网关的结合体。蕴含网关和排他网关一样,能够在外出程序流上定义条件,蕴含网关会解析它们。
蕴含网关和排他网关的次要区别是 蕴含网关能够抉择多于一条程序流,这和并行网关一样。蕴含网关的性能是基于进入和外出程序流的:
分支:所有外出程序流的条件都会被解析,后果为 true 的程序流会以并行形式继续执行,会为每个程序流创立一个分支。
汇聚:所有并行分支达到蕴含网关,会进入期待状态,直到每个蕴含流程 token 的进入程序流的分支都达到。这是与并行网关的最大不同。换句话说,蕴含网关只会期待被选中执行了的进入程序流。在汇聚之后,流程会穿过蕴含网关继续执行。
11.4.4 事件网关 EventGateway
事件网关必须有两条或以上外出程序流;
事件网关容许依据事件判断流向。网关的每个外出程序流都要连贯到一个两头捕捉事件。当流程达到一个基于事件网关,网关会进入期待状态:暂停执行。与此同时,会为每个外出程序流创立绝对的事件订阅。
事件网关的外出程序流和一般程序流不同,这些程序流不会真的 ” 执行 ”,相同它们让流程引擎去决定执行到事件网关的流程须要订阅哪些事件。要思考以下条件:
应用事件网关要思考以下条件:
- 基于事件网关必须有两条或以上外出程序流;
- 基于事件网关后,只能应用 intermediateCatchEvent 类型(activiti 不反对基于事件网关后连贯 ReceiveTask。);
- 连贯到事件网关的两头捕捉事件必须只有一个入口程序流。
intermediateCatchEvent 类型抉择如下:
intermediateCatchEvent 反对的事件类型:
- Message Event: 音讯事件
- Singal Event:信号事件
- Timer Event:定时事件
应用事件网关定义流程:
十二、如何应用 Activity 进行开发
当 Activity 集成到我的项目并且理解了如何利用 WEB 的模型编辑器绘制模型之后,咱们上面要正式开始 Activity 工作流的开发工作。
波及具体的业务之前,咱们先介绍所有业务都可能通用局部。
12.1 流程定义增删改查
流程定义增删改查蕴含两个局部,一个是模型设计,另一个是部署流程。这部分内容波及到纯正的代码。通过理解增删改查 Activity 的审批流模型代码,能够帮忙咱们相熟 API 应用。
12.1.1 增加新模型代码
新增模型咱们须要传输上面几个变量:
- modelName 模型名称:因为理论存储为 xml,所以不能有特殊字符,倡议只容许大小写字母。不容许重名
- modelFlag 模型标识:相似模型的惟一标识,同样监狱不要有特殊字符,倡议只容许大小写字母。
- classify:模型分类,可选,用于应用过程中对于业务模型进行分类
- description:形容信息,可选。
- version:每次新部署的模型就是一个新的版本,初始部署的模型版本为 1,之后每部署一次都将会 +1。相似乐观锁
@Override
public AjaxResult addNewModel(AddModelRequestDto addModelRequestDto) {Matcher matcher = ONLY_LETTER_OR_NUMBER_PATTERN.matcher(addModelRequestDto.getModelFlag());
if(!matcher.matches()){return AjaxResult.error("Approval Flow Model identification can only consist of upper and lower case letters.");
}
Model model = repositoryService.newModel();
model.setCategory(addModelRequestDto.getClassify());
model.setKey(addModelRequestDto.getModelFlag());
ObjectNode modelNode = objectMapper.createObjectNode();
modelNode.put(ModelDataJsonConstants.MODEL_NAME, addModelRequestDto.getModelName());
modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, addModelRequestDto.getDescription());
modelNode.put(ModelDataJsonConstants.MODEL_REVISION, addModelRequestDto.getVersion());
model.setMetaInfo(modelNode.toString());
model.setName(addModelRequestDto.getModelName());
model.setVersion(addModelRequestDto.getVersion());
ModelQuery modelQuery = repositoryService.createModelQuery();
List<Model> list = modelQuery.modelKey(addModelRequestDto.getModelFlag()).list();
if (0 < CollUtil.size(list)) {return AjaxResult.error(String.format("Approval Flow Model identification [%s] already exists", addModelRequestDto.getModelFlag()));
} else {
// 保留模型到 act_re_model 表
repositoryService.saveModel(model);
HashMap<String, Object> content = new HashMap();
content.put("resourceId", model.getId());
HashMap<String, String> properties = new HashMap();
properties.put("process_id", addModelRequestDto.getModelFlag());
properties.put("name", addModelRequestDto.getModelName());
properties.put("category", addModelRequestDto.getClassify());
content.put("properties", properties);
HashMap<String, String> stencilset = new HashMap();
stencilset.put("namespace", NAME_SPACE);
content.put("stencilset", stencilset);
// 保留模型文件到 act_ge_bytearray 表
try {repositoryService.addModelEditorSource(model.getId(), objectMapper.writeValueAsBytes(content));
} catch (JsonProcessingException e) {log.error("append Approval Flow Model editor error:", e);
return AjaxResult.error();}
return AjaxResult.success(model);
}
}
简略了解是将数据封装为 Hashmap,最初通过 jackson 转为字节数据写入到 Activity 的数据表即可。数据库的最终表现形式如下:
act_re_model
id_ | rev_ | name_ | key_ | category_ | create_time_ | last_update_time_ | version_ | meta_info_ | deployment_id_ | editor_source_value_id_ | editor_source_extra_value_id_ | tenant_id_ |
---|---|---|---|---|---|---|---|---|---|---|---|---|
22547 | 11 | QAZX | QAZX | QAZX | ######## | ######## | 4 | {“name”:”QAZX”,”description”:”QAZX”,”revision”:null} | 22548 | 22549 | ||
15001 | 7 | MerchantFreeze | MerchantFreeze | MerchantFreeze | ######## | ######## | 2 | {“name”:”MerchantFreeze”,”description”:”MerchantFreeze”,”revision”:null} | 112501 | 15002 | 15003 | |
135043 | 5 | RiskRulesSetting | RiskRulesSetting | RiskRulesSetting | ######## | ######## | 2 | {“name”:”RiskRulesSetting”,”description”:”RiskRulesSetting”,”revision”:null} | 135046 | 135044 | 135045 | |
155044 | 5 | MerchantFee | MerchantFee | MerchantFee | ######## | ######## | 2 | {“name”:”MerchantFee”,”description”:”MerchantFee”,”revision”:null} | 155047 | 155045 | 155046 | |
127544 | 9 | IP | IP | IP | ######## | ######## | 4 | {“name”:”IP”,”description”:”IP”,”revision”:null} | 175044 | 127545 | 127546 | |
220001 | 5 | MerchantVaRefund | MerchantVaRefund | MerchantVaRefund | ######## | ######## | 2 | {“name”:”MerchantVaRefund”,”description”:”MerchantVaRefund”,”revision”:null} | 220004 | 220002 | 220003 | |
202592 | 5 | MerchantOpenAccount | MerchantOpenAccount | MerchantOpenAccount | ######## | ######## | 2 | {“name”:”MerchantOpenAccount”,”description”:”MerchantOpenAccount”,”revision”:null} | 202595 | 202593 | 202594 |
12.1.2 查问所有流程模型
public List<Model> listModelAll() {ModelQuery modelQuery = repositoryService.createModelQuery();
return modelQuery.orderByCreateTime().desc().latestVersion().orderByCreateTime().desc().list();
}
12.1.3 分页查问流程模型
@Override
public List<Model> listModelPage(Integer pageNum, Integer rows,ModelRequestDto modelRequestDto) {ModelQuery modelQuery = repositoryService.createModelQuery();
if (StringUtils.isNotEmpty(modelRequestDto.getKey())) {modelQuery.modelKey(StrUtil.trim(modelRequestDto.getKey()));
}
if (StringUtils.isNotEmpty(modelRequestDto.getName())) {modelQuery.modelNameLike(StrUtil.trim(modelRequestDto.getName()));
}
PageHelper.startPage(pageNum, rows);
return modelQuery.orderByCreateTime().desc().listPage(pageNum, rows);
}
12.1.4 删除流程模型
@Override
public void deleteModel(String modelId) {repositoryService.deleteModel(modelId);
}
12.1.5 导出模型
有时候须要导出 model 文件,咱们能够用上面的代码实现,.bpmn20.xml
这个 20 并不是写错了,而是 bpmn2.0 标准要求的后缀,IDEA 插件也会依据此后缀进行智能辨认。
换句话说就是后缀写错了辨认不了。
@Override
public void modelExport(String modelId, HttpServletResponse response) throws IOException {byte[] modelData = repositoryService.getModelEditorSource(modelId);
JsonNode jsonNode = objectMapper.readTree(modelData);
BpmnModel bpmnModel = (new BpmnJsonConverter()).convertToBpmnModel(jsonNode);
byte[] xmlBytes = (new BpmnXMLConverter()).convertToXML(bpmnModel, "UTF-8");
ByteArrayInputStream in = new ByteArrayInputStream(xmlBytes);
org.apache.poi.util.IOUtils.copy(in, response.getOutputStream());
String filename = bpmnModel.getMainProcess().getId() + ".bpmn20.xml";
response.setHeader("Content-Disposition","attachment;filename=" + filename);
response.setHeader("content-Type", "application/xml");
response.flushBuffer();}
12.1.6 部署流程
部署模型首先须要在资源库中找到对应的模型,转化为 BpmnModel,之后须要创立部署工具设置相干参数之后执行部署。
@Override
public void deploy(String modelId) throws IOException {Model model = repositoryService.getModel(modelId);
byte[] modelData = repositoryService.getModelEditorSource(modelId);
JsonNode jsonNode = objectMapper.readTree(modelData);
BpmnModel bpmnModel = (new BpmnJsonConverter()).convertToBpmnModel(jsonNode);
Deployment deploy = repositoryService.createDeployment().category(model.getCategory())
.name(model.getName()).key(model.getKey())
.addBpmnModel(String.format("%s%s", model.getKey(), BPM20_XML), bpmnModel)
.deploy();
model.setDeploymentId(deploy.getId());
int deleteByModelDefId = saBusinessBindingService.deleteByModelDefId(deploy.getKey());
log.info("deleteByModelDefId => {}, row modified => {}", deploy.getKey(), deleteByModelDefId);
repositoryService.saveModel(model);
}
之后是保留数据库当中。数据库的最终表现形式如下:
act_re_deployment
id_ | name_ | category_ | key_ | tenant_id_ | deploy_time_ | engine_version_ |
---|---|---|---|---|---|---|
22550 | QAZX | QAZX | QAZX | ######## | ||
27501 | QAZX | QAZX | QAZX | ######## | ||
112501 | MerchantFreeze | MerchantFreeze | MerchantFreeze | ######## | ||
135046 | RiskRulesSetting | RiskRulesSetting | RiskRulesSetting | ######## | ||
155047 | MerchantFee | MerchantFee | MerchantFee | ######## | ||
165001 | regist | regist | regist | ######## | ||
175044 | IP | IP | IP | ######## | ||
180047 | regist | regist | regist | ######## | ||
202595 | MerchantOpenAccount | MerchantOpenAccount | MerchantOpenAccount | ######## | ||
220004 | MerchantVaRefund | MerchantVaRefund | MerchantVaRefund | ######## | ||
225091 | regist | regist | regist | ######## |
12.1.7 业务绑定
业务绑定实现了工作流和业务标识的绑定操作,在具体的编码过程中能够通过业务绑定标识找到指定模型,并且依据部署的模型构建不同的流程实例。
@Override
@Transactional(rollbackFor = Exception.class)
public AjaxResult bindProcessAndBusiness(String approvalProcessBusiness, String deployId, SysUser sysUser) {
// 查看当前额业务模型是否绑定
SaBusinessBinding checkAlreadyBind = saBusinessBindingMapper.selectByBusinessName(approvalProcessBusiness);
if (Objects.nonNull(checkAlreadyBind)) {return AjaxResult.error(String.format("Current Business %s Bound Approval Flow Model", checkAlreadyBind.getBusinessName()));
}
SaBusinessBinding check = selectByProcessInstanceId(deployId);
if (Objects.nonNull(check) && Objects.equals(check.getBusinessName(), approvalProcessBusiness)) {String businessName = check.getBusinessName();
return AjaxResult.error(String.format("Current Business %s Configured Approval Flow Model", businessName));
}
Model model = repositoryService.createModelQuery().deploymentId(deployId).singleResult();
if(Objects.isNull(model)){return AjaxResult.error("bindProcessAndBusiness model must be not null");
}
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
SaBusinessBinding saBusinessBinding = new SaBusinessBinding();
saBusinessBinding.setModelDefinitionMark(processDefinition.getKey());
saBusinessBinding.setBusinessName(approvalProcessBusiness);
saBusinessBinding.setCreateName(sysUser.getUserName());
saBusinessBinding.setCreateTime(model.getCreateTime() != null ? DateHelper.getString(model.getCreateTime(), DateHelper.yyyyMMdd_hhmmss) : DateHelper.getNowByNew(DateHelper.yyyyMMdd_hhmmss));
saBusinessBinding.setCreateId(sysUser.getUserId());
saBusinessBinding.setLastUpdateTime(model.getLastUpdateTime() != null ? DateHelper.getString(model.getLastUpdateTime(), DateHelper.getNowByNew(DateHelper.yyyyMMdd_hhmmss)) : DateHelper.getNowByNew(DateHelper.yyyyMMdd_hhmmss));
saBusinessBinding.setProcessStatus("1");
saBusinessBinding.setProcessInstanceId(deployId);
// 依据 deployId 删除再插入
saBusinessBindingMapper.deleteByDeployId(deployId);
saBusinessBindingMapper.insert(saBusinessBinding);
return AjaxResult.success();}
12.2 启动流程实例
构建流程增删改查之后,接下来将介绍如何构建和启动一个流程。
12.2.1 什么是流程实例
流程实例(ProcessInstance)代表流程定义的执行实例。一个流程实例包含了所有的运行节点。咱们能够利用这个对象来理解以后流程实例的进度等信息。
例如:咱们部署了一个申请的工作流模型,定义了申请流程,当初要依照流程提交一个申请申请,就是启动流程实例。
12.2.2 启动实例和增加 Businesskey(业务标识)
流程定义部署在 activiti 后,就能够在零碎中通过 activiti 去治理该流程的执行,执行流程示意流程的一次执行。启动流程实例时如果指定 businesskey,就会在 act_ru_execution 流程实例的执行表中存储 businesskey。
Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务零碎。存储业务标识就是依据业务标识来关联查问业务零碎的数据。
/** * 启动流程实例,增加 businessKey */
@Test
public void addBusinessKey(){
// 1、失去 ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、失去 RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3、启动流程实例,同时还要指定业务标识 businessKey
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myEvection","1001");
// 4、输入 processInstance 相干属性
System.out.println("业务 id=="+processInstance.getBusinessKey());
}
businessKey 对应的就是部署流程表中的 key_
这个字段,name 则对应咱们流程模型的名称:
id_ | name_ | category_ | key_ | tenant_id_ | deploy_time_ | engine_version_ |
---|---|---|---|---|---|---|
22550 | QAZX | QAZX | QAZX | ######## | ||
27501 | QAZX | QAZX | QAZX | ######## | ||
112501 | MerchantFreeze | MerchantFreeze | MerchantFreeze | ######## | ||
135046 | RiskRulesSetting | RiskRulesSetting | RiskRulesSetting | ######## | ||
155047 | MerchantFee | MerchantFee | MerchantFee | ######## | ||
165001 | regist | regist | regist | ######## | ||
175044 | IP | IP | IP | ######## | ||
180047 | regist | regist | regist | ######## | ||
202595 | MerchantOpenAccount | MerchantOpenAccount | MerchantOpenAccount | ######## | ||
220004 | MerchantVaRefund | MerchantVaRefund | MerchantVaRefund | ######## | ||
225091 | regist | regist | regist | ######## |
构建之后,从 Activiti 的 act_ru_execution 中存储业务标识,启动流程实例,操作如下数据库表:
# 流程实例执行表,记录以后流程实例的执行状况
SELECT * FROM act_ru_execution;
流程实例执行。如果以后只有一个分支,一个流程实例只有一条记录,且执行表的主键 id 和流程实例 id 雷同,如果以后有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例 id 不雷同的记录。
不管以后有几个分支,总会有一条记录的执行表的主键和流程实例 id 雷同。
查问流程实例
流程在运行过程中能够查问流程实例的状态,以后运行结点等信息。
@Test
public void queryProcessInstance() {
// 流程定义 key
String processDefinitionKey = "evection";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取 RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
List<ProcessInstance> list = runtimeService
.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)//
.list();
for (ProcessInstance processInstance : list) {System.out.println("----------------------------");
System.out.println("流程实例 id:"
+ processInstance.getProcessInstanceId());
System.out.println("所属流程定义 id:"
+ processInstance.getProcessDefinitionId());
System.out.println("是否执行实现:" + processInstance.isEnded());
System.out.println("是否暂停:" + processInstance.isSuspended());
System.out.println("以后流动标识:" + processInstance.getActivityId());
}
}
关联 BusinessKey
在 activiti 理论利用时,查问流程实例列表可能要显示出业务零碎的一些相干信息。在查问流程实例时,通过 businessKey(业务标识)关联查问业务零碎。通过上面的代码就能够获取 activiti 中所对应实例保留的业务 Key。
String businessKey = processInstance.getBusinessKey();
在 activiti 的 act_ru_execution 表,字段 BUSINESS_KEY 就是寄存业务 KEY 的。
12.3 工作流转
启动流程实例之后,接下来就是工作的增删改查,每一个 Task 对应了工作流中的流动,咱们能够通过上面的形式查问工作负责人的待办工作:
// 查问以后集体待执行的工作
@Test
public void findPersonalTaskList() {
// 流程定义 key
String processDefinitionKey = "myEvection1";
// 工作负责人
String assignee = "张三";
// 获取 TaskService
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.includeProcessVariables()
// 工作负责人
.taskAssignee(assignee)
.list();
for (Task task : taskList) {System.out.println("----------------------------");
System.out.println("流程实例 id:" + task.getProcessInstanceId());
System.out.println("工作 id:" + task.getId());
System.out.println("工作负责人:" + task.getAssignee());
System.out.println("工作名称:" + task.getName());
}
}
咱们能够通过组合查问条件,实现特定人员查问特定的业务内容。工作流转给下一个人须要先获取工作 ID,而后调用 taskService 的 complete 办法实现以后审批人的代办工作。
/**
* 实现工作,判断以后用户是否有权限
*/
@Test
public void completTask() {
// 工作 id
String taskId = "15005";
// 工作负责人
String assingee = "张三";
// 获取 processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创立 TaskService
TaskService taskService = processEngine.getTaskService();
// 实现工作前,须要校验该负责人能够实现当前任务
// 校验办法:// 依据工作 id 和工作负责人查问当前任务,如果查到该用户有权限,就实现
Task task = taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(assingee)
.singleResult();
if(task != null){taskService.complete(taskId);
System.out.println("实现工作");
}
}
12.4 实现工作
留神:在理论利用中,实现工作前须要校验工作的负责人是否具备该工作的办理权限。实现工作的模板代码和工作流转相似,然而须要额定判断以后节点是否为最初一个待办节点。
/**
* 实现工作,判断以后用户是否有权限
*/
@Test
public void completTask() {
// 工作 id
String taskId = "15005";
// 工作负责人
String assingee = "张三";
// 获取 processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创立 TaskService
TaskService taskService = processEngine.getTaskService();
// 实现工作前,须要校验该负责人能够实现当前任务
// 校验办法:// 依据工作 id 和工作负责人查问当前任务,如果查到该用户有权限,就实现
Task task = taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(assingee)
.singleResult();
if(task != null){taskService.complete(taskId);
System.out.println("实现工作");
}
}
12.4.1 如何晓得以后节点是否为最初一个待办节点?
具体能够浏览 com.sa.impl.utils.ActivityUtils#getNextNode(java.lang.String, java.lang.String, java.util.Map<java.lang.String,java.lang.Object>)
这一部分。
public List<UserTask> getNextNode(String procDefId, String taskDefKey, Map<String, Object> map) {ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
List<UserTask> userTasks = new ArrayList<>();
// 获取 BpmnModel 对象
BpmnModel bpmnModel = processEngine.getRepositoryService().getBpmnModel(procDefId);
// 获取 Process 对象
Process process = bpmnModel.getProcesses().get(bpmnModel.getProcesses().size() - 1);
// 获取所有的 FlowElement 信息
Collection<FlowElement> flowElements = process.getFlowElements();
// 获取以后节点信息
FlowElement flowElement = getFlowElementById(taskDefKey, flowElements);
getNextNode(flowElements, flowElement, map, userTasks);
return userTasks;
}
整体思路是找到以后节点的所有连线,而后判断以后连线的下一个节点是否是一个“完结节点”,如果是则证实以后节点曾经达到最初一步。
PS:倡议设计流程图仅仅为完结节点配一个前置节点和连线。
12.5 流程变量
12.5.1 什么是流程变量
流程变量在 activiti 中非常重要,比方咱们在流程变量当中保护一个“观察者角色列表”,将记录的所有可见角色到流程变量中存储,流程变量在流程管制中次要是管制流程流转的细节管制。
留神:尽管流程变量中能够存储业务数据,也能够通过 activiti 的 api 查问流程变量实现查问业务数据,然而实际上不倡议这样应用,因为业务数据查问由业务零碎负责,activiti 设置流程变量是为了流程执行须要而创立。
12.5.2 流程变量类型
数据起源:Table 10. Variable Types
。
https://www.activiti.org/5.x/userguide/#apiVariables
Type name | Description |
---|---|
string | Value is threaded as a java.lang.String . Raw JSON-text value is used when writing a variable. |
integer | Value is threaded as a java.lang.Integer . When writing, JSON number value is used as base for conversion, falls back to JSON text. |
short | Value is threaded as a java.lang.Short . When writing, JSON number value is used as base for conversion, falls back to JSON text. |
long | Value is threaded as a java.lang.Long . When writing, JSON number value is used as base for conversion, falls back to JSON text. |
double | Value is threaded as a java.lang.Double . When writing, JSON number value is used as base for conversion, falls back to JSON text. |
boolean | Value is threaded as a java.lang.Boolean . When writing, JSON boolean value is used for conversion. |
date | Value is treated as a java.util.Date . When writing, the JSON text will be converted using ISO-8601 date format. |
binary | Binary variable, treated as an array of bytes. The value attribute is null, the valueUrl contains an URL pointing to the raw binary stream. |
serializable | Serialized representation of a Serializable Java-object. As with the binary type, the value attribute is null, the valueUrl contains an URL pointing to the raw binary stream. All serializable variables (which are not of any of the above types) will be exposed as a variable of this type. |
12.5.3 流程变量作用域
流程变量的作用域能够是一个流程实例(processInstance),或一个工作(task),或一个执行实例(execution)。
globa 变量
流程变量的默认作用域是流程实例。当一个流程变量的作用域为流程实例时,能够称为 global 变量。
留神:
Global 变量:userId(变量名)、zhangsan(变量值)
global 变量中变量名不容许反复,设置雷同名称的变量,后设置的值会笼罩前设置的变量值。
local 变量
工作和执行实例仅仅是针对一个工作和一个执行实例范畴,范畴没有流程实例大,称为 local 变量。
Local 变量因为在不同的工作或不同的执行实例中,作用域互不影响,变量名能够雷同没有影响。Local 变量名也能够和 global 变量名雷同,没有影响。
12.5.4 流程变量的应用办法
能够在 assignee 处设置 UEL 表达式,表达式的值为工作的负责人,比方:${assignee},assignee 就是一个流程变量名称。
Activiti 获取 UEL 表达式的值,即流程变量 assignee 的值,将 assignee 的值作为工作的负责人进行任务分配。
12.5.5 在连线上应用 UEL 表达式
能够在连线上设置 UEL 表达式,决定流程走向。比方:${price<10000}
。price 就是一个流程变量名称,uel 表达式后果类型为布尔类型。
如果 UEL 表达式是 true,要决定 流程执行走向。
12.5.6 启动实例设置流程变量
/** * 启动流程实例, 设置流程变量的值 */
@Test
public void startProcess(){
// 获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取 RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 流程定义 key
String key = "myEvection2";
// 创立变量汇合
Map<String, Object> map = new HashMap<>();
// 创立出差 pojo 对象
Evection evection = new Evection();
// 设置出差天数
evection.setNum(2d);
// 定义流程变量,把出差 pojo 对象放入 map
map.put("evection",evection);
// 设置 assignee 的取值,用户能够在界面上设置流程的执行
map.put("assignee0","张三");
map.put("assignee1","李经理");
map.put("assignee2","王总经理");
map.put("assignee3","赵财务");
// 启动流程实例,并设置流程变量的值(把 map 传入)ProcessInstance processInstance = runtimeService .startProcessInstanceByKey(key, map);
// 输入
System.out.println("流程实例名称 ="+processInstance.getName());
System.out.println("流程定义 id=="+processInstance.getProcessDefinitionId());
}
须要留神,流程变量作用域是一个流程实例,流程变量应用 Map 存储,同一个流程实例设置变量 map 中 key 雷同,后者笼罩前者。
12.5.7 通过当前任务设置流程变量
String operateNow = DateHelper.getNowByNew(DateHelper.yyyyMMdd_hhmmss);
HashMap<String, Object> variables = getProcessGlobalVariables(newCharge, operateNow, commit, user);
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(saBusinessBinding.getModelDefinitionMark(), saBusinessBinding.getBusinessName(), variables);
Task autoTask = taskService.createTaskQuery()
.processDefinitionKey(saBusinessBinding.getModelDefinitionMark())
.processInstanceId(processInstance.getId())
.processInstanceBusinessKey(saBusinessBinding.getBusinessName()).singleResult();
Comment comment = taskService.addComment(autoTask.getId(), processInstance.getProcessInstanceId(), (String) variables.get(BusinessBindVariables.COMMENT.getKey()));
log.info("Comment => {}", comment.getFullMessage());
taskService
.complete(autoTask.getId(), variables);
log.info("{} commit Task {}", saBusinessBinding.getBusinessName(), autoTask.getId());
要害代码在上面这一串:
taskService
.complete(autoTask.getId(), variables);
第二个参数能够传递流程变量,如果 Key 在以后流程实例曾经存在就会笼罩。
12.6 历史信息查问
每个参与者在实现以后节点的“流动”之后,都会在历史信息中留下记录。
HistoricProcessInstanceQuery condition = historyService.createHistoricProcessInstanceQuery();
if (StringUtils.isNotEmpty(historyTaskRequestDto.getBussinessKey())) {condition.processInstanceBusinessKey(CharSequenceUtil.trim(historyTaskRequestDto.getBussinessKey()));
}
if (StringUtils.isNotEmpty(historyTaskRequestDto.getName())) {condition.processInstanceNameLikeIgnoreCase(CharSequenceUtil.trim(historyTaskRequestDto.getName()));
}
List<HistoricProcessInstance> processList = condition.orderByProcessInstanceStartTime().desc()
.list();
咱们能够通过 ” act_hi_* “ 结尾的的表浏览所有无关审批流的历史记录和操作信息。
表名 | 表正文 |
---|---|
act_hi_actinst | 历史节点表 |
act_hi_attachment | 历史附件表 |
act_hi_comment | 历史意见表 |
act_hi_detail | 历史详情表,提供历史变量的查问 |
act_hi_identitylink | 历史流程用户信息表 |
act_hi_procinst | 历史流程实例表 |
act_hi_taskinst | 历史工作实例表 |
act_hi_varinst | 历史变量表 |
十三、学习参考资料汇总
13.1 技术选型局部
开源流程引擎哪个好,如何选型?– 知乎 (zhihu.com)
常见的工作流计划比照
13.2 activiti6.0 在线流程设计器
activiti6.0 在线流程设计器,能够从代码中找到对应的 WEB 版本 Activity 流程设计器。
juzhizhang/springboot2-activiti-design: activiti6.0 在线流程设计器 (github.com)
13.3 最简洁的入门 DEMO 教程
# 21 年最新版 activiti7 保姆教程
集体 fork 仓库:https://github.com/lazyTimes/ActivityStudy
13.4 Activity 应用讲义材料
# Activiti 工作流引擎进阶【珍藏可做笔记系列】
13.5 工作流设计准则
企业工作流设计准则及多我的项目整合开发注意事项_麻利开发_Marilyn_InfoQ 写作社区
13.6 开源我的项目
# 举荐 2 个基于 SpringBoot 的工作流我的项目,有用!
# RuoYi-activiti
13.7 视频教程
activiti-5- 流程初体验
对应笔记:# Activiti 工作流引擎进阶【珍藏可做笔记系列】
13.8 其余教程
SpringBoot 系列——Activiti7 工作流引擎
13.9 官网疾速开始
# Quick Start Guide
# Activiti User Guide
13.10 参考 PPT
https://slideplayer.com/slide/10527039/
13.11 Camunda
javascript – 常见的工作流计划比照 – 技术博客,人生所思 – SegmentFault 思否
十四、总结
- 工作流实际优于实践,概念比较复杂,然而有很多非常成熟并且稳固的框架。
- 本着“起码常识”的准则,工作流的业务编码应该尽可能的简化和易于了解。
- 流程图布局十分重要,每一个流程设计出错都有可能理论运行的时候无奈进行。
- 工作流技术非常稳固,更新迭代十分迟缓。
- Talk is cheap. Show me your code。不要胆怯未知领域。
十五、写在最初
授人以鱼不如授人以渔,本次内容是在用工作流实现理论生产业务之后补充整顿而成,集体认为作为从零理解工作流入门开发的一个比拟好的教程和参考,心愿通过本次内容让读者能够理解到如何在碰到业务须要工作流的时候如何疾速学习和上手开发。