Merge branch 'feature/REQ-1609' into 'master'

Feature/req 1609

See merge request universal/infrastructure/backend/workflow-engine!2
This commit is contained in:
王粒 2023-12-13 05:59:12 +00:00
commit 7b4b19d087
268 changed files with 62494 additions and 3642 deletions

43561
BPMN_Spec_2.0.2.pdf Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,28 @@
# Change logs
### 1.2.1-SNAPSHOT
> - 重构前端 JSON 转 BPMN 协议的实现, 适配新的需求
这里的前端依赖 [该开源工程](https://gitee.com/gailunJAVA/dingding-mid-business-java)
### 1.2.0-SNAPSHOT
> - 接入 RocketMQ, 统一采用 `cn.axzo.workflow.core.listener` 包下的接口来实现
> - 新增运行中流程实例的执行顺序推测
> - 中台业务的接入工作流, 对流程变量中跟人相关信息的变更
### 1.1.0-SNAPSHOT
> - 枢智版本
> - 调整一些 API 的命令以及模型
> - 枢智业务上的一些入侵查询功能, 采用 NativeQuery 的方式处理, 不影响引擎
> - 新增 BpmActivityEventListener 事件接口, 方便获取流程定义中某个节点的事件
### 1.0.2-SNAPSHOT
> - 枢智版本
> - 更新会签或或签时, 针对到每个任务的审批记录状态异常的问题
### 1.0.1-SNAPSHOT
> - 枢智版本

32
ProjectDesc.md Normal file
View File

@ -0,0 +1,32 @@
## 1. 本工程结构说明
- workflow-engine-api: Feign 接口 module, 采用 SpringBoot Starter 方式对外提供, 该报中使用对象模型, 都引用自 common 模块.
需要特别注意的是, 由于绝大部分的 Feign API 都会提供对应的 WebApi, 但由于 MVC 与 OpenFeign 默认不会同时解析对应的
MappingAdapter, 而目前所有的 Feign API 的实现都是 Server 模块中的 Controller, 所以在 Server 模块中的 Controller 需要单独设置
Feign 的请求路径.
- workflow-engine-common: 通用的公共模型以及常量
- workflow-engine-core: 该 module 的目的是以 jar 包的方式,提供给二/三方应用集成,而不依赖本工程.
- workflow-engine-server: 本工程的启动目录, 提供 Web 相关功能以及特性
## 2. 启动方式
> 在 workflow-engine-server 模块下, 设置好了 bootstrap.yml, 一般情况直接设置对应的 active profiles 即可.
> 由于各个环境中的组件地址可能存在配置的是基于 K8S DNS 的内网域名,本地启动会无法连接, 所以建议通过 VM Options/ Program
> arguments/ Environment 等方式对 Nacos 中的默认配置进行覆盖即可.
>
> **严禁随意调整 Bootstrap.yml 配置文件内容**
目前提供了四种 Profile 的 nacos 地址配置信息
- local: 一般用于本地开发
> 该环境比较特殊, 用于连接自己本地的数据库, 注意账号密码请用本地覆盖
> ```text
> -Dspring.datasource.username=root -Dspring.datasource.password=123456
> ```
> ![start-config.png](imgs/start-config.png)
- dev: 一般用于开发联调
- test: 一般用于提测
- pre: 一般用于上线前验收

409
README.md
View File

@ -4,11 +4,15 @@
- [商业版](https://www.flowable.com/)
- [开源版](https://www.flowable.com/open-source)
## 2. Flowable 相关表结构说明
> jBPM -> Activiti -> camunda -> Flowable 6.7.2
## 2. Flowable 概述
> Flowable的数据库名称以ACT_开头。第二部分是表用例的两个字符的标识。
**Activiti 标准延续:**
```text
- ACT_FO_*“FO“代表表单引擎相关库。包含表单定义部署实例等。
- ACT_RE_*“RE”代表存储库。具有此前缀的表包含“静态”信息例如流程定义和流程资源。
@ -17,35 +21,395 @@
- ACT_GE_*“GE“代表自动生成的数据包括bpmn.xml、flowable自带流程图等文件用于各种用例。
```
[库表定义](https://blog.csdn.net/qq_31237581/article/details/130132221)
[库表字段定义大致含义参考](https://blog.csdn.net/qq_31237581/article/details/130132221)
**Flowable 的扩展:**
```text
- flw_*Flowable 新版本扩展功能相关的表,流程迁移的表。
```
### 2.1 术语表
| 术语名称 | 描述 |
|----------|---------------------------------------------------------------------------------|
| 流程引擎 | 是一种按照某种特性配置或程序进行执行的工具, 在此特征 Flowable 业务框架。 |
| 流程模型 | 模型类似一种盒子,用来装东西,在 Flowable 中,模型用来装“流程定义”, 且只能装一个流程定义。 |
| 流程定义 | 流程定义是一种告诉流程引擎该如何运行、流转的元配置数据。通常它会产生 BPMN 协议文件和 png 的可视化流程图片文件。一个模型下的流程定义可以有多个版本。 |
| 活动(审批节点) | 特指流程定义中需要被外部介入的节点,例如"用户任务"节点。 |
| 网关 | 流程定义中的某种节点类型, 在流程实例中,主要是用于条件分支,协助引擎确定下一步的执行路径。 |
| 多实例任务 | 针对"任务"节点的特色树形,代表该任务分配多个人后可以会签/或签, 而如果未设置多实例树形,那么就是是多个候选人审批,也仅会只有一个人 |
| 租户 | 在本工程中,租户数据等于工作台 ID,主要用于较强判断相关数据的归属。 |
| 流程实例 | 按照流程定义创建的一个具体的实例,在业务场景实际使用过程中,通常都需要关注流程实例相关信息,如实例 ID。 |
| 流程任务 | 流程定义中标记的“审批节点"的实例,在业务场景实例使用过程中,某个人能审批一个或多个任务时,这个“任务”就是流程任务。 |
### 2.2 本工程常用的库表
```text
// 运行时(仅保存运行中的流程相关数据)
act_ru_execution (执行中的实体)
act_ru_task (执行中的任务)
act_ru_variable (执行中的变量)
// 元数据配置信息
act_re_model (流程模型)
act_re_procdef (流程定义)
act_re_deployment (流程部署)
// 历史数据 (运行时产生的数据也会及时写入历史)
act_hi_procinst (历史的流程实例)
act_hi_taskinst (历史的任务实例)
act_hi_varinst (历史的变量实例)
act_ge_bytearray (很多元数据)
```
## 3.国内工作流常用操作的名称解释
| 流程操作 | 描述 |
|-------|--------------------------------------------------------------------------------------------------------------|
| 认领 | 当前节点候选人或归属于候选组的人,对当前节点进行认领 |
| 取消认领 | 当前节点处理人,取消自己处理任务的权限,使任务进入待认领状态 |
| 审批 | 当前节点处理人,对当前流程节点进行审核操作,完成后进入下一节点 |
| 驳回/回退 | 当前节点处理人,将流程驳回至之前已经处理过的任务节点,要求重新处理 |
| 委派/委托 | 当前节点处理人,将自己的主办或者经办权限转移委托至别的用户代为处理,处理完后回到当前处理人手中,并由当前处理人处理完后进入下一节点 |
| 转办 | 当前节点处理人,将操作权限转给别人处理,处理完后进入下一节点(自己不再处理) |
| 催办 | 对于时效要求高的流程,发起人可催办提醒当前节点处理人,一般以消息通知方式提醒处理人 |
| 撤销 | 发起人操作,可以撤销当前流程 |
| 取回 | 当前节点上一节点处理人操作,当前节点处理人还未处理,上一节点处理人可以将其退回自己手中重新操作(取回重办) |
| 终止 | 当前节点处理人,终止当前流程 |
| 抄送 | 当前节点处理人,处理完成之后将处理结果抄送给其他人,这里创建备注信息,并给所有抄送人创建子任务(待阅),子任务不影响流程流转 |
| 向前加签 | 当前节点处理人,需要让其他人核对流程,其他人核对完成后,回到当前节点处理人手中,当前节点处理人处理完后进入下一节点 |
| 向后加签 | 当前节点处理人,需要让其他人核对流程,其他人核对完成后,直接进入下一节点 |
| 会签 | 一般的会签就是指在流程管理中发起人可以同时对多个人发起会签,多个人可以同时处理,只有所有负责人审批通过,审批节点才会通过。(支持一票否决/一票通过/投票按照百 分比给出结论通过或不通过) |
| 交接 | 流程管理员权限,管理员将离职或换岗员工的待执行、待领取、代办他人、委托他人代办的任务转交给接管人,并删除与该员工相关的委托代理关系。交接员工所有直接参与的流 程实例中对应的参与者将自动由系统修改为接管人。(强制) |
| 暂存 | 复杂表单,一次性填写不完,需要保存草稿功能,开始节点的暂存 |
| 流程操作 | 描述 |
|-------|-------------------------------------------------------------------------------------------------------------|
| 认领 | 当前节点候选人或归属于候选组的人,对当前节点进行认领 |
| 取消认领 | 当前节点处理人,取消自己处理任务的权限,使任务进入待认领状态 |
| 审批 | 当前节点处理人,对当前流程任务(关联了一活动)进行审核操作,完成后进入下一节点 |
| 驳回/回退 | 当前节点处理人,将流程驳回至之前已经处理过的任务节点,要求重新处理 |
| 委派/委托 | 当前节点处理人,将自己的主办或者经办权限转移委托至别的用户代为处理,处理完后回到当前处理人手中,并由当前处理人处理完后进入下一节点 |
| 转办 | 当前节点处理人,将操作权限转给别人处理,处理完后进入下一节点(自己不再处理) |
| 催办 | 对于时效要求高的流程,发起人可催办提醒当前节点处理人,一般以消息通知方式提醒处理人 |
| 撤销 | 发起人操作,可以撤销当前流程 |
| 取回 | 当前节点上一节点处理人操作,当前节点处理人还未处理,上一节点处理人可以将其退回自己手中重新操作(取回重办) |
| 终止 | 当前节点处理人,终止当前流程 |
| 抄送 | 当前节点处理人,处理完成之后将处理结果抄送给其他人,这里创建备注信息,并给所有抄送人创建子任务(待阅),子任务不影响流程流转 |
| 向前加签 | 当前节点处理人,需要让其他人核对流程,其他人核对完成后,回到当前节点处理人手中,当前节点处理人处理完后进入下一节点 |
| 向后加签 | 当前节点处理人,需要让其他人核对流程,其他人核对完成后,直接进入下一节点 |
| 会签 | 一般的会签就是指在流程管理中发起人可以同时对多个人发起会签,多个人可以同时处理,只有所有负责人审批通过,审批节点才会通过。(支持一票否决/一票通过/投票按照百 分比给出结论通过或不通过) |
| 交接 | 流程管理员权限,管理员将离职或换岗员工的待执行、待领取、代办他人、委托他人代办的任务转交给接管人,并删除与该员工相关的委托代理关系。交接员工所有直接参与的流 程实例中对应的参与者将自动由系统修改为接管人。(强制) |
| 暂存 | 复杂表单,一次性填写不完,需要保存草稿功能,开始节点的暂存 |
已完成:
## 4. 如何配置一个流程?
> 流程的运行依赖一种规则,得让引擎知道何时开始执行流程? 何时终止? 如何识别和控制每个步骤该怎么处理? 执行过程中如何走向不同的路径?
> 比如: 我们常见的需求, 某个节点需要会签/或签, 或者某个节点需要判断条件是否执行该步骤等等.
>
> 以上这些规则都需要再创建流程实例时, 提前就告诉引擎, 且运行过程和步骤中是不可变的. 引擎自己也未提供相应能力.
>
> 这种规则在 Flowable 的世界中,是以 BPMN2.0 协议规则进行编写和持久化.
## 4.1 简单识别流程定义
![流程定义 png](imgs/img.png)
```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:flowable="http://flowable.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.flowable.org/processdef" exporter="Flowable Open Source Modeler" exporterVersion="6.7.2">
<process id="a1" name="1" isExecutable="true">
<startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
<exclusiveGateway id="sid-516CA2A3-F5BA-4AFF-8314-F87F2B9E2CF2" default="sid-6949581E-4B60-4263-9966-EF0C7D4D7D4F"></exclusiveGateway>
<userTask id="sid-6D86C326-54D1-4155-B564-9267C2E097BF" name="事业部审核" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-5BFB5B2B-C9A8-42D2-9AB5-AD886D1E5235" sourceRef="sid-516CA2A3-F5BA-4AFF-8314-F87F2B9E2CF2" targetRef="sid-6D86C326-54D1-4155-B564-9267C2E097BF"></sequenceFlow>
<userTask id="sid-08E30960-3B2C-40D4-8B8D-34BBC7213F2F" name="财务审核" flowable:formFieldValidation="true"></userTask>
<endEvent id="sid-DB91A9BE-D1AE-4410-B502-86331F330066"></endEvent>
<exclusiveGateway id="sid-86533789-DD27-4186-BB4F-4F9526CD930C"></exclusiveGateway>
<sequenceFlow id="sid-531523D8-9C8A-4F07-8786-D9389A9E693B" sourceRef="sid-6D86C326-54D1-4155-B564-9267C2E097BF" targetRef="sid-86533789-DD27-4186-BB4F-4F9526CD930C"></sequenceFlow>
<sequenceFlow id="sid-3D02CE81-4BBE-4611-AE68-6CF23DB0BB22" sourceRef="sid-86533789-DD27-4186-BB4F-4F9526CD930C" targetRef="sid-DB91A9BE-D1AE-4410-B502-86331F330066"></sequenceFlow>
<userTask id="sid-9134DB2B-33BF-423A-B916-1065A12A7D34" name="部门主管审核" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-C62405A5-DC20-4273-A550-D5477143908E" sourceRef="startEvent1" targetRef="sid-9134DB2B-33BF-423A-B916-1065A12A7D34"></sequenceFlow>
<sequenceFlow id="sid-7C7099A5-12BA-4E87-BE02-3A70788A2DE9" sourceRef="sid-9134DB2B-33BF-423A-B916-1065A12A7D34" targetRef="sid-516CA2A3-F5BA-4AFF-8314-F87F2B9E2CF2"></sequenceFlow>
<userTask id="sid-613723D4-E36F-4902-AE52-37F3CF6E75C9" name="董事会审核" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-454E0650-77EE-4E00-AE5C-0A6CDFF8A975" sourceRef="sid-08E30960-3B2C-40D4-8B8D-34BBC7213F2F" targetRef="sid-613723D4-E36F-4902-AE52-37F3CF6E75C9"></sequenceFlow>
<sequenceFlow id="sid-3E71B6E4-5A4B-4066-B653-029CBD856D9D" sourceRef="sid-613723D4-E36F-4902-AE52-37F3CF6E75C9" targetRef="sid-86533789-DD27-4186-BB4F-4F9526CD930C"></sequenceFlow>
<sequenceFlow id="sid-6949581E-4B60-4263-9966-EF0C7D4D7D4F" sourceRef="sid-516CA2A3-F5BA-4AFF-8314-F87F2B9E2CF2" targetRef="sid-08E30960-3B2C-40D4-8B8D-34BBC7213F2F"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_a1">
<bpmndi:BPMNPlane bpmnElement="a1" id="BPMNPlane_a1">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="120.0" y="193.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-516CA2A3-F5BA-4AFF-8314-F87F2B9E2CF2" id="BPMNShape_sid-516CA2A3-F5BA-4AFF-8314-F87F2B9E2CF2">
<omgdc:Bounds height="40.0" width="40.0" x="370.0" y="188.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-6D86C326-54D1-4155-B564-9267C2E097BF" id="BPMNShape_sid-6D86C326-54D1-4155-B564-9267C2E097BF">
<omgdc:Bounds height="80.0" width="100.0" x="450.0" y="60.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-08E30960-3B2C-40D4-8B8D-34BBC7213F2F" id="BPMNShape_sid-08E30960-3B2C-40D4-8B8D-34BBC7213F2F">
<omgdc:Bounds height="80.0" width="100.0" x="450.0" y="285.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-DB91A9BE-D1AE-4410-B502-86331F330066" id="BPMNShape_sid-DB91A9BE-D1AE-4410-B502-86331F330066">
<omgdc:Bounds height="28.0" width="28.0" x="810.5" y="204.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-86533789-DD27-4186-BB4F-4F9526CD930C" id="BPMNShape_sid-86533789-DD27-4186-BB4F-4F9526CD930C">
<omgdc:Bounds height="40.0" width="40.0" x="720.0" y="198.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-9134DB2B-33BF-423A-B916-1065A12A7D34" id="BPMNShape_sid-9134DB2B-33BF-423A-B916-1065A12A7D34">
<omgdc:Bounds height="80.0" width="100.0" x="210.0" y="168.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-613723D4-E36F-4902-AE52-37F3CF6E75C9" id="BPMNShape_sid-613723D4-E36F-4902-AE52-37F3CF6E75C9">
<omgdc:Bounds height="80.0" width="100.0" x="595.0" y="285.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-454E0650-77EE-4E00-AE5C-0A6CDFF8A975" id="BPMNEdge_sid-454E0650-77EE-4E00-AE5C-0A6CDFF8A975" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="549.95" y="325.0"></omgdi:waypoint>
<omgdi:waypoint x="594.9999999999807" y="325.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-3D02CE81-4BBE-4611-AE68-6CF23DB0BB22" id="BPMNEdge_sid-3D02CE81-4BBE-4611-AE68-6CF23DB0BB22" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="759.5520035885168" y="218.38622754491018"></omgdi:waypoint>
<omgdi:waypoint x="810.5002402842656" y="218.08303445075305"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-6949581E-4B60-4263-9966-EF0C7D4D7D4F" id="BPMNEdge_sid-6949581E-4B60-4263-9966-EF0C7D4D7D4F" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="390.5" y="227.44187392795882"></omgdi:waypoint>
<omgdi:waypoint x="390.5" y="325.0"></omgdi:waypoint>
<omgdi:waypoint x="450.0" y="325.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-531523D8-9C8A-4F07-8786-D9389A9E693B" id="BPMNEdge_sid-531523D8-9C8A-4F07-8786-D9389A9E693B" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.0" flowable:targetDockerY="20.0">
<omgdi:waypoint x="549.95" y="100.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="100.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="198.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-7C7099A5-12BA-4E87-BE02-3A70788A2DE9" id="BPMNEdge_sid-7C7099A5-12BA-4E87-BE02-3A70788A2DE9" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.0" flowable:targetDockerY="20.0">
<omgdi:waypoint x="309.9499999999041" y="208.0"></omgdi:waypoint>
<omgdi:waypoint x="370.0" y="208.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement"sid-C62405A5-DC20-4273-A550-D5477143908E" id="BPMNEdge_sid-C62405A5-DC20-4273-A550-D5477143908E" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="149.94999883049306" y="208.0"></omgdi:waypoint>
<omgdi:waypoint x="209.99999999995785" y="208.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-5BFB5B2B-C9A8-42D2-9AB5-AD886D1E5235" id="BPMNEdge_sid-5BFB5B2B-C9A8-42D2-9AB5-AD886D1E5235" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="390.5" y="188.5"></omgdi:waypoint>
<omgdi:waypoint x="390.5" y="100.0"></omgdi:waypoint>
<omgdi:waypoint x="450.0" y="100.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-3E71B6E4-5A4B-4066-B653-029CBD856D9D" id="BPMNEdge_sid-3E71B6E4-5A4B-4066-B653-029CBD856D9D" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.0" flowable:targetDockerY="20.0">
<omgdi:waypoint x="694.9499999999887" y="325.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="325.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="237.90928437792334"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
```
点击查看完整的[BPMN 协议规范](BPMN_Spec_2.0.2.pdf)文件
> 通过上面的 BPMN 协议的具体内容, 我们可以发现协议内容主要分为两大块, 一块是以`<process>`包裹, 它是主要的规则描述,
> 另一块是以`<bpmndi:BPMNDiagram>`包裹, 它是规则可视化的元数据, 这部分一般我们无需关心, 但只要我们的业务涉及到流程配置的反显,
> 它又必不可少.
### 4.2 Process 节点内容简易描述
> 通过上面的协议内容, 可以发现有以下节点, 我们对出现过的节点简单描述
- process 代表是一个可以被引擎处理的流程
- startEvent 开始事件节点(**这里的需要注意命名, Event 代表是事件, 也就是说一个流程实例的开始, 是基于事件的,截止 23 年, 在
Flowable 中开始事件分为 9 种, 我们只用了空启动事件**)
> - 空启动事件
> - 定时器事件
> - 信号启动事件
> - 消息启动事件
> - 异常启动事件
> - 注册开始事件
> - 开始变量监听事件
> - 升级开始事件
> - 条件启动事件
- exclusiveGateway 排它网关(**控制流程如何流转, 是最常用的一个基础组件,只要涉及到条件流转,都需要用它,在 Flowable 中,网关也有
4 种, 我们只用了排它网关和并行网关**)
> - 排它网关
> - 并行网关
> - 事件网关
> - 包容网关
- userTask 用户任务(**需要人为介入的节点,一般情况下只要人类不对该节点做动作,那么流程就永远卡在这里,不再执行**)
- sequenceFlow 顺序流(**用于连接两个节点间的连线,很重要的组件,如果连线不正确,或者中断,会直接影响引擎的执行**)
- endEvent 结束事件节点
### 4.3 流程模型与流程定义
> 以上仅仅是冰山一角, 大致了解了最初级的协议内容后, 同时我们也称上面的协议内容为**流程定义**, 流程定义是一种较为底层的元数据.
>
> 因为我们一般针对某个流程规则, 会时不时的修改和调整, 修改后如何保证使用方无感知, 但对于工作流来说又是可追溯的,
> 所以需要再抽象一层来保存同一个流程的所有修改过程, 我们称这个抽象层为**流程模型**.
> 也就是说, 一个流程模型是一个流程, 这个流程的的多次修改(多版本)都在这个模型下. 而使用方也就只用关心流程模型的唯一标识即可.
>
> 我们编辑好流程模型与流程定义后, 还需要对流程模型(定义)发布, 才能真正的被使用. 发布后, 这一次的流程定义将定型,不再允许改变当前版本的内容.
> 如需修改规则, 则会产生新的版本.
流程模型: 对应库表: act_re_model
流程定义: 对应库表: act_re_procdef
流程发布: 对应库表: act_re_deployment/act_ge_bytearray
以上仅仅是冰山一角, 大致了解了最初级的协议内容后, 同时我们也称上面的协议内容为`流程定义`, 我们再看看如何运行它?
## 5. 如何运行一个流程?
> 在介绍上面的协议内容时, 说了我们只用了空启动事件, 那我们怎么启动呢? 虽然说它是事件, 但我们实际发起流程并不是直接操作的事件,
> 而是操作引擎为我们提供API.
> `org.flowable.engine.RuntimeService#ProcessInstanceBuilder#start()`
### 5.1 简单了解 Flowable 引擎常用的 API (门面模式)
> API 的相关作用,都在对应类有注解, 自行查阅
- org.flowable.engine.RuntimeService 运行时相关
- org.flowable.engine.RepositoryService 元数据相关
- org.flowable.engine.HistoryService 历史数据相关
- org.flowable.engine.TaskService 审批任务相关
### 5.2 运行(前/中)的困惑?
> 有没有发现一个问题? 我们从头到现在几乎没有说审批人相关问题或配置, 包括 BPMN 协议内容中也没有审批人的信息. 能发起成功吗?
>
> 首先回答: 能正常发起,并且流程也能处理运转. 只是像之前说的没有人为操作, 流程会永远的停止在第一个审批节点,
> 然后这前提是规则里面至少有一个 userTask.
> 如果 BPMN 规则里面,只有 startEvent 直接连接的 endEvent, 那么发起即结束.
## 6. 流程引擎内部是如何运行的?
> Flowable 分为多个引擎, 包括 BPMN 引擎/CMMN 引擎/DMN 引擎/Form 引擎/IDM 引擎, 而我们主要关注 BPMN 引擎即可.
> 所有的引擎的内部功能统一的采用了命令模式, 引擎内部的每一步动作都是依靠一个Agenda(待办)
> 来临时存储后续动作的"命令".
>
> 所以我们研究引擎需要研究各种命令(XXXCmd). 这些命令是进入引擎内部的入口, 可以通过查看
> org.flowable.common.engine.impl.interceptor.Command 的继承关系看到具体的命令. 当然引擎还有更多的逻辑,
> 例如真正处理各种类型任务节点的是对应的行为(XXXBehavior),
> 可以通过查看 `org.flowable.engine.impl.delegate.ActivityBehavior`
> 的继承关系看到具体的行为.
>
> 例如: 想看发起过程需要研究: StartProcessInstanceCmd#execute
### 6.1 运行逻辑与操作库表总结
> 通常情况系下,业务要接入引擎,需要从模型配置开始,整体操作逻辑如下:
> 流程模型配置 -> 流程定义配置 -> 发布模型及定义 -> 业务发起指定模型 -> 引擎生成对应审批任务及事件 ->
> 业务操作任务通过/拒绝或撤销 -> 引擎流转节点直至结束.
>
> 大致逻辑了解后,再描述下具体调用 API 以及每个步骤操作的库表
### 6.2 创建模型
> 创建模型可以同步将定义传入,形成原子操作,当然操作上也是允许分步配置的.
>
API: cn.axzo.workflow.client.feign.bpmn.ProcessModelApi#create
操作的库表: act_re_model/act_ge_bytearray
### 6.3 创建/更新流程定义
> 如果未在模型创建时,同步传入定义内容, 则可以通过独立更新方式,仅更新定义内容. 需要注意的是流程定义是有版本概念的,
> 但激活的仅能只有一个版本,其他均为挂起
> API:
**(模型中更新)**: cn.axzo.workflow.client.feign.bpmn.ProcessModelApi#update
**(独立更新)**: cn.axzo.workflow.client.feign.bpmn.ProcessDefinitionApi#updateProcessDefinition
操作的库表: act_re_model/act_ge_bytearray
### 6.4 发布模型
> 发布模型的方式有很多,这里只列举了一种, 通过 ID 部署, 也可以通过 Key 部署, Key 是模型的必要属性, 业务也可以通过这个 Key
> 进行流程的发起.
>
API: cn.axzo.workflow.client.feign.bpmn.ProcessModelApi#deployById
操作的库表: act_re_procdef/act_re_deployment
### 6.5 创建流程实例
> 业务接入后,主要使用的 API, 基于某个流程模型进行实例的发起.
>
API: cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi#createProcessInstance
操作的库表:
**(运行时)**: act_ru_execution/act_ru_task/act_ru_actinst/act_ru_variable/
**(历史数据)**: act_hi_procinst/act_hi_taskinst/act_hi_actinst/act_hi_varinst/
### 6.6 流程任务审批
> 单纯操作流程任务相关的 API, 可以被业务使用.
>
API:
**(通过任务)**: cn.axzo.workflow.client.feign.bpmn.ProcessTaskApi.approveTask
**(拒绝任务)**: cn.axzo.workflow.client.feign.bpmn.ProcessTaskApi.rejectTask
操作的库表:
**(运行时)**: act_ru_task/act_ru_variable/act_ru_actinst/
**(历史数据)**: act_hi_taskinst/act_hi_varinst/act_hi_actinst/act_hi_procinst/
### 6.7 流程评论+附件
API:
**(添加评论)**: cn.axzo.workflow.core.service.BpmnProcessTaskService.commentTask
**(添加附件)**: cn.axzo.workflow.core.service.BpmnProcessTaskService.attachmentTask
操作的库表:
**(评论)**: act_hi_comment
**(附件)**: act_hi_attachment
## 7. 如何与外部交互?
> 工作流引擎作为服务端, 与外部系统交互分为两类, 一类是操作引擎, 一般提供 API 入口, 由外部推动. 另一类是引擎内部状态通知给使用方,
> 是由服务端广播出去.
### 7.1 外部操作引擎
> 本工程提供两种使用接入方式, 一种是 jar 包集成, 可参考[这个说明](workflow-engine-core/src/main/resources/readme.md),
> 另一种是 Feign API 集成, 主要参考`workflow-engine-server`module
> 中包路径为 `src/main/java/cn/axzo/workflow/server/controller/web` 下的文件.
### 7.2 引擎内部事件广播
> 引擎内部的事件类型非常多, org.flowable.common.engine.api.delegate.event.FlowableEngineEventType 通过这个类可以看到引擎会触发的事件类型.
>
> 本工程根据公司需要, 将引擎部分事件广播给使用方, 其他事件则不广播. 同时这些事件也被用于本工程的一些场景的消费.
**广播的事件接口**
- cn.axzo.workflow.core.listener.BpmnActivityEventListener
- cn.axzo.workflow.core.listener.BpmnTaskEventListener
- cn.axzo.workflow.core.listener.BpmnProcessEventListener
**以下总结哪些接口触发哪些事件**
- cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi.createProcessInstance
> 发起流程 MQ 触发规则:
> 1. 当前流程实例会依次触发 process-instance-created 和 process-instance-started 事件
> 2. 第一个审批任务会依次触发 process-task-assigned 和 process-task-created 事件
- cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi.cancelProcessInstance
> 取消流程 MQ 触发规则:
> 1. 当前流程实例中现存的任务会依次触发 process-task-deleted 事件
> 2. 当前流程实例会触发 process-instance-cancelled 事件
- cn.axzo.workflow.client.feign.bpmn.ProcessTaskApi.approveTask
> 任务节点通过审批 MQ 触发规则:
> 1. 当前审批任务会依次触发 process-task-completed 和 process-task-deleted 事件(如果有下一级审批,则会触发第 2.1 点中的事件,
如果当前审核任务最后一级审批,则会触发第 2.2 点中的事件)
> 2.1. 下一级审批任务会依次触发 process-task-assigned 和 process-task-created 事件
> 2.2. 流程实例正常结束会触发 process-instance-completed 事件
- cn.axzo.workflow.client.feign.bpmn.ProcessTaskApi.rejectTask
> MQ 触发规则:
> 1. 当前审批任务会触发 process-task-deleted 事件
> 2. 当前流程实例会触发 process-instance-rejected 事件
## 8. Flowable 过时的中文文档参考
[Flowable BPMN 用户手册 v6.3.0](https://tkjohn.github.io/flowable-userguide/)
## 99.建设状态(过时)
### 99.1 已完成:
1. 全新搭建工作流微服务
2. 工作流模型中 JSON 格式的协议支持
@ -55,7 +419,7 @@
5. 重构工作流服务 Process 引擎相关接口,并按照框架简单接入和测试内置表单
6. 重构业务分类/流程状态/审批状态运行时以及历史数据的处理方式
待办项:
### 99.2 待办项:
1. BPMN 协议兼容(XML/JSON已支持)
2. 工作流内部接入组织架构
@ -63,4 +427,3 @@
4. 审批模型审批人配置管理与持久化
5. 审批模型表单能力的接入和调整
5. Flowable Event MQ 事件生产与消费

BIN
imgs/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
imgs/start-config.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

12
pom.xml
View File

@ -15,7 +15,7 @@
<name>workflow-engine</name>
<properties>
<revision>1.2.0-SNAPSHOT</revision>
<revision>1.2.1-SNAPSHOT</revision>
<axzo-bom.version>2.0.0-SNAPSHOT</axzo-bom.version>
<axzo-dependencies.version>2.0.0-SNAPSHOT</axzo-dependencies.version>
<lombok.version>1.18.22</lombok.version>
@ -60,6 +60,16 @@
<artifactId>workflow-engine-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.axzo.maokai</groupId>
<artifactId>maokai-api</artifactId>
<version>${axzo-dependencies.version}</version>
</dependency>
<dependency>
<groupId>cn.axzo.karma</groupId>
<artifactId>karma-api</artifactId>
<version>${axzo-dependencies.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>

View File

@ -0,0 +1,25 @@
package cn.axzo.workflow.client.feign.bpmn;
import cn.azxo.framework.common.model.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotBlank;
/**
* 流程活动的 API
*
* @author wangli
* @since 2023/11/17 16:28
*/
@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}")
public interface ProcessActivityApi {
/**
* 业务节点唤醒
*/
@GetMapping("/api/process/activity/trigger")
CommonResponse<Boolean> trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId);
}

View File

@ -1,6 +1,7 @@
package cn.axzo.workflow.client.feign.bpmn;
import cn.axzo.workflow.common.model.request.bpmn.definition.BpmnProcessDefinitionUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessDefinitionPageDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO;
@ -77,11 +78,21 @@ public interface ProcessDefinitionApi {
/**
* 获取指定模型的 Category 激活
* 获取指定模型的定义 ID
*
* @return 流程定义ID
*/
@GetMapping("/api/process/definition/active/getByCategory")
CommonResponse<String> getActiveProcessDefinitionId(@NotBlank(message = "租户不能为空") @RequestParam String tenantId,
@NotBlank(message = "分类不能为空") @RequestParam String category);
CommonResponse<String> getActiveProcessDefinitionId(@RequestParam(required = false) String tenantId,
@NotBlank(message = "分类不能为空") @RequestParam(required = false) String category);
/**
* 获取指定模型激活的流程定义 JSON 模型
*
* @return 流程定义ID
*/
@GetMapping("/api/process/definition/active/json/model")
CommonResponse<BpmnModelUpdateDTO> getActiveProcessDefinitionJsonModel(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId,
@NotBlank(message = "分类不能为空") @RequestParam(required = false) String category,
@RequestParam(required = false) String tenantId);
}

View File

@ -1,5 +1,6 @@
package cn.axzo.workflow.client.feign.bpmn;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO;
@ -105,4 +106,8 @@ public interface ProcessInstanceApi {
@GetMapping("/api/process/instance/node/forecasting")
CommonResponse<List<ProcessNodeDetailVO>> processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId,
@Nullable @RequestParam(required = false) String tenantId);
@GetMapping("/api/process/instance/cooperation-org")
CommonResponse<CooperationOrgDTO> getCooperationOrg(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId,
@Nullable @RequestParam(required = false) String tenantId);
}

View File

@ -5,7 +5,7 @@ import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelDetailVO;
import cn.axzo.workflow.common.model.response.form.model.FormModelBaseVO;
import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO;
import cn.azxo.framework.common.model.CommonResponse;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.cloud.openfeign.FeignClient;
@ -18,6 +18,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 流程模型 API
@ -30,7 +32,7 @@ public interface ProcessModelApi {
@Operation(summary = "流程模型列表")
@GetMapping("/api/process/model/page")
CommonResponse<BpmPageResult<FormModelBaseVO>> page(@Validated @RequestBody BpmnModelSearchDTO dto);
CommonResponse<BpmPageResult<BpmnModelDetailVO>> page(@Validated @RequestBody BpmnModelSearchDTO dto);
/**
* 创建流程,
@ -46,7 +48,7 @@ public interface ProcessModelApi {
@Operation(summary = "通过模型ID查询指定流程模型")
@GetMapping("/api/process/model/get")
CommonResponse<BpmnModelDetailVO> getById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId,
@NotBlank(message = "租户不能为空") @RequestParam(required = false) String tenantId);
@RequestParam(required = false) String tenantId);
/**
@ -64,10 +66,9 @@ public interface ProcessModelApi {
*
* @return true 能更新,表明该模型关联的所有定义版本都是挂起状态
*/
@Operation(summary = "校验指定的模型是否能修改")
@PutMapping("/api/process/model/update/check")
CommonResponse<Boolean> checkCanUpdate(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId,
@NotBlank(message = "租户不能为空") @RequestParam(required = false) String tenantId);
@Operation(summary = "获取指定模型的扩展属性")
@GetMapping("/api/process/model/ext")
CommonResponse<BpmnModelExtVO> getModelExt(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId);
/**
* 修改流程信息
@ -84,7 +85,8 @@ public interface ProcessModelApi {
@Operation(summary = "通过模型 ID 部署流程模型")
@PostMapping("/api/process/model/deploy")
CommonResponse<String> deployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId,
@NotBlank(message = "租户不能为空") @RequestParam(required = false) String tenantId);
@RequestParam(required = false, defaultValue = "") String modelTenantId,
@RequestParam(required = false) String operator);
/**
* 部署模型
@ -93,12 +95,14 @@ public interface ProcessModelApi {
@Operation(summary = "通过模型 KEY 部署流程模型")
@PostMapping("/api/process/model/deployByKey")
CommonResponse<String> deployByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam(required = false) String processModelKey,
@NotBlank(message = "租户不能为空") @RequestParam(required = false) String tenantId);
@NotBlank(message = "租户不能为空") @RequestParam(required = false) String modelTenantId,
@RequestParam(required = false) String operator);
@Operation(summary = "通过模块 ID 取消部署流程模型")
@PostMapping("/api/process/model/undeploy")
CommonResponse<Void> unDeployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId,
@NotBlank(message = "租户不能为空") @RequestParam(required = false) String tenantId);
@RequestParam(required = false, defaultValue = "") String tenantId,
@RequestParam(required = false) String operator);
/**
* 删除模型
@ -112,4 +116,14 @@ public interface ProcessModelApi {
@DeleteMapping("/api/process/model/deleteByKey")
CommonResponse<Void> deleteByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam String processModelKey,
@NotBlank(message = "租户不能为空") @RequestParam String tenantId);
@Operation(summary = "修改模型状态")
@PostMapping("/api/process/model/changeStatus")
CommonResponse<Void> changeStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId,
@NotNull(message = "状态不能为空") @RequestParam Integer status,
@RequestParam(required = false) String operator);
@Operation(summary = "查询流程模型使用的分类列表")
@GetMapping("/api/process/model/category/ids")
CommonResponse<List<String>> getModelCategoryList();
}

View File

@ -2,14 +2,16 @@ package cn.axzo.workflow.client.feign.bpmn;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAssigneeDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskPageSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskRemindDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceGroupVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO;
import cn.axzo.workflow.common.valid.group.ValidGroup;
import cn.azxo.framework.common.model.CommonResponse;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.cloud.openfeign.FeignClient;
@ -46,41 +48,6 @@ public interface ProcessTaskApi {
@GetMapping("/api/process/task/page/done")
CommonResponse<BpmPageResult<BpmnTaskDonePageItemVO>> getDoneTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto);
/**
* 同意
*
* <pre>
* MQ 触发规则:
* 1. 当前审批任务会依次触发 process-task-completed process-task-deleted 事件(如果有下一级审批,则会触发第 2.1 点中的事件,
* 如果当前审核任务最后一级审批,则会触发第 2.2 点中的事件)
* 2.1. 下一级审批任务会依次触发 process-task-assigned process-task-created 事件
* 2.2. 流程实例正常结束会触发 process-instance-completed 事件
* </pre>
*/
@PutMapping("/api/process/task/approve")
CommonResponse<Boolean> approveTask(@Validated(ValidGroup.Insert.class) @RequestBody BpmnTaskAuditDTO dto);
/**
* 拒绝
*
* <pre>
* MQ 触发规则:
* 1. 当前审批任务会触发 process-task-deleted 事件
* 2. 当前流程实例会触发 process-instance-rejected 事件
* </pre>
*/
@PutMapping("/api/process/task/reject")
CommonResponse<Boolean> rejectTask(@Validated(ValidGroup.Update.class) @RequestBody BpmnTaskAuditDTO dto);
/**
* 转办
*
* @param dto
* @return
*/
@Operation(summary = "直接修改审批任务的审批人")
@PutMapping("/api/process/task/assignee")
CommonResponse<Boolean> assigneeTask(@Validated @RequestBody BpmnTaskAssigneeDTO dto);
/**
* 获取指定流程实例的审批过程信息
@ -106,4 +73,110 @@ public interface ProcessTaskApi {
@GetMapping("/api/process/task/active/list")
CommonResponse<List<BpmnTaskInstanceVO>> getActiveTasksByProcessInstanceId(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @NotBlank(message = "租户不能为空") @RequestParam String tenantId);
/**
* 同意
*
* <pre>
* MQ 触发规则:
* 1. 当前审批任务会依次触发 process-task-completed process-task-deleted 事件(如果有下一级审批,则会触发第 2.1 点中的事件,
* 如果当前审核任务最后一级审批,则会触发第 2.2 点中的事件)
* 2.1. 下一级审批任务会依次触发 process-task-assigned process-task-created 事件
* 2.2. 流程实例正常结束会触发 process-instance-completed 事件
* </pre>
*/
@PutMapping("/api/process/task/approve")
CommonResponse<Boolean> approveTask(@Validated @RequestBody BpmnTaskAuditDTO dto);
/**
* 驳回
*
* <pre>
* MQ 触发规则:
* 1. 当前审批任务会触发 process-task-deleted 事件
* 2. 当前流程实例会触发 process-instance-rejected 事件
* </pre>
*/
@PutMapping("/api/process/task/reject")
CommonResponse<Boolean> rejectTask(@Validated @RequestBody BpmnTaskAuditDTO dto);
/**
* 转交
*
* @param dto
* @return
*/
@Operation(summary = "直接修改审批任务的审批人")
@PutMapping("/api/process/task/transfer")
CommonResponse<Boolean> transferTask(@Validated @RequestBody BpmnTaskAssigneeDTO dto);
/**
* 评论
*
* @param dto 评论请求参数
* @return
*/
@Operation(summary = "审批流程评论")
@PutMapping("/api/process/task/comment")
CommonResponse<Boolean> commentTask(@Validated @RequestBody BpmnTaskCommentDTO dto);
/**
* 加签
*
* @param dto 加签请求参数
* @return
*/
@Operation(summary = "审批流程加签")
@PutMapping("/api/process/task/countersign")
CommonResponse<Boolean> countersignTask(@Validated @RequestBody BpmnTaskCountersignDTO dto);
/**
* 催办
*
* @param dto
* @return
*/
@Operation(summary = "审批流程催办")
@PutMapping("/api/process/task/remind")
CommonResponse<Boolean> remindTask(@Validated @RequestBody BpmnTaskRemindDTO dto);
//
//
// /**
// * 抄送
// *
// * @param dto
// * @return
// */
// @Operation(summary = "审批流程抄送")
// @PutMapping("/api/process/task/copy")
// CommonResponse<Boolean> copyTask(@Validated @RequestBody BpmnTaskAssigneeDTO dto);
//
//
// /**
// * 回退
// *
// * @param dto
// * @return
// */
// @Operation(summary = "审批流程回退")
// @PutMapping("/api/process/task/rollback")
// CommonResponse<Boolean> rollbackTask(@Validated @RequestBody BpmnTaskAssigneeDTO dto);
//
//
// /**
// * 撤回
// *
// * @param dto
// * @return
// */
// @Operation(summary = "审批流程撤回")
// @PutMapping("/api/process/task/revocation")
// CommonResponse<Boolean> revocationTask(@Validated @RequestBody BpmnTaskAssigneeDTO dto);
//
//
}

View File

@ -0,0 +1,91 @@
package cn.axzo.workflow.client.feign.manage;
import cn.axzo.workflow.common.model.request.category.CategoryCreateDTO;
import cn.axzo.workflow.common.model.request.category.CategorySearchDTO;
import cn.axzo.workflow.common.model.request.category.CategoryUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import cn.azxo.framework.common.model.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* OMS流程业务管理API
*
* @author zuoqinbo
* @version V1.0
* @date 2023/11/6 16:01
*/
@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}")
public interface ProcessCategoryApi {
/**
* 获取指定业务分类
* @return
*/
@GetMapping("/api/process/category/get")
CommonResponse<CategoryItemVO> get(@RequestParam Long id);
@GetMapping("/api/process/category/getByIds")
CommonResponse<List<CategoryItemVO>> getByIds(@RequestParam List<Long> ids);
/**
* 新增业务分类
*
* @return
*/
@PostMapping("/api/process/category/create")
CommonResponse<CategoryItemVO> create(@Validated @RequestBody CategoryCreateDTO req);
/**
* 更新业务分类
*
* @return
*/
@PutMapping("/api/process/category/update")
CommonResponse<CategoryItemVO> update(@Validated @RequestBody CategoryUpdateDTO dto);
/**
* 删除指定业务分类
* @param id
* @return
*/
@DeleteMapping("/api/process/category/delete")
CommonResponse<Boolean> delete(@RequestParam Long id);
/**
* 更新指定分类状态
* @param id
* @param state
* @return
*/
@PutMapping("/api/process/category/update/state")
CommonResponse<Boolean> updateState(@RequestParam Long id, @RequestParam Boolean state);
/**
* 获取指定业务分类集合
*
* @return
*/
@PostMapping("/api/process/category/list")
CommonResponse<List<CategoryItemVO>> list(@RequestBody CategorySearchDTO dto);
/**
* 搜索业务分类
*
* @return
*/
@PostMapping("/api/process/category/page/search")
CommonResponse<BpmPageResult<CategoryItemVO>> search(@RequestBody CategorySearchDTO dto);
}

View File

@ -0,0 +1,32 @@
package cn.axzo.workflow.client.feign.manage;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
import cn.azxo.framework.common.model.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
/**
* OMS流程业务管理API
*
* @author zuoqinbo
* @version V1.0
* @date 2023/11/6 16:01
*/
@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}")
public interface ProcessConfigApi {
/**
* 获取流程操作按钮列表
*
* @param webContextPath 容器上下文路径
* @return 流程操作按钮列表
*/
@GetMapping("/api/process/config/button/list")
CommonResponse<List<BpmnButtonMetaInfo>> getDefaultButtons();
}

View File

@ -1,73 +0,0 @@
package cn.axzo.workflow.common.constant;
public interface BpmConstants {
String BPMN_FILE_SUFFIX = ".bpmn";
String FORM_FILE_SUFFIX = ".form";
String SERVER_APP_ID = "server-app-id";
/**
* 引擎自己的隐藏指令
*/
String FLOWABLE_SKIP_EXPRESSION_ENABLE = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
/**
* 指定单位标识
*/
String INTERNAL_EXECUTIVE_UNIT_ID = "_INTERNAL_EXECUTIVE_UNIT_ID";
String INTERNAL_INITIATOR = "_INTERNAL_INITIATOR";
String INTERNAL_INITIATOR_OU_ID = "_INTERNAL_INITIATOR_OU_ID";
String INTERNAL_INITIATOR_OU_NAME = "_INTERNAL_INITIATOR_OU_NAME";
String INTERNAL_END_USER_ID = "_INTERNAL_END_USER_ID";
String INTERNAL_END_TENANT_ID = "_INTERNAL_END_TENANT_ID";
String INTERNAL_END_USER_NAME = "_INTERNAL_END_USER_NAME";
String INTERNAL_TASK_COMMENT = "_INTERNAL_CUSTOM_TASK_COMMENT";
String INTERNAL_DELETE_PROCESS_FLAG = "_INTERNAL_DELETE_PROCESS_FLAG";
String INTERNAL_PROCESS_TYPE_CANCEL = "_INTERNAL_PROCESS_TYPE_CANCEL";
String INTERNAL_PROCESS_TYPE_REJECT = "_INTERNAL_PROCESS_TYPE_REJECT";
String INTERNAL_SPECIFY_NEXT_APPROVER = "_INTERNAL_SPECIFY_NEXT_APPROVER";
// String INTERNAL_TASK_RELATION_ASSIGNEE_INFO = "[_ASSIGNEE_INFO_]";
// 用于多实例审批时,保存计算出来的审批人
String INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO = "[_ASSIGNEE_LIST_INFO_]";
// 单任务节点,
String INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT = "[_ASSIGNEE_INFO_SNAPSHOT_]";
String INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT = "[_ASSIGNEE_LIST_INFO_SNAPSHOT_]";
String PROCESS_PREFIX = "Flowable";
String START_EVENT_ID = "startEventNode";
String END_EVENT_ID = "endEventNode";
String BPM_MODEL_CATEGORY = "bpm_model_category";
String BPM_ALLOW_SKIP_USER_TASK = "_INTERNAL_SKIP_USER_TASK";
/**
* 用于国内审批节点填写审批建议
* <p>
* 其他类型 @see org.flowable.engine.impl.persistence.entity.CommentEntity
*/
String COMMENT_TYPE_ADVICE = "advice";
String INITIATOR_REVOKE_THE_APPROVAL = "发起人主动撤销审批";
String APPROVAL_ENDS_AUTOMATICALLY = "审批拒绝自动结束";
String NUMBER_OF_INSTANCES = "nrOfInstances";
String MULTI_INSTANCE_LOOP_COUNTER = "loopCounter";
/**
* 会签表达式
*/
String AND_SIGN_EXPRESSION = "${nrOfInstances == nrOfCompletedInstances}";
/**
* 或签表达式
*/
String OR_SIGN_EXPRESSION = "${nrOfCompletedInstances > 0}";
/**
* 全局的启用/上架等状态描述
*/
Integer ENABLED = 1;
/**
* 全局的停用/下架等状态描述
*/
Integer DISABLED = 0;
}

View File

@ -0,0 +1,128 @@
package cn.axzo.workflow.common.constant;
public interface BpmnConstants {
String BPMN_FILE_SUFFIX = ".bpmn";
String FORM_FILE_SUFFIX = ".form";
String SERVER_APP_ID = "server-app-id";
/**
* 引擎自己的隐藏指令
*/
String FLOWABLE_SKIP_EXPRESSION_ENABLE = "[_FLOWABLE_SKIP_EXPRESSION_ENABLED_]";
String INTERNAL_INITIATOR = "[_INTERNAL_INITIATOR_]";
@Deprecated
String OLD_INTERNAL_INITIATOR = "_INTERNAL_INITIATOR";
String INTERNAL_END_USER_ID = "[_INTERNAL_END_USER_ID_]";
String INTERNAL_END_TENANT_ID = "[_INTERNAL_END_TENANT_ID_]";
String INTERNAL_END_USER_NAME = "[_INTERNAL_END_USER_NAME_]";
String INTERNAL_INITIATOR_OU_ID = "[_INTERNAL_INITIATOR_OU_ID_]";
String INTERNAL_INITIATOR_OU_NAME = "[_INTERNAL_INITIATOR_OU_NAME_]";
String INTERNAL_DELETE_PROCESS_FLAG = "[_INTERNAL_DELETE_PROCESS_FLAG_]";
String INTERNAL_PROCESS_TYPE_CANCEL = "[_INTERNAL_PROCESS_TYPE_CANCEL_]";
String INTERNAL_PROCESS_TYPE_REJECT = "[_INTERNAL_PROCESS_TYPE_REJECT_]";
String INTERNAL_SPECIFY_NEXT_APPROVER = "[_INTERNAL_SPECIFY_NEXT_APPROVER_]";
String BIZ_ORG_RELATION = "[_BIZ_ORG_RELATION_]";
// 用于多实例审批时,保存计算出来的审批人
String INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO = "[_ASSIGNEE_LIST_INFO_]";
// 单任务节点,
String INTERNAL_TASK_RELATION_ASSIGNEE_INFO = "[_ASSIGNEE_INFO_]";
@Deprecated
String OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT = "[_ASSIGNEE_INFO_SNAPSHOT_]";
String INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT = "[_ACTIVITY_INFO_SNAPSHOT_]";
String COUNTERSIGN_REMAIN_ASSIGNER_LIST = "[_COUNTERSIGN_REMAIN_ASSIGNER_LIST_]";
String COUNTERSIGN_ORIGIN_ASSIGNER = "[_COUNTERSIGN_ORIGIN_ASSIGNER_]";
String PROCESS_PREFIX = "Flowable";
String TASK_ASSIGNEE_SKIP_FLAT = "taskSkip";
String FLOW_NODE_JSON = "jsonValue";
String FLOW_SERVER_VERSION = "serverVersion";
String FLOW_SERVER_VERSION_121 = "1.2.1";
String CONFIG_NOTICE = "noticeConfig";
String TEMPLATE_NOTICE_MESSAGE_ID = "noticeMessageId";
String TEMPLATE_PENDING_MESSAGE_ID = "pendingMessageId";
String TEMPLATE_SMS_MESSAGE_ID = "smsMessageId";
String CONFIG_BUTTON = "buttonConfig";
String CONFIG_BUTTON_META = "button";
String CONFIG_FIELD = "fieldConfig";
String CONFIG_APPROVAL_METHOD = "approvalMethod";
String CONFIG_APPROVER_SCOPE = "approverScope";
String CONFIG_APPROVER_SPECIFY = "approverSpecify";
String CONFIG_APPROVER_MODE_TYPE = "approverModeType";
String CONFIG_APPROVER_EMPTY_HANDLE_TYPE = "approverEmptyHandleType";
String CONFIG_FIELD_META = "field";
String CONFIG_FIELD_PERMISSION = "fieldPermission";
String CONFIG_FIELD_OPTION = "option";
String CONFIG_NODE_TYPE = "nodeType";
String CONFIG_BUTTON_TYPE_INITIATOR = "initiator";
String CONFIG_BUTTON_TYPE_CURRENT = "current";
String CONFIG_BUTTON_TYPE_HISTORY = "history";
String CONFIG_BUTTON_TYPE_CARBON_COPY = "carbonCopy";
String ELEMENT_ATTRIBUTE_NAME = "name";
String ELEMENT_ATTRIBUTE_VALUE = "value";
String ELEMENT_ATTRIBUTE_DESC = "desc";
String ELEMENT_ATTRIBUTE_CODE = "code";
String ELEMENT_ATTRIBUTE_KEY = "key";
String ELEMENT_ATTRIBUTE_ORDER = "order";
String ELEMENT_ATTRIBUTE_CHECKED = "checked";
String ELEMENT_ATTRIBUTE_DISABLED = "disabled";
String ELEMENT_ATTRIBUTE_TYPE = "type";
String START_EVENT_ID = "startEventNode";
String SEQUENCE_FLOW_ID = "SequenceFlowId";
String END_EVENT_ID = "endEventNode";
String BPM_MODEL_CATEGORY = "bpm_model_category";
String BPM_ALLOW_SKIP_USER_TASK = "_INTERNAL_SKIP_USER_TASK_";
String BPM_ALLOW_AUTO_REJECTION = "_INTERNAL_AUTO_REJECTION";
/**
* 用于国内审批节点填写审批建议
* <p>
* 其他类型 @see org.flowable.engine.impl.persistence.entity.CommentEntity
*/
String COMMENT_TYPE_ADVICE = "advice";
String COMMENT_TYPE_AUTO_PASSED = "operation-autoPassed";
String COMMENT_TYPE_OPERATION_COUNTERSIGN = "operation-countersign";
String COMMENT_TYPE_OPERATION_TRANSFER = "operation-transfer";
String NUMBER_OF_INSTANCES = "nrOfInstances";
String MULTI_INSTANCE_LOOP_COUNTER = "loopCounter";
String TASK_COMPLETE_OPERATION_TYPE = "_TASK_COMPLETE_TYPE";
/**
* 会签表达式
*/
String AND_SIGN_EXPRESSION = "${nrOfInstances == nrOfCompletedInstances}";
/**
* 或签表达式, 只要当同意的人数大于 1 即可, 拒绝不影响该节点
*/
String OR_SIGN_EXPRESSION_ONE_PASS = "${nrOfCompletedInstances > 0}";
/**
* 或签表达式, 只要一个人同意或驳回就结束该节点
*/
String OR_SIGN_EXPRESSION_ONLY_ONE = "${nrOfInstances != nrOfActiveInstances}";
/**
* 全局的启用/上架等状态描述
*/
Integer ENABLED = 1;
/**
* 全局的停用/下架等状态描述
*/
Integer DISABLED = 0;
//=============== 消息推送时的变量集合中 key 的命名 =================
String VAR_INITIATOR_USER_NAME = "initiatorUserName";
String VAR_PROCESS_INSTANCE_NAME = "processInstanceName";
String VAR_PROCESS_INSTANCE_ID = "processInstanceId";
String VAR_PROCESS_START_TIME = "processStartTime";
String VAR_PROCESS_END_TIME = "processEndTime";
String VAR_PROCESS_TENANT_ID = "tenantId";
String VAR_BUSINESS_NAME = "businessName";
String VAR_TASK_ID = "taskId";
String VAR_TASK_START_TIME = "taskStartTime";
String VAR_TASK_USER_NAME = "taskUserName";
String VAR_ACTIVITY_ID = "activityId";
String VAR_ACTIVITY_NAME = "activityName";
String VAR_PROCESS_RESULT = "processResult";
String VAR_OPERATOR_TYPE = "operatorType";
}

View File

@ -11,7 +11,5 @@ public interface MetaInfoConstants {
String MODEL_TYPE = "modelType";
String MODEL_TYPE_PROCESS = "MODEL_PROCESS";
String MODEL_TYPE_FORM = "MODEL_FORM";
String MODEL_DESCRIPTION = "modelDescription";
}

View File

@ -0,0 +1,40 @@
package cn.axzo.workflow.common.enums;
/**
* 审批方式枚举
*
* @author wangli
* @since 2023/11/16 10:10
*/
public enum ApprovalMethodEnum {
human("human", "人工审批"),
autoPassed("autoPassed", "自动通过"),
autoRejection("autoRejection", "自动驳回"),
nobody("nobody", "不审批[仅业务节点可能有该值]"),
;
private String type;
private String desc;
ApprovalMethodEnum(String type, String desc) {
this.type = type;
this.desc = desc;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}

View File

@ -0,0 +1,39 @@
package cn.axzo.workflow.common.enums;
/**
* 审批人为空时的处理方式枚举
*
* @author wangli
* @since 2023/11/16 10:22
*/
public enum ApproverEmptyHandleTypeEnum {
autoPassed("autoPassed", "自动通过"),
autoRejection("autoRejection", "自动驳回"),
autoSkipped("autoSkipped", "自动跳过"),
transferToAdmin("transferToAdmin", "转交给管理员"),
specifyAssignee("specifyAssignee", "指定审批人"),
;
private String type;
private String desc;
ApproverEmptyHandleTypeEnum(String type, String desc) {
this.type = type;
this.desc = desc;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}

View File

@ -0,0 +1,48 @@
package cn.axzo.workflow.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 审批人所在范围枚举
*
* @author wangli
* @since 2023/11/16 10:14
*/
@Getter
@AllArgsConstructor
public enum ApproverScopeEnum {
entWorkspace("entWorkspace", "企业工作台", "entWorkspaceProcessor"),
projectWorkspace("projectWorkspace", "项目工作台","projectWorkspaceProcessor"),
preTaskUser("preTaskUser", "上节点审批人所在单位","preTaskUserProcessor"),
preTaskSpecified("preTaskSpecified", "上节点审批人指定","preTaskUserProcessor"),
;
private String type;
private String desc;
private String processor;
ApproverScopeEnum(String type, String desc) {
this.type = type;
this.desc = desc;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public boolean selectWorkspace() {
return this == ApproverScopeEnum.projectWorkspace;
}
}

View File

@ -0,0 +1,42 @@
package cn.axzo.workflow.common.enums;
/**
* 审批人指定枚举
*
* @author wangli
* @since 2023/11/16 10:16
*/
public enum ApproverSpecifyEnum {
position("position", "指定职位"),
role("role", "指定角色"),
identity("identity", "指定身份"),
initiatorLeader("initiatorLeader", "发起人主管"),
initiatorLeaderRecursion("initiatorLeaderRecursion", "发起人多级主管"),
fixedPerson("fixedPerson", "固定人员"),
preNodeSpecified("preNodeSpecified", "上级节点指定"),
;
private String type;
private String desc;
ApproverSpecifyEnum(String type, String desc) {
this.type = type;
this.desc = desc;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}

View File

@ -0,0 +1,38 @@
package cn.axzo.workflow.common.enums;
/**
* 附件类型枚举
*
* @author wangli
* @since 2023/11/22 23:19
*/
public enum AttachmentTypeEnum {
image("image", "图片"),
file("file", "文件"),
;
private String type;
private String desc;
AttachmentTypeEnum(String type, String desc) {
this.type = type;
this.desc = desc;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}

View File

@ -0,0 +1,67 @@
package cn.axzo.workflow.common.enums;
/**
* Flowable Event Enum For RocketMQ
*
* @author wangli
* @since 2023/9/4 10:38
*/
public enum BpmnButtonEnum {
/**
* 同意按钮
*/
BPMN_APPROVE(1, "BPMN_APPROVE", "同意"),
/**
* 驳回按钮
*/
BPMN_REJECT(2, "BPMN_REJECT", "驳回"),
/**
* 撤回按钮
*/
BPMN_REVOCATION(3, "BPMN_REVOCATION", "撤回"),
/**
* 转交按钮
*/
BPMN_TRANSFER(4, "BPMN_TRANSFER", "转交"),
/**
* 加签按钮
*/
BPMN_COUNTERSIGN(5, "BPMN_COUNTERSIGN", "加签"),
/**
* 评论按钮
*/
BPMN_COMMENT(6, "BPMN_COMMENT", "评论"),
/**
* 回退按钮
*/
BPMN_ROLLBACK(7, "BPMN_ROLLBACK", "回退"),
/**
* 抄送按钮
*/
BPMN_COPY(8, "BPMN_COPY", "抄送");
public int getOrder() {
return order;
}
public String getBtnKey() {
return btnKey;
}
public String getBtnName() {
return btnName;
}
private final int order;
private final String btnKey;
private final String btnName;
BpmnButtonEnum(int order, String btnKey, String btnName) {
this.order = order;
this.btnKey = btnKey;
this.btnName = btnName;
}
}

View File

@ -0,0 +1,61 @@
package cn.axzo.workflow.common.enums;
import org.apache.commons.lang3.StringUtils;
/**
* 加签类型
*
* @author zuoqinbo
*/
public enum BpmnCountersignType {
/**
* 向前加签
*/
FORWARD_COUNTERSIGN("FORWARD_COUNTERSIGN", "向前加签"),
/**
* 向后加签
*/
BACK_COUNTERSIGN("BACK_COUNTERSIGN", "向后加签");
private String type;
private String desc;
BpmnCountersignType(String type, String desc) {
this.type = type;
this.desc = desc;
}
public boolean isEqual(String type) {
return this.type.equals(type);
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public static BpmnCountersignType isValidAppType(String countersignType) {
if (StringUtils.isBlank(countersignType)) {
return null;
}
BpmnCountersignType[] countersignTypeEnums = BpmnCountersignType.values();
for (BpmnCountersignType countersign : countersignTypeEnums) {
if (countersign.getType().equals(countersignType)) {
return countersign;
}
}
return null;
}
}

View File

@ -2,10 +2,12 @@ package cn.axzo.workflow.common.enums;
public enum BpmnFlowNodeMode {
STARTNODE("STARTNODE", "发起节点"),
GENERAL("GENERAL", "普通"),
OR("OR", "或签"),
AND("AND", "会签");
AND("AND", "会签"),
BUSINESS("BUSINESS", "业务节点"),
COPY("COPY", "抄送节点"),
;
private String type;
private String desc;
@ -14,6 +16,7 @@ public enum BpmnFlowNodeMode {
this.type = type;
this.desc = desc;
}
public boolean isEqual(String type) {
return this.type.equals(type);
}

View File

@ -0,0 +1,56 @@
package cn.axzo.workflow.common.enums;
import java.util.Arrays;
import java.util.Objects;
public enum BpmnFlowNodeType {
//0 发起人 1审批 2抄送 3条件 4路由
NODE_STARTER("NODE_STARTER", "发起人节点"), // ROOT
NODE_EXCLUSIVE_GATEWAY("NODE_EXCLUSIVE_GATEWAY", "排它网关"), // CONDITIONS
NODE_CONDITION("NODE_CONDITION", "条件节点 - 隶属于排它网关"), // CONDITION
NODE_PARALLEL_GATEWAY("NODE_PARALLEL_GATEWAY", "并行网关"),
NODE_PARALLEL("NODE_PARALLEL", "并行节点 - 隶属于并行网关"),
NODE_TASK("NODE_TASK", "审核节点"), // APPROVAL
NODE_BUSINESS("NODE_BUSINESS", "业务节点"),
NODE_CARBON_COPY("NODE_CARBON_COPY", "抄送节点"),
NODE_TRIGGER("NODE_TRIGGER", "触发器节点"),
NODE_EMPTY("NODE_EMPTY", "空节点"),
;
private String type;
private String desc;
BpmnFlowNodeType(String type, String desc) {
this.type = type;
this.desc = desc;
}
public boolean isEqual(String type) {
return this.type.equals(type);
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public static BpmnFlowNodeType valueOfType(String type) {
return Arrays.stream(BpmnFlowNodeType.values())
.filter(i -> Objects.equals(i.getType(), type))
.findAny()
.orElse(null);
}
}

View File

@ -0,0 +1,40 @@
package cn.axzo.workflow.common.enums;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAGE_ID;
/**
* 通知类型枚举
*
* @author wangli
* @since 2023/11/22 11:02
*/
public enum BpmnNoticeEnum {
notice("notice", TEMPLATE_NOTICE_MESSAGE_ID, "通知模板"),
pending("pending", TEMPLATE_PENDING_MESSAGE_ID, "待办模板"),
sms("sms", TEMPLATE_SMS_MESSAGE_ID, "短信模板"),
;
private final String key;
private final String configName;
private final String desc;
BpmnNoticeEnum(String key, String configName, String desc) {
this.key = key;
this.configName = configName;
this.desc = desc;
}
public String getKey() {
return key;
}
public String getConfigName() {
return configName;
}
public String getDesc() {
return desc;
}
}

View File

@ -2,11 +2,13 @@ package cn.axzo.workflow.common.enums;
import java.util.Arrays;
public enum BpmProcessInstanceResultEnum {
public enum BpmnProcessInstanceResultEnum {
PROCESSING("PROCESSING", "审批中"),
APPROVED("APPROVED", "已通过"),
REJECTED("REJECTED", "已驳回"),
CANCELLED("CANCELLED", "已撤销"),
TRANSFER("TRANSFER", "已转交"),
COUNTERSIGN("COUNTERSIGN", "已加签"),
;
/**
* 结果
@ -17,7 +19,7 @@ public enum BpmProcessInstanceResultEnum {
*/
private final String desc;
BpmProcessInstanceResultEnum(String status, String desc) {
BpmnProcessInstanceResultEnum(String status, String desc) {
this.status = status;
this.desc = desc;
}
@ -43,7 +45,7 @@ public enum BpmProcessInstanceResultEnum {
CANCELLED.getStatus()).contains(result);
}
public static BpmProcessInstanceResultEnum valueOfStatus(String status) {
public static BpmnProcessInstanceResultEnum valueOfStatus(String status) {
return Arrays.stream(values()).filter(it -> it.getStatus().equals(status)).findFirst()
.orElse(null);
}

View File

@ -1,21 +1,28 @@
package cn.axzo.workflow.common.enums;
public enum BpmFlowNodeType {
//0 发起人 1审批 2抄送 3条件 4路由
NODE_STARTER("NODE_STARTER", "发起人节点"),
NODE_ROUTER("NODE_ROUTER", "路由节点节点"),
NODE_CONDITION("NODE_CONDITION", "条件节点"),
NODE_TASK("NODE_TASK", "审核节点");
/**
* 加签类型
*
* @author zuoqinbo
*/
public enum BpmnReminderType {
/**
* 短信
*/
TEXT_MESSAGE("TEXT_MESSAGE", "短信"),
/**
* 站内信
*/
INBOX_MESSAGE("INBOX_MESSAGE", "站内信");
private String type;
private String desc;
BpmFlowNodeType(String type, String desc) {
BpmnReminderType(String type, String desc) {
this.type = type;
this.desc = desc;
}
public boolean isEqual(String type) {
return this.type.equals(type);
}

View File

@ -1,33 +1,33 @@
package cn.axzo.workflow.core.common.enums;
package cn.axzo.workflow.common.enums;
import cn.axzo.framework.rocketmq.Event;
/**
* Flowable Event Enum For RocketMQ
* 流程活动节点相关的 MQ 事件枚举定义
*
* @author wangli
* @since 2023/9/4 10:38
* @since 2023/11/21 17:24
*/
public enum FlowableEventModuleEnum {
PROCESS_CREATED("process", "process-created", "流程创建"),
public enum ProcessActivityEventEnum {
PROCESS_ACTIVITY_STARTED("process-activity", "process-activity-started", "流程活动节点已开始"),
PROCESS_ACTIVITY_COMPLETED("process-activity", "process-activity-completed", "流程活动节点已完成"),
PROCESS_ACTIVITY_CANCELLED("process-activity", "process-activity-cancelled", "流程活动节点已取消"),
;
private final String module;
private final String tag;
private final String desc;
private Event.EventCode eventCode;
private final Event.EventCode eventCode;
FlowableEventModuleEnum(String module, String tag, String desc) {
this.module = module;
this.tag = tag;
this.desc = desc;
ProcessActivityEventEnum(String module, String tag, String desc) {
this.eventCode = Event.EventCode.builder()
.module(module)
.name(tag)
.build();
this.module = module;
this.tag = tag;
this.desc = desc;
}
public String getModule() {

View File

@ -13,7 +13,7 @@ public enum ProcessInstanceEventEnum {
PROCESS_INSTANCE_CREATED("process-instance", "process-instance-created", "流程实例已创建"),
PROCESS_INSTANCE_STARTED("process-instance", "process-instance-started", "流程实例已开始"),
PROCESS_INSTANCE_CANCELLED("process-instance", "process-instance-cancelled", "流程实例已取消"),
PROCESS_INSTANCE_REJECTED("process-instance", "process-instance-rejected", "流程实例已拒绝"),
PROCESS_INSTANCE_REJECTED("process-instance", "process-instance-rejected", "流程实例已驳回"),
PROCESS_INSTANCE_COMPLETED("process-instance", "process-instance-completed", "流程实例已结束"),
;
private final String module;

View File

@ -0,0 +1,50 @@
package cn.axzo.workflow.common.enums;
import cn.axzo.framework.rocketmq.Event;
/**
* 流程消息推送相关的 MQ 事件枚举定义
*
* @author wangli
* @since 2023/11/22 13:58
*/
public enum ProcessMessagePushEventEnum {
PROCESS_PUSH_NOTICE("process-push", "process-push-notice", "站内信推送"),
PROCESS_PUSH_PENDING("process-push", "process-push-pending", "待办推送"),
PROCESS_PUSH_PENDING_COMPLETE("process-push", "process-push-pending-complete", "完成待办"),
PROCESS_PUSH_SMS("process-push", "process-push-sms", "短信推送"),
;
private final String module;
private final String tag;
private final String desc;
private Event.EventCode eventCode;
ProcessMessagePushEventEnum(String model, String tag, String desc) {
this.eventCode = Event.EventCode.builder()
.module(model)
.name(tag)
.build();
this.module = model;
this.tag = tag;
this.desc = desc;
}
public String getModule() {
return module;
}
public String getTag() {
return tag;
}
public String getDesc() {
return desc;
}
public Event.EventCode getEventCode() {
return eventCode;
}
}

View File

@ -0,0 +1,31 @@
package cn.axzo.workflow.common.enums;
import java.util.Arrays;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author syl
* @date 2023/11/21
*/
@Getter
@AllArgsConstructor
public enum WorkspaceType {
/**
* 工作台类型
* 1- 企业 2-项目 6-oms
*/
ENT(1, "企业"),
WORKSPACE(2, "项目"),
OMS(6, "oms工作台");
private Integer code;
private String desc;
public static WorkspaceType getType(Integer code) {
return Arrays.stream(values()).filter(it -> it.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@ -0,0 +1,52 @@
package cn.axzo.workflow.common.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 组织架构人员选择器
* 协同组织列表的限制条件
*
* @author tanjie@axzo.cn
* @date 2023/11/14 11:29
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class CooperationOrgDTO implements Serializable {
/**
* 企业组织架构范围
**/
private List<OrgScope> orgScopes;
/**
* 班组组织架构范围
**/
private List<OrgScope> workerTeamScopes;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class OrgScope implements Serializable {
/**
* 工作台类型
*/
private Integer workspaceType;
/**
* 工作台ID
*/
private Long workspaceId;
/**
* 单位 ID/班组单位 ID
*/
private Long ouId;
}
}

View File

@ -12,35 +12,14 @@ import java.io.Serializable;
public class BpmPageParam implements Serializable {
private static final Integer PAGE_NO = 1;
private static final Integer PAGE_SIZE = 10;
@ApiModelProperty(
value = "页码,从 1 开始",
required = true,
example = "1"
)
@NotNull(
message = "页码不能为空"
)
@Min(
value = 1L,
message = "页码最小值为 1"
)
@ApiModelProperty(value = "页码,从 1 开始", required = true, example = "1")
@NotNull(message = "页码不能为空")
@Min(value = 1L, message = "页码最小值为 1")
private Integer pageNo;
@ApiModelProperty(
value = "每页条数,最大值为 100",
required = true,
example = "10"
)
@NotNull(
message = "每页条数不能为空"
)
@Min(
value = 1L,
message = "页码最小值为 1"
)
@Max(
value = 100L,
message = "页码最大值为 100"
)
@ApiModelProperty(value = "每页条数,最大值为 9999", required = true, example = "10")
@NotNull(message = "每页条数不能为空")
@Min(value = 1L, message = "页码最小值为 1")
@Max(value = 9999L, message = "页码最大值为 9999")
private Integer pageSize;
public BpmPageParam() {

View File

@ -0,0 +1,81 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 流程定义中的按钮配置
*
* @author wangli
* @since 2023/11/13 13:44
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的按钮管理")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnButtonConf implements Serializable {
/**
* 发起人的按钮配置信息, 需要给全量按钮的配置
*/
@ApiModelProperty(value = "发起人的按钮配置信息")
private List<BpmnButtonMetaInfo> initiator;
/**
* 当前审批人的按钮配置信息, JSON 格式
*/
@ApiModelProperty(value = "当前审批人的按钮配置信息")
private List<BpmnButtonMetaInfo> current;
/**
* 历史审批人的按钮配置信息, JSON 格式
*/
@ApiModelProperty(value = "历史审批人的按钮配置信息")
private List<BpmnButtonMetaInfo> history;
/**
* 抄送人的按钮配置信息, JSON 格式
*/
@ApiModelProperty(value = "抄送人的按钮配置信息")
private List<BpmnButtonMetaInfo> carbonCopy;
public List<BpmnButtonMetaInfo> getInitiator() {
return initiator;
}
public void setInitiator(List<BpmnButtonMetaInfo> initiator) {
this.initiator = initiator;
}
public List<BpmnButtonMetaInfo> getCurrent() {
return current;
}
public void setCurrent(List<BpmnButtonMetaInfo> current) {
this.current = current;
}
public List<BpmnButtonMetaInfo> getHistory() {
return history;
}
public void setHistory(List<BpmnButtonMetaInfo> history) {
this.history = history;
}
public List<BpmnButtonMetaInfo> getCarbonCopy() {
return carbonCopy;
}
public void setCarbonCopy(List<BpmnButtonMetaInfo> carbonCopy) {
this.carbonCopy = carbonCopy;
}
}

View File

@ -0,0 +1,34 @@
package cn.axzo.workflow.common.model.request.bpmn;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 按钮元数据
*
* @author wangli
* @since 2023/11/14 23:53
*/
@Data
@Accessors(chain = true)
public class BpmnButtonMetaInfo implements Serializable {
private Integer order;
private String btnKey;
private String btnName;
/**
* 复选框的值
*/
private Boolean checked;
/**
* 是否禁用勾选
*/
private Boolean disabled;
}

View File

@ -0,0 +1,172 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 条件
*
* @author wangli
* @since 2023/11/13 20:33
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的条件")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnCondition {
/**
* 条件的中文名称, 前端使用的参数
*/
@ApiModelProperty(value = "条件的中文名称")
private String name;
/**
* string(字符串)/number(数字)/radio(单选)/checkbox(复选)
*/
@ApiModelProperty(value = "基础属性:字段类型", notes = "string, number, radio, checkbox", example = "string")
private String type;
/**
* 字段 code
*/
@ApiModelProperty(value = "基础属性:字段 code", notes = "字段管理中字段的 Code", example = "fieldCode")
private String code;
/**
* 操作符(与字段类型有关)
* <p>
* 当字段类型为 string , 操作符为 contains, notContains <br/>
* 当字段类型为 number , 操作符为 eq, ne, gt, gte, lt, lte, between <br/>
* 当字段类型为 radio , 操作符为 eq <br/>
* 当字段类型为 checkbox , 操作符为 in <br/>
*/
@ApiModelProperty(value = "基础属性:操作符", notes = "操作符", example = "eq")
private String operator;
/**
* 默认的比较值
*/
@ApiModelProperty(value = "基础属性:默认的比较值", notes = "同时也用于 fieldDateType = radio", example = "1")
private String defaultValue;
/**
* 只有 fieldDateType = checkbox 才有值
*/
@ApiModelProperty(value = "当 fieldDateType = checkbox时, 选中的值")
private List<String> defaultValues;
/**
* 只有 operator = between 才有值 lt, lte
*/
@ApiModelProperty(value = "当 fieldDateType = number 并且 operator = between 时, 左侧操作符")
private String leftOperator;
/**
* 只有 operator = between 才有值 lt, lte,
*/
@ApiModelProperty(value = "当 fieldDateType = number 并且 operator = between 时, 右侧操作符")
private String rightOperator;
/**
* 只有 operator = between 才有值
*/
@ApiModelProperty(value = "当 fieldDateType = number 并且 operator = between 时, 左侧比较值")
private String leftValue;
/**
* 只有 operator = between 才有值
*/
@ApiModelProperty(value = "当 fieldDateType = number 并且 operator = between 时, 右侧比较值")
private String rightValue;
/**
* 记录前端的下拉选项, 后端无任何逻辑
*/
@ApiModelProperty(value = "记录前端的下拉选项, 后端无任何逻辑")
private List<BpmnFieldOptionConf> options;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public List<String> getDefaultValues() {
return defaultValues;
}
public void setDefaultValues(List<String> defaultValues) {
this.defaultValues = defaultValues;
}
public String getLeftOperator() {
return leftOperator;
}
public void setLeftOperator(String leftOperator) {
this.leftOperator = leftOperator;
}
public String getRightOperator() {
return rightOperator;
}
public void setRightOperator(String rightOperator) {
this.rightOperator = rightOperator;
}
public String getLeftValue() {
return leftValue;
}
public void setLeftValue(String leftValue) {
this.leftValue = leftValue;
}
public String getRightValue() {
return rightValue;
}
public void setRightValue(String rightValue) {
this.rightValue = rightValue;
}
}

View File

@ -0,0 +1,48 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 条件组
* <p>
* 目前条件中的所有项都是"并且",所以没有第二个属性,如果以后有"或者"的情况,再加
*
* @author wangli
* @since 2023/11/13 19:58
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的条件组")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnConditionGroup {
@ApiModelProperty(value = "条件组")
private List<BpmnCondition> conditions;
/**
* 同条件组内的多个条件之间的模式,默认为"and"
*/
private String conditionsType = "and";
public List<BpmnCondition> getConditions() {
return conditions;
}
public void setConditions(List<BpmnCondition> conditions) {
this.conditions = conditions;
}
public String getConditionsType() {
return conditionsType;
}
public void setConditionsType(String conditionsType) {
this.conditionsType = conditionsType;
}
}

View File

@ -0,0 +1,52 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.List;
/**
* 流程定义中的字段管理
*
* @author wangli
* @since 2023/11/13 13:53
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的字段管理")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnFieldConf implements Serializable {
/**
* 字段的 code
*/
@ApiModelProperty(value = "字段的 code", example = "fieldCode", notes = "字段的 code 必须唯一")
@NotBlank(message = "字段的 code 不能为空")
private String code;
/**
* 字段的名称
*/
@ApiModelProperty(value = "字段的名称", example = "字段名称")
@NotBlank(message = "字段的名称不能为空")
private String name;
/**
* 字段的类型 string, number, radio, checkbox
*/
@ApiModelProperty(value = "字段的类型", example = "string", notes = "string, number, radio, checkbox")
@NotBlank(message = "字段的类型不能为空")
private String type;
/**
* 单选或多选的下拉选择框中的数据, 只有单选或多选的时候才会有值,并且内部的属性不应该为空
*/
@ApiModelProperty(value = "单选或多选的下拉选择框中的数据")
private List<BpmnFieldOptionConf> options;
}

View File

@ -0,0 +1,47 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 单选/多选的选项配置
*/
@Accessors(chain = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BpmnFieldOptionConf implements Serializable {
/**
* 选项的名称
*/
@ApiModelProperty(value = "选项的名称", example = "选项1")
private String name;
/**
* 选项的值
*/
@ApiModelProperty(value = "选项的值", example = "1")
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,49 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.Valid;
import java.io.Serializable;
import java.util.List;
/**
* 新版本的流程定义模型 JSON 结构
*
* @author wangli
* @since 2023/11/18 16:57
*/
@ApiModel("新版本的流程定义模型 JSON 结构")
@Data
@Accessors(chain = true)
public class BpmnJsonModel implements Serializable {
/**
* 流程的Json 结构
*/
@ApiModelProperty(value = "流程的 Json 结构")
private BpmnJsonNode node;
/**
* 通知管理配置
*/
@ApiModelProperty(value = "通知管理配置")
@Valid
private BpmnNoticeConf noticeConf;
/**
* 流程定义的全局默认按钮权限数据
*/
@ApiModelProperty(value = "流程按钮配置")
@Valid
private BpmnButtonConf buttonConf;
/**
* 流程定义的全局字段管理数据
*/
@ApiModelProperty(value = "流程字段配置")
@Valid
private List<BpmnFieldConf> fieldConf;
}

View File

@ -1,15 +1,17 @@
package cn.axzo.workflow.common.model.request.bpmn;
import cn.axzo.workflow.common.enums.BpmFlowNodeType;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.HashMap;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* JSON 版本的 BPMN 协议模型
@ -19,25 +21,52 @@ import java.util.Map;
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnJsonNode {
public class BpmnJsonNode implements Serializable {
/**
* 节点唯一标识
*/
@ApiModelProperty(value = "节点ID")
@NotBlank(message = "节点的 ID 不能为空")
private String id;
/**
* 上级节点唯一标识
*/
@ApiModelProperty(value = "父节点ID")
private String parentId;
@ApiModelProperty(value = "节点类型 NODE_STARTER/NODE_TASK/NODE_ROUTER/NODE_CONDITION")
private BpmFlowNodeType type;
/**
* 节点类型 NODE_STARTER(发起人) NODE_TASK(审核节点) NODE_BUSINESS(业务节点) NODE_CARBON_COPY(抄送节点) NODE_EMPTY(空节点)
* NODE_EXCLUSIVE_GATEWAY(排它网关) NODE_CONDITION(分支)
*/
@ApiModelProperty(value = "节点类型")
@NotNull(message = "节点类型不能为空")
private BpmnFlowNodeType type;
/**
* 节点名称
*/
@ApiModelProperty(value = "节点名称")
private String name;
/**
* 子节点信息(一般子节点的类型为审批节点/业务节点/抄送节点; 条件分支用 branches 字段)
*/
@ApiModelProperty(value = "子节点信息")
private BpmnJsonNode children;
/**
* 分支节点信息(只用存储条件分支的信息, 而不能将审批节点/业务节点/抄送节点放到这里)
*/
@ApiModelProperty(value = "分支节点信息")
private List<BpmnJsonNode> branches;
/**
* 节点扩展属性, 比如审批方式/审批人指定等针对节点的配置信息
*/
@ApiModelProperty(value = "节点扩展属性")
private BpmnJsonNodeProperty property;
/* 内部使用,不需要外界传 */
private transient Map incoming = new HashMap();
private transient List<String> incoming = new ArrayList<>();
/* 内部使用, 用于 JSON 格式转换 */
private transient BpmnJsonNode preJsonNode;
public String getId() {
return id;
@ -55,11 +84,11 @@ public class BpmnJsonNode {
this.parentId = parentId;
}
public BpmFlowNodeType getType() {
public BpmnFlowNodeType getType() {
return type;
}
public void setType(BpmFlowNodeType type) {
public void setType(BpmnFlowNodeType type) {
this.type = type;
}
@ -95,11 +124,19 @@ public class BpmnJsonNode {
this.property = property;
}
public Map getIncoming() {
public List<String> getIncoming() {
return incoming;
}
public void setIncoming(Map incoming) {
public void setIncoming(List<String> incoming) {
this.incoming = incoming;
}
public BpmnJsonNode getPreJsonNode() {
return preJsonNode;
}
public void setPreJsonNode(BpmnJsonNode preJsonNode) {
this.preJsonNode = preJsonNode;
}
}

View File

@ -1,10 +1,18 @@
package cn.axzo.workflow.common.model.request.bpmn;
import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* JSON 版本的 BPMN 协议中 UserTask 节点的属性扩展模型
*/
@ -12,41 +20,110 @@ import lombok.Data;
@Data
public class BpmnJsonNodeProperty {
//************* 审批方式Start **************//
/**
* 是否是多实例Task节点
* 审批方式: human(人工审批), autoPassed(自动通过), autoRejection(自动驳回), nobody(不审批[仅业务节点可能有该值])
*/
@ApiModelProperty("是否是多实例Task节点")
private Boolean isMultiTask;
@ApiModelProperty(value = "任务节点: 审批方式", notes = "human: 人工审批, autoPassed: 自动通过, autoRejection: 自动驳回, nobody: " +
"不审批[仅业务节点可能有该值]")
@NotBlank(message = "审批方式不能为空")
private ApprovalMethodEnum approvalMethod;
//************* 审批方式End **************//
//************* 审批人所在范围Start **************//
/**
* 审批人所在范围: entWorkspace(企业工作台), projectWorkspace(项目工作台), preTaskUser(上节点审批人所在单位)
*/
@ApiModelProperty(value = "任务节点: 审批人所在范围", notes = "entWorkspace: 企业工作台, projectWorkspace: 项目工作台, preTaskUser:" +
" 上节点审批人所在单位")
private ApproverScopeEnum approverScope;
//************* 审批人所在范围End **************//
//************* 审批人指定Start **************//
/**
* 审批人指定: position(指定岗位), role(指定角色), identity(指定身份), initiatorLeader(发起人主管), initiatorLeaderRecursion(发起人多级主管),
* fixedPerson(固定人员)
*/
@ApiModelProperty(value = "任务节点: 审批人指定", notes = "position: 指定岗位, role: 指定角色, identity: 指定身份, initiatorLeader: " +
"发起人主管, initiatorLeaderRecursion: 发起人多级主管, fixedPerson: 固定人员")
@NotBlank(message = "审批人指定不能为空")
private ApproverSpecifyEnum approverSpecify;
/**
* 具体的配置值
* <p>
* 如果是固定人员那么这里是人员集合
* [{"assignee":"111","assigneeType": "", "assignerName":"wangli", "personId": "", "tenantId":"296", "ouId":""}]
*/
@ApiModelProperty(value = "任务节点: 审批人指定的具体值")
private String specifyValue;
//************* 审批人指定End **************//
//************* 多人审批时审批方式Start **************//
/**
* 是否是多实例Task节点, 在安心筑业务中, 所有节点应该都是多实例节点
*/
@ApiModelProperty(value = "任务节点: 是否是多实例Task节点", notes = "在安心筑业务中, 所有节点应该都是多实例节点")
private Boolean isMultiTask = true;
/**
* Task 多实例模式OR"或签"AND"会签"
*/
@ApiModelProperty(value = "Task 多实例模式OR或签AND会签")
@ApiModelProperty(value = "任务节点: 多实例模式OR或签AND会签")
@NotNull(message = "多实例模式不能为空")
private BpmnFlowNodeMode multiMode;
//************* 多人审批时审批方式End **************//
//************* 审批人为空时Start **************//
/**
* 审批人为空处理方式 autoPassed: 自动通过, autoRejection: 自动驳回, transferToAdmin: 转交给管理员
*/
@ApiModelProperty(value = "任务节点: 审批人为空处理方式", notes = "autoPassed: 自动通过, autoRejection: 自动驳回, transferToAdmin: 转交给管理员")
@NotBlank(message = "审批人为空处理方式不能为空")
private ApproverEmptyHandleTypeEnum approverEmptyHandleType;
@ApiModelProperty(value = "审批人为空时,指定的人", notes = "list 的 String 格式, String 是 BpmnTaskDelegateAssigner 对象")
private String emptyApproverSpecify;
//************* 审批人为空时的策略End **************//
/**
* 审批人为空是否允许自动跳过
* 表单字段权限, JSON 格式,按照 UI 进行自定义组装, 引擎不做任何解析, 之后的需求会让业务方前端开发来消费这里的配置
*/
@ApiModelProperty(value = "审批人为空是否允许自动跳过")
private Boolean allowSkip = false;
@ApiModelProperty(value = "发起人节点/任务节点: 字段权限集合", notes = "后端不做任何解析, 前端给什么样,就返什么样")
private String fieldPermission;
/**
* 条件节点中是否是默认分支可以都不传;
* 按钮权限
*/
@ApiModelProperty(value = "条件节点中是否是默认分支,可以都不传")
private Boolean defaultCondition;
@ApiModelProperty(value = "发起人节点/任务节点: 按钮权限集合")
private BpmnButtonConf buttonPermission;
//************* 条件节点Start **************//
/**
* 条件节点专属属性: 是否是默认分支;
*/
@ApiModelProperty(value = "条件节点: 是否是默认条件分支")
@NotNull(message = "是否是默认条件分支不能为空")
private Boolean defaultBranch;
/**
* 条件分支的Key
* 条件节点专属属性: 条件分组集合
*/
@ApiModelProperty(value = "条件分支的Key")
private String conditionBranchKey;
@ApiModelProperty(value = "条件节点: 条件分组集合")
private List<BpmnConditionGroup> groups;
/**
* 条件节点当ConditionKey的值是多少走该分支
* 条件节点专属属性: 条件组之间的计算模型,目前仅支持 or
*/
@ApiModelProperty(value = "条件节点当ConditionKey的值是多少走该分支")
private Integer conditionBranchValue;
@ApiModelProperty(value = "条件组之间的计算模型,目前仅支持 or")
private String groupsType = "or";
//************* 条件节点End **************//
/**
* 发起时使用的表单 key

View File

@ -0,0 +1,66 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 流程定义中的通知配置
*
* @author wangli
* @since 2023/11/13 13:38
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的通知管理")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnNoticeConf implements Serializable {
/**
* 通知的配置
*/
@Valid
// @NotNull(message = "消息模板不能为空")
private BpmnNoticeProperty notice;
/**
* 待办的配置
*/
@Valid
@NotNull(message = "待办模板不能为空")
private BpmnPendingProperty pending;
/**
* 短信的配置
*/
private BpmnSmsProperty sms;
public BpmnNoticeProperty getNotice() {
return notice;
}
public void setNotice(BpmnNoticeProperty notice) {
this.notice = notice;
}
public BpmnPendingProperty getPending() {
return pending;
}
public void setPending(BpmnPendingProperty pending) {
this.pending = pending;
}
public BpmnSmsProperty getSms() {
return sms;
}
public void setSms(BpmnSmsProperty sms) {
this.sms = sms;
}
}

View File

@ -0,0 +1,35 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 站内信
*
* @author wangli
* @since 2023/12/2 18:36
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的通知管理的站内信")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnNoticeProperty implements Serializable {
/**
* 通知消息模板 ID
*/
@ApiModelProperty(value = "通知消息模板 ID")
// @NotBlank(message = "通知消息模板 ID 不能为空")
private String noticeMessageId;
/**
* 用于前端回显数据, 服务端不解析
*/
@ApiModelProperty(value = "用于前端回显数据, 服务端不解析")
private String viewJson;
}

View File

@ -0,0 +1,34 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
/**
* 待办
*
* @author wangli
* @since 2023/12/2 18:36
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的通知管理的待办")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnPendingProperty {
/**
* 待办消息模板 ID
*/
@ApiModelProperty(value = "待办消息模板 ID")
@NotBlank(message = "待办消息模板 ID 不能为空")
private String pendingMessageId;
/**
* 用于前端回显数据, 服务端不解析
*/
@ApiModelProperty(value = "用于前端回显数据, 服务端不解析")
private String viewJson;
}

View File

@ -0,0 +1,32 @@
package cn.axzo.workflow.common.model.request.bpmn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 短信
*
* @author wangli
* @since 2023/12/2 18:36
*/
@ApiModel("JSON 版本的 BPMN 协议模型中的通知管理的短信")
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class BpmnSmsProperty {
/**
* 短信模板 ID
*/
@ApiModelProperty(value = "短信模板 ID")
private String smsId;
/**
* 用于前端回显数据, 服务端不解析
*/
@ApiModelProperty(value = "用于前端回显数据, 服务端不解析")
private String viewJson;
}

View File

@ -0,0 +1,37 @@
package cn.axzo.workflow.common.model.request.bpmn.basic;
import cn.axzo.workflow.common.model.request.BpmPageParam;
import lombok.Data;
import java.util.List;
/**
* yoke
*
* @author zuoqinbo
* @version V1.0
* @date 2023/11/15 17:20
*/
@Data
public class ProcessBasicInfoQuery extends BpmPageParam {
/**
* 流程名称
* 模糊查询
*/
private String processName;
/**
* 流程所属业务ID列表
*/
private List<String> businessIdList;
/**
* 流程状态 1开启 0关闭
*
*/
private Integer status;
}

View File

@ -0,0 +1,75 @@
package cn.axzo.workflow.common.model.request.bpmn.basic;
import lombok.Data;
import java.util.Date;
/**
* yoke
*
* @author zuoqinbo
* @version V1.0
* @date 2023/11/15 17:20
*/
@Data
public class ProcessBasicInfoResp {
/**
* 流程ID
* 模糊查询
*/
private String processModelId;
/**
* 流程名称
* 模糊查询
*/
private String processName;
/**
* 流程所属业务
* 模糊查询
*/
private String ownBusiness;
/**
* 流程所属业务ID列表
*/
private String ownBusinessId;
/**
* 流程工作台ID
*/
private String workBenchId;
/**
* 流程工作台名称
*/
private String workBenchName;
/**
* 流程状态 1开启 0关闭
*
*/
private Integer status;
/**
* 当前版本号
*/
private String version;
/**
* 最后更新时间
*/
private Date updateTime;
}

View File

@ -1,6 +1,6 @@
package cn.axzo.workflow.common.model.request.bpmn.definition;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -20,8 +20,20 @@ public class BpmnProcessDefinitionUpdateDTO {
@ApiModelProperty(value = "流程模型 ID")
private String processModelId;
@ApiModelProperty(value = "KEY")
private String key;
@ApiModelProperty(value = "分类")
private String category;
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "描述")
private String description;
@ApiModelProperty(value = "JSON 转义的流程定义内容", notes = "枢智业务线在用")
@Valid
@NotNull(message = "模型定义内容不能为空")
private BpmnJsonNode bpmJson;
private BpmnJsonModel jsonModel;
}

View File

@ -1,13 +1,15 @@
package cn.axzo.workflow.common.model.request.bpmn.model;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* 创建流程模型的入参模型
@ -15,11 +17,10 @@ import javax.validation.constraints.NotBlank;
@ApiModel("创建流程模型的入参模型")
@Data
@Accessors(chain = true)
public class BpmnModelCreateDTO {
public class BpmnModelCreateDTO implements Serializable {
@ApiModelProperty(value = "流程模型标识", example = "process_key", hidden = true)
@Length(max = 255, message = "流程标识最长只支持255个字符")
@NotBlank(message = "流程模型表示不能为空")
private String key;
/**
@ -33,7 +34,8 @@ public class BpmnModelCreateDTO {
/**
* 自定义分类
*/
@ApiModelProperty(value = "自定义分类", notes = "由业务自定义", example = "1")
@ApiModelProperty(value = "自定义分类", notes = "由业务自定义")
@NotBlank(message = "自定义分类不能为空")
private String category;
/**
@ -42,16 +44,13 @@ public class BpmnModelCreateDTO {
@ApiModelProperty(value = "描述", notes = "存放与底层模型中 meta_info 中")
private String description;
/**
* 流程的Json 结构
*/
@ApiModelProperty(value = "流程的Json 结构", example = "1")
private BpmnJsonNode node;
@ApiModelProperty(value = "流程模型的 Json 结构")
@Valid
private BpmnJsonModel jsonModel;
/**
* 租户Id
*/
@ApiModelProperty(value = "租户Id", example = "1")
@NotBlank(message = "租户 ID 不能为空")
private String tenantId;
private String tenantId = "";
}

View File

@ -6,6 +6,8 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 流程模型搜索入参模型
*/
@ -29,8 +31,8 @@ public class BpmnModelSearchDTO extends BpmPageParam {
/**
* 流程分类
*/
@ApiModelProperty(value = "流程模型分类", example = "1")
private String category;
@ApiModelProperty(value = "流程模型分类集合", example = "1")
private List<String> categories;
/**
* 模型状态
@ -39,9 +41,15 @@ public class BpmnModelSearchDTO extends BpmPageParam {
private Integer status;
/**
* 工作台ID
* 工作台ID集合
*/
@ApiModelProperty(value = "租户 ID", example = "1")
private String tenantId;
@ApiModelProperty(value = "租户 ID 集合", example = "1")
private List<String> tenantIds;
/**
* 业务需求的参数,是否代运营
*/
@ApiModelProperty(value = "是否为代运营")
private Boolean agent;
}

View File

@ -21,6 +21,9 @@ public class BpmnProcessDefinitionPageDTO extends BpmPageParam {
@NotBlank(message = "流程标识不能为空")
private String key;
/**
* 如果是配置台(公共模型)则不传或传空字符串
*/
@ApiModelProperty(value = "租户Id", example = "1")
private String tenantId;
private String tenantId = "";
}

View File

@ -1,11 +1,14 @@
package cn.axzo.workflow.common.model.request.bpmn.process;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
@ -22,15 +25,32 @@ public class BpmnProcessInstanceCreateDTO {
private String processDefinitionId;
/**
* 流程定义的标识
* <p>
* [对应业务分类的 businessId]
*/
@NotEmpty(message = "流程定义的标识不能为空")
private String processDefinitionKey;
/**
* 模型归属租户 ID
* <p>
* 为空时,默认是编辑公共流程模型, 如果是代运营创建,则必填
*/
@ApiModelProperty(value = "发起的模型是属于哪个租户")
private String tenantId = "";
/**
* 流程实例关联的变量
*/
private Map<String, Object> variables = new HashMap<>();
/**
* 组织关系
*/
@ApiModelProperty(value = "组织关系")
@NotNull(message = "组织关系不能为空")
private CooperationOrgDTO cooperationOrg;
/**
* 业务的唯一标识
* <p>
@ -40,6 +60,8 @@ public class BpmnProcessInstanceCreateDTO {
private String businessKey;
@ApiModelProperty(value = "发起人信息")
@Valid
@NotNull(message = "发起人不能为空")
private BpmnTaskDelegateAssigner initiator;
/**
@ -52,5 +74,7 @@ public class BpmnProcessInstanceCreateDTO {
* 下级审批人
*/
@ApiModelProperty(value = "下级审批人", notes = "可为空,定义选择审批人,如果不为空,则覆盖下一级任务的审核人")
@Valid
private BpmnTaskDelegateAssigner nextApprover;
}

View File

@ -1,6 +1,6 @@
package cn.axzo.workflow.common.model.request.bpmn.process;
import cn.axzo.workflow.common.enums.BpmProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.request.BpmPageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -30,8 +30,8 @@ public class BpmnProcessInstanceMyPageReqVO extends BpmPageParam {
@ApiModelProperty(value = "业务KEY")
private String businessKey;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已拒绝,CANCELLED:已取消)", example = "APPROVED")
private BpmProcessInstanceResultEnum result;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已驳回,CANCELLED:已取消)", example = "APPROVED")
private BpmnProcessInstanceResultEnum result;
@ApiModelProperty(value = "自定义分类")
private String category;

View File

@ -0,0 +1,50 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import cn.axzo.workflow.common.enums.AttachmentTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 附件模型
*
* @author wangli
* @since 2023/11/22 23:17
*/
@ApiModel("附件模型")
@Data
public class AttachmentDTO {
private String id;
/**
* 附件类型, 图片还是附件
*/
@ApiModelProperty(value = "附件类型")
@NotNull(message = "附件类型不能为空")
private AttachmentTypeEnum type;
/**
* 文件名称
*/
@ApiModelProperty(value = "文件名称不能为空")
@NotBlank(message = "文件名称不能为空")
private String name;
/**
* 文件描述
*/
@ApiModelProperty(value = "文件描述")
private String description;
/**
* 文件地址
*/
@ApiModelProperty(value = "附件地址")
@NotBlank(message = "附件地址不能为空")
private String url;
}

View File

@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
* 转交审批任务的入参模型
@ -20,10 +21,19 @@ public class BpmnTaskAssigneeDTO {
@NotBlank(message = "任务 ID 不能为空")
private String taskId;
@ApiModelProperty(value = "转交意见")
private String advice;
/**
* 附件列表
*/
@ApiModelProperty(value = "附件列表")
private List<AttachmentDTO> attachmentList;
@ApiModelProperty(value = "审批任务原审批人", notes = "可以为空,意义在于可以将没有指派给任何人的任务转交给其他人")
private BpmnTaskDelegateAssigner originAssigner;
@ApiModelProperty(value = "审批任务转发给谁", notes = "可以为空,意义在于如果分配给某离职的人,可能需要置为空")
private BpmnTaskDelegateAssigner targetAssigner;
}

View File

@ -0,0 +1,45 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 对审批任务添加附件的入参模型
*
* @author wangli
* @since 2023/10/31 10:08
*/
@ApiModel("对审批任务添加附件的入参模型")
@Data
public class BpmnTaskAttachmentDTO {
@ApiModelProperty(value = "附件类型", notes = "一般是指格式, 如 png/jpeg/jpg/pdf 等, 强烈建议前端回传")
@NotBlank(message = "附件类型不能为空")
private String type;
@ApiModelProperty(value = "关联的审批流程实例 ID")
@NotBlank(message = "流程实例 ID 不能为空")
private String processInstanceId;
@ApiModelProperty(value = "关联的审批任务 ID")
@NotBlank(message = "任务 ID 不能为空")
private String taskId;
@ApiModelProperty(value = "附件名称", notes = "如果为空,则使用文件名")
private String name;
@ApiModelProperty(value = "附件描述", notes = "现在的业务不会使用到")
private String description;
@ApiModelProperty(value = "附件地址")
@NotBlank(message = "附件地址不能为空")
private String url;
/**
* 该字段引擎本来支撑,但不建议给前端使用
*/
// private InputStream content;
}

View File

@ -1,12 +1,14 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import cn.axzo.workflow.common.valid.group.ValidGroup;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 审批任务节点的入参模型
@ -14,10 +16,11 @@ import javax.validation.constraints.NotNull;
*/
@ApiModel("审批任务节点的入参模型")
@Data
@Validated
public class BpmnTaskAuditDTO {
@ApiModelProperty(value = "任务编号", required = true, example = "1024")
@NotEmpty(message = "任务编号不能为空", groups = {ValidGroup.Insert.class, ValidGroup.Update.class})
@NotEmpty(message = "任务编号不能为空")
private String taskId;
/**
@ -26,15 +29,24 @@ public class BpmnTaskAuditDTO {
@ApiModelProperty(value = "审批意见")
private String advice;
/**
* 附件列表
*/
@ApiModelProperty(value = "附件列表")
private List<AttachmentDTO> attachmentList;
/**
* 当前审核人信息
*/
@NotNull(message = "审批人信息不能为空", groups = {ValidGroup.Insert.class, ValidGroup.Update.class})
@ApiModelProperty(value = "当前审核人信息", notes = "可为空,则该任务不验证用户归属")
@Valid
@NotNull(message = "审批人不能为空")
private BpmnTaskDelegateAssigner approver;
/**
* 下级审批人
*/
@ApiModelProperty(value = "下级审批人信息", notes = "可为空,定义选择审批人,如果不为空,则覆盖下一级任务的审核人")
@Valid
private BpmnTaskDelegateAssigner nextApprover;
}

View File

@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 对审批任务添加评论的入参模型
@ -24,9 +25,9 @@ public class BpmnTaskCommentDTO {
@NotBlank(message = "流程实例 ID 不能为空")
private String processInstanceId;
@ApiModelProperty(value = "租户 ID")
@NotBlank(message = "tenantId")
private String tenantId;
@ApiModelProperty(value = "操作人")
@NotNull
private BpmnTaskDelegateAssigner operator;
@ApiModelProperty(value = "评论内容")
private String comment;

View File

@ -0,0 +1,59 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* 统一的工作流中的人员模型,兼容不同的业务方,如安心筑"唯一"人的定义需要工作台+身份 ID+身份类型;而枢智业务则只需要简单的 userId 即可
* <p>
* 为了解决各种业务的差异,统一定义该模型,已大范围覆盖小范围的方式进行兼容.
*
* @author zuoqinbo
*/
@Data
@Accessors(chain = true)
public class BpmnTaskCountersignDTO implements Serializable {
private static final long serialVersionUID = -8106887960942113552L;
@ApiModelProperty(value = "审批任务ID")
@NotEmpty(message = "任务ID不能为空")
private String taskId;
/**
* 加签类型
* 向前加签向后加签
*
* @see cn.axzo.workflow.common.enums.BpmnCountersignType
*/
@ApiModelProperty(value = "加签类型")
@NotEmpty(message = "加签类型不能为空")
private String countersignType = "FORWARD_COUNTERSIGN";
@ApiModelProperty(value = "加签意见")
private String advice;
/**
* 附件列表
*/
@ApiModelProperty(value = "附件列表")
private List<AttachmentDTO> attachmentList;
@ApiModelProperty(value = "加签任务转发给谁")
@NotNull(message = "加签任务接收人不能为空")
private List<BpmnTaskDelegateAssigner> targetAssignerList;
@ApiModelProperty(value = "加签任务原发起人")
@NotNull(message = "加签任务发起人不能为空")
private BpmnTaskDelegateAssigner originAssigner;
}

View File

@ -1,22 +1,31 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* 统一的工作流中的人员模型,兼容不同的业务方,如安心筑"唯一"人的定义需要工作台+身份 ID+身份类型;而枢智业务则只需要简单的 userId 即可
* 统一的工作流中的人员模型,兼容不同的业务方,如安心筑"唯一"人的定义需要工作台+身份 ID+身份类型;<br/>
* 而枢智业务则只需要简单的 userId 即可
* <p>
* 为了解决各种业务的差异,统一定义该模型,大范围覆盖小范围的方式进行兼容.
* 为了解决各种业务的差异,统一定义该模型,大范围覆盖小范围的方式进行兼容.
*
* @author wangli
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@Validated
public class BpmnTaskDelegateAssigner implements Serializable {
private static final long serialVersionUID = -8106887960942113552L;
@ -63,10 +72,16 @@ public class BpmnTaskDelegateAssigner implements Serializable {
@NotBlank(message = "租户不能为空")
private String tenantId;
public String buildAssigneeId() {
/**
* 人所在的单位 ID
* 仅安心筑使用
*/
private String ouId;
public final String buildAssigneeId() {
if (StringUtils.hasLength(assigneeType)) {
return assignee + "_" + assigneeType;
return tenantId + "|" + assignee + "|" + assigneeType;
}
return assignee;
return tenantId + "|" + assignee;
}
}

View File

@ -1,6 +1,6 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import cn.axzo.workflow.common.enums.BpmProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.request.BpmPageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -36,8 +36,8 @@ public class BpmnTaskPageSearchDTO extends BpmPageParam {
@ApiModelProperty(value = "业务 KEY")
private String businessKey;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已拒绝,CANCELLED:已取消)", example = "APPROVED")
private List<BpmProcessInstanceResultEnum> results;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已驳回,CANCELLED:已取消)", example = "APPROVED")
private List<BpmnProcessInstanceResultEnum> results;
@ApiModelProperty(value = "流程任务名", example = "芋道")
private String name;

View File

@ -0,0 +1,47 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* 催办功能入参模型
*
* @author wangli
* @since 2023/11/22 10:33
*/
@ApiModel("催办功能入参模型")
@Data
public class BpmnTaskRemindDTO {
/**
* 审批节点唯一标识
*/
@ApiModelProperty(value = "审批节点唯一标识")
private String taskDefinitionKey;
/**
* 催办方式
* <p>
* 站内信:notice 短信:sms
*/
@NotEmpty(message = "催办方式不能为空")
private List<String> remindTypes;
/**
* 流程实例 ID
*/
@ApiModelProperty("流程实例 ID")
@NotBlank(message = "流程实例 ID 不能为空")
private String processInstanceId;
/**
* 租户 ID
*/
@ApiModelProperty("租户 ID")
private String tenantId;
}

View File

@ -0,0 +1,32 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 扩展的历史任务表搜索入参模型
*
* @author wangli
* @since 2023/12/9 23:50
*/
@ApiModel("扩展的历史任务表搜索入参模型")
@Data
public class ExtHiTaskSearchDTO {
@ApiModelProperty("流程实例 ID")
private String processInstanceId;
@ApiModelProperty("流程任务节点标识")
private String taskDefinitionKey;
@ApiModelProperty("流程任务 ID")
private String taskId;
@ApiModelProperty("操作人")
private String assignee;
@ApiModelProperty("任务状态")
private BpmnProcessInstanceResultEnum status;
}

View File

@ -0,0 +1,34 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* workflow-engine
*
* @author zuoqinbo
* @version V1.0
* @date 2023/11/10 14:26
*/
@Data
@Accessors(chain = true)
public class ProcessNotify {
/**
* 待办模版ID
*/
private String todoTemplateId;
/**
* 通知消息模板ID
*/
private String msgNotifyTemplateId;
/**
* 短信模板ID
*/
private String texMsgTemplateId;
}

View File

@ -32,8 +32,13 @@ public class CategoryCreateDTO {
@ApiModelProperty(value = "状态", example = "true or false")
private Boolean status;
@ApiModelProperty(value = "创建人姓名", example = "张三")
private String operatorName;
@ApiModelProperty(value = "工作台类型值")
private String workspaceCodeType;
@ApiModelProperty(value = "租户", example = "1")
@NotBlank(message = "租户不能为空")
private String tenantId;
private String tenantId = "";
}

View File

@ -27,7 +27,10 @@ public class CategorySearchDTO extends BpmPageParam {
@ApiModelProperty(value = "状态", example = "0 or 1")
private Integer status;
@ApiModelProperty(value = "工作台类型值")
private String workspaceTypeCode;
@ApiModelProperty(value = "租户 ID", example = "1")
@NotBlank(message = "租户不能为空")
private String tenantId;
private String tenantId = "";
}

View File

@ -47,6 +47,9 @@ public class BpmnModelBaseVO {
@ApiModelProperty(value = "部署 ID")
private String deploymentId;
@ApiModelProperty(value = "模型的编辑器源 ID", hidden = true)
private transient String editorSourceId;
@ApiModelProperty(value = "模型元信息")
private String metaInfo;

View File

@ -1,6 +1,6 @@
package cn.axzo.workflow.common.model.response.bpmn.model;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -20,6 +20,11 @@ public class BpmnModelDetailVO extends BpmnModelBaseVO {
* 流程的 Json 结构
*/
@ApiModelProperty(value = "流程的 Json 结构")
private BpmnJsonNode bpmJson;
private BpmnJsonModel bpmJson;
@ApiModelProperty(value = "启用状态")
private Integer status;
@ApiModelProperty(value = "描述")
private String description;
}

View File

@ -0,0 +1,20 @@
package cn.axzo.workflow.common.model.response.bpmn.model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 模型扩展VO
*
* @author wangli
* @since 2023/11/21 11:03
*/
@Data
public class BpmnModelExtVO {
@ApiModelProperty(value = "模型ID")
private String modelId;
@ApiModelProperty(value = "模型状态")
private Integer status;
}

View File

@ -1,10 +1,13 @@
package cn.axzo.workflow.common.model.response.bpmn.process;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.Date;
@ApiModel("流程定义响应模型")
@Data
@ -30,10 +33,19 @@ public class BpmnProcessDefinitionVO {
@NotEmpty(message = "流程分类不能为空")
private String category;
@ApiModelProperty(value = "中断状态 1:激活 2:挂起", required = true, example = "1", notes = "参见 SuspensionState 枚举")
@ApiModelProperty(value = "状态 1:生效中(激活) 2:历史(挂起)", required = true, example = "1", notes = "参见 SuspensionState 枚举")
private Integer suspensionState;
@ApiModelProperty(value = "流程模型 JSON 数据")
private BpmnJsonModel jsonModel;
@ApiModelProperty(value = "租户 ID")
private String tenantId;
@ApiModelProperty(value = "创建时间", example = "2023-11-18 22:00:00")
private Date createTime;
@ApiModelProperty(value = "操作人信息")
private BpmnTaskDelegateAssigner operator;
}

View File

@ -1,6 +1,6 @@
package cn.axzo.workflow.common.model.response.bpmn.process;
import cn.axzo.workflow.common.enums.BpmProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -23,8 +23,8 @@ public class BpmnProcessInstancePageItemVO {
@ApiModelProperty(value = "流程分类", required = true, notes = "参见 bpm_model_category 数据字典", example = "1")
private String category;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已拒绝,CANCELLED:已取消)", example = "APPROVED")
private BpmProcessInstanceResultEnum result;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已驳回,CANCELLED:已取消)", example = "APPROVED")
private BpmnProcessInstanceResultEnum result;
@ApiModelProperty(value = "提交时间", required = true)
private Date startTime;

View File

@ -1,7 +1,7 @@
package cn.axzo.workflow.common.model.response.bpmn.process;
import cn.axzo.workflow.common.enums.BpmProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -26,8 +26,8 @@ public class BpmnProcessInstanceVO {
@ApiModelProperty(value = "流程分类", notes = "参见 bpm_model_category 数据字典", example = "1")
private String category;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已拒绝,CANCELLED:已取消)", example = "APPROVED")
private BpmProcessInstanceResultEnum result;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已驳回,CANCELLED:已取消)", example = "APPROVED")
private BpmnProcessInstanceResultEnum result;
@ApiModelProperty(value = "提交时间")
private Date createAt;

View File

@ -1,9 +1,13 @@
package cn.axzo.workflow.common.model.response.bpmn.process;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 流程实例下的节点信息
*
@ -34,4 +38,14 @@ public class ProcessNodeDetailVO {
*/
private String formKey;
/**
* 按钮配置信息,内部已计算兜底配置
*/
private BpmnButtonConf buttonConf;
/**
* 推测出来的审批人
*/
private List<BpmnTaskDelegateAssigner> forecastAssigners;
}

View File

@ -1,6 +1,6 @@
package cn.axzo.workflow.common.model.response.bpmn.task;
import cn.axzo.workflow.common.enums.BpmProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -24,7 +24,7 @@ public class BpmnHistoricTaskInstanceGroupVO {
private String processInstanceId;
@ApiModelProperty(value = "审批任务节点的最终的状态")
private BpmProcessInstanceResultEnum result;
private BpmnProcessInstanceResultEnum result;
@ApiModelProperty(value = "该审批节点下的所有任务")
private List<BpmnHistoricTaskInstanceVO> tasks;

View File

@ -1,6 +1,7 @@
package cn.axzo.workflow.common.model.response.bpmn.task;
import cn.axzo.workflow.common.enums.BpmProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -49,8 +50,8 @@ public class BpmnHistoricTaskInstanceVO {
@ApiModelProperty(value = "租户")
private String tenantId;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已拒绝,CANCELLED:已取消)", example = "APPROVED")
private BpmProcessInstanceResultEnum result;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已驳回,CANCELLED:已取消)", example = "APPROVED")
private BpmnProcessInstanceResultEnum result;
@ApiModelProperty(value = "任务审批意见")
private String advice;
@ -58,9 +59,15 @@ public class BpmnHistoricTaskInstanceVO {
@ApiModelProperty(value = "当前任务关联评论信息")
private List<BpmnHistoricCommentVO> comments;
@ApiModelProperty(value = "任务关联的附件")
private List<AttachmentDTO> attachments;
@ApiModelProperty(value = "删除原因")
private String deleteReason;
@ApiModelProperty(value = "操作描述")
private String operationDesc;
@ApiModelProperty(value = "审批人的快照信息", notes = "完整的审批人信息,assignee 仅是唯一标识")
private BpmnTaskDelegateAssigner assigneeSnapshot;

View File

@ -1,6 +1,6 @@
package cn.axzo.workflow.common.model.response.bpmn.task;
import cn.axzo.workflow.common.enums.BpmProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -21,8 +21,8 @@ public class BpmnTaskDonePageItemVO extends BpmnTaskTodoPageItemVO {
@ApiModelProperty(value = "持续时间", required = true, example = "1000")
private Long durationInMillis;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已拒绝,CANCELLED:已取消)", example = "APPROVED")
private BpmProcessInstanceResultEnum result;
@ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已驳回,CANCELLED:已取消)", example = "APPROVED")
private BpmnProcessInstanceResultEnum result;
@ApiModelProperty(value = "审批建议", required = true, example = "不请假了!")
private String comment;

View File

@ -4,6 +4,8 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@ApiModel("自定义分类详情响应模型")
@Data
public class CategoryItemVO {
@ -25,4 +27,13 @@ public class CategoryItemVO {
@ApiModelProperty(value = "状态")
private Integer status;
@ApiModelProperty(value = "操作人姓名")
private String operatorName;
@ApiModelProperty(value = "工作台类型值")
private String workspaceTypeCode;
@ApiModelProperty(value = "更新时间")
private Date updateAt;
}

View File

@ -0,0 +1,51 @@
package cn.axzo.workflow.common.model.response.mq;
import cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Map;
/**
* 消息推送 MQ payload 模型
*
* @author wangli
* @since 2023/11/22 15:06
*/
@Data
@Accessors(chain = true)
public class MessagePushDTO implements Serializable {
/**
* 推送的消息的类型
*/
private ProcessMessagePushEventEnum type;
/**
* 流程实例 ID
*/
private String processInstanceId;
/**
* 流程任务 ID
*/
private String taskId;
/**
* 消息模板 ID
*/
private String templateId;
/**
* 消息的接收人
*/
private BpmnTaskDelegateAssigner receivePerson;
/**
* 可用的变量集合
*/
private Map<String, Object> variables;
}

View File

@ -0,0 +1,68 @@
package cn.axzo.workflow.common.model.response.mq;
import cn.axzo.workflow.common.enums.ProcessActivityEventEnum;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 流程活动 MQ payload 模型
*
* @author wangli
* @since 2023/11/21 18:01
*/
@Data
@Accessors(chain = true)
public class ProcessActivityDTO implements Serializable {
/**
* 流程实例相关 MQ 事件的数据类型
*/
private ProcessActivityEventEnum type;
/**
* 节点唯一标识
*/
private String activityId;
/**
* 节点名称
*/
private String activityName;
/**
* 流程实例
*/
private String processInstanceId;
/**
* 流程定义版本
*/
private String processDefinitionId;
/**
* 如果是"业务节点", 需要业务使用该 id 进行推动流程后续执行
*/
private String triggerId;
/**
* 流程实例的所有变量
*/
private Map<String, Object> variables;
/**
* 通过的人员
*/
private List<BpmnTaskDelegateAssigner> passedAssigners;
/**
* 模型对应的通知模板配置信息
*/
private BpmnNoticeConf noticeConf;
}

View File

@ -1,6 +1,7 @@
package cn.axzo.workflow.common.model.response.mq;
import cn.axzo.workflow.common.enums.ProcessInstanceEventEnum;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import lombok.Data;
import lombok.experimental.Accessors;
@ -88,4 +89,9 @@ public class ProcessInstanceDTO implements Serializable {
* 取消流程实例的特殊字段
*/
private String cancelReason;
/**
* 模型对应的通知模板配置信息
*/
private BpmnNoticeConf noticeConf;
}

View File

@ -1,12 +1,15 @@
package cn.axzo.workflow.common.model.response.mq;
import cn.axzo.workflow.common.enums.BpmnNoticeEnum;
import cn.axzo.workflow.common.enums.ProcessTaskEventEnum;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
@ -78,4 +81,14 @@ public class ProcessTaskDTO implements Serializable {
* 审批人信息
*/
private BpmnTaskDelegateAssigner approver;
/**
* 通知方式
*/
private List<BpmnNoticeEnum> noticeMethod;
/**
* 模型对应的通知模板配置信息
*/
private BpmnNoticeConf noticeConf;
}

View File

@ -1,55 +0,0 @@
//package cn.axzo.workflow.client.model.response.process;
//
//import lombok.Data;
//import org.flowable.engine.history.HistoricProcessInstance;
//import org.springframework.util.CollectionUtils;
//
//import java.util.ArrayList;
//import java.util.Collections;
//import java.util.Date;
//import java.util.List;
//
///**
// * TODO
// *
// * @author wangli
// * @since 2023/7/26 20:32
// */
//@Data
//public class CustomProcInstVO {
//
// private String businessKey;
//
// private String name;
//
// private String category;
//
// private String startUserId;
//
// private Date startTime;
//
// private String businessStatus;
//
// private Date endTime;
//
// public static CustomProcInstVO vo(HistoricProcessInstance instance, String category) {
// CustomProcInstVO vo = new CustomProcInstVO();
// vo.setName(instance.getName());
// vo.setCategory(category);
// vo.setBusinessKey(instance.getBusinessKey());
// vo.setStartUserId(instance.getStartUserId());
// vo.setStartTime(instance.getStartTime());
// vo.setEndTime(instance.getEndTime());
// vo.setBusinessStatus(instance.getBusinessStatus());
// return vo;
// }
//
// public static List<CustomProcInstVO> vos(List<HistoricProcessInstance> list, String category) {
// if (CollectionUtils.isEmpty(list)) {
// return Collections.emptyList();
// }
// List<CustomProcInstVO> vos = new ArrayList<>();
// list.forEach(i -> vos.add(vo(i, category)));
// return vos;
// }
//}

View File

@ -20,6 +20,10 @@
</properties>
<dependencies>
<dependency>
<groupId>cn.axzo.basics</groupId>
<artifactId>basics-common</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-processor-spring-boot-starter</artifactId>
@ -76,5 +80,10 @@
<groupId>cn.axzo.workflow</groupId>
<artifactId>workflow-engine-api</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.workflow</groupId>
<artifactId>workflow-engine-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,28 +0,0 @@
package cn.axzo.workflow.core.common.enums;
public enum BpmProcessInstanceStatusEnum {
RUNNING(1, "进行中"),
FINISH(2, "已完成");
/**
* 状态
*/
private final Integer status;
/**
* 描述
*/
private final String desc;
BpmProcessInstanceStatusEnum(Integer status, String desc) {
this.status = status;
this.desc = desc;
}
public Integer getStatus() {
return status;
}
public String getDesc() {
return desc;
}
}

View File

@ -11,14 +11,21 @@ import lombok.Getter;
*/
@Getter
@AllArgsConstructor
public enum BpmErrorCode implements IProjectRespCode {
public enum BpmnErrorCode implements IProjectRespCode {
// ========== category 01-001 ==========
CATEGORY_VALUE_EXISTS("01001", "分类值【{}】已经存在"),
CATEGORY_ID_NOT_EXISTS("01002", "指定分类【{}】不存在"),
CATEGORY_DATA_ERROR("01003", "分类数据异常"),
// ========== convertor 02-001 ==========
CONVERTOR_UNKNOW_NODE_TYPE("02001", "节点类型【{}】暂不支持"),
CONVERTOR_META_DATA_FORMAT_ERROR("02002", "JSON 数据格式有误"),
CONVERTOR_META_DATA_FORMAT_ERROR("02002", "JSON 数据格式有误-{}: {}"),
CONVERTOR_COMMON_ERROR("02003", "JSON 转 BPMN 失败, 原因:【{}】"),
CONVERTOR_NODE_TYPE_NOT_SUPPORT("02004", "【{}】节点类型, ID:【{}】暂不支持"),
CONVERTOR_OPERATION_STRING_TYPE_ERROR("02005", "条件节点运算符【{}】暂不支持"),
CONVERTOR_OPERATION_NUMBER_TYPE_ERROR("02006", "条件节点运算符【{}】暂不支持"),
CONVERTOR_OPERATION_RADIO_TYPE_ERROR("02007", "条件节点运算符【{}】暂不支持"),
CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR("02008", "条件节点运算符【{}】暂不支持"),
// ========== bpmn model 03-001 ==========
@ -42,12 +49,17 @@ public enum BpmErrorCode implements IProjectRespCode {
PROCESS_OPERATION_PARAM_VALID_ERROR("05001", "参数缺失, 请确保传入了 ID 或 businessKey 对流程实例进行操作"),
PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS("05002", "流程取消失败,流程不处于运行中"),
PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF("05003", "流程取消失败,该流程不是你发起的"),
PROCESS_INSTANCE_NOT_EXISTS("05004", "流程实例不存在"),
PROCESS_INSTANCE_NOT_EXISTS("05004", "流程实例不存在或已结束"),
PROCESS_INSTANCE_ID_NOT_EXISTS("05005", "流程实例【{}】不存在"),
PROCESS_INSTANCE_CANCELLED("05006", "流程实例已取消"),
// ========== bpmn task 06-001 ==========
TASK_COMPLETE_FAIL_NOT_EXISTS("06001", "未找到指定审批任务"),
TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF("06002", "该任务的审批人不是你"),
TASK_APOSTILLE_NOT_SUPPORT("06003", "当前加签模式不支持"),
TASK_REMIND_ERROR_NOT_EXISTS("06004", "当前审批节点没有待审批任务, 不能催办"),
ACTIVITY_TRIGGER_NOT_EXISTS("06005", "触发 ID:【{}】不存在"),
CALC_TASK_ASSIGNEE_ERROR("06006", "执行计算审批候选人出现异常: {}"),
// ========== form Model 07-001 ==========
FORM_MODEL_NOT_EXISTS("07001", "表单模型不存在"),
FORM_MODEL_ID_NOT_EXISTS("07002", "表单模型ID【{}】不存在"),
@ -57,6 +69,14 @@ public enum BpmErrorCode implements IProjectRespCode {
// ========== form Instance 09-001 ==========
// ========== flowable Engine 10-001 ==========
ENGINE_EXECUTION_LOST_ID_ERROR("10001", "Execution 丢失"),
ENGINE_USER_TASK_CALC_ERROR("10002", "计算用户任务节点的审批发生异常: 【{}】"),
ENGINE_USER_TASK_TYPE_NOT_SUPPORT("10003", "审批指定方式暂不支持"),
// ========== flowable Engine 99-001 ==========
MES_PUSH_OBJECT_BUILD_ERROR("99001", "构建消息推送对象异常"),
REPEAT_SUBMIT_TIME_ERROR_TIPS("99002", "重复提交间隔时间不能小于{}秒"),
REPEAT_SUBMIT_ERROR_TIPS("99002", "{}"),
// // ========== 流程模型 01-001 ==========

View File

@ -16,6 +16,7 @@ public enum BpmnProcessTaskResultEnum {
// 引擎默认的标识,不允许修改
DELETE_MI_EXECUTION("Delete MI execution", "多实例任务被删除"),
INITIATOR_REVOCATION("INITIATOR_REVOCATION", "发起者主动撤销"),
REJECTION_AUTO_COMPLETED("REJECTION_AUTO_COMPLETED", "审批驳回自动结束"),
BACKED("BACKED", "退回");
private final String status;

View File

@ -19,10 +19,6 @@ public class WorkflowEngineException extends ServiceException {
this.code = code.getRespCode();
}
public WorkflowEngineException(String message) {
super(message);
}
@Override
public String getCode() {
return this.code;

View File

@ -1,7 +1,7 @@
package cn.axzo.workflow.core.common.utils;
import cn.axzo.workflow.common.enums.BpmFlowNodeType;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNodeProperty;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
@ -35,18 +35,21 @@ import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.constant.BpmConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmConstants.BPM_ALLOW_SKIP_USER_TASK;
import static cn.axzo.workflow.common.constant.BpmConstants.END_EVENT_ID;
import static cn.axzo.workflow.common.constant.BpmConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO;
import static cn.axzo.workflow.common.constant.BpmConstants.OR_SIGN_EXPRESSION;
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.CONVERTOR_META_DATA_FORMAT_ERROR;
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.CONVERTOR_UNKNOW_NODE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.END_EVENT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.OR_SIGN_EXPRESSION_ONE_PASS;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.CONVERTOR_META_DATA_FORMAT_ERROR;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.CONVERTOR_UNKNOW_NODE_TYPE;
import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION;
import static org.flowable.engine.delegate.BaseExecutionListener.EVENTNAME_END;
import static org.flowable.engine.delegate.BaseExecutionListener.EVENTNAME_START;
/**
* 该工具是以前枢智版本使用候选迭代都通过 BpmnJsonConverterUtil 该工具操作
*/
@Slf4j
@Deprecated
public class BpmTransformUtil {
public static byte[] transformBpmnJsonToXml(BpmnJsonNode bpmnJson, Model model) {
return transformBpmnJsonToXml(bpmnJson, model, null);
@ -60,7 +63,7 @@ public class BpmTransformUtil {
process.setId(model.getKey());
process.setName(model.getName());
if (BpmFlowNodeType.NODE_STARTER.equals(bpmnJson.getType())) {
if (BpmnFlowNodeType.NODE_STARTER.equals(bpmnJson.getType())) {
process.addFlowElement(createStartEvent(bpmnJson.getId(), Objects.nonNull(bpmnJson.getProperty()) ?
bpmnJson.getProperty().getFormKey() : ""));
}
@ -114,17 +117,24 @@ public class BpmTransformUtil {
if (StringUtils.isNotBlank(parentId)) {
BpmnJsonNode parentNode = childNodeMap.get(parentId);
if (parentNode != null) {
if (BpmFlowNodeType.NODE_CONDITION.equals(parentNode.getType())) {
if (BpmnFlowNodeType.NODE_CONDITION.equals(parentNode.getType())) {
sequenceFlowId = parentNode.getId();
flow.setName(parentNode.getName());
if (!ObjectUtils.isEmpty(parentNode.getProperty()) && !Boolean.TRUE.equals(parentNode.getProperty().getDefaultCondition())) {
if (!ObjectUtils.isEmpty(parentNode.getProperty()) && !Boolean.TRUE.equals(parentNode.getProperty().getDefaultBranch())) {
//解析条件表达式
StringBuffer conditionExpression = new StringBuffer();
conditionExpression.append("${ ");
conditionExpression.append("var:eq('" + parentNode.getProperty().getConditionBranchKey() + "', " + parentNode.getProperty().getConditionBranchValue() + ")");
conditionExpression.append(" }");
flow.setConditionExpression(conditionExpression.toString());
// StringBuffer conditionExpression = new StringBuffer();
// conditionExpression.append("${ ")
// .append("var:eq('")
// .append(parentNode.getProperty()
// .getConditionBranchKey())
// .append("', ")
// .append(parentNode.getProperty()
// .getConditionBranchValue())
// .append(")");
// conditionExpression.append(" }");
//
// flow.setConditionExpression(conditionExpression
// .toString());
}
}
}
@ -158,12 +168,11 @@ public class BpmTransformUtil {
public static String create(String fromId, BpmnJsonNode flowNode, Process process, BpmnModel bpmnModel,
List<SequenceFlow> sequenceFlows, Map<String, BpmnJsonNode> childNodeMap) throws InvocationTargetException, IllegalAccessException {
String nodeType = flowNode.getType().getType();
if (BpmFlowNodeType.NODE_ROUTER.isEqual(nodeType)) {
if (BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY.isEqual(nodeType)) {
return createExclusiveGatewayBuilder(fromId, flowNode, process, bpmnModel, sequenceFlows, childNodeMap);
} else if (BpmFlowNodeType.NODE_TASK.isEqual(nodeType)) {
} else if (BpmnFlowNodeType.NODE_TASK.isEqual(nodeType)) {
childNodeMap.put(flowNode.getId(), flowNode);
Map incoming = flowNode.getIncoming();
incoming.put("incoming", Collections.singletonList(fromId));
flowNode.setIncoming(Collections.singletonList(fromId));
String id = createTask(process, flowNode, sequenceFlows, childNodeMap);
// 如果当前任务还有后续任务则遍历创建后续任务
BpmnJsonNode children = flowNode.getChildren();
@ -172,10 +181,9 @@ public class BpmTransformUtil {
} else {
return id;
}
} else if (BpmFlowNodeType.NODE_STARTER.isEqual(nodeType)) {
} else if (BpmnFlowNodeType.NODE_STARTER.isEqual(nodeType)) {
childNodeMap.put(flowNode.getId(), flowNode);
Map incoming = flowNode.getIncoming();
incoming.put("incoming", Collections.singletonList(fromId));
flowNode.setIncoming(Collections.singletonList(fromId));
String id = createTask(process, flowNode, sequenceFlows, childNodeMap);
// 如果当前任务还有后续任务则遍历创建后续任务
BpmnJsonNode children = flowNode.getChildren();
@ -216,7 +224,7 @@ public class BpmTransformUtil {
List<Map> conditions = Lists.newCopyOnWriteArrayList();
for (BpmnJsonNode element : branches) {
if (!ObjectUtils.isEmpty(element.getProperty())) {
Boolean typeElse = element.getProperty().getDefaultCondition();
Boolean typeElse = element.getProperty().getDefaultBranch();
if (Boolean.TRUE.equals(typeElse)) {
exclusiveGateway.setDefaultFlow(element.getId());
}
@ -232,19 +240,19 @@ public class BpmTransformUtil {
Map condition = new HashMap();
//解析条件表达式
StringBuffer conditionExpression = new StringBuffer();
conditionExpression.append("${ ");
conditionExpression.append("var:eq('" + element.getProperty().getConditionBranchKey() + "', " + element.getProperty().getConditionBranchValue() + ") ");
conditionExpression.append(" }");
condition.put("nodeName", nodeName);
condition.put("expression", conditionExpression.toString());
// StringBuffer conditionExpression = new StringBuffer();
// conditionExpression.append("${ ");
// conditionExpression.append("var:eq('" + element.getProperty().getConditionBranchKey
// () + "', " + element.getProperty().getConditionBranchValue() + ") ");
// conditionExpression.append(" }");
// condition.put("nodeName", nodeName);
// condition.put("expression", conditionExpression.toString());
conditions.add(condition);
continue;
}
// 只生成一个任务同时设置当前任务的条件
Map incomingObj = children.getIncoming();
incomingObj.put("incoming", Collections.singletonList(exclusiveGatewayId));
children.setIncoming(Collections.singletonList(exclusiveGatewayId));
String identifier = create(exclusiveGatewayId, children, process, bpmnModel, sequenceFlows, childNodeMap);
List<SequenceFlow> flows = sequenceFlows.stream().filter(flow -> StringUtils.equals(exclusiveGatewayId,
flow.getSourceRef()))
@ -255,7 +263,6 @@ public class BpmTransformUtil {
e.setName(nodeName);
}
// 设置条件表达式
// TODO 不知道什么意思
// if (Objects.isNull(e.getConditionExpression()) && StringUtils
// .isNotBlank(expression)) {
// e.setConditionExpression(expression);
@ -273,16 +280,15 @@ public class BpmTransformUtil {
if (Objects.nonNull(childNode) && StringUtils.isNotBlank(childNode.getId())) {
String parentId = childNode.getParentId();
BpmnJsonNode parentChildNode = childNodeMap.get(parentId);
if (BpmFlowNodeType.NODE_ROUTER.equals(parentChildNode.getType())) {
if (BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY.equals(parentChildNode.getType())) {
String endExId = parentChildNode.getId() + "end";
process.addFlowElement(createExclusiveGateWayEnd(endExId));
if (incoming == null || incoming.isEmpty()) {
return create(exclusiveGatewayId, childNode, process, bpmnModel, sequenceFlows,
childNodeMap);
} else {
Map incomingObj = childNode.getIncoming();
// 所有 service task 连接 end exclusive gateway
incomingObj.put("incoming", incoming);
childNode.setIncoming(incoming);
FlowElement flowElement = bpmnModel.getFlowElement(incoming.get(0));
// 1.0 先进行边连接, 暂存 nextNode
BpmnJsonNode nextNode = childNode.getChildren();
@ -339,8 +345,7 @@ public class BpmTransformUtil {
private static String createTask(Process process, BpmnJsonNode flowNode, List<SequenceFlow> sequenceFlows,
Map<String, BpmnJsonNode> childNodeMap) {
Map incomingJson = flowNode.getIncoming();
List<String> incoming = (List<String>) incomingJson.get("incoming");
List<String> incoming = flowNode.getIncoming();
// 自动生成id
// String id = id("serviceTask");
String id = flowNode.getId();
@ -383,15 +388,16 @@ public class BpmTransformUtil {
// 设置多实例属性
userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
if (BpmnFlowNodeMode.OR.getType().equals(mode.getType())) {
multiInstanceLoopCharacteristics.setCompletionCondition(OR_SIGN_EXPRESSION);
multiInstanceLoopCharacteristics.setCompletionCondition(OR_SIGN_EXPRESSION_ONE_PASS);
} else if (BpmnFlowNodeMode.AND.getType().equals(mode.getType())) {
multiInstanceLoopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION);
}
}
// 设置审批人为空时,允许自动通过
if (!ObjectUtils.isEmpty(flowNode.getProperty()) && flowNode.getProperty().getAllowSkip()) {
userTask.setSkipExpression("${" + BPM_ALLOW_SKIP_USER_TASK + "}");
}
// if (!ObjectUtils.isEmpty(flowNode.getProperty()) && flowNode.getProperty()
// .getAllowSkip()) {
// userTask.setSkipExpression("${" + BPM_ALLOW_SKIP_USER_TASK + "}");
// }
if (!ObjectUtils.isEmpty(flowNode.getProperty()) && StringUtils.isNotBlank(flowNode.getProperty().getFormKey())) {
userTask.setFormKey(flowNode.getProperty().getFormKey());
}

View File

@ -4,14 +4,24 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.ImmutableMap;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class BpmCollectionUtils {
public class BpmnCollectionUtils {
public static boolean containsAny(Object source, Object... targets) {
return Arrays.asList(targets).contains(source);

View File

@ -0,0 +1,114 @@
package cn.axzo.workflow.core.common.utils;
import cn.axzo.workflow.common.model.request.bpmn.BpmnCondition;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Objects;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.CONVERTOR_OPERATION_NUMBER_TYPE_ERROR;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.CONVERTOR_OPERATION_RADIO_TYPE_ERROR;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.CONVERTOR_OPERATION_STRING_TYPE_ERROR;
/**
* 表达式翻译器
*
* @author wangli
* @since 2023/11/16 23:30
*/
public final class BpmnExpressionTranslator {
private BpmnExpressionTranslator() {}
public static String translateString(BpmnCondition condition) {
// ${var:contains('variableName', 'hello')};
// ${!var:contains('variableName', 'hello')}
StringBuilder sb = new StringBuilder();
if (Objects.equals(condition.getOperator(), "contains")) {
sb.append("var:contains('")
.append(condition.getCode())
.append("', '")
.append(condition.getDefaultValue())
.append("')");
} else if (Objects.equals(condition.getOperator(), "notContains")) {
sb.append("!var:contains('")
.append(condition.getCode())
.append("', '")
.append(condition.getDefaultValue())
.append("')");
} else {
// 其他非法的操作符都过滤掉,或在这里抛出异常
throw new WorkflowEngineException(CONVERTOR_OPERATION_STRING_TYPE_ERROR, condition.getOperator());
}
return sb.toString();
}
public static String translateNumber(BpmnCondition condition) {
List<String> operators = Lists.newArrayList("eq", "ne", "gt", "gte", "lt", "lte", "between");
if (operators.contains(condition.getOperator())) {
StringBuilder sb = new StringBuilder();
if ("between".equals(condition.getOperator())) {
sb.append("var:")
.append(condition.getLeftOperator())
.append("('")
.append(condition.getCode())
.append("', ")
.append(condition.getLeftValue())
.append(")")
.append(" && ")
.append("var:")
.append(condition.getRightOperator())
.append("('")
.append(condition.getCode())
.append("', ")
.append(condition.getRightValue())
.append(")");
} else {
sb.append("var:")
.append(condition.getOperator())
.append("('")
.append(condition.getCode())
.append("', ")
.append(condition.getDefaultValue())
.append(")");
}
return sb.toString();
} else {
throw new WorkflowEngineException(CONVERTOR_OPERATION_NUMBER_TYPE_ERROR, condition.getOperator());
}
}
public static String translateRadio(BpmnCondition condition) {
if (Objects.equals(condition.getOperator(), "eq")) {
return "var:" +
condition.getOperator() +
"('" +
condition.getCode() +
"', " +
condition.getDefaultValue() +
")";
} else {
throw new WorkflowEngineException(CONVERTOR_OPERATION_RADIO_TYPE_ERROR, condition.getOperator());
}
}
public static String translateCheckbox(BpmnCondition condition) {
if (Objects.equals(condition.getOperator(), "in")) {
StringBuilder sb = new StringBuilder("var:containsAny(")
.append("'")
.append(condition.getCode())
.append("', ");
condition.getDefaultValues().forEach(i -> {
sb.append("'").append(i).append("',");
});
sb.delete(sb.length() - 1, sb.length());
sb.append(")");
return sb.toString();
} else {
throw new WorkflowEngineException(CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR, condition.getOperator());
}
}
}

View File

@ -1,13 +1,98 @@
package cn.axzo.workflow.core.common.utils;
import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
import cn.axzo.workflow.common.model.request.bpmn.BpmnFieldConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.converter.json.AbstractBpmnJsonConverter;
import cn.axzo.workflow.core.converter.json.EndEventJsonConverter;
import cn.axzo.workflow.core.converter.json.ExclusiveGatewayJsonConverter;
import cn.axzo.workflow.core.converter.json.NotSupportConverter;
import cn.axzo.workflow.core.converter.json.ParallelGatewayJsonConverter;
import cn.axzo.workflow.core.converter.json.ReceiveTaskJsonConverter;
import cn.axzo.workflow.core.converter.json.SequenceFlowJsonConverter;
import cn.axzo.workflow.core.converter.json.ServiceTaskJsonConverter;
import cn.axzo.workflow.core.converter.json.StartEventJsonConverter;
import cn.axzo.workflow.core.converter.json.UserTaskJsonConverter;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BaseElement;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.ExclusiveGateway;
import org.flowable.bpmn.model.ExtensionAttribute;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Gateway;
import org.flowable.bpmn.model.ParallelGateway;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.ReceiveTask;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import static cn.axzo.workflow.core.common.enums.BpmErrorCode.CONVERTOR_COMMON_ERROR;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_META;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CARBON_COPY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CURRENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_HISTORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_META;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_OPTION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NOTICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CHECKED;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CODE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DISABLED;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_KEY;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_NAME;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_ORDER;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE;
import static cn.axzo.workflow.common.constant.BpmnConstants.END_EVENT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_NODE_JSON;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121;
import static cn.axzo.workflow.common.constant.BpmnConstants.SEQUENCE_FLOW_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.START_EVENT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAGE_ID;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CONDITION;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EMPTY;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.CONVERTOR_COMMON_ERROR;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getButtonConfig;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getFieldConfig;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNoticeConfig;
/**
* BPMN json 格式转换工具
@ -15,7 +100,26 @@ import static cn.axzo.workflow.core.common.enums.BpmErrorCode.CONVERTOR_COMMON_E
* @author wangli
* @since 2023/10/12 11:43
*/
public class BpmnJsonConverterUtil {
public final class BpmnJsonConverterUtil {
private BpmnJsonConverterUtil() {
}
private static final Logger log = LoggerFactory.getLogger(BpmnJsonConverterUtil.class);
private static final Map<Class<? extends BaseElement>, AbstractBpmnJsonConverter<? extends BaseElement>> CONVERTERS =
new HashMap<>();
static {
CONVERTERS.put(NotSupportConverter.NotSupportFlowElement.class, new NotSupportConverter());
CONVERTERS.put(StartEvent.class, new StartEventJsonConverter());
CONVERTERS.put(SequenceFlow.class, new SequenceFlowJsonConverter());
CONVERTERS.put(EndEvent.class, new EndEventJsonConverter());
CONVERTERS.put(ExclusiveGateway.class, new ExclusiveGatewayJsonConverter());
CONVERTERS.put(ParallelGateway.class, new ParallelGatewayJsonConverter());
CONVERTERS.put(UserTask.class, new UserTaskJsonConverter());
CONVERTERS.put(ServiceTask.class, new ServiceTaskJsonConverter());
CONVERTERS.put(ReceiveTask.class, new ReceiveTaskJsonConverter());
}
/**
* BpmnModel 转成 JSON 格式对应对象
@ -23,71 +127,495 @@ public class BpmnJsonConverterUtil {
* @param bpmnModel Flowable 标准模型
* @return {@link BpmnJsonNode} json 格式对象,可以直接解析
*/
public static BpmnJsonNode convertToJson(BpmnModel bpmnModel) {
return new BpmnJsonNode();
public static BpmnJsonModel convertToJson(BpmnModel bpmnModel) {
BpmnJsonModel model = new BpmnJsonModel();
Process mainProcess = bpmnModel.getMainProcess();
BpmnJsonNode bpmnJsonNode = JSON.parseObject(mainProcess.getAttributeValue(null, FLOW_NODE_JSON),
BpmnJsonNode.class);
model.setNode(bpmnJsonNode);
getNoticeConfig(mainProcess).ifPresent(model::setNoticeConf);
getButtonConfig(mainProcess).ifPresent(model::setButtonConf);
getFieldConfig(mainProcess).ifPresent(model::setFieldConf);
return model;
}
/**
* json 格式数据转的 BpmnJsonNode 对象,转换成 Flowable 标准的模型 {@link BpmnModel}
*
* @param bpmnJsonNode json 格式对象,可以直接解析
* @param noticeConf
* @param buttonConf
* @param fieldConf
* @return {@link BpmnModel}
*/
public static BpmnModel convertToBpmn(BpmnJsonNode bpmnJsonNode) {
public static BpmnModel convertToBpmn(BpmnJsonNode bpmnJsonNode, String id, String name, String documentation,
BpmnNoticeConf noticeConf, BpmnButtonConf buttonConf,
List<BpmnFieldConf> fieldConf) {
if (Objects.isNull(bpmnJsonNode)) {
throw new WorkflowEngineException(CONVERTOR_COMMON_ERROR, "JSON 数据为空");
}
switch (bpmnJsonNode.getType()) {
case NODE_STARTER:
break;
// 提交的 BpmnJsonNode 全部都是任务是可执行节点, 这里的转换是全局都会增加开始和结束节点. 因为前段的提交的数据是不包含开始和结束
BpmnModel bpmnModel = new BpmnModel();
ExtensionAttribute serverVersion = new ExtensionAttribute();
serverVersion.setName(FLOW_SERVER_VERSION);
serverVersion.setValue(FLOW_SERVER_VERSION_121);
ExtensionAttribute jsonMetaValue = new ExtensionAttribute();
jsonMetaValue.setName(FLOW_NODE_JSON);
jsonMetaValue.setValue(JSON.toJSONString(bpmnJsonNode));
Process mainProcess = new Process();
mainProcess.setId(id);
mainProcess.setName(name);
mainProcess.setDocumentation(documentation);
mainProcess.addAttribute(serverVersion);
mainProcess.addAttribute(jsonMetaValue);
// 设置流程的通知管理配置
setProcessNoticeConfig(noticeConf, mainProcess);
// 设置流程的默认的按钮配置
setProcessButtonConfig(buttonConf, mainProcess);
// 设置流程的字段元数据配置
setProcessFieldConfig(fieldConf, mainProcess);
bpmnModel.addProcess(mainProcess);
// 创建固定的开始节点
mainProcess.addFlowElement(convertJsonToElement(StartEvent.class, mainProcess));
// 创建固定的结束节点
mainProcess.addFlowElement(convertJsonToElement(EndEvent.class, mainProcess));
// 解析前端传入的模型设计 json
List<String> lastNodeIds = create(bpmnJsonNode, mainProcess, bpmnModel, START_EVENT_ID);
if (CollectionUtils.isEmpty(lastNodeIds)) {
throw new WorkflowEngineException(CONVERTOR_COMMON_ERROR, "未找到链接结束节点的节点数据");
}
return new BpmnModel();
lastNodeIds.forEach(lastNodeId -> {
SequenceFlow sequenceFlow = new SequenceFlow();
sequenceFlow.setId(id(SEQUENCE_FLOW_ID));
sequenceFlow.setSourceRef(lastNodeId);
sequenceFlow.setTargetRef(END_EVENT_ID);
mainProcess.addFlowElement(sequenceFlow);
});
new BpmnAutoLayout(bpmnModel).execute();
return bpmnModel;
}
public static void main(String[] args) {
String jsonStr = "{\n" +
" \"id\": \"NODE_STARTER\",\n" +
" \"type\": \"NODE_STARTER\",\n" +
" \"name\": \"发起\",\n" +
" \"children\": {\n" +
" \"id\": \"NODE_CONFIG\",\n" +
" \"parentId\": \"NODE_STARTER\",\n" +
" \"type\": \"NODE_TASK\",\n" +
" \"name\": \"权限配置\",\n" +
" \"children\": {\n" +
" \"id\": \"NODE_RELEASE_DEV\",\n" +
" \"parentId\": \"NODE_CONFIG\",\n" +
" \"type\": \"NODE_TASK\",\n" +
" \"name\": \"发布 DEV\",\n" +
" \"children\": {\n" +
" \"id\": \"NODE_RELEASE_TEST\",\n" +
" \"parentId\": \"NODE_RELEASE_DEV\",\n" +
" \"type\": \"NODE_TASK\",\n" +
" \"name\": \"发布 TEST\",\n" +
" \"children\": {\n" +
" \"id\": \"NODE_RELEASE_PRE\",\n" +
" \"parentId\": \"NODE_RELEASE_TEST\",\n" +
" \"type\": \"NODE_TASK\",\n" +
" \"name\": \"发布 PRE\",\n" +
" \"children\": {\n" +
" \"id\": \"NODE_RELEASE_PROD\",\n" +
" \"parentId\": \"NODE_RELEASE_PRE\",\n" +
" \"type\": \"NODE_TASK\",\n" +
" \"name\": \"发布生产\",\n" +
" \"children\": {\n" +
" \"id\": \"NODE_ACCEPTANCE\",\n" +
" \"parentId\": \"NODE_RELEASE_PROD\",\n" +
" \"type\": \"NODE_TASK\",\n" +
" \"name\": \"产品验收\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }";
BpmnJsonNode bpmnJsonNode = JSON.parseObject(jsonStr, BpmnJsonNode.class);
BpmnModel bpmnModel = convertToBpmn(bpmnJsonNode);
System.out.println("bpmnModel = " + bpmnModel);
private static void setProcessFieldConfig(List<BpmnFieldConf> fieldConf, Process mainProcess) {
if (CollectionUtils.isEmpty(fieldConf)) {
return;
}
ExtensionElement fieldConfigElement = new ExtensionElement();
fieldConfigElement.setName(CONFIG_FIELD);
if (!CollectionUtils.isEmpty(fieldConf)) {
fieldConf.forEach(i -> {
ExtensionElement field = new ExtensionElement();
field.setName(CONFIG_FIELD_META);
ExtensionAttribute fieldDataType = new ExtensionAttribute();
fieldDataType.setName(ELEMENT_ATTRIBUTE_TYPE);
fieldDataType.setValue(i.getType());
field.addAttribute(fieldDataType);
ExtensionAttribute fieldName = new ExtensionAttribute();
fieldName.setName(ELEMENT_ATTRIBUTE_NAME);
fieldName.setValue(i.getName());
field.addAttribute(fieldName);
ExtensionAttribute fieldCode = new ExtensionAttribute();
fieldCode.setName(ELEMENT_ATTRIBUTE_CODE);
fieldCode.setValue(i.getCode());
field.addAttribute(fieldCode);
if (!CollectionUtils.isEmpty(i.getOptions())) {
i.getOptions().forEach(j -> {
ExtensionElement option = new ExtensionElement();
option.setName(CONFIG_FIELD_OPTION);
ExtensionAttribute optionName = new ExtensionAttribute();
optionName.setName(ELEMENT_ATTRIBUTE_NAME);
optionName.setValue(j.getName());
option.addAttribute(optionName);
ExtensionAttribute optionValue = new ExtensionAttribute();
optionValue.setName(ELEMENT_ATTRIBUTE_VALUE);
optionValue.setValue(j.getValue());
option.addAttribute(optionValue);
field.addChildElement(option);
});
}
fieldConfigElement.addChildElement(field);
});
}
mainProcess.addExtensionElement(fieldConfigElement);
}
private static void setProcessButtonConfig(BpmnButtonConf buttonConf, Process mainProcess) {
if (Objects.isNull(buttonConf)) {
return;
}
ExtensionElement buttonConfigElement = new ExtensionElement();
buttonConfigElement.setName(CONFIG_BUTTON);
buildMetaButton(buttonConf.getInitiator(), CONFIG_BUTTON_TYPE_INITIATOR, buttonConfigElement);
buildMetaButton(buttonConf.getCurrent(), CONFIG_BUTTON_TYPE_CURRENT, buttonConfigElement);
buildMetaButton(buttonConf.getHistory(), CONFIG_BUTTON_TYPE_HISTORY, buttonConfigElement);
buildMetaButton(buttonConf.getCarbonCopy(), CONFIG_BUTTON_TYPE_CARBON_COPY, buttonConfigElement);
mainProcess.addExtensionElement(buttonConfigElement);
}
public static void buildMetaButton(List<BpmnButtonMetaInfo> buttonMetaInfos, String buttonType,
ExtensionElement buttonConfigElement) {
if (!CollectionUtils.isEmpty(buttonMetaInfos)) {
ExtensionElement initiator = new ExtensionElement();
initiator.setName(buttonType);
buttonMetaInfos.forEach(i -> {
ExtensionElement button = new ExtensionElement();
button.setName(CONFIG_BUTTON_META);
ExtensionAttribute keyAttribute = new ExtensionAttribute();
keyAttribute.setName(ELEMENT_ATTRIBUTE_KEY);
keyAttribute.setValue(i.getBtnKey());
button.addAttribute(keyAttribute);
ExtensionAttribute nameAttribute = new ExtensionAttribute();
nameAttribute.setName(ELEMENT_ATTRIBUTE_NAME);
nameAttribute.setValue(i.getBtnName());
button.addAttribute(nameAttribute);
ExtensionAttribute valueAttribute = new ExtensionAttribute();
valueAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED);
valueAttribute.setValue(String.valueOf(i.getChecked()));
button.addAttribute(valueAttribute);
ExtensionAttribute orderAttribute = new ExtensionAttribute();
orderAttribute.setName(ELEMENT_ATTRIBUTE_ORDER);
orderAttribute.setValue(String.valueOf(i.getOrder()));
button.addAttribute(orderAttribute);
ExtensionAttribute disabledAttribute = new ExtensionAttribute();
disabledAttribute.setName(ELEMENT_ATTRIBUTE_DISABLED);
disabledAttribute.setValue(String.valueOf(i.getDisabled()));
button.addAttribute(disabledAttribute);
initiator.addChildElement(button);
});
buttonConfigElement.addChildElement(initiator);
}
}
private static void setProcessNoticeConfig(BpmnNoticeConf noticeConf, Process mainProcess) {
if (Objects.isNull(noticeConf)) {
return;
}
ExtensionElement noticeConfigElement = new ExtensionElement();
noticeConfigElement.setName(CONFIG_NOTICE);
// 通知消息模板配置
if (Objects.nonNull(noticeConf.getNotice()) && Objects.nonNull(noticeConf.getNotice().getNoticeMessageId())) {
ExtensionElement noticeMessage = new ExtensionElement();
noticeMessage.setName(TEMPLATE_NOTICE_MESSAGE_ID);
ExtensionAttribute noticeMessageAttribute = new ExtensionAttribute();
noticeMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
noticeMessageAttribute.setValue(noticeConf.getNotice().getNoticeMessageId());
noticeMessage.addAttribute(noticeMessageAttribute);
noticeMessage.setElementText(StringUtils.hasLength(noticeConf.getNotice().getViewJson()) ?
noticeConf.getNotice().getViewJson() : "");
noticeConfigElement.addChildElement(noticeMessage);
}
// 代办消息模板配置
if (Objects.nonNull(noticeConf.getPending()) && Objects.nonNull(noticeConf.getPending().getPendingMessageId())) {
ExtensionElement pendingMessage = new ExtensionElement();
pendingMessage.setName(TEMPLATE_PENDING_MESSAGE_ID);
ExtensionAttribute pendingMessageAttribute = new ExtensionAttribute();
pendingMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
pendingMessageAttribute.setValue(noticeConf.getPending().getPendingMessageId());
pendingMessage.addAttribute(pendingMessageAttribute);
pendingMessage.setElementText(StringUtils.hasLength(noticeConf.getPending().getViewJson()) ?
noticeConf.getPending().getViewJson() : "");
noticeConfigElement.addChildElement(pendingMessage);
}
// 短信模板配置
if (Objects.nonNull(noticeConf.getSms()) && Objects.nonNull(noticeConf.getSms().getSmsId())) {
ExtensionElement smsMessage = new ExtensionElement();
smsMessage.setName(TEMPLATE_SMS_MESSAGE_ID);
ExtensionAttribute smsMessageAttribute = new ExtensionAttribute();
smsMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
smsMessageAttribute.setValue(noticeConf.getSms().getSmsId());
smsMessage.addAttribute(smsMessageAttribute);
smsMessage.setElementText(StringUtils.hasLength(noticeConf.getSms().getViewJson()) ?
noticeConf.getSms().getViewJson() : "");
noticeConfigElement.addChildElement(smsMessage);
}
mainProcess.addExtensionElement(noticeConfigElement);
}
public static byte[] transformBytes(BpmnModel bpmnModel) {
byte[] xmlMeta = new BpmnXMLConverter().convertToXML(bpmnModel);
String xmlResult = new String(xmlMeta);
log.debug("xmlMeta: " + xmlResult);
return xmlMeta;
}
/**
* 设置 BpmnModel TargetNamespace
*
* @param bpmnModel 被设置的模型
* @param category category 的值
*/
public static void setCategory(BpmnModel bpmnModel, String category) {
bpmnModel.setTargetNamespace(category);
}
/**
* 创建对应类型节点并保存在 bpmnModel
*
* @param preNodeIds 上级节点的 Id 结合,用于链接当前创建节点
* @param bpmnJsonNode 前端传入节点数据,不是全数据,而是对应层级
* @param mainProcess Process 对象
* @param bpmnModel 最终的 BPMN model
* @return 创建的节点的 ID
*/
private static List<String> create(BpmnJsonNode bpmnJsonNode, Process mainProcess,
BpmnModel bpmnModel, String... preNodeIds) {
// 设置来源节点
bpmnJsonNode.setIncoming(Lists.newArrayList(preNodeIds));
Class<? extends BaseElement> clz;
switch (bpmnJsonNode.getType()) {
case NODE_STARTER:
// 该节点主要解决仿钉钉的设计器里面无法设置标准开始和结束节点,而是特殊的一个发起人节点
case NODE_TASK:
clz = UserTask.class;
break;
case NODE_EXCLUSIVE_GATEWAY:
clz = ExclusiveGateway.class;
break;
case NODE_PARALLEL_GATEWAY:
clz = ParallelGateway.class;
break;
case NODE_CONDITION:
// 这个节点非常特殊,整个协议转换完全不会走到这里
clz = SequenceFlow.class;
break;
case NODE_BUSINESS:
// "业务节点"是很特殊包装节点,因为它在前端有两种配法:
// 一种是: 可以不配置人, 当运行到该节点时, 需要由外部发送信号来触发(这个信号的发送方通常是程序应用), 流程继续运行.
// 另外一种是: 可以配置人, 当运行到该节点时, 有需要人为来处理. 这种情况下, 该节点就是一个普通的任务节点.
clz = ReceiveTask.class;
if (!Objects.equals(ApprovalMethodEnum.nobody, bpmnJsonNode.getProperty().getApprovalMethod())) {
clz = UserTask.class;
}
break;
case NODE_TRIGGER:
// 这个类型目前暂不支持
case NODE_CARBON_COPY:
// 这里可以细化, 待后续有实际场景了,再处理, 现目前只有 "抄送" 功能可能会用到
clz = ServiceTask.class;
break;
default:
clz = NotSupportConverter.NotSupportFlowElement.class;
break;
}
// 根据 BpmnJsonNode 创建节点
FlowElement flowElement = convertJsonToElement(clz, bpmnJsonNode, mainProcess);
mainProcess.addFlowElement(flowElement);
if (Lists.newArrayList(preNodeIds).isEmpty()) {
// first time entrance, do nothing.
} else if (Lists.newArrayList(preNodeIds).size() == 1 && !NODE_CONDITION.equals(bpmnJsonNode.getType())) {
mainProcess.addFlowElement(convertJsonToElement(SequenceFlow.class, bpmnJsonNode, mainProcess));
} else {
// 将网关分支的最末级节点 ID 关联到网关的 children 节点上网关协议转换才算闭环
Arrays.stream(preNodeIds).forEach(income -> {
bpmnJsonNode.setIncoming(Lists.newArrayList(income));
FlowElement sequenceFlow = convertJsonToElement(SequenceFlow.class, bpmnJsonNode, mainProcess);
mainProcess.addFlowElement(sequenceFlow);
});
}
// 只有网关才会涉及到 branch
List<String> branchLastNodeIds = new ArrayList<>();
if (!CollectionUtils.isEmpty(bpmnJsonNode.getBranches())) {
Gateway gateway = (Gateway) flowElement;
for (BpmnJsonNode branch : bpmnJsonNode.getBranches()) {
// branch == node_condition
BpmnJsonNode nextJsonNode =
(Objects.isNull(branch.getChildren()) || Objects.isNull(branch.getChildren().getType()) || Objects.equals(NODE_EMPTY, branch.getChildren().getType()))
? bpmnJsonNode.getChildren() : branch.getChildren();
if (Objects.isNull(nextJsonNode) || Objects.isNull(nextJsonNode.getId()) ||
(Objects.equals(NODE_EMPTY, nextJsonNode.getType()) &&
(Objects.isNull(nextJsonNode.getChildren()) || Objects.isNull(nextJsonNode.getChildren().getId())))) {
BpmnJsonNode tempEndNode = new BpmnJsonNode();
tempEndNode.setIncoming(Lists.newArrayList(gateway.getId()));
tempEndNode.setId(END_EVENT_ID);
nextJsonNode = tempEndNode;
} else {
if (Objects.equals(NODE_EMPTY, nextJsonNode.getType()) && Objects.nonNull(nextJsonNode.getChildren())) {
nextJsonNode = nextJsonNode.getChildren();
}
nextJsonNode.setIncoming(Lists.newArrayList(gateway.getId()));
// node_condition 放入计算节点的上级属性中方便顺序流转换器对条件进行处理
nextJsonNode.setPreJsonNode(branch);
}
SequenceFlow sequenceFlow = (SequenceFlow) convertJsonToElement(SequenceFlow.class, nextJsonNode,
mainProcess);
mainProcess.addFlowElement(sequenceFlow);
// 设置网关默认流
if (Objects.nonNull(branch.getProperty()) && Boolean.TRUE.equals(branch.getProperty().getDefaultBranch())) {
// 如果是默认流, 以防止前端输入错误, 强制置空
sequenceFlow.setConditionExpression(null);
gateway.setDefaultFlow(sequenceFlow.getId());
}
if (Objects.nonNull(branch.getChildren()) && StringUtils.hasLength(branch.getChildren().getId())
&& !Objects.equals(NODE_EMPTY, branch.getChildren().getType())) {
branchLastNodeIds.addAll(create(branch.getChildren(), mainProcess, bpmnModel));
}
}
}
// 开始处理下级节点
BpmnJsonNode children = bpmnJsonNode.getChildren();
if (Objects.isNull(children) || (Objects.equals(NODE_EMPTY, children.getType()) && (Objects.isNull(children.getChildren()) || Objects.isNull(children.getChildren().getId()))) || !StringUtils.hasLength(children.getId())) {
if (CollectionUtils.isEmpty(branchLastNodeIds)) {
return Lists.newArrayList(flowElement.getId());
} else {
return branchLastNodeIds;
}
} else {
if (Objects.equals(NODE_EMPTY, children.getType()) && Objects.nonNull(children.getChildren()) && Objects.nonNull(children.getChildren().getId())) {
children = children.getChildren();
}
if (CollectionUtils.isEmpty(branchLastNodeIds)) {
return create(children, mainProcess, bpmnModel, flowElement.getId());
}
return create(children, mainProcess, bpmnModel, branchLastNodeIds.toArray(new String[0]));
}
}
private static void setDefaultFlow(BpmnJsonNode bpmnJsonNode, Process mainProcess, FlowElement sequenceFlow) {
// 查找并设置网关的默认流
if (Objects.nonNull(bpmnJsonNode.getPreJsonNode()) && Objects.nonNull(bpmnJsonNode.getPreJsonNode().getProperty())
&& Boolean.TRUE.equals(bpmnJsonNode.getPreJsonNode().getProperty().getDefaultBranch())) {
FlowElement preFlowNode;
if (Objects.nonNull(preFlowNode =
mainProcess.getFlowElement(bpmnJsonNode.getPreJsonNode().getId(), true))
&& preFlowNode instanceof Gateway) {
Gateway gateway = (Gateway) preFlowNode;
gateway.setDefaultFlow(sequenceFlow.getId());
}
}
}
private static FlowElement convertJsonToElement(Class<? extends BaseElement> clz, Process process) {
return convertJsonToElement(clz, null, process);
}
private static FlowElement convertJsonToElement(Class<? extends BaseElement> clz, BpmnJsonNode bpmnJsonNode,
Process process) {
AbstractBpmnJsonConverter converter = CONVERTERS.getOrDefault(clz, new NotSupportConverter());
FlowElement flowElement = converter.convertJsonToElement(bpmnJsonNode, process);
if (Objects.nonNull(bpmnJsonNode)) {
converter.addNodeTypeAttribute(flowElement, copy(bpmnJsonNode));
converter.addJsonValueAttribute(flowElement, copy(bpmnJsonNode));
}
return flowElement;
}
private static BpmnJsonNode copy(BpmnJsonNode source) {
if (Objects.isNull(source)) {
return null;
}
BpmnJsonNode target = new BpmnJsonNode();
target.setId(source.getId());
target.setParentId(source.getParentId());
target.setType(source.getType());
target.setName(source.getName());
target.setProperty(source.getProperty());
target.setIncoming(source.getIncoming());
return target;
}
public static String id(String prefix) {
return prefix + "_" + UUID.randomUUID().toString().replace("-", "").toLowerCase();
}
public static void main(String[] args) throws IOException {
// String fileName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
// "/resources/配置台模型.json";
// String fileName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
// "/resources/权限点模型.json";
String fileName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
"/resources/temp2.json";
byte[] bytes = Files.readAllBytes(Paths.get(fileName));
String content = new String(bytes, StandardCharsets.UTF_8);
BpmnModelCreateDTO model = JSON.parseObject(content, BpmnModelCreateDTO.class);
BpmnModel bpmnModel = convertToBpmn(model.getJsonModel().getNode(), "id", "测试", "remark",
model.getJsonModel().getNoticeConf(),
model.getJsonModel().getButtonConf(), model.getJsonModel().getFieldConf());
System.out.println("bpmnModel = " + bpmnModel);
convertToJson(bpmnModel);
transformBytes(bpmnModel);
generateImage(bpmnModel);
}
public static void generateImage(BpmnModel bpmnModel) {
String pngName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
"/resources/test.png";
ProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
// 设置字体渲染选项
System.setProperty("java.awt.headless", "false");
System.setProperty("awt.useSystemAAFontSettings", "on");
System.setProperty("swing.aatext", "true");
System.setProperty("sun.java2d.xrender", "true");
try {
// 创建输出流
OutputStream outputStream = Files.newOutputStream(new File(pngName).toPath());
// 生成图像并写入输出流
InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", new ArrayList<>(), 1.0d, true);
// 将图像流写入输出流
try {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = imageStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} catch (Exception e) {
// 处理异常
} finally {
try {
imageStream.close();
outputStream.close();
} catch (Exception e) {
// 处理异常
}
}
// 关闭输出流
outputStream.close();
} catch (Exception e) {
// 处理异常
}
}
}

View File

@ -0,0 +1,272 @@
package cn.axzo.workflow.core.common.utils;
import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
import cn.axzo.workflow.common.model.request.bpmn.BpmnFieldConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnFieldOptionConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeProperty;
import cn.axzo.workflow.common.model.request.bpmn.BpmnPendingProperty;
import cn.axzo.workflow.common.model.request.bpmn.BpmnSmsProperty;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.ReceiveTask;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.Task;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVAL_METHOD;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_EMPTY_HANDLE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_SCOPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_SPECIFY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_META;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CARBON_COPY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CURRENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_HISTORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_META;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_OPTION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NODE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NOTICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CHECKED;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CODE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DISABLED;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_KEY;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_NAME;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_ORDER;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAGE_ID;
/**
* 协助解析 BPMN 文件中的自定义扩展字段和属性
*
* @author wangli
* @since 2023/11/15 22:51
*/
public final class BpmnMetaParserHelper {
private BpmnMetaParserHelper() {}
public static Optional<String> getProcessServerVersion(Process process) {
return Optional.ofNullable(process.getAttributeValue(null, FLOW_SERVER_VERSION));
}
public static Optional<BpmnNoticeConf> getNoticeConfig(Process process) {
List<ExtensionElement> elements = process.getExtensionElements().getOrDefault(CONFIG_NOTICE,
Collections.emptyList());
if (CollectionUtils.isEmpty(elements)) {
return Optional.empty();
}
BpmnNoticeConf conf = new BpmnNoticeConf();
elements.get(0).getChildElements().forEach((k, v) -> {
if (TEMPLATE_NOTICE_MESSAGE_ID.equals(k)) {
BpmnNoticeProperty notice = new BpmnNoticeProperty();
notice.setNoticeMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
notice.setViewJson(v.get(0).getElementText());
conf.setNotice(notice);
} else if (TEMPLATE_PENDING_MESSAGE_ID.equals(k)) {
BpmnPendingProperty pending = new BpmnPendingProperty();
pending.setPendingMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
pending.setViewJson(v.get(0).getElementText());
conf.setPending(pending);
} else if (TEMPLATE_SMS_MESSAGE_ID.equals(k)) {
BpmnSmsProperty sms = new BpmnSmsProperty();
sms.setSmsId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
sms.setViewJson(v.get(0).getElementText());
conf.setSms(sms);
}
});
return Optional.of(conf);
}
public static Optional<BpmnButtonConf> getButtonConfig(Process process) {
return parseButtonConfig(process.getExtensionElements().getOrDefault(CONFIG_BUTTON, Collections.emptyList()));
}
private static Optional<BpmnButtonConf> parseButtonConfig(List<ExtensionElement> elements) {
if (CollectionUtils.isEmpty(elements) || CollectionUtils.isEmpty(elements.get(0).getChildElements())) {
return Optional.empty();
}
BpmnButtonConf conf = new BpmnButtonConf();
elements.get(0).getChildElements().forEach((k, v) -> {
List<BpmnButtonMetaInfo> buttonMetaInfos = new ArrayList<>();
buildButtonMetaInfo(v, buttonMetaInfos);
if (CONFIG_BUTTON_TYPE_INITIATOR.equals(k)) {
conf.setInitiator(buttonMetaInfos);
} else if (CONFIG_BUTTON_TYPE_CURRENT.equals(k)) {
conf.setCurrent(buttonMetaInfos);
} else if (CONFIG_BUTTON_TYPE_HISTORY.equals(k)) {
conf.setHistory(buttonMetaInfos);
} else if (CONFIG_BUTTON_TYPE_CARBON_COPY.equals(k)) {
conf.setCarbonCopy(buttonMetaInfos);
}
});
return Optional.of(conf);
}
public static Optional<BpmnButtonConf> getButtonConfig(Process process, String taskDefinitionKey) {
if (StringUtils.hasLength(taskDefinitionKey)) {
Task task = (Task) process.getFlowElement(taskDefinitionKey);
if (Objects.isNull(task)) {
return getButtonConfig(process);
} else {
Optional<BpmnButtonConf> optConfig =
parseButtonConfig(task.getExtensionElements().getOrDefault(CONFIG_BUTTON,
Collections.emptyList()));
if (optConfig.isPresent()) {
return optConfig;
} else {
return getButtonConfig(process);
}
}
} else {
return getButtonConfig(process);
}
}
private static void buildButtonMetaInfo(List<ExtensionElement> v, List<BpmnButtonMetaInfo> buttonMetaInfos) {
v.get(0).getChildElements().get(CONFIG_BUTTON_META).forEach(i -> {
BpmnButtonMetaInfo buttonMetaInfo = new BpmnButtonMetaInfo();
buttonMetaInfo.setBtnKey(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_KEY));
buttonMetaInfo.setBtnName(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_NAME));
buttonMetaInfo.setChecked(Boolean.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED)));
buttonMetaInfo.setOrder(Integer.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_ORDER)));
buttonMetaInfo.setDisabled(Boolean.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_DISABLED)));
buttonMetaInfos.add(buttonMetaInfo);
});
}
public static Optional<List<BpmnFieldConf>> getFieldConfig(Process process) {
List<ExtensionElement> elements = process.getExtensionElements().getOrDefault(CONFIG_FIELD,
Collections.emptyList());
if (CollectionUtils.isEmpty(elements)) {
return Optional.empty();
}
List<BpmnFieldConf> fields = new ArrayList<>();
elements.get(0).getChildElements().getOrDefault(CONFIG_FIELD_META, Collections.emptyList()).forEach(i -> {
BpmnFieldConf conf = new BpmnFieldConf();
conf.setType(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_TYPE));
conf.setName(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_NAME));
conf.setCode(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_CODE));
List<BpmnFieldOptionConf> options = new ArrayList<>();
if (!CollectionUtils.isEmpty(i.getChildElements())) {
i.getChildElements().get(CONFIG_FIELD_OPTION).forEach(j -> {
BpmnFieldOptionConf option = new BpmnFieldOptionConf();
option.setName(j.getAttributeValue(null, ELEMENT_ATTRIBUTE_NAME));
option.setValue(j.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
options.add(option);
});
conf.setOptions(options);
}
fields.add(conf);
});
return Optional.of(fields);
}
public static Optional<BpmnFlowNodeType> getNodeType(FlowElement flowElement) {
if (flowElement instanceof UserTask
|| flowElement instanceof ServiceTask
|| flowElement instanceof ReceiveTask) {
return defaultValid(flowElement, CONFIG_NODE_TYPE)
.map(element -> BpmnFlowNodeType.valueOf(element.getElementText()));
}
return Optional.empty();
}
public static Optional<ApprovalMethodEnum> getApprovalMethod(UserTask userTask) {
return defaultValid(userTask, CONFIG_APPROVAL_METHOD)
.map(element -> ApprovalMethodEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)));
}
public static Optional<ApproverScopeEnum> getApproverScope(UserTask userTask) {
return defaultValid(userTask, CONFIG_APPROVER_SCOPE)
.map(element -> ApproverScopeEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)));
}
public static Optional<ApproverSpecifyEnum> getApproverSpecify(UserTask userTask) {
return defaultValid(userTask, CONFIG_APPROVER_SPECIFY)
.map(element -> ApproverSpecifyEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)));
}
public static Optional<String> getApproverSpecifyValue(UserTask userTask) {
return defaultValid(userTask, CONFIG_APPROVER_SPECIFY)
.map(element -> StringUtils.hasLength(element.getElementText())
? element.getElementText()
: "[]");
}
public static Optional<ApproverEmptyHandleTypeEnum> getApproverEmptyHandleType(UserTask userTask) {
return defaultValid(userTask, CONFIG_APPROVER_EMPTY_HANDLE_TYPE)
.map(element -> ApproverEmptyHandleTypeEnum.valueOf(element.getAttributeValue(null,
ELEMENT_ATTRIBUTE_VALUE)));
}
public static Optional<String> getEmptyApproverSpecify(UserTask userTask) {
return defaultValid(userTask, CONFIG_APPROVER_EMPTY_HANDLE_TYPE)
.map(element -> StringUtils.hasLength(element.getElementText())
? element.getElementText()
: "[]");
}
private static Optional<ExtensionElement> defaultValid(FlowElement flowElement, String elementName) {
if (CollectionUtils.isEmpty(flowElement.getExtensionElements())
|| !flowElement.getExtensionElements().containsKey(elementName)) {
return Optional.empty();
}
List<ExtensionElement> elements = flowElement.getExtensionElements().getOrDefault(elementName,
Collections.emptyList());
if (CollectionUtils.isEmpty(elements)) {
return Optional.empty();
}
return Optional.ofNullable(elements.get(0));
}
public static Optional<UserTask> getPreNode(FlowElement userTask, DelegateExecution execution,
BpmnModel bpmnModel) {
if (Objects.isNull(bpmnModel)) {
bpmnModel = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId());
}
if (userTask instanceof UserTask) {
UserTask task = (UserTask) userTask;
if (CollectionUtils.isEmpty(task.getIncomingFlows())) {
return Optional.empty();
}
//preNode
FlowElement flowElement = bpmnModel.getFlowElement(task.getIncomingFlows().get(0).getSourceRef());
if (Objects.isNull(flowElement)) {
return Optional.empty();
} else if (!(flowElement instanceof UserTask)) {
return getPreNode(flowElement, execution, bpmnModel);
} else {
return Optional.of((UserTask) flowElement);
}
}
return Optional.empty();
}
}

View File

@ -15,17 +15,17 @@ import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class BpmMyBatisUtils {
public class BpmnMyBatisUtils {
private static final String MYSQL_ESCAPE_CHARACTER = "`";
public BpmMyBatisUtils() {
public BpmnMyBatisUtils() {
}
public static <T> Page<T> buildPage(BpmPageParam pageParam) {
return buildPage(pageParam, (Collection)null);
}
public static <T> Page<T> buildPage(BpmPageParam pageParam, Collection<BpmSortingField> sortingFields) {
public static <T> Page<T> buildPage(BpmPageParam pageParam, Collection<BpmnSortingField> sortingFields) {
Page<T> page = new Page((long)pageParam.getPageNo(), (long)pageParam.getPageSize());
if (!CollectionUtil.isEmpty(sortingFields)) {
page.addOrder((List)sortingFields.stream().map((sortingField) -> {

View File

@ -7,9 +7,9 @@ package cn.axzo.workflow.core.common.utils;
public class BpmnNativeQueryUtil {
public static String sqlConnectors(StringBuilder stringBuilder) {
if (stringBuilder.indexOf("WHERE") < 0) {
if (stringBuilder.indexOf("ON") < 0 && stringBuilder.indexOf("WHERE") < 0) {
return " WHERE";
} else if (stringBuilder.indexOf("LEFT") >= 0 && stringBuilder.indexOf("ON") < 0) {
} else if (stringBuilder.indexOf("JOIN") >= 0 && stringBuilder.indexOf("ON") < 0) {
return " ON";
}
return " AND";
@ -20,7 +20,7 @@ public class BpmnNativeQueryUtil {
if ((start = stringBuilder.indexOf("SELECT")) < 0) {
return stringBuilder.toString();
}
if (stringBuilder.indexOf("LEFT JOIN") < 0) {
if (stringBuilder.indexOf("JOIN") < 0) {
return stringBuilder.replace(start + 7, 8, "count(1)").toString();
} else {
return stringBuilder.replace(start + 7, 10, "count(1)").toString();

View File

@ -2,16 +2,16 @@ package cn.axzo.workflow.core.common.utils;
import java.io.Serializable;
public class BpmSortingField implements Serializable {
public class BpmnSortingField implements Serializable {
public static final String ORDER_ASC = "asc";
public static final String ORDER_DESC = "desc";
private String field;
private String order;
public BpmSortingField() {
public BpmnSortingField() {
}
public BpmSortingField(String field, String order) {
public BpmnSortingField(String field, String order) {
this.field = field;
this.order = order;
}
@ -20,7 +20,7 @@ public class BpmSortingField implements Serializable {
return this.field;
}
public BpmSortingField setField(String field) {
public BpmnSortingField setField(String field) {
this.field = field;
return this;
}
@ -29,7 +29,7 @@ public class BpmSortingField implements Serializable {
return this.order;
}
public BpmSortingField setOrder(String order) {
public BpmnSortingField setOrder(String order) {
this.order = order;
return this;
}

View File

@ -0,0 +1,29 @@
package cn.axzo.workflow.core.common.utils;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import com.alibaba.ttl.TransmittableThreadLocal;
/**
* 线程本地变量表
*
* @author wangli
* @since 2023/11/18 21:42
*/
public class ContextHolder {
private static final TransmittableThreadLocal<BpmnTaskDelegateAssigner> CONTEXT = new TransmittableThreadLocal<>();
private ContextHolder() {}
public static void set(BpmnTaskDelegateAssigner assigner) {
CONTEXT.set(assigner);
}
public static BpmnTaskDelegateAssigner get() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}

View File

@ -193,7 +193,6 @@ public class DateUtils extends PropertyEditorSupport {
try {
_date = sformat.parse(date);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sformat.format(_date);

Some files were not shown because too many files have changed in this diff Show More