Compare commits

...

232 Commits
master ... dev

Author SHA1 Message Date
006932a9e5 Merge branch 'feature/REQ-5965' into dev 2025-12-22 14:23:02 +08:00
ecdd054c12 feat(REQ-5965) - 调整处理条件变量字段的逻辑 2025-12-22 14:21:15 +08:00
253e8d4eb6 Merge branch 'feature/REQ-5965' into dev 2025-12-22 14:15:37 +08:00
f5415dd354 feat(REQ-5965) - 处理图片类型为空时,设置为透明默认图 2025-12-22 14:14:10 +08:00
a1cb68cafa Merge branch 'feature/REQ-5965' into dev 2025-12-22 11:09:10 +08:00
94b9a0848c feat(REQ-5965) - 兼容历史的存储模式 2025-12-22 11:08:40 +08:00
6af33ce3b6 Merge remote-tracking branch 'origin/dev' into dev 2025-12-22 10:41:12 +08:00
24db7ff862 Merge branch 'feature/REQ-5965' into dev 2025-12-22 10:40:52 +08:00
34d4cdea1e feat(REQ-5965) - 调整单选复现条件的存储模式 2025-12-22 10:40:06 +08:00
a7dc35862a Merge remote-tracking branch 'origin/master' into feature/REQ-5965
# Conflicts:
#	workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessActivityController.java
2025-12-22 10:17:24 +08:00
05e7ad4563 feat(REQ-5965) - 调整原来的方法 2025-12-22 10:15:33 +08:00
f9ab24fcea Merge remote-tracking branch 'origin/feature/REQ-5965' into feature/REQ-5965
# Conflicts:
#	workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/WpsUtil.java
2025-12-22 10:00:14 +08:00
wangli
b85c2ac097 feat(REQ-5965) - 调整同意审批时记录审批人信息时 NPE 的异常 2025-12-20 10:48:44 +08:00
wangli
3b1c1b3ba8 feat(REQ-5965) - 调整业务自定义文档在存入 process_sign 表时,丢失属性的情况 2025-12-20 02:26:54 +08:00
wangli
1e4d9dcca5 feat(REQ-5965) - 调整业务自定义文档在存入 process_sign 表时,丢失属性的情况 2025-12-20 02:24:39 +08:00
wangli
6bf313d78a feat(REQ-5965) - 调整业务自定义文档在存入 process_sign 表时,丢失属性的情况 2025-12-20 01:54:13 +08:00
wangli
e322a62002 feat(REQ-5965) - 调整业务自定义文档在存入 process_sign 表时,丢失属性的情况 2025-12-20 01:41:21 +08:00
wangli
8b7a79b8d0 feat(REQ-5965) - 调整业务自定义文档在存入 process_sign 表时,丢失属性的情况 2025-12-20 01:13:11 +08:00
wangli
5b769faf6e feat(REQ-5965) - 优化审批同意时,记录操作人信息 2025-12-20 00:34:11 +08:00
wangli
b94ca42ca0 feat(REQ-5965) - 优化审批同意时,记录操作人信息 2025-12-19 23:41:18 +08:00
wangli
88b9e8e148 feat(REQ-5965) - 增加日志打印 2025-12-19 23:16:43 +08:00
5e0204cd73 feat(REQ-5965) - 新增wps 替换变量时,需要对无内存变量修改为空串 2025-12-19 18:10:43 +08:00
cef429d4ab feat(REQ-5965) - 调整模型更新时,条件权限的数据类型 2025-12-19 17:55:46 +08:00
王粒
1bd164af25 Merge branch 'feature/REQ-5965' into 'dev'
feat(REQ-5965) - 调整按照工程过滤时,由于开启了过滤,但未传入工程导致的 NPE

See merge request universal/infrastructure/backend/workflow-engine!25
2025-12-19 08:37:04 +00:00
7d53d2ad71 feat(REQ-5965) - 调整按照工程过滤时,由于开启了过滤,但未传入工程导致的 NPE 2025-12-19 16:35:37 +08:00
wangli
e3e30f9d71 Merge branch 'feature/REQ-5965' into dev 2025-12-19 11:23:41 +08:00
wangli
16bcecb62b feat(REQ-5965) - 调整“查看文档”按钮无法正确打开业务传入的文档 2025-12-19 11:23:22 +08:00
c0fff77824 Merge branch 'feature/REQ-5965' into dev 2025-12-18 11:23:16 +08:00
fa1acf573d feat(REQ-5965) - 调整创建审批的入参,支持业务文档可以不进行变量替换 2025-12-18 11:22:44 +08:00
9429126cd8 Merge branch 'feature/RDMP-3845' into dev 2025-12-18 10:23:54 +08:00
98f560dc77 feat(REQ-3845) - 调整获取流程模型表单定义内容 2025-12-18 10:23:25 +08:00
da8b379058 Merge branch 'hotfix/async_cancel' into dev 2025-12-11 09:49:21 +08:00
e6cb590f6a fix - 异步撤回功能修复 2025-12-10 19:53:07 +08:00
b6b6dfa7a5 Merge branch 'feature/REQ-5965' into dev 2025-12-03 14:58:10 +08:00
cf3618ae67 feat(REQ-5965) - 移除变量日志打印 2025-12-03 14:57:47 +08:00
eeadc34060 Merge branch 'feature/RDMP-3845' into dev 2025-12-03 14:51:48 +08:00
b8d5fb8778 Merge branch 'feature/REQ-5965' into dev 2025-12-03 14:51:36 +08:00
e264b5b59b feat(REQ-5965) - 调整单选条件的表达式设置 2025-12-03 14:50:58 +08:00
279d066b3b feat(REQ-5965) - 调整待办发送时的变量获取方式 2025-12-03 14:19:03 +08:00
43fc084016 Merge branch 'feature/REQ-5965' into dev 2025-12-03 10:42:37 +08:00
6b83bc655b feat(REQ-5965) - 调整待办发送时的变量获取方式 2025-12-03 10:42:16 +08:00
7b93802e7b Merge branch 'feature/REQ-5965' into dev 2025-12-03 10:13:04 +08:00
783d441a8d feat(REQ-5965) - 调整待办发送时的变量获取方式 2025-12-03 10:12:03 +08:00
2f4156c14f Merge branch 'feature/REQ-5965' into dev 2025-12-02 19:45:52 +08:00
5172810fa5 feat(REQ-5965) - 调整待办发送时的变量获取方式 2025-12-02 19:45:30 +08:00
e9acaaddf5 Merge branch 'feature/REQ-5965' into dev 2025-12-02 18:53:04 +08:00
74a12dcd28 feat(REQ-5965) - 增加发送待办前的变量日志 2025-12-02 18:51:53 +08:00
wangli
09cdb84b41 Merge branch 'feature/REQ-5965' into dev 2025-11-28 16:05:07 +08:00
wangli
e99de0c2a2 feat(REQ-5965) - 调整系统操作 form 表单 2025-11-28 16:03:18 +08:00
09cae5c96e Merge branch 'feature/REQ-5865' into dev 2025-11-28 15:45:59 +08:00
c980ddfc45 Merge branch 'feature/REQ-5865' into dev
# Conflicts:
#	workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerCmd.java
2025-11-28 15:15:05 +08:00
15c0cb69b0 Merge branch 'feature/REQ-5965' into dev 2025-11-28 14:21:20 +08:00
753e68a55d feat(REQ-5965) - 完善 web/process/form 同意和驳回传 personID=0 自动选择审批人同意 2025-11-28 14:20:55 +08:00
edd4c57a7a feat(REQ-5965) - 完善 web/process/form 同意和驳回传 personID=0 自动选择审批人同意 2025-11-28 14:20:22 +08:00
9993ea1c4b Merge branch 'feature/RDMP-3845' into dev 2025-11-27 19:14:41 +08:00
f59b795e22 feat(RDMP-3845) - 处理模型响应时,单选复选的 options 丢失问题 2025-11-27 19:14:10 +08:00
357d7e0a03 feat(REQ-5965) - 调整 trigger 日志,记录请求方服务 2025-11-27 15:03:36 +08:00
12d6b5c466 feat(REQ-5965) - 调整 trigger 日志,记录请求方服务 2025-11-27 14:48:33 +08:00
6c51985a03 Merge branch 'feature/REQ-6570' into dev 2025-11-26 14:49:26 +08:00
c2f6ab1a08 Merge branch 'feature/REQ-5965' into dev 2025-11-25 15:40:06 +08:00
8e628d9769 feat(REQ-5965) - 管理实例的授权码输入框改成密码风格 2025-11-25 15:39:26 +08:00
0f24168d32 Merge branch 'feature/RDMP-3845' into dev 2025-11-25 10:29:17 +08:00
141151acc3 feat(RDMP-3845) - 调整复选框消费时的多值链接符 2025-11-25 10:27:12 +08:00
61d4b5cd4f Merge branch 'feature/REQ-5865' into dev 2025-11-25 10:10:40 +08:00
832b3d36e0 Merge branch 'feature/REQ-5965' into dev 2025-11-24 11:53:41 +08:00
65b8d9f9c4 Merge branch 'feature/RDMP-3845' into dev 2025-11-24 11:53:34 +08:00
75842bcb96 feat(REQ-5965) - feignAPI 接口实现,获取客户端服务名称信息 2025-11-24 11:52:41 +08:00
b8c0405795 feat(RDMP-3845) - 调整单选复选框没有设置 options 导致的问题 2025-11-24 11:51:09 +08:00
7f08a971c6 Merge branch 'feature/REQ-5965' into dev 2025-11-21 17:25:15 +08:00
0c53b57a9c feat(REQ-5965) - 调整 api 生成位置 2025-11-21 17:24:56 +08:00
333de1e1af feat(RDMP-3845) - 调整 MQ 广播事件中的流程变量输出,过滤一些对业务无关紧要的数据 2025-11-21 16:00:53 +08:00
0ceacfb3bd Merge branch 'feature/REQ-5965' into dev 2025-11-21 13:34:43 +08:00
c9acc9cad8 feat(RDMP-3845) - 调整 MQ 广播事件中的流程变量输出,过滤一些对业务无关紧要的数据 2025-11-21 13:34:17 +08:00
4d16b99b86 Merge branch 'feature/RDMP-3845' into dev 2025-11-21 11:00:23 +08:00
f6847ad087 Merge branch 'feature/REQ-5965' into dev 2025-11-21 11:00:09 +08:00
4694ed6280 feat(RDMP-3845) - 调整 MQ 广播事件中的流程变量输出,过滤一些对业务无关紧要的数据 2025-11-21 10:59:35 +08:00
f1d93a4d0f feat(RDMP-3845) - 调整 MQ 广播事件中的流程变量输出,过滤一些对业务无关紧要的数据 2025-11-21 10:58:28 +08:00
d9ecc6ce35 Merge branch 'feature/RDMP-3845' into dev 2025-11-20 17:00:49 +08:00
e6d260a05b feat(RDMP-3845) - wps 文档变量替换逻辑完善,支持单选复选 2025-11-20 17:00:02 +08:00
2785433054 Merge branch 'feature/REQ-5865' into dev 2025-11-20 15:53:12 +08:00
57c433a620 Merge branch 'feature/RDMP-3845' into dev 2025-11-20 14:32:58 +08:00
37d6c117fb feat(RDMP-3845) - 调整公共打印模板返回单选、复选框的数据 2025-11-20 14:32:35 +08:00
cf118d42f6 Merge branch 'feature/RDMP-3845' into dev 2025-11-20 13:58:48 +08:00
530a8f6206 feat(RDMP-3845) - 调整公共打印模板返回单选、复选框的数据 2025-11-20 13:57:17 +08:00
2d0c4a9b70 Merge branch 'feature/REQ-5865' into dev 2025-11-20 11:41:10 +08:00
4833b4b85e feat(RDMP-3845) - 新增单选、复选的代码逻辑 2025-11-20 11:36:43 +08:00
d9c1b037d0 Merge branch 'feature/RDMP-3845' into dev 2025-11-20 11:04:04 +08:00
ce0630f831 feat(RDMP-3845) - 新增单选、复选的代码逻辑 2025-11-20 11:03:41 +08:00
464e94f368 feat(RDMP-3845) - 新增单选、复选的代码逻辑 2025-11-19 19:52:50 +08:00
03757f4a0f Merge branch 'feature/REQ-5965' into dev 2025-11-19 17:03:20 +08:00
d2e82a4f6b feat(REQ-5965) - 调整 URL 2025-11-19 17:02:59 +08:00
1ff58b6371 Merge branch 'feature/REQ-5965' into dev 2025-11-19 16:49:43 +08:00
9b1f366e05 feat(REQ-5965) - 结合容器环境返回完全的 url 地址 2025-11-19 16:49:20 +08:00
0944d584d5 Merge branch 'feature/REQ-5965' into dev 2025-11-19 16:06:53 +08:00
6b39b5e413 feat(REQ-5965) - 结合容器环境返回完全的 url 地址 2025-11-19 16:05:57 +08:00
b28b2871fc Merge branch 'feature/REQ-5965' into dev 2025-11-19 15:54:46 +08:00
42999bedae feat(REQ-5965) - 结合容器环境返回完全的 url 地址 2025-11-19 15:54:23 +08:00
475b86896a Merge branch 'feature/REQ-5965' into dev 2025-11-19 15:33:21 +08:00
43702038f5 feat(REQ-5965) - 调整网页,配合显示后端的异常信息 2025-11-19 15:33:01 +08:00
d1833584b2 Merge branch 'feature/REQ-5965' into dev 2025-11-19 15:26:04 +08:00
b20861ee56 feat(REQ-5965) - 调整网页代码 2025-11-19 15:25:43 +08:00
9f05c76d32 feat(REQ-5965) - 增加ctx 2025-11-19 14:51:56 +08:00
f670109959 Merge branch 'feature/REQ-5965' into dev 2025-11-19 14:33:46 +08:00
25ef7e9f58 feat(REQ-5965) - 增加授权码获取逻辑 2025-11-19 14:33:29 +08:00
4f7f75bde2 feat(REQ-5965) - 增加授权码获取逻辑 2025-11-19 14:30:42 +08:00
ac18aa7bd9 feat(REQ-5965) - 增加授权码逻辑 2025-11-19 13:47:39 +08:00
624f1e5b92 Merge branch 'feature/REQ-5965' into dev 2025-11-19 10:29:23 +08:00
8cfb698553 feat(REQ-5965) - 完善通过界面操作流程的功能 2025-11-19 10:27:08 +08:00
b52408571d feat(REQ-5965) - 新增流程后端推动操作 2025-11-19 10:13:56 +08:00
d45aeec5bb feat(REQ-5965) - 调整创建流程的 API 入参模型注释 2025-11-18 10:45:19 +08:00
1e52b12bf1 Merge branch 'feature/REQ-5965' into dev 2025-11-14 14:32:15 +08:00
cd3ee2f46a feat(REQ-5965) - 优化流程日志 PDF 生成逻辑 2025-11-14 14:31:25 +08:00
ebdf81e3a9 Merge branch 'feature/REQ-5965' into dev 2025-11-13 18:10:51 +08:00
edf617cb6b feat(REQ-5965) - 调整查询审批日期 PDF的参数模型 2025-11-13 18:07:01 +08:00
d274905609 Merge branch 'feature/REQ-5965' into dev 2025-11-13 15:51:50 +08:00
6a23d85806 feat(REQ-5965) - 调整接口内部逻辑 2025-11-13 15:49:58 +08:00
90daa5f77b Merge branch 'feature/REQ-5965' into dev 2025-11-11 14:06:50 +08:00
6cd2bf9da8 Merge remote-tracking branch 'origin/master' into feature/REQ-5965 2025-11-11 14:06:04 +08:00
34d0ff8ffc Merge branch 'feature/REQ-5965' into dev 2025-11-10 16:47:26 +08:00
f4f97fdbf3 feat(REQ-5965) - 解决查询流程文档 api 内部进支持运行时的情况 2025-11-10 16:46:16 +08:00
8e4b009483 Merge branch 'feature/REQ-5965' into dev 2025-11-10 15:29:12 +08:00
127a692b7a feat(REQ-5965) - 解决流程没有传入自定义文档时的NPE 2025-11-10 15:28:45 +08:00
3ff1654e65 Merge branch 'feature/REQ-5965' into dev 2025-11-10 15:28:01 +08:00
91e81a9a78 feat(REQ-5965) - 解决流程没有传入自定义文档时的NPE 2025-11-10 15:27:38 +08:00
20bf9e4d7b Merge branch 'feature/REQ-5965' into dev 2025-11-10 15:17:48 +08:00
62aec2b9ee feat(REQ-5965) - 解决流程没有传入自定义文档时的NPE 2025-11-10 15:17:30 +08:00
f135a14eb5 Merge branch 'feature/REQ-5965' into dev 2025-11-10 11:43:35 +08:00
9b2d95cdde feat(REQ-5965) - 流程引擎测增加审批日志pdf 结果查询 API 2025-11-10 11:43:07 +08:00
37871f67e7 Merge branch 'feature/REQ-5965' into dev 2025-11-07 18:24:44 +08:00
2d25ff475d feat(REQ-5965) - 流程引擎测增加审批日志pdf 结果查询 API 2025-11-07 18:23:35 +08:00
f117062124 Merge branch 'feature/REQ-5965' into dev 2025-11-07 17:24:01 +08:00
956c7d1314 feat(REQ-5965) - 调整在待办中查看附件的顺序 2025-11-07 17:22:49 +08:00
86f9d925c8 Merge branch 'hotfix/20251107_mq_header_error' into feature/REQ-5965 2025-11-07 16:17:21 +08:00
8722e5baea feat(REQ-5965) - 调整同意功能的日志数据存储 2025-11-07 15:53:27 +08:00
48feb8f46c Merge branch 'feature/REQ-5965' into dev 2025-11-06 18:36:57 +08:00
ccf56781fc feat(REQ-5965) - 移除同意操作时的空值变量 2025-11-06 18:33:23 +08:00
3a755e0d90 feat(REQ-5965) - 解决 ibatis 注解不能正常响应 json 字段的问题 2025-11-06 18:32:43 +08:00
9d7507ef9f feat(REQ-5965) - 完善审批任务入参注释 2025-11-06 18:32:17 +08:00
b72909449b feat(REQ-5965) - 防御性编程,兼容 NPE 2025-11-06 18:31:42 +08:00
2e64ce2063 Merge branch 'feature/REQ-5965' into dev 2025-11-06 11:52:33 +08:00
3988c3bb60 feat(REQ-5965) - 调整因组织不存在设置的节点日志隐藏 2025-11-06 11:52:11 +08:00
afa34eba61 Merge remote-tracking branch 'origin/master' into dev 2025-11-05 18:03:13 +08:00
8838fca027 Merge branch 'feature/REQ-5965' into dev 2025-11-05 17:06:32 +08:00
98ec67641f feat(REQ-5965) - 调整查询条件的逻辑 2025-11-05 17:06:15 +08:00
0602f69140 Merge remote-tracking branch 'origin/feature/REQ-5965' into dev 2025-11-05 10:31:03 +08:00
1800d98711 feat(REQ-5965) - 调整包含业务自定义文档后的内部已读逻辑 2025-11-05 10:30:35 +08:00
377dca787e Merge branch 'feature/REQ-5965' into dev 2025-11-04 15:30:32 +08:00
8af2839cef feat(REQ-5965) - 调整日志内部处理字段的逻辑 2025-11-04 15:30:05 +08:00
7d09b1c2ec Merge branch 'feature/REQ-5965' into dev 2025-11-04 13:59:12 +08:00
48cbd02164 feat(REQ-5965) - 调整创建审批日期转 pdf 的任务内部逻辑 2025-11-04 13:55:07 +08:00
e2802ec78e feat(REQ-5965) - 调整创建审批的入参字段注释 2025-11-04 10:56:25 +08:00
3e79c7d9fd Merge branch 'feature/REQ-5965' into dev 2025-11-04 09:52:27 +08:00
496eef6b5c feat(REQ-5965) - 调整第二版日志打印接口的逻辑,优化网址的传入 2025-11-04 09:34:52 +08:00
33897ac687 feat(REQ-5965) - 处理节点隐藏逻辑 2025-11-03 10:45:09 +08:00
a662e6fab7 feat(REQ-5965) - 新增创建审批流日志的 PDF 的异步任务 2025-10-31 17:41:45 +08:00
8a05ffd968 Merge branch 'master' into dev 2025-10-31 13:58:30 +08:00
51d5ea99b8 Merge branch 'feature/REQ-5965' into dev 2025-10-30 16:06:30 +08:00
c26659c364 feat(REQ-5965) - 调整接口内部逻辑 2025-10-30 16:04:45 +08:00
744b8f19ac feat(REQ-5965) - 调整接口内部逻辑 2025-10-30 15:59:25 +08:00
f7221a53e2 Merge branch 'feature/REQ-5965' into dev 2025-10-30 15:45:40 +08:00
91d263bc10 feat(REQ-5965) - 调整接口内部逻辑 2025-10-30 15:45:17 +08:00
8e23dcdc8c Merge branch 'feature/REQ-5965' into dev 2025-10-30 15:19:07 +08:00
50c423d7d2 feat(REQ-5965) - 调整接口模型的注解配置 2025-10-30 15:18:36 +08:00
7f235042fa Merge branch 'feature/REQ-5965' into dev 2025-10-30 14:59:49 +08:00
388de9eb52 feat(REQ-5965) - 新增审批日志打印的数据接口 2025-10-30 14:57:40 +08:00
cdbe89c05a feat(REQ-5965) - 调整 BPMN 协议,增加条件字段权限配置信息 2025-10-29 18:31:43 +08:00
936833b73e feat(REQ-5965) - 新增获取流程节点下的条件字段权限配置信息 2025-10-29 18:30:56 +08:00
1f54f82c34 feat(REQ-5965) - 新增获取流程节点下的条件字段权限配置信息 2025-10-29 18:30:43 +08:00
4c9d91e154 feat(REQ-5965) - 调整调用二方人钢架的接口传参 2025-10-29 18:29:20 +08:00
5c47507f83 feat(REQ-5965) - 新增条件字段权限控制属性 2025-10-28 14:36:16 +08:00
58ad7dfe8b Merge remote-tracking branch 'origin/master' into feature/countersign_ext 2025-10-27 14:13:50 +08:00
57e698061f Merge branch 'feature/REQ-5250' into dev 2025-10-24 18:36:47 +08:00
bb57f7eb92 feat - 解决流程引擎框架中的 NPE 2025-10-24 17:49:47 +08:00
5ef2d94adc feat - 流程实例创建接口调整参数 2025-10-24 17:39:23 +08:00
06301087d5 feat - 流程实例创建接口调整参数 2025-10-24 17:39:06 +08:00
wangli
8d97f6ffc3 fix - 调整工作流的入参,支持完整控制顺序 2025-10-24 16:12:55 +08:00
50ae5ed25b feat - 新增更新流程实例变量的接口 2025-10-24 13:47:26 +08:00
a0244b7dca feat - 新增文档配置页,审批节点的二级属性 2025-10-23 16:38:04 +08:00
0a8a58d71f Merge branch 'master' into dev
# Conflicts:
#	workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java
2025-10-23 14:46:37 +08:00
c7bff3bf34 Merge remote-tracking branch 'origin/master' into feature/countersign_ext
# Conflicts:
#	workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java
#	workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java
2025-10-23 09:56:09 +08:00
a7e28e6914 Merge branch 'feature/countersign_ext' into dev
# Conflicts:
#	workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java
2025-10-22 10:12:19 +08:00
5298b5f320 feat - 修复测试仅业务自定义文档是发现的一些问题 2025-10-22 10:09:38 +08:00
8b1fc0e108 Merge branch 'feature/countersign_ext' into dev 2025-10-21 11:51:34 +08:00
ba95e4850f feat - 新增创建流程实例时,支持业务传入自定义文档 2025-10-21 10:51:48 +08:00
c4701c1bd3 Merge branch 'feature/countersign_ext' into dev 2025-10-20 19:59:20 +08:00
0161cddf03 feat - 调整启动时的动态加签恢复可能产生的 NPE 2025-10-20 19:58:45 +08:00
67a760d083 Merge branch 'feature/countersign_ext' into dev 2025-10-20 19:44:13 +08:00
c6c62f2f85 feat - 同意的动作,增加修改或新增流程变量的逻辑 2025-10-20 19:31:41 +08:00
1070085454 feat - 审批日志增加节点其他属性 2025-10-20 18:12:03 +08:00
3b771aa153 feat - 表单字段用于打印模板,文档模板左侧时,增加是否支持打印的参数控制 2025-10-20 17:30:13 +08:00
ba439c5706 Merge branch 'feature/countersign_ext' into dev 2025-10-20 16:54:38 +08:00
edaeb9101c feat - 审批人同意时,添加操作日期和审批结果 2025-10-20 16:29:42 +08:00
0bf8a65f6f Merge branch 'feature/countersign_ext' into dev 2025-10-20 15:41:46 +08:00
a37fc25db5 feat - 修改加签逻辑中的节点名称处理生成异常的问题 2025-10-20 15:41:22 +08:00
a1e65b73f6 Merge branch 'feature/countersign_ext' into dev 2025-10-20 14:39:49 +08:00
a422f44c69 feat - 修改后加签实现逻辑,采用前加签相同原理实现 2025-10-20 14:39:25 +08:00
7479f983f2 feat - 调整加签次数记录变量名 2025-10-20 13:48:02 +08:00
c8daa07437 Merge branch 'feature/countersign_ext' into dev 2025-10-20 13:39:36 +08:00
ad1efc673a feat - 加签实现调整新节点的 ID 生成逻辑 2025-10-20 13:38:44 +08:00
64cb5b5098 Merge branch 'feature/countersign_ext' into dev 2025-10-20 11:23:02 +08:00
7ac49ca863 feat - 解决编译问题 2025-10-20 11:22:38 +08:00
d44367db5a feat - 移除旧的告警节点 2025-10-20 11:19:19 +08:00
74af59508d Merge branch 'feature/countersign_ext' into dev
# Conflicts:
#	workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java
2025-10-20 11:16:54 +08:00
1e4d9e2ea9 feat - 项目启动完成后,提供 web 服务前,恢复动态加签逻辑 2025-10-20 11:15:41 +08:00
4d1193d333 feat - 新增一张加签记录表,用于 JVM 实例重启后的动态模型重载 2025-10-17 18:39:14 +08:00
565e6d320d feat - 测试前加签代码逻辑 2025-10-17 16:26:22 +08:00
770b5bc9d3 Merge branch 'feature/countersign_ext' into dev 2025-10-17 13:41:01 +08:00
c8238ba1ba feat - 增加前加签代码逻辑 2025-10-15 20:46:11 +08:00
2b81ac21f4 Merge remote-tracking branch 'origin/dev' into dev 2025-10-15 11:11:02 +08:00
015bcd8f53 Merge branch 'feature/countersign_ext' into dev 2025-10-15 11:10:57 +08:00
TanJ
b2653baec2 Merge branch 'feature/RDMP-5834' into dev 2025-10-15 10:10:03 +08:00
5187af00cd feat - 增加后加签的代码,待测试 2025-10-14 20:03:59 +08:00
047c035d38 Merge branch 'refs/heads/feature/countersign_ext' into dev 2025-10-14 17:40:22 +08:00
56ad249278 Merge branch 'feature/REQ-5369' into dev 2025-10-11 18:30:09 +08:00
0a1be99b3e feat(REQ-5965) - 添加前加签代码逻辑,自测流程引擎内部是否正常 2025-10-11 18:06:12 +08:00
e555b06a85 Merge branch 'feature/countersign_ext' into dev 2025-10-10 15:28:39 +08:00
f461d17d2c feat(REQ-5965) - 添加前加签代码逻辑,自测流程引擎内部是否正常 2025-10-10 15:27:19 +08:00
678e19c3a6 feat(REQ-5965) - 前后加签功能文案调整 2025-10-10 15:00:36 +08:00
51c29cc660 Merge branch 'feature/starter_add_doc_event' into dev 2025-10-10 11:01:49 +08:00
91b2dfe088 Merge branch 'feature/starter_add_doc_event' into dev 2025-10-10 10:45:08 +08:00
8cccac3d2e Merge branch 'feature/starter_add_doc_event' into dev 2025-10-10 10:12:11 +08:00
fb790b724f feat - 新增前后加签的分支 2025-09-29 14:21:41 +08:00
e1e6b038c7 Merge branch 'feature/starter_add_doc_event' into dev 2025-09-29 11:38:52 +08:00
1352028fa5 Merge branch 'feature/REQ-4100' into dev 2025-09-18 15:00:00 +08:00
3306f961ba Merge branch 'hotfix/activty_trigger_error' into dev 2025-09-17 11:54:31 +08:00
03148df3d3 fix - 修改业务节点触发异常的问题 2025-09-17 11:54:00 +08:00
afca132e5d Merge branch 'feature/REQ-5369' into dev 2025-09-15 11:55:05 +08:00
eedee65c92 Merge branch 'feature/REQ-5369' into dev 2025-09-15 11:49:36 +08:00
917c0afea6 Merge branch 'feature/REQ-5369' into dev
# Conflicts:
#	workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/PrintFieldCategoryEnum.java
2025-09-15 11:46:58 +08:00
fd4c4b3e28 Merge branch 'master' into dev 2025-09-15 10:01:41 +08:00
1294f4d096 Merge branch 'hotfix/node-alter' into dev 2025-09-08 10:37:56 +08:00
74b462d1fc Merge remote-tracking branch 'origin/feature/REQ-4418_Enum' into dev
# Conflicts:
#	workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java
#	workflow-engine-server/src/main/java/cn/axzo/workflow/server/alter/DingTalkAlter.java
#	workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java
#	workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/check/ImplementationReadyChecker.java
2025-09-08 10:37:24 +08:00
103 changed files with 4175 additions and 239 deletions

View File

@ -17,11 +17,13 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCre
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO;
import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo;
import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO;
@ -180,6 +182,17 @@ public interface ProcessInstanceApi {
CommonResponse<Map<String, Object>> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId,
@Nullable @RequestParam(required = false) String tenantId);
/**
* 更新流程实例中的业务自定义变量集合
*
* @param dto
* @return
*/
@Operation(summary = "更新流程实例中的业务自定义变量集合")
@PostMapping("/api/process/instance/biz/custom/variables/update")
@InvokeMode(SYNC)
CommonResponse<Boolean> updateProcessBizCustomVariables(@Validated @RequestBody BpmnProcessInstanceVariablesUpdateDTO dto);
/**
* 查询所有的审批流
*
@ -358,4 +371,15 @@ public interface ProcessInstanceApi {
@Manageable
@InvokeMode(SYNC)
CommonResponse<List<ExtProcessLogVO>> getProcessLogByInstanceIdAndPersonId(@Validated @RequestBody LogApproveSearchDTO dto);
/**
* 获取流程实例的条件字段信息仅用于同意抽屉展示
*
* @param processInstanceId
* @return
*/
@Operation(summary = "获取流程实例的条件字段信息, 仅用于同意抽屉展示")
@GetMapping("/api/process/instance/conditions")
@InvokeMode(SYNC)
CommonResponse<List<ConditionPermissionMetaInfo>> getConditions(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId);
}

View File

@ -46,7 +46,6 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC;
*/
//@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class)
@WorkflowEngineFeignClient
@Manageable
public interface ProcessModelApi {
/**
@ -57,6 +56,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "流程模型列表")
@GetMapping("/api/process/model/page")
@Manageable
@InvokeMode(SYNC)
CommonResponse<BpmPageResult<BpmnModelDetailVO>> page(@Validated @RequestBody BpmnModelSearchDTO dto);
@ -66,6 +66,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "创建流程模型")
@PostMapping("/api/process/model/create")
@Manageable
@InvokeMode(SYNC)
CommonResponse<String> create(@Validated @RequestBody BpmnModelCreateDTO dto);
@ -74,6 +75,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "通过模型ID查询指定流程模型")
@GetMapping("/api/process/model/get")
@Manageable
@InvokeMode(SYNC)
CommonResponse<BpmnModelDetailVO> getById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId,
@RequestParam(required = false) String tenantId);
@ -83,6 +85,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "通过模型KEY查询指定流程模型")
@GetMapping("/api/process/model/getByKey")
@Manageable
@InvokeMode(SYNC)
CommonResponse<BpmnModelDetailVO> getByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam(required = false) String processModelKey,
@NotBlank(message = "租户不能为空") @RequestParam(required = false) String tenantId);
@ -96,6 +99,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "获取指定模型的扩展属性")
@GetMapping("/api/process/model/ext")
@Manageable
@InvokeMode(SYNC)
CommonResponse<BpmnModelExtVO> getModelExt(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId);
@ -104,6 +108,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "更新流程模型")
@PutMapping("/api/process/model/update")
@Manageable
@InvokeMode(SYNC)
CommonResponse<String> update(@RequestBody BpmnModelUpdateDTO dto);
@ -115,6 +120,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "通过模型 ID 部署流程模型")
@PostMapping("/api/process/model/deploy")
@Manageable
@InvokeMode(SYNC)
CommonResponse<String> deployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId,
@RequestParam(required = false, defaultValue = "") String modelTenantId,
@ -127,6 +133,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "通过模型 KEY 部署流程模型")
@PostMapping("/api/process/model/deployByKey")
@Manageable
@InvokeMode(SYNC)
CommonResponse<String> deployByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam(required = false) String processModelKey,
@NotBlank(message = "租户不能为空") @RequestParam(required = false) String modelTenantId,
@ -142,6 +149,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "通过模型 ID 取消部署流程模型")
@PostMapping("/api/process/model/undeploy")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Void> unDeployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId,
@RequestParam(required = false, defaultValue = "") String tenantId,
@ -152,6 +160,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "删除指定模型 ID 的流程模型")
@DeleteMapping("/api/process/model/delete")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Void> deleteById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam String processModelId,
@RequestParam(required = false, defaultValue = "") String tenantId);
@ -165,6 +174,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "删除指定模型 KEY 的流程模型")
@DeleteMapping("/api/process/model/deleteByKey")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Void> deleteByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam String processModelKey,
@RequestParam(required = false, defaultValue = "") String tenantId);
@ -179,6 +189,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "修改模型状态")
@PostMapping("/api/process/model/changeStatus")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> changeStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId,
@NotNull(message = "状态不能为空") @RequestParam Integer status,
@ -194,6 +205,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "修改模型打印开关状态")
@PostMapping("/api/process/model/print/changeStatus")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> changePrintStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId,
@NotNull(message = "状态不能为空") @RequestParam Integer status,
@ -206,6 +218,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "查询流程模型使用的分类列表")
@GetMapping("/api/process/model/category/ids")
@Manageable
@InvokeMode(SYNC)
CommonResponse<List<String>> getModelCategoryList();
@ -216,6 +229,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "查询模型的租户集合")
@GetMapping("/api/process/model/tenant/ids")
@Manageable
@InvokeMode(SYNC)
CommonResponse<List<String>> getModelTenantIds();
@ -227,6 +241,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "获取打印模板配置内容")
@PostMapping("/api/process/model/print/template/config/query")
@Manageable
@InvokeMode(SYNC)
CommonResponse<PrintModelDTO> getPrintTemplateConfig(@Validated @RequestBody PrintTemplateConfigQueryDTO dto);
@ -238,6 +253,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "代运营重置打印模板")
@PostMapping(value = "/api/process/model/print/template/config/reset")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> resetPrintTemplateConfig(@Validated @RequestBody RestPrintTemplateConfigDTO dto);
@ -249,6 +265,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "搜索文档列表")
@PostMapping(value = "/api/process/model/doc/page")
@Manageable
@InvokeMode(SYNC)
CommonResponse<BpmPageResult<DocBaseVO>> docPage(@Validated @RequestBody DocSearchDTO dto);
@ -259,6 +276,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "获取指定 docIds 文档列表")
@PostMapping(value = "/api/process/model/doc/ids")
@Manageable
@InvokeMode(SYNC)
CommonResponse<List<DocBaseVO>> docByIds(@Validated @RequestBody DocByIdDTO dto);
@ -281,6 +299,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "获取关联 HiPrint 类型文档模板内容")
@PostMapping(value = "/api/process/model/hi-print/content/get")
@Manageable
@InvokeMode(SYNC)
CommonResponse<String> getHiPrintContent(@RequestParam String fileRelationId);
@ -291,6 +310,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "添加关联文档")
@PutMapping(value = "/api/process/model/doc/create")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> createDoc(@Validated @RequestBody DocCreateDTO dto);
@ -301,6 +321,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "修改关联文档")
@PostMapping(value = "/api/process/model/doc/update")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> updateDoc(@Validated @RequestBody DocUpdateDTO dto);
@ -312,6 +333,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "克隆关联文档")
@PostMapping(value = "/api/process/model/doc/clone")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> cloneDoc(@RequestParam("id") Long docId);
@ -322,6 +344,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "删除指定文档")
@DeleteMapping(value = "/api/process/model/doc/delete")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> deleteDoc(@RequestParam("id") Long docId);
@ -333,6 +356,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "关联文档配置排序")
@PostMapping(value = "/api/process/model/doc/order")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> orderDoc(@Validated @RequestBody DocOrderDTO dto);
@ -344,6 +368,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "重置关联文档配置")
@PostMapping(value = "/api/process/model/doc/reset")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> resetDoc(@Validated @RequestBody DocResetDTO dto);
@ -355,6 +380,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "设置关联文档的停启用状态")
@PostMapping(value = "/api/process/model/doc/status")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> statusDoc(@Validated @RequestBody DocStatusDTO dto);
@ -365,6 +391,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "设置关联文档的必选状态")
@PostMapping(value = "/api/process/model/doc/require")
@Manageable
@InvokeMode(SYNC)
CommonResponse<Boolean> requireDoc(@Validated @RequestBody DocStatusDTO dto);
@ -375,6 +402,7 @@ public interface ProcessModelApi {
*/
@Operation(summary = "特殊的查询设置过关联过文档的工作台 ID 集合")
@PostMapping(value = "/api/process/model/has/docs/tenantId")
@Manageable
@InvokeMode(SYNC)
CommonResponse<List<Long>> hasFilesTenantIds(@Validated @RequestBody DocTenantQueryDTO dto);
}

View File

@ -4,8 +4,13 @@ import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient;
import cn.axzo.workflow.common.annotation.InvokeMode;
import cn.axzo.workflow.common.annotation.Manageable;
import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.Print4ProcessLogDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.PrintFieldQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.PrintProcessLogPdfDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.QueryProcessLogPdfDTO;
import cn.axzo.workflow.common.model.response.bpmn.process.PrintData4LogVO;
import cn.axzo.workflow.common.model.response.print.ProcessLogPdfResultDTO;
import cn.azxo.framework.common.model.CommonResponse;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.validation.annotation.Validated;
@ -66,7 +71,7 @@ public interface PrintAdminApi {
* 获取指定流程下用于替换打印的相关变量
*
* @param processInstanceId
* @return
* @return 仅是 kv 集合
*/
@Operation(summary = "获取指定流程下用于替换打印的相关变量")
@GetMapping("/api/print/admin/field/variables")
@ -74,4 +79,40 @@ public interface PrintAdminApi {
@InvokeMode(SYNC)
CommonResponse<Map<String, Object>> getPrintFieldVariables(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId,
@RequestParam(required = false, defaultValue = "true") Boolean throwException);
/**
* 获取用于打印审批日志公共模板的数据
*
* @param dto
* @return
*/
@Operation(summary = "获取用于打印审批日志公共模板的数据")
@PostMapping("/api/print/admin/process/log/data/v2")
@Manageable
@InvokeMode(SYNC)
CommonResponse<PrintData4LogVO> getPrintDataForProcessLog(@Validated @RequestBody Print4ProcessLogDTO dto);
/**
* 后端请求指定流程日志 PDF 文件生成, 实现是异步的
* <p>
* 请使用 {@link PrintAdminApi#queryProcessLogPdfResult(QueryProcessLogPdfDTO)} 函数查询
* 或者使用 {@link cn.axzo.nanopart.doc.api.conversion.DocConversionApi#queryConvertResultByBiz(cn.axzo.nanopart.doc.api.conversion.req.QueryConversionTaskRequestV2)} 函数查询该接口入参默认情况下应该为bizCode:固定为"workflow-process-log", bizKey:为实例 ID+":"+访问人 personId
*
* @return
*/
@Operation(summary = "后端请求指定流程日志 PDF 文件生成")
@PostMapping("/api/print/admin/process/log/pdf")
@InvokeMode(SYNC)
CommonResponse<String> createProcessLogPdf(@Validated @RequestBody PrintProcessLogPdfDTO dto);
/**
* 后端查询指定审批日志 PDF 文件的生成结果
*
* @param dto
* @return
*/
@Operation(summary = "后端查询指定审批日志 PDF 文件的生成结果")
@PostMapping("/api/print/admin/process/log/pdf/result")
@InvokeMode(SYNC)
CommonResponse<ProcessLogPdfResultDTO> queryProcessLogPdfResult(@Validated @RequestBody QueryProcessLogPdfDTO dto);
}

View File

@ -44,6 +44,7 @@ public enum BpmnTaskRespCode implements IModuleRespCode {
REMIND_TASK_TOO_MANY("027", "催办任务数据异常"),
PROCESS_SET_ASSIGNEE_PARAM_ERROR("028", "当前审批业务审批人模型中 NodeId 必传topNodeId/nodeId均可, 触发 ID: 【{}】"),
TASK_OPERATION_PARAM_INVALID("029", "流程实例 ID 与任务 ID 必须二选一"),
COOPERATION_NOT_EXIST_WITH_NODE("030", "查询审批人时,组织不存在"),
;
private final String code;

View File

@ -21,6 +21,8 @@ public enum ConvertorRespCode implements IModuleRespCode {
CONVERTOR_OPERATION_NUMBER_TYPE_ERROR("006", "条件节点(数字)运算符【{}】暂不支持"),
CONVERTOR_OPERATION_RADIO_TYPE_ERROR("007", "条件节点(单选)运算符【{}】暂不支持"),
CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR("008", "条件节点(复选)运算符【{}】暂不支持"),
CREATE_BPMN_USER_TASK_ERROR("009", "创建 UserTask 失败, 不支持的节点模式【{}】"),
CREATE_BPMN_PRE_SIGN_ERROR("010", "创建前加签节点失败,原因:【{}】"),
;
private final String code;

View File

@ -23,6 +23,8 @@ public enum FormInstanceRespCode implements IModuleRespCode {
FORM_DATA_PARSE_ERROR_BY_IMAGE("008", "表单图片组件的数据解析异常"),
FORM_DATA_PARSE_ERROR_BY_CUSTOM_COMPONENT("009", "表单自定义组件的数据解析异常"),
FORM_DATA_PARSE_ERROR_BY_AMOUNT("010", "表单金额组件的数据解析异常"),
FORM_DATA_PARSE_ERROR_BY_CHECKBOX("011", "表单复选框组件的数据解析异常"),
FORM_DATA_PARSE_ERROR_BY_RADIO("012", "表单单选框组件的数据解析异常"),
;
private final String code;

View File

@ -22,7 +22,8 @@ public enum OtherRespCode implements IModuleRespCode {
ASYNC_JOB_EXECUTION_ERROR("007", "获取指定实例 ID【{}】的锁失败"),
ILLEGAL_PARAM_ERROR("008", "非法的参数:【{}】"),
MESSAGE_IM_EVENT_BUILD_ERROR("009", "不能使用 createEvent 函数创建`IM 消息`的事件, 请调用 createIMEvent 函数"),
ASSIGNEE_NODE_ID_NOT_EXISTS("010", "【{}】 nodeId 不存在, 请检查参数是否正确")
ASSIGNEE_NODE_ID_NOT_EXISTS("010", "【{}】 nodeId 不存在, 请检查参数是否正确"),
CANT_GENERATE_PROCESS_LOG_PDF("011", "流程未处于终态不能用默认参数创建,请自行添加 BizCode和 BizKey"),
;
private final String code;

View File

@ -42,9 +42,12 @@ public interface BpmnConstants {
@Deprecated
String OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT = "[_ASSIGNEE_INFO_SNAPSHOT_]";
String INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT = "[_ACTIVITY_INFO_SNAPSHOT_]";
String INTERNAL_ACTIVITY_FORWARD_COUNTERSIGN = "[_FORWARD_COUNTERSIGN_]";
String INTERNAL_ACTIVITY_BACK_COUNTERSIGN = "[_BACK_COUNTERSIGN_]";
String BIZ_NODE_ALTER = "[_BIZ_NODE_ALTER_]";
String INITIATOR_SPECIFY = "[_INITIATOR_SPECIFY_]";
String SIGNATURE_COLLECTION = "[_SIGNATURE_COLLECTION_]";
String COUNTERSIGN_COUNT = "[_COUNTERSIGN_COUNT_]";
String PROCESS_PREFIX = "Flowable";
@Deprecated
String OLD_TASK_ASSIGNEE_SKIP_FLAT = "taskSkip";
@ -114,6 +117,9 @@ public interface BpmnConstants {
String CONFIG_ACTIVITY_SIGNATURE = "signature";
String CONFIG_FIELD_META = "field";
String CONFIG_FIELD_PERMISSION = "fieldPermission";
String CONFIG_CONDITION_PERMISSION = "conditionPermission";
String CONFIG_FIELD_OPTIONS = "options";
@Deprecated
String CONFIG_FIELD_OPTION = "option";
String CONFIG_NODE_TYPE = "nodeType";
String CONFIG_BUTTON_TYPE_INITIATOR = "initiator";
@ -123,6 +129,7 @@ public interface BpmnConstants {
String CONFIG_SIGN_TYPE = "signType";
String CONFIG_AREA_FILTER_ENABLE = "areaFilterEnable";
String CONFIG_SPECIALTY_FILTER_ENABLE = "specialtyFilterEnable";
String CONFIG_ONLY_IN_PROJECT_ENABLE = "onlyInProjectEnable";
String ELEMENT_ATTRIBUTE_NAME = "name";
String ELEMENT_ATTRIBUTE_VALUE = "value";
String ELEMENT_ATTRIBUTE_DESC = "desc";
@ -155,6 +162,7 @@ public interface BpmnConstants {
String NUMBER_OF_INSTANCES = "nrOfInstances";
String MULTI_INSTANCE_LOOP_COUNTER = "loopCounter";
String TASK_COMPLETE_OPERATION_TYPE = "_TASK_COMPLETE_TYPE";
String TASK_LOG_NODE_HAS_BEEN_HIDDEN = "_TASK_LOG_HIDDEN";
String TASK_ATTACHMENTS_VAR_NAME = "TASK_ATTACHMENTS";
/**
@ -249,6 +257,18 @@ public interface BpmnConstants {
* 签署业务发起流程实例时重新选择的文档tag 集合
*/
String SIGN_PROCESS_ENABLE_DOC_IDS = "[_SIGN_PROCESS_ENABLE_DOC_IDS_]";
/**
* 签署业务自定义业务传入的文档集合
*/
String SIGN_BIZ_CUSTOM_DOCS = "[_SIGN_BIZ_CUSTOM_DOCS_]";
/**
* 签署业务自定义文档的顺序位置类型
*/
String SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE = "[_SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE_]";
/**
* 签署业务业务对所有文档顺序排序
*/
String SIGN_BIZ_BASED_FILE_TAG_ORDER = "[_SIGN_BIZ_BASED_FILE_TAG_ORDER_]";
/**
* 签署业务基于业务自定义变量的传入
*/
@ -262,4 +282,12 @@ public interface BpmnConstants {
* 提级审批变量标识
*/
String SUPPORT_UPGRADE_VARIABLE = "[_SUPPORT_UPGRADE_]";
/**
* 前加签节点 ID 片段
*/
String FORWARD_ACTIVITY_FRAGMENT = "[forward_sign]";
/**
* 后加签节点 ID 片段
*/
String BACK_ACTIVITY_FRAGMENT = "[back_sign]";
}

View File

@ -29,14 +29,20 @@ public interface VariableConstants {
//=============== 打印时的变量集合中 key 的命名 =================
String VAR_PREFIX = "业务变量";
String PRINT_VAR_PROCESS_NAME = "processName";
String PRINT_VAR_PROCESS_NAME_DESC = "审批名称";
String PRINT_VAR_PROCESS_DEFINITION_KEY = "processDefinitionKey";
String PRINT_VAR_PROCESS_DEFINITION_KEY_DESC = "业务名称";
String PRINT_VAR_PROCESS_BELONG_TENANT_ID = "tenantId";
String PRINT_VAR_PROCESS_BELONG_TENANT_ID_DESC = "所属租户";
String PRINT_VAR_PROCESS_INSTANCE_ID = "processInstanceId";
String PRINT_VAR_PROCESS_INSTANCE_ID_DESC = "审批编号";
String PRINT_VAR_PROCESS_START_TIME = "startTime";
String PRINT_VAR_PROCESS_START_TIME_DESC = "发起时间";
String PRINT_VAR_PROCESS_END_TIME = "endTime";
String PRINT_VAR_PROCESS_END_TIME_DESC = "审批结束时间";
String PRINT_VAR_PROCESS_RESULT = "processResult";
String PRINT_VAR_PROCESS_RESULT_DESC = "审批结果";
String PRINT_VAR_PROCESS_INITIATOR = "initiator";
String PRINT_VAR_PROCESS_INITIATOR_DESC = "发起者";
String PRINT_VAR_PROCESS_INITIATOR_NAME = "initiatorName";
@ -45,12 +51,16 @@ public interface VariableConstants {
String PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC = "发起人岗位";
String PRINT_VAR_PROCESS_INITIATOR_PHONE = "initiatorPhone";
String PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC = "发起人联系方式";
String PRINT_VAR_PROCESS_INITIATOR_UNIT = "initiatorUnit";
String PRINT_VAR_PROCESS_INITIATOR_UNIT_DESC = "发起人单位";
String PRINT_VAR_PROCESS_LOGS = "processLogs";
String PRINT_VAR_PROCESS_LOGS_DESC = "审批日志";
String PRINT_VAR_PROCESS_LOG_ACTIVITY_NODE_TYPE = "activityNodeType";
String PRINT_VAR_PROCESS_LOG_ACTIVITY_NODE_TYPE_DESC = "节点类型";
String PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME = "activityName";
String PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME_DESC = "节点名称";
String PRINT_VAR_PROCESS_LOG_APPROVER_NAME = "approverName";
String PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC = "审批人";
String PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC = "姓名";
String PRINT_VAR_PROCESS_LOG_UNIT = "unit";
String PRINT_VAR_PROCESS_LOG_UNIT_DESC = "单位";
String PRINT_VAR_PROCESS_LOG_POSITION = "position";
@ -61,4 +71,10 @@ public interface VariableConstants {
String PRINT_VAR_PROCESS_LOG_OPERATION_TIME_DESC = "审批时间";
String PRINT_VAR_PROCESS_LOG_SIGNATURE = "signature";
String PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC = "电子签名";
String PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT = "activityResult";
String PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT_DESC = "审批结果";
String PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME = "activityOperationTime";
String PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME_DESC = "日期";
String PRINT_VAR_PROCESS_LOG_OPERATION = "operationDesc";
String PRINT_VAR_PROCESS_LOG_OPERATION_DESC = "操作描述";
}

View File

@ -12,6 +12,7 @@ public enum BpmnFlowNodeMode {
GENERAL("GENERAL", "普通节点"),
OR("OR", "或签节点"),
AND("AND", "会签节点"),
SEQUENCE("SEQUENCE", "顺序节点"),
EXCEPTIONAL("EXCEPTIONAL", "异常"),
@JsonEnumDefaultValue
UNKNOWN("UNKNOWN", "未知"),

View File

@ -19,6 +19,7 @@ public enum BpmnProcessInstanceResultEnum {
UPGRADED("UPGRADED", "已提级"),
COMMENTED("COMMENTED", "已评论"),
DELETED("DELETED", "已删除"),
HIDDEN("HIDDEN", "已隐藏"),
@JsonEnumDefaultValue
UNKNOWN("UNKNOWN", "未知"),
;

View File

@ -46,6 +46,10 @@ public enum FormFieldTypeEnum {
}),
decimal("decimal", "小数", new TypeReference<Map<String, Object>>() {
}),
checkbox("checkbox", "复选框", new TypeReference<List<String>>() {
}),
radio("radio", "单选框", new TypeReference<String>() {
})
;
private final String type;

View File

@ -0,0 +1,75 @@
package cn.axzo.workflow.common.exception;
import cn.axzo.framework.domain.ServiceException;
import cn.axzo.framework.domain.web.code.IRespCode;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
/**
* 流程内部计算审批人的异常
*
* @author wangli
* @since 2025-11-03 10:11
*/
@Slf4j
public class WorkflowApproverCalcException extends ServiceException {
private String code;
public WorkflowApproverCalcException(IRespCode code) {
super(code.getMessage());
this.code = code.getRespCode();
}
public WorkflowApproverCalcException(IRespCode code, String... params) {
super(doFormat(code.getCode(), code.getMessage(), params));
this.code = code.getRespCode();
}
public WorkflowApproverCalcException(IRespCode code, Throwable cause, String... params) {
super(doFormat(code.getCode(), code.getMessage(), params), cause);
this.code = code.getRespCode();
}
@Override
public String getCode() {
return this.code;
}
/**
* 将错误编号对应的消息使用 params 进行格式化
*
* @param code 错误编号
* @param messagePattern 消息模版
* @param params 参数
* @return 格式化后的提示
*/
@VisibleForTesting
private static String doFormat(String code, String messagePattern, Object... params) {
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
int i = 0;
int j;
int l;
for (l = 0; l < params.length; l++) {
j = messagePattern.indexOf("{}", i);
if (j == -1) {
log.warn("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
if (i == 0) {
return messagePattern;
} else {
sbuf.append(messagePattern.substring(i));
return sbuf.toString();
}
} else {
sbuf.append(messagePattern, i, j);
sbuf.append(params[l]);
i = j + 2;
}
}
if (messagePattern.indexOf("{}", i) != -1) {
log.warn("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
}
sbuf.append(messagePattern.substring(i));
return sbuf.toString();
}
}

View File

@ -40,6 +40,11 @@ public class CooperationOrgDTO implements Serializable {
*/
private List<String> includeSpecialtyCodes;
/**
* 该参数仅应用于节点的高级设置中的"仅支持工程内人员参与审批”
*/
private List<Long> projectIds;
/**
* 企业组织架构范围
**/

View File

@ -0,0 +1,68 @@
package cn.axzo.workflow.common.model.dto;
import cn.axzo.workflow.common.enums.FileTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 签署文件记录信息
*
* @author wangli
* @since 2025-04-03 11:21
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CustomDocDTO implements Serializable {
private static final long serialVersionUID = -8709597975507074853L;
/**
* 该属性内部使用无需赋值
*/
private Long id;
/**
* 文件名称
*/
@NotBlank(message = "业务自定义文件名称不能为空")
private String fileName;
/**
* 文件的标签
*/
private String fileTag;
/**
* 如果业务是使用在线文档则一定会有 wps code如果有则传入
* <p>
* wps 文件的标识通过{@link cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi#createFile(cn.axzo.nanopart.doc.api.anonymous.request.AnonymousCreateFileRequest)} 接口创建文件后返回的 fileCode
*/
private String fileCode;
/**
* 不管是在线文件还是本地上传必须包含 oss 地址的文件标识
*/
@NotBlank(message = "业务自定义文件的 oss key 不能为空")
private String fileKey;
/**
* 文件的类型,如果要替换变量支持 docx 格式doc 格式不支持
*/
@NotNull(message = "业务自定义文件的类型不能为空")
private FileTypeEnum fileType;
/**
* 文件是否需要变量替换默认不替换
* <p>
* 不建议无脑设置为 true否则可能会影响性能
*/
private Boolean needReplaceVariables = false;
}

View File

@ -57,4 +57,9 @@ public class SignFileDTO implements Serializable {
* 替换变量后的文件 fileKey
*/
private String fileKey;
/**
* 是否需要替换变量
*/
private Boolean needReplaceVariables;
}

View File

@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
@ -39,7 +40,11 @@ public class SignatureDTO implements Serializable {
@Accessors(chain = true)
public static class SignDetail implements Serializable {
private static final long serialVersionUID = 1L;
//审批人姓名
private String approverName;
private String signature;
private String advice;
private String result;
private Date operationTime;
}
}

View File

@ -27,7 +27,7 @@ public class BpmnFieldOptionConf implements Serializable {
* 选项的值
*/
@ApiModelProperty(value = "选项的值", example = "1")
private String value;
private Object value;
public String getName() {
return name;
@ -37,11 +37,11 @@ public class BpmnFieldOptionConf implements Serializable {
this.name = name;
}
public String getValue() {
public Object getValue() {
return value;
}
public void setValue(String value) {
public void setValue(Object value) {
this.value = value;
}
}

View File

@ -10,6 +10,7 @@ import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.enums.CooperateShipTypeEnum;
import cn.axzo.workflow.common.enums.InitiatorSpecifiedRangeEnum;
import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum;
import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo;
import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -201,6 +202,12 @@ public class BpmnJsonNodeProperty {
@ApiModelProperty(value = "表单字段权限控制")
private List<FormPermissionMetaInfo> fieldPermission;
/**
* 条件字段权限配置
*/
@ApiModelProperty(value = "条件字段权限控制")
private List<ConditionPermissionMetaInfo> conditionPermission;
/**
* 区域过滤开关
*/
@ -213,4 +220,9 @@ public class BpmnJsonNodeProperty {
@ApiModelProperty(value = "专业过滤开关", notes = "true: 开启专业过滤, false: 关闭专业过滤")
private Boolean specialtyFilterEnable;
/**
* 工程内人员开关
*/
@ApiModelProperty(value = "仅工程内人员开关", notes = "true: 仅工程内人员, false: 非仅工程内人员")
private Boolean onlyInProjectEnable;
}

View File

@ -0,0 +1,32 @@
package cn.axzo.workflow.common.model.request.bpmn.print;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
/**
* 获取内置公共模板打印数据的入参模型
*
* @author wangli
* @since 2025-10-30 10:43
*/
@ApiModel("获取内置公共模板打印数据的入参模型")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Print4ProcessLogDTO {
/**
* 流程实例 ID
*/
@ApiModelProperty(value = "流程实例 ID")
@NotBlank(message = "流程实例 ID 不能为空")
private String processInstanceId;
}

View File

@ -35,6 +35,11 @@ public class PrintFieldQueryDTO {
@ApiModelProperty(value = "租户 ID")
private String tenantId;
/**
* 是否过滤仅支持打印字段
*/
@ApiModelProperty(value = "是否过滤仅支持打印字段")
private Boolean filterEnablePrint;
/**
* 是否抛出内部异常
*/

View File

@ -0,0 +1,57 @@
package cn.axzo.workflow.common.model.request.bpmn.print;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.StringUtils;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotBlank;
/**
* 请求审批日志转 pdf 的入参模型
*
* @author wangli
* @since 2025-10-31 17:15
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PrintProcessLogPdfDTO {
/**
* 审批实例 ID
*/
@ApiModelProperty(value = "审批实例 ID")
@NotBlank(message = "审批实例 ID 不能为空")
private String processInstanceId;
/**
* 实例日志访问者的personId
*/
@ApiModelProperty(value = "访问者的 PersonId")
@NotBlank(message = "访问者的 personId 不能为空")
private String personId;
/**
* 自定义该审批日志的 bizCode
* 可不传默认值为 workflow-process-log
*/
private String bizCode;
/**
* 自定义该审批日志的 bizKey
* 可不传 默认值为{@link PrintProcessLogPdfDTO#processInstanceId}
*/
private String bizKey;
@AssertTrue(message = "bizKey和bizCode必须同时为空或同时不为空")
public boolean isBizKeyAndCodeValid() {
boolean hasBizCode = StringUtils.hasText(bizCode);
boolean hasBizKey = StringUtils.hasText(bizKey);
return hasBizCode == hasBizKey;
}
}

View File

@ -0,0 +1,58 @@
package cn.axzo.workflow.common.model.request.bpmn.print;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.StringUtils;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotBlank;
/**
* 查询审批日志转 pdf 的入参模型
*
* @author wangli
* @since 2025-10-31 17:15
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class QueryProcessLogPdfDTO {
/**
* 审批实例 ID
*/
@ApiModelProperty(value = "审批实例 ID")
@NotBlank(message = "审批实例 ID 不能为空")
private String processInstanceId;
/**
* 访问人的 personId
*/
@ApiModelProperty(value = "对应生成时的访问人 personId")
@NotBlank(message = "访问者的 personId 不能为空")
private String personId;
/**
* 自定义该审批日志的 bizCode
* 可不传默认值为 workflow-process-log
*/
private String bizCode;
/**
* 自定义该审批日志的 bizKey
* 可不传 默认值为{@link QueryProcessLogPdfDTO#processInstanceId}
*/
private String bizKey;
@AssertTrue(message = "bizKey和bizCode必须同时为空或同时不为空")
public boolean isBizKeyAndCodeValid() {
boolean hasBizCode = StringUtils.hasText(bizCode);
boolean hasBizKey = StringUtils.hasText(bizKey);
return hasBizCode == hasBizKey;
}
}

View File

@ -2,6 +2,7 @@ package cn.axzo.workflow.common.model.request.bpmn.process;
import cn.axzo.workflow.common.constant.BpmnConstants;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.dto.CustomDocDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -10,6 +11,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.util.CollectionUtils;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@ -120,6 +122,30 @@ public class BpmnProcessInstanceCreateDTO extends BpmnProcessInstanceCreateWithF
@ApiModelProperty(value = "签署业务发起时,选择的文档")
private List<Long> docIds;
/**
* "签字业务"专用
* <p>
* 业务自定义的文档
*/
@ApiModelProperty(value = "业务自定义文档")
private List<CustomDocDTO> customDocs;
/**
* "签字业务"专用自定义文档的顺序位置信息在流程模板配置文档之前还是之后, 该属性与{@link BpmnProcessInstanceCreateDTO#basedFileTagOrder} 互斥
* <p>
* 可选值first(之前)last(之后), 如果为空默认为 last
*/
@ApiModelProperty(value = "自定义文档顺序位置", notes = "可选值first(之前)、last(之后), 如果为空,默认为 last")
private String docAddOrderType = "last";
/**
* "签字业务"专用, 该属性与{@link BpmnProcessInstanceCreateDTO#docAddOrderType} 互斥
* <p>
* 业务对所有文档的顺序覆盖必须对全量文档进行设置
*/
@ApiModelProperty(value = "业务对所有文档的顺序覆盖")
public List<String> basedFileTagOrder;
/**
* 仅针对签署业务设置审批完成后的最终签署人列表该属性仅为透传业务消费时请从 MQ 广播事件中的 variables 中通过 key= {@link BpmnConstants#SIGNATORIES } 获取
*/
@ -132,4 +158,14 @@ public class BpmnProcessInstanceCreateDTO extends BpmnProcessInstanceCreateWithF
}
return null;
}
public List<CustomDocDTO> getCustomDocs() {
if (CollectionUtils.isEmpty(customDocs)) {
return customDocs;
}
for (int i = 0; i < customDocs.size(); i++) {
customDocs.get(i).setId(i - (i + 1L)); // 负数 ID避免冲突
}
return customDocs;
}
}

View File

@ -1,6 +1,5 @@
package cn.axzo.workflow.common.model.request.bpmn.process;
import cn.axzo.workflow.common.model.dto.UploadFieldDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -20,15 +19,7 @@ public class BpmnProcessInstanceCreateWithFormDTO {
/**
* 审批使用了表单请一定注意传参
* <p>
* 图片类型和附件类型组件请用 @see {@link UploadFieldDTO} 对象集合传入
* <pre>
* // form_image 为表单项的 key value UploadFileDTO 对象集合如果前端使用了组件一般建议回传所有属性特殊情况下可以只传 fileUrl
* "form_image": [{
* "fileName": "",
* "fileUrl": "http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960",
* "fileKey": 123
* }]
* </pre>
* 请先查看{@see https://alidocs.dingtalk.com/i/nodes/ZgpG2NdyVXKy17o6fQ5nKGvMWMwvDqPk}文档根据不同类型传不同的 value 对象
*/
@ApiModelProperty(value = "通过表单创建流程时传入的初始表单数据")
private Map<String, Object> startFormVariables;

View File

@ -0,0 +1,38 @@
package cn.axzo.workflow.common.model.request.bpmn.process;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import java.util.Map;
/**
* 更新流程实例中变量集合的入参模型
*
* @author wangli
* @since 2025-10-24 10:56
*/
@ApiModel("更新流程实例中变量集合的入参模型")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BpmnProcessInstanceVariablesUpdateDTO {
/**
* 流程实例 ID
*/
@NotBlank(message = "流程实例 ID 不能为空")
private String processInstanceId;
/**
* 业务管理中定义变量的入参, 如果 key 在创建时已存在则进行覆盖更新否则新增变量
* <p>
* 对应创建流程实例中的 {@link BpmnProcessInstanceCreateDTO#bizCustomVariables} 属性
*/
private Map<String, Object> bizCustomVariables;
}

View File

@ -16,6 +16,7 @@ import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
import java.util.Map;
/**
* 审批任务节点的入参模型
@ -103,4 +104,11 @@ public class BpmnTaskAuditDTO {
*/
@Deprecated
private String operationDesc;
/**
* 更新或新增流程变量,
* <p>
* 如果不修改则map value 设置为 null内部会自动过滤
*/
private Map<String, Object> variables;
}

View File

@ -93,4 +93,10 @@ public class CategoryCreateDTO {
@ApiModelProperty(value = "版本号")
private Integer version;
/**
* 自定替换未设置的变量为空串
*/
@ApiModelProperty(value = "是否自动替换变量为空串默认false")
private Boolean autoReplaceVariables;
}

View File

@ -0,0 +1,105 @@
package cn.axzo.workflow.common.model.request.form;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 表单字段权限信息
*
* @author wangli
* @since 2024-11-07 11:09
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ConditionPermissionMetaInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 条件标识
*/
private String conditionCode;
/**
* 条件名称
*/
private String conditionName;
/**
* 单选框
* 条件类型(text/number/checkbox/radio)
*/
private String conditionType;
/**
* 可编辑必填
*/
@Builder.Default
private Boolean required = false;
/**
* 可编辑非必填
*/
@Builder.Default
private Boolean editable = false;
/**
* 只读
*/
@Builder.Default
private Boolean readonly = true;
/**
* 隐藏
*/
@Builder.Default
private Boolean hidden = false;
/**
* 前端回显字段后端不做任何消费逻辑
*/
private Object value;
/**
* 类型是单选复选时的选项值
*/
private String options;
// 将对象的属性转换为对应的整数表示
public int toBinary() {
int binaryValue = 0;
binaryValue |= (required ? 1 : 0) << 3;
binaryValue |= (editable ? 1 : 0) << 2;
binaryValue |= (readonly ? 1 : 0) << 1;
binaryValue |= (hidden ? 1 : 0);
return binaryValue;
}
// 从整数表示还原出对象
public static ConditionPermissionMetaInfo fromBinary(String conditionCode, String conditionName, String conditionType, int binaryValue) {
boolean required = ((binaryValue >> 3) & 1) == 1;
boolean editable = ((binaryValue >> 2) & 1) == 1;
boolean readonly = ((binaryValue >> 1) & 1) == 1;
boolean hidden = (binaryValue & 1) == 1;
return new ConditionPermissionMetaInfo(conditionCode, conditionName, conditionType, required, editable, readonly, hidden, null, null);
}
public ConditionPermissionMetaInfo toReadonly() {
if (required || editable || readonly) {
setRequired(false);
setEditable(false);
setReadonly(true);
setHidden(false);
}
return this;
}
public String toBinaryString() {
return String.format("%04d", Integer.parseInt(Integer.toBinaryString(toBinary()), 10));
}
}

View File

@ -22,7 +22,7 @@ import java.util.Map;
public class FormFieldDTO {
/**
* 字段类型, 如果是分组:FormContainer,其他表单组件:FormField
* 字段类型, 如果是分组:FormContainer,单选复选组件OptionFormField,其他表单组件:FormField,
*/
@ApiModelProperty(value = "表单字段类型")
@NotBlank(message = "字段类型不能为空")
@ -41,6 +41,9 @@ public class FormFieldDTO {
* { label: "变洽签单", value: "changeSignatureOrder" },
* { label: "通讯录", value: "contacts" },
* { label: "金额", value: "amount" },
* { label: "复选", value: "checkbox" },
* { label: "单选", value: "radio" },
*
*/
@ApiModelProperty(value = "前端的组件类型")
private String type;
@ -76,6 +79,14 @@ public class FormFieldDTO {
@ApiModelProperty(value = "该表单字段的其他扩展属性,期望 JSON 格式")
private Map<String, Object> params;
/**
* 单选复选框的选项
* <p>
* {@see org.flowable.form.model.OptionFormField}
*/
@ApiModelProperty(value = "单选复选框的选项,配合 fieldType=OptionFormField 使用")
private List<OptionDTO> options;
/**
* fieldType=FormContainer ,这里的代表内部的集合
*/

View File

@ -0,0 +1,24 @@
package cn.axzo.workflow.common.model.request.form.definition;
import io.swagger.annotations.ApiModel;
import lombok.Data;
/**
* 单选复选框的选项入参模型
*
* @author wangli
* @since 2025-11-20 10:57
*/
@ApiModel("单选复选框的选项入参模型")
@Data
public class OptionDTO {
/**
* 选项 ID
*/
private String id;
/**
* 选项名称
*/
private String name;
}

View File

@ -0,0 +1,71 @@
package cn.axzo.workflow.common.model.response;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 审批日志项模型
*
* @author wangli
* @since 2025-10-30 14:22
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProcessLogItemDTO {
@Builder.Default
private String label = "审批流程";
/**
* 审批意见
*/
private String advice;
/**
* 节点名称
*/
@ApiModelProperty(value = "节点名称")
private String activityName;
/**
* 操作描述
*/
@ApiModelProperty(value = "操作描述")
private String operationDesc;
/**
* 图片列表
*/
@ApiModelProperty(value = "图片列表")
private List<AttachmentDTO> imageList;
/**
* 附件列表
*/
@ApiModelProperty(value = "附件列表")
private List<AttachmentDTO> fileList;
/**
* 手写签名地址
*/
@ApiModelProperty(value = "手写签名地址")
private String signatureUrl;
/**
* 操作时间
*/
@ApiModelProperty(value = "操作时间")
private String operationTime;
/**
* 日志项结果对应{@link BpmnProcessInstanceResultEnum#name()}
*/
@ApiModelProperty(value = "日志项结果")
private String result;
}

View File

@ -0,0 +1,35 @@
package cn.axzo.workflow.common.model.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 审批日志公共打印模板的字段项模型
*
* @author wangli
* @since 2025-10-30 10:38
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TableItemDTO {
/**
* 中文
*/
private String label;
/**
* 字段 code
*/
private String code;
/**
* 字段类型
*/
private String type;
/**
*
*/
private Object value;
}

View File

@ -0,0 +1,54 @@
package cn.axzo.workflow.common.model.response.bpmn.process;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.response.ProcessLogItemDTO;
import cn.axzo.workflow.common.model.response.TableItemDTO;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 新版的审批日志公共模板的数据响应模型
*
* @author wangli
* @since 2025-10-30 10:28
*/
@ApiModel("新版的审批日志公共模板的数据响应模型")
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class PrintData4LogVO {
/**
* 标题
*/
private String processName;
/**
* 发起租户名称
*/
private String tenantName;
/**
* 创建时间
*/
private String createAt;
/**
* 审批状态
*/
private BpmnProcessInstanceResultEnum result;
/**
* 系统变量表格项
*/
private List<TableItemDTO> systemVarItems;
/**
* 审批日志表格项
*/
private List<ProcessLogItemDTO> logItems;
}

View File

@ -47,6 +47,9 @@ public class CategoryItemVO {
@ApiModelProperty(value = "版本号")
private Integer version;
@ApiModelProperty(value = "自定替换未设置的变量为空串")
private Boolean autoReplaceVariables;
@ApiModelProperty(value = "更新时间")
private Date updateAt;
}

View File

@ -0,0 +1,36 @@
package cn.axzo.workflow.common.model.response.print;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 审批日志 PDF 查询结果相应模型
*
* @author wangli
* @since 2025-11-07 18:13
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProcessLogPdfResultDTO {
/**
* 转换状态
* INIT("初始"),
* <p>
* CONVERTING("转换中"),
* <p>
* SUCCESS("转换完成"),
* <p>
* FAILED("转换失败");
*/
private String status;
/**
* 转换成功后的oss fileKey
*/
private String pdfFileKey;
}

View File

@ -1,8 +1,9 @@
package cn.axzo.workflow.core.common.utils;
import cn.axzo.workflow.common.model.request.bpmn.BpmnCondition;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.BpmnCondition;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.math.NumberUtils;
import java.util.List;
import java.util.Objects;
@ -20,7 +21,8 @@ import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_OPERATION
* @since 2023/11/16 23:30
*/
public final class BpmnExpressionTranslator {
private BpmnExpressionTranslator() {}
private BpmnExpressionTranslator() {
}
public static String translateString(BpmnCondition condition) {
// ${var:contains('variableName', 'hello')};
@ -88,7 +90,8 @@ public final class BpmnExpressionTranslator {
"('" +
condition.getCode() +
"', " +
condition.getDefaultValue() +
(NumberUtils.isDigits(condition.getDefaultValue()) ? condition.getDefaultValue() : "'" + condition.getDefaultValue() + "'")
+
")";
} else {
throw new WorkflowEngineException(CONVERTOR_OPERATION_RADIO_TYPE_ERROR, condition.getOperator());

View File

@ -90,7 +90,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_
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_FIELD_OPTIONS;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NOTICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_TYPE;
@ -346,22 +346,10 @@ public final class BpmnJsonConverterUtil {
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);
});
ExtensionAttribute fieldOptions = new ExtensionAttribute();
fieldOptions.setName(CONFIG_FIELD_OPTIONS);
fieldOptions.setValue(JSON.toJSONString(i.getOptions()));
field.addAttribute(fieldOptions);
}
fieldConfigElement.addChildElement(field);
});

View File

@ -31,6 +31,7 @@ import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnSignPendingProperty;
import cn.axzo.workflow.common.model.request.bpmn.BpmnSmsProperty;
import cn.axzo.workflow.common.model.request.bpmn.BpmnUpgradeApprovalConf;
import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo;
import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
@ -82,9 +83,11 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPIE
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_OBJECT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_SPECIFY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CONDITION_PERMISSION;
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_FIELD_OPTIONS;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_PERMISSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_LEADER_RANGE_UNIT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_EXCLUDE_COOPERATE_TYPES;
@ -93,6 +96,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SP
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_RANGE;
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.CONFIG_ONLY_IN_PROJECT_ENABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_LIMIT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_ORG_LIMIT;
@ -430,15 +434,20 @@ public final class BpmnMetaParserHelper {
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);
String fieldOptionsJsonStr = i.getAttributeValue(null, CONFIG_FIELD_OPTIONS);
if (StringUtils.hasText(fieldOptionsJsonStr)) {
conf.setOptions(JSON.parseArray(fieldOptionsJsonStr, BpmnFieldOptionConf.class));
} else {
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);
@ -648,6 +657,10 @@ public final class BpmnMetaParserHelper {
return defaultValid(flowElement, CONFIG_SPECIALTY_FILTER_ENABLE).map(element -> Boolean.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))).orElse(false);
}
public static Boolean getOnlyInProjectEnable(FlowElement flowElement) {
return defaultValid(flowElement, CONFIG_ONLY_IN_PROJECT_ENABLE).map(element -> Boolean.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))).orElse(false);
}
private static Optional<ExtensionElement> defaultValid(FlowElement flowElement, String elementName) {
if (Objects.isNull(flowElement)) {
return Optional.empty();
@ -692,6 +705,11 @@ public final class BpmnMetaParserHelper {
}.getType()));
}
public static Optional<List<ConditionPermissionMetaInfo>> getConditionPermissionConf(FlowElement flowElement) {
return defaultValid(flowElement, CONFIG_CONDITION_PERMISSION).map(element -> JSON.parseObject(element.getElementText(), new TypeReference<List<ConditionPermissionMetaInfo>>() {
}.getType()));
}
public static Optional<ImmutableTable<String, String, Integer>> getFormFieldPermissionForCalc(FlowElement flowElement) {
List<FormPermissionMetaInfo> fieldMetaInfos = getFormFieldPermissionConf(flowElement).orElse(new ArrayList<>());
return getFormFieldPermissionForModel(fieldMetaInfos);

View File

@ -2,6 +2,7 @@ package cn.axzo.workflow.core.conf;
import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.engine.behavior.CustomActivityBehaviorFactory;
import cn.axzo.workflow.core.engine.cfg.CustomDefaultInternalJobManager;
import cn.axzo.workflow.core.engine.cmd.CustomCommandContextFactory;
import cn.axzo.workflow.core.engine.formhandler.CustomFormFieldHandler;
import cn.axzo.workflow.core.engine.id.BasedNacosSnowflakeIdGenerator;
@ -26,6 +27,7 @@ import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncJobLogClearT
import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncRunnableExceptionExceptionHandler;
import cn.axzo.workflow.core.engine.persistence.CustomMybatisHistoricProcessInstanceDataManager;
import cn.axzo.workflow.core.service.BpmnProcessActivityService;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.azxo.framework.common.constatns.Constants;
import cn.hutool.core.util.ReflectUtil;
@ -82,6 +84,7 @@ public class FlowableConfiguration {
ObjectProvider<FlowableEventListener> listeners,
CustomActivityBehaviorFactory customActivityBehaviorFactory,
ExtAxHiTaskInstService extAxHiTaskInstService,
ExtAxDynamicSignRecordService extAxDynamicSignRecordService,
BpmnProcessActivityService bpmnProcessActivityService,
List<JobProcessor> jobProcessors,
NacosServiceManager nacosServiceManager,
@ -109,7 +112,7 @@ public class FlowableConfiguration {
configuration.addCustomJobHandler(new AsyncApproveTaskJobHandler());
configuration.addCustomJobHandler(new AsyncBackTaskJobHandler());
configuration.addCustomJobHandler(new AsyncCancelProcessInstanceJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService, extAxDynamicSignRecordService));
configuration.addCustomJobHandler(new AsyncExtTaskInstJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncRejectTaskJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncTransferUserTaskJobHandler());
@ -141,6 +144,7 @@ public class FlowableConfiguration {
configuration.setFormFieldValidationEnabled(true);
configuration.setFormFieldHandler(new CustomFormFieldHandler());
configuration.setConfigurators(Lists.newArrayList(new CustomJobServiceConfiguration()));
configuration.setInternalJobManager(new CustomDefaultInternalJobManager(configuration));
};
}

View File

@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit;
@Data
@RefreshScope
public class SupportRefreshProperties {
@Value("${workflow.apiLog.enable: false}")
private Boolean apiLogEnable;
@ -108,4 +109,7 @@ public class SupportRefreshProperties {
@Value("${workflow.ignoreMqAlterApplicationNames:}")
private List<String> ignoreMqAlterApplicationNames;
@Value("${workflow.processLogHtmlUrl:https://taskflow-web.axzo.cn/#/document/log?processInstanceId=%s&personId=%s}")
private String processLogHtmlUrl;
}

View File

@ -25,6 +25,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPIE
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_OBJECT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_SPECIFY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CONDITION_PERMISSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_PERMISSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_LEADER_RANGE_UNIT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_EXCLUDE_COOPERATE_TYPES;
@ -68,6 +69,8 @@ public class ServiceTaskJsonConverter extends AbstractBpmnJsonConverter<ServiceT
// "表单权限设置"
setFormFieldExtensionElement(node, serviceTask);
// "条件权限设置"
setConditionExtensionElement(node, serviceTask);
return serviceTask;
}
@ -399,6 +402,17 @@ public class ServiceTaskJsonConverter extends AbstractBpmnJsonConverter<ServiceT
serviceTask.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
}
private static void setConditionExtensionElement(BpmnJsonNode node, ServiceTask serviceTask) {
if (Objects.isNull(node.getProperty())) {
return;
}
ExtensionElement fieldElement = new ExtensionElement();
fieldElement.setName(CONFIG_CONDITION_PERMISSION);
fieldElement.setElementText(Objects.nonNull(node.getProperty().getConditionPermission()) ?
JSON.toJSONString(node.getProperty().getConditionPermission()) : null);
serviceTask.addExtensionElement(fieldElement);
}
private static void setFormFieldExtensionElement(BpmnJsonNode node, ServiceTask serviceTask) {
if (Objects.isNull(node.getProperty())) {
return;

View File

@ -45,12 +45,14 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_
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_CONDITION_PERMISSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_PERMISSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_LEADER_RANGE_UNIT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_EXCLUDE_COOPERATE_TYPES;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_EXCLUDE_IDENTITY_TYPES;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_FILTER;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_INITIATOR_SPECIFIED_RANGE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_ONLY_IN_PROJECT_ENABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_LIMIT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_ORG_LIMIT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_ROLE_LIMIT;
@ -99,6 +101,8 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter<UserTask> {
setApprovalExtensionElement(node, userTask);
// "表单权限设置"
setFormFieldExtensionElement(node, userTask);
// "条件权限设置"
setConditionExtensionElement(node, userTask);
// "高级设置",包含按钮配置,自动过审配置
setAdvancedExtensionElement(node, userTask);
// "待办消息模板配置"
@ -183,6 +187,14 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter<UserTask> {
specialtyFilterEnableElement.addAttribute(specialtyFilterEnableAttribute);
userTask.addExtensionElement(specialtyFilterEnableElement);
ExtensionElement onlyInProjectEnableElement = new ExtensionElement();
onlyInProjectEnableElement.setName(CONFIG_ONLY_IN_PROJECT_ENABLE);
ExtensionAttribute onlyInProjectEnableAttribute = new ExtensionAttribute();
onlyInProjectEnableAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED);
onlyInProjectEnableAttribute.setValue(String.valueOf(Boolean.TRUE.equals(node.getProperty().getOnlyInProjectEnable())));
onlyInProjectEnableElement.addAttribute(onlyInProjectEnableAttribute);
userTask.addExtensionElement(onlyInProjectEnableElement);
//添加自动审批配置
ExtensionElement autoApprovalExtensionElement = new ExtensionElement();
ExtensionAttribute pendingMessageAttribute = new ExtensionAttribute();
@ -191,6 +203,17 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter<UserTask> {
userTask.addExtensionElement(autoApprovalExtensionElement);
}
private static void setConditionExtensionElement(BpmnJsonNode node, UserTask userTask) {
if (Objects.isNull(node.getProperty()) || CollectionUtils.isEmpty(node.getProperty().getConditionPermission())) {
return;
}
ExtensionElement fieldElement = new ExtensionElement();
fieldElement.setName(CONFIG_CONDITION_PERMISSION);
fieldElement.setElementText(Objects.nonNull(node.getProperty().getConditionPermission()) ?
JSON.toJSONString(node.getProperty().getConditionPermission()) : null);
userTask.addExtensionElement(fieldElement);
}
private static void setFormFieldExtensionElement(BpmnJsonNode node, UserTask userTask) {
if (Objects.isNull(node.getProperty()) || CollectionUtils.isEmpty(node.getProperty().getFieldPermission())) {
return;
@ -686,7 +709,7 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter<UserTask> {
* @param node
* @param userTask
*/
private static void setExecutionListeners(BpmnJsonNode node, UserTask userTask) {
public static void setExecutionListeners(BpmnJsonNode node, UserTask userTask) {
List<FlowableListener> executionListeners = new ArrayList<>();
// 设置执行监听
@ -725,7 +748,7 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter<UserTask> {
*
* @param userTask
*/
private static void setTaskListeners(UserTask userTask) {
public static void setTaskListeners(UserTask userTask) {
List<FlowableListener> taskListeners = new ArrayList<>();
// 设置任务监听
FlowableListener taskListener = new FlowableListener();

View File

@ -0,0 +1,31 @@
package cn.axzo.workflow.core.engine.cfg;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.impl.cfg.DefaultInternalJobManager;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.job.api.Job;
/**
* 为解决引擎底层定时任务的 NPE 问题
*
* @author wangli
* @since 2025-10-24 17:42
*/
@Slf4j
public class CustomDefaultInternalJobManager extends DefaultInternalJobManager {
public CustomDefaultInternalJobManager(ProcessEngineConfigurationImpl processEngineConfiguration) {
super(processEngineConfiguration);
}
@Override
protected void handleJobDeleteInternal(Job job) {
ExecutionEntity executionEntity = getExecutionEntityManager().findById(job.getExecutionId());
log.info("handleJobDeleteInternal job.eId:{}, job.piId:{} ", job.getExecutionId(), job.getProcessInstanceId());
if (executionEntity == null) {
log.warn("handleJobDeleteInternal executionEntity is null for job.eId:{}, job.piId:{} ", job.getExecutionId(), job.getProcessInstanceId());
return;
}
super.handleJobDeleteInternal(job);
}
}

View File

@ -2,6 +2,7 @@ package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.enums.AttachmentTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.enums.BpmnProcessTaskResultEnum;
import cn.axzo.workflow.common.model.dto.SignatureDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
@ -28,6 +29,7 @@ import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -38,6 +40,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIG
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER;
import static cn.axzo.workflow.common.constant.BpmnConstants.PENDING_TEMPLATE_VARIABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
@ -81,6 +84,7 @@ public class CustomApproveTaskCmd extends AbstractCommand<Void> implements Seria
* 指定节点类型
*/
private List<BpmnFlowNodeType> nodeTypes;
private Map<String, Object> variables;
@Override
public String paramToJsonString() {
@ -92,6 +96,7 @@ public class CustomApproveTaskCmd extends AbstractCommand<Void> implements Seria
params.put("approver", JSON.toJSONString(approver));
params.put("nextApprover", JSON.toJSONString(nextApprover));
params.put("nodeTypes", JSON.toJSONString(nodeTypes));
params.put("variables", JSON.toJSONString(variables));
return JSON.toJSONString(params);
}
@ -115,6 +120,7 @@ public class CustomApproveTaskCmd extends AbstractCommand<Void> implements Seria
} else {
this.operationDesc = "已同意";
}
this.variables = dto.getVariables();
}
@Override
@ -156,8 +162,22 @@ public class CustomApproveTaskCmd extends AbstractCommand<Void> implements Seria
}
task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus());
if (!CollectionUtils.isEmpty(variables)) {
Map<String, Object> pendingVariables = runtimeService.getVariable(task.getProcessInstanceId(), PENDING_TEMPLATE_VARIABLE, Map.class);
if (!CollectionUtils.isEmpty(pendingVariables)) {
variables.forEach((k, v) -> {
if (pendingVariables.containsKey(k)) {
pendingVariables.put(k, v);
}
});
}
// 如果有待办模板变量则更新待办模板变量
variables.put(PENDING_TEMPLATE_VARIABLE, pendingVariables);
// 更新流程内的变量
runtimeService.setVariables(task.getProcessInstanceId(), variables);
}
// 记录电子签名的图片
recordSignature(task, runtimeService);
recordSignature(task, runtimeService, attachmentList, advice, approver);
resetApproverNode(task.getProcessInstanceId());
@ -185,7 +205,14 @@ public class CustomApproveTaskCmd extends AbstractCommand<Void> implements Seria
approver.setNodeId(logs.get(0).getAssigneeFull().get(0).getNodeId());
}
private void recordSignature(TaskEntity task, RuntimeService runtimeService) {
public static void recordSignature(TaskEntity task,
RuntimeService runtimeService,
List<AttachmentDTO> attachmentList,
String advice,
BpmnTaskDelegateAssigner approver) {
if (Objects.isNull(approver)) {
return;
}
List<SignatureDTO> signatures = runtimeService.getVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, List.class);
if (Objects.isNull(signatures)) {
signatures = new ArrayList<>();
@ -196,13 +223,16 @@ public class CustomApproveTaskCmd extends AbstractCommand<Void> implements Seria
.setActivityId(task.getTaskDefinitionKey())
.setActivityName(task.getName())
.setSignatures(new ArrayList<>()));
ListUtils.emptyIfNull(attachmentList).stream()
.filter(i -> Objects.equals(i.getType(), AttachmentTypeEnum.signature))
.findFirst()
.ifPresent(attachment -> dto.getSignatures().add(0,
new SignatureDTO.SignDetail()
.setSignature(attachment.getUrl())
.setAdvice(advice)));
dto.getSignatures().add(0, new SignatureDTO.SignDetail()
.setApproverName(approver.getAssignerName())
.setSignature(ListUtils.emptyIfNull(attachmentList).stream()
.filter(i -> Objects.equals(i.getType(), AttachmentTypeEnum.signature))
.findFirst().orElse(new AttachmentDTO()).getUrl())
.setAdvice(advice)
.setResult(BpmnProcessTaskResultEnum.APPROVED.getDesc())
.setOperationTime(new Date())
);
if (!any.isPresent()) {
signatures.add(dto);
}

View File

@ -1,9 +1,7 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.enums.AttachmentTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.SignatureDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
@ -14,7 +12,6 @@ import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import com.alibaba.fastjson.JSON;
import org.apache.commons.collections4.ListUtils;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
@ -34,12 +31,10 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -48,10 +43,10 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIG
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_SUBMIT_FORM_VARIABLE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd.recordSignature;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask;
@ -97,6 +92,10 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand<Void> implemen
* 表单数据
*/
private final Map<String, Object> formVariables;
/**
* 更新或新增的流程变量
*/
private final Map<String, Object> variables;
@Override
public String paramToJsonString() {
@ -109,6 +108,7 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand<Void> implemen
params.put("nextApprover", JSON.toJSONString(nextApprover));
params.put("nodeTypes", JSON.toJSONString(nodeTypes));
params.put("formVariables", JSON.toJSONString(formVariables));
params.put("variables", JSON.toJSONString(variables));
return JSON.toJSONString(params);
}
@ -133,6 +133,7 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand<Void> implemen
} else {
this.operationDesc = "已同意";
}
this.variables = dto.getVariables();
}
@Override
@ -174,9 +175,10 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand<Void> implemen
nextApprover);
}
task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus());
// 更新流程实例变量
runtimeService.setVariables(task.getProcessInstanceId(), variables);
// 记录电子签名的图片
recordSignature(task, runtimeService);
recordSignature(task, runtimeService, attachmentList, advice, approver);
resetApproverNode(task.getProcessInstanceId());
@ -255,30 +257,4 @@ public class CustomApproveTaskWithFormCmd extends AbstractCommand<Void> implemen
}
}
private void recordSignature(TaskEntity task, RuntimeService runtimeService) {
List<SignatureDTO> signatures = runtimeService.getVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, List.class);
if (Objects.isNull(signatures)) {
signatures = new ArrayList<>();
}
Optional<SignatureDTO> any = signatures.stream()
.filter(i -> Objects.equals(i.getActivityId(), task.getTaskDefinitionKey())).findAny();
SignatureDTO dto = any.orElse(new SignatureDTO()
.setActivityId(task.getTaskDefinitionKey())
.setActivityName(task.getName())
.setSignatures(new ArrayList<>()));
ListUtils.emptyIfNull(attachmentList).stream()
.filter(i -> Objects.equals(i.getType(), AttachmentTypeEnum.signature))
.findFirst()
.ifPresent(attachment -> dto.getSignatures().add(0,
new SignatureDTO.SignDetail()
.setSignature(attachment.getUrl())
.setAdvice(advice)));
if (!any.isPresent()) {
signatures.add(dto);
}
if (!CollectionUtils.isEmpty(signatures)) {
runtimeService.setVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, signatures);
}
}
}

View File

@ -1,22 +1,30 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.engine.cmd.helper.CustomBpmnModelHelper;
import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper;
import cn.axzo.workflow.core.engine.model.AddComment;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
@ -29,17 +37,25 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.OtherRespCode.ASSIGNEE_NODE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.BACK_ACTIVITY_FRAGMENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_ASSIGNER_SHOW_NUMBER;
import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_COUNT;
import static cn.axzo.workflow.common.constant.BpmnConstants.FORWARD_ACTIVITY_FRAGMENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.FORWARD_COUNTERSIGN;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.COUNTERSIGN;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCategoryVersion;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addMultiTask;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.completeVirtualTask;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVirtualTask;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.deleteMultiTasks;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.getDuplicatePendingTasks;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerCount;
@ -63,11 +79,14 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
private final List<AttachmentDTO> attachmentList;
private final List<BpmnTaskDelegateAssigner> targetTaskAssigneeList;
private final ExtAxHiTaskInstService extAxHiTaskInstService;
private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService;
public CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum countersignType, String originTaskId,
BpmnTaskDelegateAssigner originTaskAssignee, String advice,
List<AttachmentDTO> attachmentList,
List<BpmnTaskDelegateAssigner> targetTaskAssigneeList, ExtAxHiTaskInstService extAxHiTaskInstService) {
List<BpmnTaskDelegateAssigner> targetTaskAssigneeList,
ExtAxHiTaskInstService extAxHiTaskInstService,
ExtAxDynamicSignRecordService extAxDynamicSignRecordService) {
this.countersignType = countersignType;
this.originTaskId = originTaskId;
this.originTaskAssignee = originTaskAssignee;
@ -75,6 +94,7 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
this.attachmentList = attachmentList;
this.targetTaskAssigneeList = targetTaskAssigneeList;
this.extAxHiTaskInstService = extAxHiTaskInstService;
this.extAxDynamicSignRecordService = extAxDynamicSignRecordService;
}
@Override
@ -100,6 +120,7 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
TaskService taskService = processEngineConfiguration.getTaskService();
TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(originTaskId).singleResult();
// 1.5.4 版本要求审批人必须回传 nodeId
validTargetAssigneeNodeId(task.getProcessDefinitionId());
validTask(historicTaskInstance, task, originTaskAssignee, null);
@ -130,9 +151,11 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
switch (countersignType) {
case FORWARD_COUNTERSIGN:
// 加签的一种方式前加签具体定义由后续产品需求来定
forwardAndBackCountSign(commandContext, task, valuTargetAssigneeList);
break;
case BACK_COUNTERSIGN:
// 加签的另一种方式
forwardAndBackCountSign(commandContext, task, valuTargetAssigneeList);
break;
default:
// 共享签不区分顺序
@ -143,6 +166,148 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
return null;
}
/**
* 后加签
* <p>
* 使用内存动态变更模型连接实现前加签功能
*
* @param commandContext
* @param task
* @param valuTargetAssigneeList
*/
private void forwardAndBackCountSign(CommandContext commandContext, TaskEntity task, List<BpmnTaskDelegateAssigner> valuTargetAssigneeList) {
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
Process process = ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId());
UserTask originalUserTask = (UserTask) process.getFlowElement(task.getTaskDefinitionKey());
// 获取当前实例前加签次数
Long counterSignCount = runtimeService.getVariable(processInstance.getId(), COUNTERSIGN_COUNT, Long.class);
if (Objects.isNull(counterSignCount)) {
counterSignCount = 0L;
} else {
counterSignCount = counterSignCount + 1;
}
runtimeService.setVariable(processInstance.getId(), COUNTERSIGN_COUNT, counterSignCount);
BpmnFlowNodeMode nodeMode = Objects.equals(originalUserTask.getLoopCharacteristics().getCompletionCondition(), AND_SIGN_EXPRESSION) ? BpmnFlowNodeMode.AND : BpmnFlowNodeMode.OR;
// 生成加签节点ID
String newActivityId = originalUserTask.getId();
switch (countersignType) {
case FORWARD_COUNTERSIGN:
newActivityId = processActivityId(newActivityId, FORWARD_ACTIVITY_FRAGMENT, counterSignCount);
break;
case BACK_COUNTERSIGN:
newActivityId = processActivityId(newActivityId, BACK_ACTIVITY_FRAGMENT, counterSignCount);
default:
break;
}
// 创建被加签节点
UserTask newUserTask = CustomBpmnModelHelper.createUserTask(processEngineConfiguration, originalUserTask, newActivityId, nodeMode, valuTargetAssigneeList);
// 加入模型
process.addFlowElement(newUserTask);
// 重新连接顺序流 (Sequence Flow)
CustomBpmnModelHelper.rewireSequenceFlows(process, originalUserTask, newUserTask, countersignType);
saveCounterSignRecord(valuTargetAssigneeList, processInstance, originalUserTask, newUserTask, nodeMode);
if (Objects.equals(FORWARD_COUNTERSIGN, countersignType)) {
log.info("前加签任务处理完成原任务ID:{},新任务ID:{}", task.getId(), newUserTask.getId());
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveActivityIdTo(task.getTaskDefinitionKey(), newUserTask.getId())
.changeState();
} else {
}
}
/**
* 前后加签动作记录用于 JVM 重启后的动态恢复
*
* @param valuTargetAssigneeList
* @param processInstance
* @param originalUserTask
* @param newUserTask
* @param nodeMode
*/
private void saveCounterSignRecord(List<BpmnTaskDelegateAssigner> valuTargetAssigneeList, ProcessInstance processInstance, UserTask originalUserTask, UserTask newUserTask, BpmnFlowNodeMode nodeMode) {
ExtAxDynamicSignRecord entity = new ExtAxDynamicSignRecord();
entity.setProcessInstanceId(processInstance.getProcessInstanceId());
entity.setProcessDefinitionId(processInstance.getProcessDefinitionId());
entity.setOriginalActivityId(originalUserTask.getId());
entity.setTargetActivityId(newUserTask.getId());
entity.setCounterSignType(countersignType.getType());
entity.setTargetNodeMode(nodeMode.getType());
entity.setAssignerList(valuTargetAssigneeList);
extAxDynamicSignRecordService.saveOrUpdate(entity);
}
// 提取公共处理方法
private String processActivityId(String originalId, String fragment, long count) {
if (originalId.contains(fragment)) {
int fragmentIndex = originalId.indexOf(fragment);
return originalId.substring(0, fragmentIndex) + fragment + count;
}
return originalId + fragment + count; // 不包含目标片段时返回原字符串
}
/**
* 后加签
* <p>
* 基于当前节点的后加签内部实现主要依靠加减签的能力实现
*
* @param commandContext
* @param historicTaskInstance
* @param task
* @param valuTargetAssigneeList
*/
@Deprecated
private void backCountSign(CommandContext commandContext,
HistoricTaskInstance historicTaskInstance,
TaskEntity task,
List<BpmnTaskDelegateAssigner> valuTargetAssigneeList) {
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
TaskService taskService = processEngineConfiguration.getTaskService();
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(historicTaskInstance.getProcessDefinitionId());
FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
if (flowElement instanceof UserTask) {
UserTask userTask = (UserTask) flowElement;
BpmnFlowNodeMode nodeMode = Objects.equals(userTask.getLoopCharacteristics().getCompletionCondition(), AND_SIGN_EXPRESSION) ? BpmnFlowNodeMode.AND : BpmnFlowNodeMode.OR;
switch (nodeMode) {
case AND:
// 这里仅是加签还需要再触发当前人的同意
shareCountSign(commandContext, task, valuTargetAssigneeList);
break;
case OR:
// 修改审批人快照
List<BpmnTaskDelegateAssigner> originAssigners = runtimeService.getVariable(task.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class);
originAssigners.removeIf(e -> !Objects.equals(e.buildAssigneeId(), originTaskAssignee.buildAssigneeId()));
originAssigners.addAll(valuTargetAssigneeList);
runtimeService.setVariable(task.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), originAssigners);
// 后加签的人
valuTargetAssigneeList.forEach(e -> addMultiTask(commandContext, task, e));
// 删除除当前审批人的task
List<Task> currentTasks = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).taskDefinitionKey(task.getTaskDefinitionKey()).active().list();
currentTasks.removeIf(e -> Objects.equals(e.getAssignee(), originTaskAssignee.buildAssigneeId()));
deleteMultiTasks(commandContext, currentTasks);
break;
default:
break;
}
}
}
/**
* 共享签
*
@ -167,7 +332,6 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
private void resolveOriginTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService,
TaskService taskService, TaskEntity task) {
// 构建评论内容
StringBuilder message = new StringBuilder("添加");
int end = Math.min(targetTaskAssigneeList.size(), COUNTERSIGN_ASSIGNER_SHOW_NUMBER);
//加签人员数量显示指定个数
@ -181,6 +345,19 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
message.append("");
}
message.append(targetTaskAssigneeList.size()).append("人进行审批");
switch (countersignType) {
case FORWARD_COUNTERSIGN:
message.append("(前加签)");
break;
case BACK_COUNTERSIGN:
message.append("(后加签)");
break;
default:
message.append("(并加签)");
break;
}
Task virtualTask = createVirtualTask(commandContext, extAxHiTaskInstService, task.getProcessInstanceId(), task.getName(),
task.getTaskDefinitionKey(), advice, originTaskAssignee, COUNTERSIGN.getStatus(), new AddComment(message.toString()));
batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, originTaskAssignee);

View File

@ -0,0 +1,64 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import com.alibaba.fastjson.JSON;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 获取指定流程节点配置的条件权限信息
*
* @author wangli
* @since 2025-10-29 18:02
*/
public class CustomGetConditionPermissionsCmd extends AbstractCommand<List<ConditionPermissionMetaInfo>> {
private final String processInstanceId;
public CustomGetConditionPermissionsCmd(String processInstanceId) {
this.processInstanceId = processInstanceId;
}
@Override
public String paramToJsonString() {
Map<String, Object> params = new HashMap<>();
params.put("processInstanceId", processInstanceId);
return JSON.toJSONString(params);
}
@Override
public List<ConditionPermissionMetaInfo> execute(CommandContext commandContext) {
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (Objects.isNull(processInstance)) {
return Collections.emptyList();
}
TaskService taskService = processEngineConfiguration.getTaskService();
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).active().list();
if (CollectionUtils.isEmpty(tasks)) {
return Collections.emptyList();
}
BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processInstance.getProcessDefinitionId());
FlowElement flowElement = bpmnModel.getFlowElement(tasks.get(0).getTaskDefinitionKey());
List<ConditionPermissionMetaInfo> conditions = BpmnMetaParserHelper.getConditionPermissionConf(flowElement).orElse(Collections.emptyList());
conditions.forEach(e -> e.setValue(null));
return conditions;
}
}

View File

@ -0,0 +1,52 @@
package cn.axzo.workflow.core.engine.cmd;
import com.alibaba.fastjson.JSON;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.form.api.FormEngineConfigurationApi;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormInstance;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.api.FormService;
import org.springframework.util.CollectionUtils;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 通过审批实例 ID 获取对应的表单定义实体的自定义命令
*
* @author wangli
* @since 2025-11-20 10:18
*/
public class CustomGetFormModelByProcessInstanceIdCmd extends AbstractCommand<FormInfo> implements Serializable {
private final String processInstanceId;
public CustomGetFormModelByProcessInstanceIdCmd(String processInstanceId) {
this.processInstanceId = processInstanceId;
}
@Override
public String paramToJsonString() {
Map<String, Object> params = new HashMap<>();
params.put("processInstanceId", processInstanceId);
return JSON.toJSONString(params);
}
@Override
public FormInfo executeInternal(CommandContext commandContext) {
FormEngineConfigurationApi formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(commandContext);
FormService formService = formEngineConfiguration.getFormService();
List<FormInstance> list = formService.createFormInstanceQuery().processInstanceId(processInstanceId).orderBySubmittedDate().desc().list();
if (CollectionUtils.isEmpty(list)) {
return null;
}
String formDefinitionId = list.get(0).getFormDefinitionId();
FormRepositoryService formRepositoryService = formEngineConfiguration.getFormRepositoryService();
return formRepositoryService.getFormModelById(formDefinitionId);
}
}

View File

@ -1,5 +1,6 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.dto.SignatureDTO;
import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.service.CategoryService;
@ -22,10 +23,13 @@ import java.util.Objects;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_BELONG_TENANT_ID;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_NAME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_RESULT;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
@ -63,9 +67,15 @@ public class CustomGetProcessInstanceVariablesCmd extends AbstractCommand<Map<St
HistoryService historyService = processEngineConfiguration.getHistoryService();
HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId).includeProcessVariables().singleResult();
if (Objects.isNull(instance)) {
return variables;
}
variables.put(PRINT_VAR_PROCESS_NAME, instance.getName());
variables.put(PRINT_VAR_PROCESS_BELONG_TENANT_ID, instance.getTenantId());
// 添加流程开始时间
variables.put(PRINT_VAR_PROCESS_START_TIME, sdf.format(instance.getStartTime()));
variables.put(PRINT_VAR_PROCESS_END_TIME, Objects.nonNull(instance.getEndTime()) ? sdf.format(instance.getEndTime()) : null);
variables.put(PRINT_VAR_PROCESS_RESULT, BpmnProcessInstanceResultEnum.fromValue(instance.getBusinessStatus()));
// 添加流程业务 ID
addProcessDefinitionKey(variables, instance);

View File

@ -15,6 +15,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.HistoryService;
@ -27,6 +28,8 @@ import org.flowable.form.api.FormService;
import org.flowable.form.model.FormContainer;
import org.flowable.form.model.FormField;
import org.flowable.form.model.FormFieldTypes;
import org.flowable.form.model.Option;
import org.flowable.form.model.OptionFormField;
import org.flowable.form.model.SimpleFormModel;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@ -42,6 +45,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_CHECKBOX;
import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_RADIO;
import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
@ -75,7 +80,7 @@ public class CustomGetProcessInstanceVariablesToObjectCmd extends AbstractComman
private final String processInstanceId;
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
private static final List<String> SUPPORTED_FORM_TYPES = Lists.newArrayList("input", "date", "textarea", "image", "contacts", "amount", "decimal");
private static final List<String> SUPPORTED_FORM_TYPES = Lists.newArrayList("input", "date", "textarea", "image", "contacts", "amount", "decimal", "checkbox", "radio");
public CustomGetProcessInstanceVariablesToObjectCmd(String processInstanceId) {
this.processInstanceId = processInstanceId;
@ -219,6 +224,39 @@ public class CustomGetProcessInstanceVariablesToObjectCmd extends AbstractComman
.type(convert(field.getType()))
.build());
}
} else if (Objects.equals(field.getType(), "checkbox")) {
if (field instanceof OptionFormField) {
OptionFormField optionField = (OptionFormField) field;
if (StringUtils.hasText(fieldValue.toString())
&& fieldValue.toString().startsWith("[")
&& fieldValue.toString().endsWith("]")) {
List<String> selectedOptions = JSON.parseArray((String) fieldValue, String.class);
List<String> optionNames = ListUtils.emptyIfNull(optionField.getOptions()).stream().filter(i -> selectedOptions.contains(i.getId())).map(Option::getName).collect(Collectors.toList());
variables.add(VariableObjectDTO.builder()
.key(field.getId())
.desc(field.getName())
.value(StringUtils.collectionToDelimitedString(optionNames, ""))
.type(convert(field.getType()))
.build());
}
} else {
throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_CHECKBOX);
}
} else if (Objects.equals(field.getType(), "radio")) {
if (field instanceof OptionFormField) {
OptionFormField optionField = (OptionFormField) field;
if (StringUtils.hasText(fieldValue.toString())) {
List<String> optionNames = ListUtils.emptyIfNull(optionField.getOptions()).stream().filter(i -> Objects.equals(fieldValue, i.getId())).map(Option::getName).collect(Collectors.toList());
variables.add(VariableObjectDTO.builder()
.key(field.getId())
.desc(field.getName())
.value(StringUtils.collectionToDelimitedString(optionNames, ""))
.type(convert(field.getType()))
.build());
}
} else {
throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_RADIO);
}
} else {
variables.add(VariableObjectDTO.builder()
.key(field.getId())
@ -234,6 +272,7 @@ public class CustomGetProcessInstanceVariablesToObjectCmd extends AbstractComman
}
private void addSignature(List<VariableObjectDTO> variables, List<SignatureDTO> signatures) {
log.info("addSignature{}", JSON.toJSONString(signatures));
if (CollectionUtils.isEmpty(signatures)) {
return;
}
@ -272,15 +311,10 @@ public class CustomGetProcessInstanceVariablesToObjectCmd extends AbstractComman
groupVariables.forEach(group -> {
if (!CollectionUtils.isEmpty(group.getVars())) {
group.getVars().forEach(variable -> {
Object value = bizVariables.getOrDefault(variable.getCode(), null);
if (Objects.isNull(value)) {
// 目前为了减少 wps 替换耗时所以将没有值的变量直接踢出
return;
}
variables.add(VariableObjectDTO.builder()
.key(variable.getCode())
.desc(group.getGroupName() + variable.getName())
.value(value)
.value(bizVariables.getOrDefault(variable.getCode(), null))
.type(convert(variable.getType()))
.build());
});
@ -310,6 +344,8 @@ public class CustomGetProcessInstanceVariablesToObjectCmd extends AbstractComman
case "amount":
case "contacts":
case "decimal":
case "checkbox":
case "radio":
return VariableObjectDTO.Type.text;
case "image":
return VariableObjectDTO.Type.img;

View File

@ -0,0 +1,61 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.util.CollectionUtils;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
/**
* 覆写指定流程中的变量
*
* @author wangli
* @since 2025-02-08 10:14
*/
@Slf4j
public class CustomOverrideProcessVariablesCmd extends AbstractCommand<Void> implements Serializable {
private static final long serialVersionUID = 1L;
private final String processInstanceId;
private final Map<String, Object> variables;
public CustomOverrideProcessVariablesCmd(String processInstanceId, Map<String, Object> variables) {
this.processInstanceId = processInstanceId;
this.variables = variables;
}
@Override
public String paramToJsonString() {
Map<String, Object> params = new HashMap<>();
params.put("processInstanceId", processInstanceId);
params.put("formVariables", variables);
return JSON.toJSONString(params);
}
@Override
public Void executeInternal(CommandContext commandContext) {
if (CollectionUtils.isEmpty(variables)) {
return null;
}
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (Objects.nonNull(processInstance)) {
throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
}
runtimeService.setVariables(processInstanceId, variables);
return null;
}
}

View File

@ -0,0 +1,220 @@
package cn.axzo.workflow.core.engine.cmd.helper;
import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.ExtensionAttribute;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.bpmn.parser.factory.ActivityBehaviorFactory;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.List;
import static cn.axzo.workflow.common.code.ConvertorRespCode.CREATE_BPMN_PRE_SIGN_ERROR;
import static cn.axzo.workflow.common.code.ConvertorRespCode.CREATE_BPMN_USER_TASK_ERROR;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
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_SPECIFY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NODE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE;
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_ONLY_ONE;
import static cn.axzo.workflow.common.constant.BpmnConstants.SEQUENCE_FLOW_ID;
import static cn.axzo.workflow.core.common.utils.BpmnJsonConverterUtil.id;
import static cn.axzo.workflow.core.converter.json.UserTaskJsonConverter.setExecutionListeners;
import static cn.axzo.workflow.core.converter.json.UserTaskJsonConverter.setTaskListeners;
/**
* 动态操作 BPMN 定义内容的帮助类
*
* @author wangli
* @since 2025-10-15 17:05
*/
@Slf4j
public class CustomBpmnModelHelper {
private CustomBpmnModelHelper() {
}
/**
* 创建 BPMN 中的 UserTask
*
* @param processEngineConfiguration
* @param originalUserTask
* @param nodeMode
*/
public static UserTask createUserTask(ProcessEngineConfigurationImpl processEngineConfiguration, UserTask originalUserTask, String customActivityId, BpmnFlowNodeMode nodeMode, List<BpmnTaskDelegateAssigner> assigners) {
UserTask newUserTask = new UserTask();
if (StringUtils.hasText(customActivityId)) {
newUserTask.setId(customActivityId);
} else {
newUserTask.setId(originalUserTask.getId());
}
newUserTask.setName(originalUserTask.getName());
newUserTask.setDocumentation("" + originalUserTask.getId() + "节点加签生成");
MultiInstanceLoopCharacteristics loopCharacteristics = new MultiInstanceLoopCharacteristics();
loopCharacteristics.setInputDataItem(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + newUserTask.getId());
loopCharacteristics.setElementVariable("assigneeName");
newUserTask.setLoopCharacteristics(loopCharacteristics);
newUserTask.setAssignee("${assigneeName}");
switch (nodeMode) {
case AND:
loopCharacteristics.setSequential(false);
loopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION);
break;
case OR:
loopCharacteristics.setSequential(false);
loopCharacteristics.setCompletionCondition(OR_SIGN_EXPRESSION_ONLY_ONE);
break;
case SEQUENCE:
loopCharacteristics.setSequential(true);
loopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION);
break;
default:
throw new WorkflowEngineException(CREATE_BPMN_USER_TASK_ERROR, nodeMode.getType());
}
setTaskListeners(newUserTask);
setExecutionListeners(new BpmnJsonNode(), newUserTask);
ExtensionElement approvalMethodElement = new ExtensionElement();
approvalMethodElement.setName(CONFIG_APPROVAL_METHOD);
ExtensionAttribute approvalMethodValueAttribute = new ExtensionAttribute();
approvalMethodValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
approvalMethodValueAttribute.setValue(ApprovalMethodEnum.human.getType());
approvalMethodElement.addAttribute(approvalMethodValueAttribute);
ExtensionAttribute approvalMethodDescAttribute = new ExtensionAttribute();
approvalMethodDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC);
approvalMethodDescAttribute.setValue("审批方式");
approvalMethodElement.addAttribute(approvalMethodDescAttribute);
newUserTask.addExtensionElement(approvalMethodElement);
ExtensionElement approverSpecifyElement = new ExtensionElement();
approverSpecifyElement.setName(CONFIG_APPROVER_SPECIFY);
ExtensionAttribute approverSpecifyValueAttribute = new ExtensionAttribute();
approverSpecifyValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
approverSpecifyValueAttribute.setValue(ApproverSpecifyEnum.fixedPerson.getType());
approverSpecifyElement.addAttribute(approverSpecifyValueAttribute);
ExtensionAttribute approverSpecifyDescAttribute = new ExtensionAttribute();
approverSpecifyDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC);
approverSpecifyDescAttribute.setValue("审批人指定");
approverSpecifyElement.addAttribute(approverSpecifyDescAttribute);
approverSpecifyElement.setElementText(JSON.toJSONString(assigners));
newUserTask.addExtensionElement(approverSpecifyElement);
ExtensionElement nodeTypeElement = new ExtensionElement();
nodeTypeElement.setName(CONFIG_NODE_TYPE);
nodeTypeElement.setElementText(BpmnFlowNodeType.NODE_TASK.getType());
newUserTask.addExtensionElement(nodeTypeElement);
// 追加设置审批人为空的配置因为查找审批人模式是固定人员会执行退场离职校验导致最终审批人可能为空
// 审批人为空时
ExtensionElement approverEmptyHandleTypeElement = new ExtensionElement();
approverEmptyHandleTypeElement.setName(CONFIG_APPROVER_EMPTY_HANDLE_TYPE);
ExtensionAttribute approverEmptyHandleTypeValueAttribute = new ExtensionAttribute();
approverEmptyHandleTypeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
approverEmptyHandleTypeValueAttribute.setValue(ApproverEmptyHandleTypeEnum.autoPassed.getType());
approverEmptyHandleTypeElement.setElementText("[]");
approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeValueAttribute);
ExtensionAttribute approverEmptyHandleTypeDescAttribute = new ExtensionAttribute();
approverEmptyHandleTypeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC);
approverEmptyHandleTypeDescAttribute.setValue("审批人为空时");
approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeDescAttribute);
newUserTask.addExtensionElement(approverEmptyHandleTypeElement);
ActivityBehaviorFactory activityBehaviorFactory = processEngineConfiguration.getActivityBehaviorFactory();
UserTaskActivityBehavior userTaskActivityBehavior = activityBehaviorFactory.createUserTaskActivityBehavior(newUserTask);
ParallelMultiInstanceBehavior behavior = activityBehaviorFactory.createParallelMultiInstanceBehavior(newUserTask, userTaskActivityBehavior);
behavior.setCollectionVariable(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + newUserTask.getId());
behavior.setCollectionElementVariable("assigneeName");
behavior.setCompletionCondition(loopCharacteristics.getCompletionCondition());
newUserTask.setBehavior(behavior);
return newUserTask;
}
public static void rewireSequenceFlows(Process process, UserTask originalUserTask, UserTask targetUserTask, BpmnCountersignTypeEnum countersignType) {
switch (countersignType) {
case FORWARD_COUNTERSIGN:
// 1. 找到所有指向原始节点的输入流
List<SequenceFlow> incomingFlows = originalUserTask.getIncomingFlows();
if (incomingFlows.isEmpty()) {
throw new WorkflowEngineException(CREATE_BPMN_PRE_SIGN_ERROR, "节点 " + originalUserTask.getId() + " 没有输入流,无法进行前加签");
}
// 2. 将这些输入流的目标从 originalUserTask 修改为 targetUserTask
for (SequenceFlow incomingFlow : incomingFlows) {
incomingFlow.setTargetRef(targetUserTask.getId());
// 如果需要也可以更新FlowElement中的引用但通常改TargetRef即可
}
targetUserTask.setIncomingFlows(incomingFlows);
// 3. 创建一个新的顺序流 targetUserTask 指向 originalUserTask
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId(id(SEQUENCE_FLOW_ID + "_ForwardSign"));
newSequenceFlow.setSourceRef(targetUserTask.getId());
newSequenceFlow.setSourceFlowElement(targetUserTask);
newSequenceFlow.setTargetRef(originalUserTask.getId());
newSequenceFlow.setTargetFlowElement(originalUserTask);
targetUserTask.setOutgoingFlows(Collections.singletonList(newSequenceFlow));
process.addFlowElement(newSequenceFlow);
originalUserTask.setIncomingFlows(Collections.singletonList(newSequenceFlow));
break;
case BACK_COUNTERSIGN:
// 1. 找到所有指向原始节点的输出流
List<SequenceFlow> outgoingFlows = originalUserTask.getOutgoingFlows();
if (outgoingFlows.isEmpty()) {
throw new WorkflowEngineException(CREATE_BPMN_PRE_SIGN_ERROR, "节点 " + originalUserTask.getId() + " 没有输出流,无法进行后加签");
}
// 2. 将这些输出流的源从 originalUserTask 修改为 targetUserTask
for (SequenceFlow outgoingFlow : outgoingFlows) {
outgoingFlow.setSourceRef(targetUserTask.getId());
// 如果需要也可以更新FlowElement中的引用但通常改SourceRef即可
}
targetUserTask.setOutgoingFlows(outgoingFlows);
// 3. 创建一个新的顺序流 originalUserTask 指向 targetUserTask
SequenceFlow backSequenceFlow = new SequenceFlow();
backSequenceFlow.setId(id(SEQUENCE_FLOW_ID + "_BackSign"));
backSequenceFlow.setSourceRef(originalUserTask.getId());
backSequenceFlow.setSourceFlowElement(originalUserTask);
backSequenceFlow.setTargetRef(targetUserTask.getId());
backSequenceFlow.setTargetFlowElement(targetUserTask);
originalUserTask.setOutgoingFlows(Collections.singletonList(backSequenceFlow));
process.addFlowElement(backSequenceFlow);
targetUserTask.setIncomingFlows(Collections.singletonList(backSequenceFlow));
break;
default:
break;
}
}
}

View File

@ -78,6 +78,10 @@ import static org.flowable.task.api.Task.DEFAULT_PRIORITY;
@Slf4j
public class CustomTaskHelper {
private CustomTaskHelper() {
}
public static void addMultiTask(CommandContext commandContext, TaskEntity originTask,
BpmnTaskDelegateAssigner newTaskAssignee) {
if (Objects.isNull(originTask)) {
@ -225,7 +229,7 @@ public class CustomTaskHelper {
}
/**
* 校验人员数量是否超过限制
* 校验人员数量是否超过60个人的限制
*
* @param runtimeService
* @param taskEntity
@ -360,8 +364,8 @@ public class CustomTaskHelper {
*/
public static TaskEntity createVirtualTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService
, String processInstanceId, String nodeName, String taskDefinitionKey, String advice,
BpmnTaskDelegateAssigner assigner,
String extTaskInstStatus, AddComment addComment) {
BpmnTaskDelegateAssigner assigner,
String extTaskInstStatus, AddComment addComment) {
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
HistoryService historyService = processEngineConfiguration.getHistoryService();

View File

@ -1,6 +1,5 @@
package cn.axzo.workflow.core.engine.job;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
@ -34,8 +33,8 @@ public class AsyncCancelProcessInstanceJobHandler extends AbstractExecuteWithLoc
log.info("AsyncCancelProcessInstanceHandler executing...,jobInfo:{}", JSONUtil.toJsonStr(job));
log(job);
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
BpmnProcessInstanceCancelDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnProcessInstanceCancelDTO.class);
processEngineConfiguration.getCommandExecutor().execute(new CustomCancelProcessInstanceCmd((SuperBpmnProcessInstanceCancelDTO) dto, extAxHiTaskInstService));
SuperBpmnProcessInstanceCancelDTO dto = JSONUtil.toBean(job.getCustomValues(), SuperBpmnProcessInstanceCancelDTO.class);
processEngineConfiguration.getCommandExecutor().execute(new CustomCancelProcessInstanceCmd(dto, extAxHiTaskInstService));
}
}

View File

@ -3,6 +3,7 @@ package cn.axzo.workflow.core.engine.job;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
import cn.axzo.workflow.core.engine.cmd.CustomCountersignUserTaskCmd;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
@ -19,9 +20,12 @@ public class AsyncCountersignUserTaskJobHandler extends AbstractJobHandler imple
public static final String TYPE = "async-countersign-task";
private final ExtAxHiTaskInstService extAxHiTaskInstService;
private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService;
public AsyncCountersignUserTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) {
public AsyncCountersignUserTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService,
ExtAxDynamicSignRecordService extAxDynamicSignRecordService) {
this.extAxHiTaskInstService = extAxHiTaskInstService;
this.extAxDynamicSignRecordService = extAxDynamicSignRecordService;
}
@Override
@ -42,6 +46,7 @@ public class AsyncCountersignUserTaskJobHandler extends AbstractJobHandler imple
dto.getAdvice(),
dto.getAttachmentList(),
dto.getTargetAssignerList(),
extAxHiTaskInstService));
extAxHiTaskInstService,
extAxDynamicSignRecordService));
}
}

View File

@ -1,10 +1,16 @@
package cn.axzo.workflow.core.listener;
import cn.axzo.workflow.common.constant.BpmnConstants;
import cn.axzo.workflow.core.common.context.OperationContext;
import cn.hutool.json.JSONUtil;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC;
/**
@ -43,4 +49,30 @@ public abstract class AbstractBpmnEventListener<T extends OperationContext> impl
}
return processDefinitionId.split(":")[0];
}
/**
* 移除一些业务不需要关心的变量
*
* @param originVariables
* @return
*/
public static Map<String, Object> removeBpmnConstantsVariables(Map<String, Object> originVariables) {
if (originVariables == null) return new HashMap<>();
// 定义需要移除的前缀列表
List<String> prefixesToRemove = Arrays.asList(
BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO,
BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT,
BpmnConstants.TASK_COMPLETE_OPERATION_TYPE,
BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO
);
return originVariables.entrySet().stream()
.filter(entry -> entry.getKey() != null)
// 核心修改检查 key 是否以任一前缀开头
.filter(entry -> prefixesToRemove.stream()
.noneMatch(prefix -> entry.getKey().startsWith(prefix)))
.collect(HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), HashMap::putAll);
}
}

View File

@ -84,4 +84,9 @@ public class ExtAxDict extends BaseEntity<ExtAxDict> {
* 版本号
*/
private Integer version;
/**
* 自定替换未设置的变量为空串
*/
private Boolean autoReplaceVariables;
}

View File

@ -0,0 +1,56 @@
package cn.axzo.workflow.core.repository.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.conf.handler.ListAssigneeTypeHandler;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
/**
* 扩展动态前后加签记录,用于应用重启后的模型内容恢复
*
* @author wangli
* @since 2025-10-17 18:23
*/
@EqualsAndHashCode(callSuper = true)
@TableName(value = "ext_ax_dynamic_sign_record", autoResultMap = true)
@Data
@ToString(callSuper = true)
public class ExtAxDynamicSignRecord extends BaseEntity<ExtAxDynamicSignRecord> {
/**
* 流程实例 ID
*/
private String processInstanceId;
/**
* 流程实例对应的流程定义 ID
*/
private String processDefinitionId;
/**
* 加签原节点
*/
private String originalActivityId;
/**
* 加签目标节点
*/
private String targetActivityId;
/**
* 加签类型前加签/后加签
* {@link BpmnCountersignTypeEnum}
*/
private String counterSignType;
/**
* 目标节点的审批方式会签或签顺序签
*/
private String targetNodeMode;
/**
* 被加签的审批人
*/
@TableField(value = "assigner_list", typeHandler = ListAssigneeTypeHandler.class)
private List<BpmnTaskDelegateAssigner> assignerList;
}

View File

@ -0,0 +1,14 @@
package cn.axzo.workflow.core.repository.mapper;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import org.apache.ibatis.annotations.Mapper;
/**
* 动态前后加签记录 Mapper
*
* @author wangli
* @since 2025-10-17 18:28
*/
@Mapper
public interface ExtAxDynamicSignRecordMapper extends BaseMapperX<ExtAxDynamicSignRecord> {
}

View File

@ -1,9 +1,13 @@
package cn.axzo.workflow.core.repository.mapper;
import cn.axzo.workflow.core.conf.handler.ListAssigneeTypeHandler;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.type.JdbcType;
@Mapper
public interface ExtAxProcessLogMapper extends BaseMapperX<ExtAxProcessLog> {
@ -13,5 +17,8 @@ public interface ExtAxProcessLogMapper extends BaseMapperX<ExtAxProcessLog> {
@Select("select * from ext_ax_process_log WHERE process_instance_id = #{processInstanceId} and task_id = #{taskId}")
@Results({
@Result(column = "assignee_full", property = "assigneeFull", jdbcType = JdbcType.ARRAY, typeHandler = ListAssigneeTypeHandler.class)
})
ExtAxProcessLog findByProcessIdAndTaskIdWithDeleted(String processInstanceId, String taskId);
}

View File

@ -10,10 +10,12 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCre
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO;
import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo;
import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO;
@ -211,4 +213,8 @@ public interface BpmnProcessInstanceService {
boolean hasPrintTemplate(String processInstanceId, String processDefinitionId);
List<DocPendingVO> processInstanceSelectDocs(ProcessDocQueryDTO dto);
void overrideProcessVariables(BpmnProcessInstanceVariablesUpdateDTO dto);
List<ConditionPermissionMetaInfo> getConditions(String processInstanceId);
}

View File

@ -0,0 +1,13 @@
package cn.axzo.workflow.core.service;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 动态前后加签记录服务接口
*
* @author wangli
* @since 2025-10-17 18:30
*/
public interface ExtAxDynamicSignRecordService extends IService<ExtAxDynamicSignRecord> {
}

View File

@ -1,5 +1,6 @@
package cn.axzo.workflow.core.service;
import cn.axzo.workflow.common.model.dto.CustomDocDTO;
import cn.axzo.workflow.common.model.dto.SimpleDocDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO;
@ -18,5 +19,5 @@ public interface ExtAxReadRecordService extends IService<ExtAxReadRecord> {
List<SimpleDocDTO> queryReadStatus(ApproverReadStatusDTO dto);
Boolean changeReadStatus(ChangeApproverReadStatusDTO dto);
Boolean changeReadStatus(ChangeApproverReadStatusDTO dto, List<CustomDocDTO> customDocs);
}

View File

@ -14,6 +14,7 @@ import cn.axzo.workflow.common.enums.BusinessTypeEnum;
import cn.axzo.workflow.common.enums.ButtonVisibleScopeEnum;
import cn.axzo.workflow.common.enums.WorkspaceType;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.CustomDocDTO;
import cn.axzo.workflow.common.model.dto.SignFileDTO;
import cn.axzo.workflow.common.model.dto.SimpleDocDTO;
import cn.axzo.workflow.common.model.request.BpmnApproveConf;
@ -29,6 +30,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCre
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO;
@ -38,6 +40,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO;
import cn.axzo.workflow.common.model.request.category.CategorySearchDTO;
import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo;
import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.BatchOperationItemResultVO;
@ -64,8 +67,10 @@ import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCarbonCopyUserSelectorCmd;
import cn.axzo.workflow.core.engine.cmd.CustomForecastUserTaskAssigneeCmd;
import cn.axzo.workflow.core.engine.cmd.CustomGetConditionPermissionsCmd;
import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd;
import cn.axzo.workflow.core.engine.cmd.CustomOverrideFormVariablesByLatestInstanceCmd;
import cn.axzo.workflow.core.engine.cmd.CustomOverrideProcessVariablesCmd;
import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst;
@ -154,6 +159,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CREATE_PARAM_ERROR;
@ -187,6 +193,9 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_INITIA
import static cn.axzo.workflow.common.constant.BpmnConstants.PENDING_TEMPLATE_VARIABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_OWNERSHIP_APPLICATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATORIES;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_BASED_FILE_TAG_ORDER;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOCS;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_PROCESS_ENABLE_DOC_IDS;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_VARIABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION;
@ -495,6 +504,12 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
dto.getVariables().put(SIGN_PROCESS_ENABLE_DOC_IDS, dto.getDocIds());
dto.getVariables().put(SIGN_VARIABLE, dto.getBizCustomVariables());
dto.getVariables().put(SIGNATORIES, dto.getSignatories());
// 业务自定义文档
dto.getVariables().put(SIGN_BIZ_CUSTOM_DOCS, dto.getCustomDocs());
// 业务自定义文档追加顺序类型
dto.getVariables().put(SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE, StringUtils.hasText(dto.getDocAddOrderType()) ? dto.getDocAddOrderType() : "last");
// 基于业务的标签排序
dto.getVariables().put(SIGN_BIZ_BASED_FILE_TAG_ORDER, dto.getBasedFileTagOrder());
}
});
dto.getVariables().put(INTERNAL_INITIATOR, dto.getInitiator().toJson());
@ -1798,7 +1813,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
});
}
public List<AttachmentDTO> getAttachmentByType(Map<String, List<Attachment>> attachmentByTaskMap, String
public static List<AttachmentDTO> getAttachmentByType(Map<String, List<Attachment>> attachmentByTaskMap, String
taskId, AttachmentTypeEnum type) {
return ListUtils.emptyIfNull(attachmentByTaskMap.get(taskId)).stream()
.filter(attachment -> Objects.equals(type.getType(), attachment.getType()))
@ -1874,6 +1889,9 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
List<DocBaseVO> docs = commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(), true, extAxModelDocMapper, extAxReModelService));
// 获取业务自定义传入的文档
getAndAddBizCustomDocs(dto.getProcessInstanceId(), docs);
Map<Long, Boolean> readStatusMap = new HashMap<>();
if (Objects.nonNull(dto.getAssigner())) {
readStatusMap.putAll(extAxReadRecordService.queryReadStatus(ApproverReadStatusDTO.builder()
@ -1892,8 +1910,74 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
if (Objects.nonNull(archive)) {
t.setFileRelationId(archive.getFileCode());
t.setFileKey(archive.getFileKey());
} else {
// 业务自定义文档
t.setFileKey(s.getFileRelationId());
}
t.setReadStatus(readStatusMap.getOrDefault(t.getId(), false));
});
}
private List<DocBaseVO> getAndAddBizCustomDocs(String processInstanceId, List<DocBaseVO> docs) {
String tenantId;
if (CollectionUtils.isEmpty(docs)) {
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
tenantId = Objects.nonNull(processInstance) ? processInstance.getTenantId() : "";
} else {
tenantId = docs.get(0).getTenantId();
}
HistoricVariableInstance historicVariableInstance = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId)
.variableName(SIGN_BIZ_CUSTOM_DOCS).singleResult();
if (Objects.isNull(historicVariableInstance)) {
return Collections.emptyList();
}
List<CustomDocDTO> bizCustomDocs = Optional.ofNullable((List<CustomDocDTO>) historicVariableInstance.getValue())
.orElse(Collections.emptyList());
// 业务自定义文档
List<DocBaseVO> customBizDocs = BeanMapper.copyList(bizCustomDocs, DocBaseVO.class, (s, t) -> {
t.setStatus(true);
t.setTempFile(false);
t.setTemplateName(s.getFileName());
t.setTag(s.getFileTag());
t.setTenantId(tenantId);
t.setFileRelationId(s.getFileKey());
});
HistoricVariableInstance signBizOrder = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName(SIGN_BIZ_BASED_FILE_TAG_ORDER).singleResult();
List<String> basedFileTagOrder = Optional.ofNullable((List<String>) signBizOrder.getValue()).orElse(Collections.emptyList());
if (!CollectionUtils.isEmpty(basedFileTagOrder)) {
docs.addAll(customBizDocs);
// 基于 fileTag 排序
Map<String, Integer> fileTagOrderMap = IntStream.range(0, basedFileTagOrder.size())
.boxed()
.collect(Collectors.toMap(basedFileTagOrder::get, i -> i, (a, b) -> a));
docs.sort(Comparator.comparing(d -> fileTagOrderMap.getOrDefault(d.getTag(), Integer.MAX_VALUE)));
docs = docs.stream().sorted(Comparator.comparingInt(d -> fileTagOrderMap.getOrDefault(d.getTag(), Integer.MAX_VALUE)))
.collect(Collectors.toList());
} else {
HistoricVariableInstance bizDocOrderType = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName(SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE).singleResult();
String customAddType = String.valueOf(bizDocOrderType.getValue());
if ("last".equals(customAddType)) {
docs.addAll(customBizDocs);
} else {
docs.addAll(0, customBizDocs);
}
}
return docs;
}
@Override
public void overrideProcessVariables(BpmnProcessInstanceVariablesUpdateDTO dto) {
CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
commandExecutor.execute(new CustomOverrideProcessVariablesCmd(dto.getProcessInstanceId(), dto.getBizCustomVariables()));
}
@Override
public List<ConditionPermissionMetaInfo> getConditions(String processInstanceId) {
CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
return commandExecutor.execute(new CustomGetConditionPermissionsCmd(processInstanceId));
}
}

View File

@ -53,6 +53,7 @@ import cn.axzo.workflow.core.engine.cmd.CustomTransferUserTaskCmd;
import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessTaskService;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter;
@ -187,6 +188,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
private ExtAxProcessLogService processLogService;
@Resource
private SupportRefreshProperties refreshProperties;
@Resource
private ExtAxDynamicSignRecordService extAxDynamicSignRecordService;
@Override
public BpmPageResult<BpmnTaskTodoPageItemVO> getTodoTaskPage(BpmnTaskPageSearchDTO dto) {
@ -862,7 +865,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
} else {
commandExecutor.execute(new CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum.valueOfType(dto.getCountersignType()), dto.getTaskId(),
dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(),
extAxHiTaskInstService));
extAxHiTaskInstService, extAxDynamicSignRecordService));
}
}

View File

@ -119,6 +119,7 @@ public class CategoryServiceImpl extends ServiceImpl<ExtAxDictMapper, ExtAxDict>
dict.setIcon(dto.getIcon());
dict.setDisplayInitiateMenu(dto.getDisplayInitiateMenu());
dict.setVersion(dto.getVersion());
dict.setAutoReplaceVariables(Boolean.TRUE.equals(dto.getAutoReplaceVariables()));
}
@Override

View File

@ -0,0 +1,24 @@
package cn.axzo.workflow.core.service.impl;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import cn.axzo.workflow.core.repository.mapper.ExtAxDynamicSignRecordMapper;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 流程签署业务信息记录
*
* @author wangli
* @since 2025-04-02 10:06
*/
@Service
@Slf4j
public class ExtAxDynamicSignRecordServiceImpl extends ServiceImpl<ExtAxDynamicSignRecordMapper, ExtAxDynamicSignRecord> implements ExtAxDynamicSignRecordService {
@Resource
private ExtAxDynamicSignRecordMapper extAxDynamicSignRecordMapper;
}

View File

@ -1,5 +1,6 @@
package cn.axzo.workflow.core.service.impl;
import cn.axzo.workflow.common.model.dto.CustomDocDTO;
import cn.axzo.workflow.common.model.dto.SimpleDocDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO;
@ -11,7 +12,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.TaskService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@ -34,8 +34,6 @@ public class ExtAxReadRecordServiceImpl extends ServiceImpl<ExtAxReadRecordMappe
@Resource
private ExtAxReadRecordMapper extAxReadRecordMapper;
@Resource
private TaskService taskService;
@Resource
private ExtAxModelDocService modelDocService;
@Override
@ -48,7 +46,7 @@ public class ExtAxReadRecordServiceImpl extends ServiceImpl<ExtAxReadRecordMappe
}
@Override
public Boolean changeReadStatus(ChangeApproverReadStatusDTO dto) {
public Boolean changeReadStatus(ChangeApproverReadStatusDTO dto, List<CustomDocDTO> customDocs) {
ExtAxReadRecord entity = new ExtAxReadRecord();
entity.setProcessInstanceId(dto.getProcessInstanceId());
entity.setPersonId(Long.valueOf(dto.getAssigner().getPersonId()));
@ -63,7 +61,11 @@ public class ExtAxReadRecordServiceImpl extends ServiceImpl<ExtAxReadRecordMappe
record.setPersonId(Long.valueOf(dto.getAssigner().getPersonId()));
record.setReadStatus(Lists.newArrayList(SimpleDocDTO.builder()
.id(dto.getDocId())
.tag(modelDocService.get(dto.getDocId()).getTag())
.tag(dto.getDocId() > 0 ? modelDocService.get(dto.getDocId()).getTag()
: customDocs.stream()
.filter(i -> Objects.equals(i.getId(), dto.getDocId()))
.findFirst()
.orElse(new CustomDocDTO()).getFileTag())
.readStatus(dto.getReadStatus())
.build()));
extAxReadRecordMapper.insert(record);
@ -76,7 +78,11 @@ public class ExtAxReadRecordServiceImpl extends ServiceImpl<ExtAxReadRecordMappe
} else {
record.getReadStatus().add(SimpleDocDTO.builder()
.id(dto.getDocId())
.tag(modelDocService.get(dto.getDocId()).getTag())
.tag(dto.getDocId() > 0 ? modelDocService.get(dto.getDocId()).getTag()
: customDocs.stream()
.filter(i -> Objects.equals(i.getId(), dto.getDocId()))
.findFirst()
.orElse(new CustomDocDTO()).getFileTag())
.readStatus(dto.getReadStatus())
.build());
}

View File

@ -3,6 +3,7 @@ package cn.axzo.workflow.form.service.converter;
import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO;
import org.flowable.form.model.FormContainer;
import org.flowable.form.model.FormField;
import org.flowable.form.model.OptionFormField;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@ -34,6 +35,16 @@ public interface FormFieldConverter extends EntityConverter<FormFieldDTO, FormFi
@Mapping(target = "params", source = "entity.params")
FormFieldDTO toVo(FormField entity);
@Mapping(target = "fieldType", expression = "java(entity.getClass().getSimpleName())")
@Mapping(target = "id", source = "entity.id")
@Mapping(target = "name", source = "entity.name")
@Mapping(target = "type", source = "entity.type")
@Mapping(target = "value", expression = "java(ConversionUtils.convertObject(entity.getValue()))")
@Mapping(target = "placeholder", source = "entity.placeholder")
@Mapping(target = "params", source = "entity.params")
@Mapping(target = "options", source = "entity.options")
FormFieldDTO toVo(OptionFormField entity);
@Mapping(target = "fieldType", expression = "java(entity.getClass().getSimpleName())")
@Mapping(target = "id", source = "entity.id")
@Mapping(target = "name", source = "entity.name")
@ -50,7 +61,9 @@ public interface FormFieldConverter extends EntityConverter<FormFieldDTO, FormFi
for (FormField entity : entities) {
if(entity instanceof FormContainer) {
dtos.add(toVo((FormContainer) entity));
} else{
} else if (entity instanceof OptionFormField) {
dtos.add(toVo((OptionFormField) entity));
} else {
dtos.add(toVo((FormField) entity));
}
}

View File

@ -1,15 +1,19 @@
package cn.axzo.workflow.form.service.converter;
import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO;
import cn.axzo.workflow.common.model.request.form.definition.OptionDTO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import cn.axzo.workflow.common.model.response.form.model.FormModelVO;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormModel;
import org.flowable.form.model.FormContainer;
import org.flowable.form.model.FormField;
import org.flowable.form.model.Option;
import org.flowable.form.model.OptionFormField;
import org.flowable.form.model.SimpleFormModel;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.springframework.util.CollectionUtils;
import java.util.Arrays;
import java.util.List;
@ -64,6 +68,11 @@ public interface FormInstanceConverter extends EntityConverter<FormInstanceVO, F
formFieldDTO.setValue(ConversionUtils.convertObject(formField.getValue()));
formFieldDTO.setPlaceholder(formField.getPlaceholder());
formFieldDTO.setParams(formField.getParams());
if (formField instanceof OptionFormField) {
OptionFormField optionFormField = (OptionFormField) formField;
formFieldDTO.setFieldType(optionFormField.getClass().getSimpleName());
formFieldDTO.setOptions(optionsToOptionDTOs(optionFormField.getOptions()));
}
if (formField instanceof FormContainer) {
FormContainer formContainer = (FormContainer) formField;
formFieldDTO.setFieldType(formContainer.getClass().getSimpleName());
@ -72,6 +81,19 @@ public interface FormInstanceConverter extends EntityConverter<FormInstanceVO, F
return formFieldDTO;
}
default List<OptionDTO> optionsToOptionDTOs(List<Option> options) {
if (CollectionUtils.isEmpty(options)) {
return null;
}
return options.stream()
.map(option -> {
OptionDTO optionDTO = new OptionDTO();
optionDTO.setId(option.getId());
optionDTO.setName(option.getName());
return optionDTO;
})
.collect(Collectors.toList());
}
// 辅助方法用于处理List<FormContainer>到List<FormFieldDTO>的转换调用上面自定义的转换方法
default List<FormFieldDTO> formContainersToFormFieldDTOs(List<FormField> formContainers) {

View File

@ -158,6 +158,18 @@
<groupId>cn.axzo.infra</groupId>
<artifactId>adapter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -12,7 +12,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@MapperScan({"cn.axzo.workflow.core.**.mapper", "cn.axzo.workflow.admin.**.mapper"})
@ComponentScan({"cn.axzo.workflow"})
@ComponentScan({"cn.axzo.workflow", "cn.axzo.oss"})
@EnableFeignClients({"cn.axzo.oss", "cn.axzo.riven.client.feign", "cn.axzo.msg.center", "cn.axzo.basics.profiles"})
@SpringBootApplication(exclude = RabbitAutoConfiguration.class)
@EnableTransactionManagement

View File

@ -13,6 +13,7 @@ import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.time.Duration;
import java.util.Enumeration;
import java.util.Objects;
@ -22,10 +23,10 @@ import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_A
import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_HTTP_CLIENT;
import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_HTTP_CLIENT_VALUE;
import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_130;
import static cn.axzo.workflow.common.constant.StarterConstants.ENABLE_MANAGEABLE;
import static cn.axzo.workflow.common.code.OtherRespCode.CLIENT_VERSION_SUPPORT;
import static cn.axzo.workflow.common.code.OtherRespCode.MICRO_SERVER_NEED_REBUILD;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_130;
import static cn.axzo.workflow.common.constant.StarterConstants.ENABLE_MANAGEABLE;
/**
* 客户端与服务端的版本比较
@ -47,11 +48,11 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (Objects.equals(HEADER_HTTP_CLIENT_VALUE, request.getHeader(HEADER_HTTP_CLIENT))) {
String headerClientVersion = request.getHeader(HEADER_API_VERSION)
.replaceAll("-SNAPSHOT", "")
.replaceAll("-RELEASE", "");
.replaceAll("-SNAPSHOT", "")
.replaceAll("-RELEASE", "");
serviceVersion = serviceVersion
.replaceAll("-SNAPSHOT", "")
.replaceAll("-RELEASE", "");
.replaceAll("-SNAPSHOT", "")
.replaceAll("-RELEASE", "");
DefaultArtifactVersion minimumSupportedVersion = new DefaultArtifactVersion(FLOW_SERVER_VERSION_130);
DefaultArtifactVersion clientVersion = new DefaultArtifactVersion(headerClientVersion);
DefaultArtifactVersion serverVersion = new DefaultArtifactVersion(serviceVersion);
@ -65,10 +66,29 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor {
throw new WorkflowEngineException(CLIENT_VERSION_SUPPORT, serviceVersion, headerClientVersion);
}
}
if (request.getRequestURI().contains("/web/process/validate-auth")) {
return true;
}
if (request.getRequestURI().contains("/web/process/form")) {
HttpSession session = request.getSession();
// 检查session中是否有"已验证"标记
Boolean isAuthenticated = (Boolean) session.getAttribute("isAuthenticated");
if (isAuthenticated == null || !isAuthenticated) {
// 未验证转发到原页面由页面展示授权码输入框
return true; // 不拦截由页面逻辑处理
// 或重定向到单独的授权页面response.sendRedirect("/auth/page");
}
}
// feignApi 才需要检查版本
if (!request.getRequestURI().contains("/web/") && !request.getRequestURI().contains("checkDeath")
&& !request.getRequestURI().contains("/error")
&& !StringUtils.hasText(request.getHeader(HEADER_HTTP_CLIENT))) {
&& !request.getRequestURI().contains(".ico")// 这三行主要解决form.html页面访问
&& !request.getRequestURI().contains(".json")//
&& !request.getRequestURI().contains(".html")//
&& !request.getRequestURI().contains("/error")
&& !StringUtils.hasText(request.getHeader(HEADER_HTTP_CLIENT))) {
String serverName = request.getHeader(HEADER_SERVER_NAME);
printHeader(request);
log.error(MICRO_SERVER_NEED_REBUILD.getMessage(), serverName);
@ -104,7 +124,7 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor {
}
ExtAxProperty property = extAxProperty.get();
if (Objects.equals(property.getValue(), clientVersion.toString())
&& Objects.equals(property.getManageable().toString(), manageableStatus)) {
&& Objects.equals(property.getManageable().toString(), manageableStatus)) {
return;
}
property.setName(requestApplicationName);

View File

@ -0,0 +1,64 @@
package cn.axzo.workflow.server.common.util;
/**
* 处理字符串视觉显示宽度的工具类
* 适用于中英文混排的对齐场景
*/
public class VisualStringUtils {
// 私有构造器防止实例化
private VisualStringUtils() {
throw new UnsupportedOperationException("Utility class");
}
/**
* 将输入字符串转换为等效视觉长度的空格字符串
* "Hi中" -> " " (4个空格H=1, i=1, =2)
*
* @param input 原始字符串
* @return 等长的空格字符串如果输入为null则返回空串
*/
public static String toEqualLengthSpace(String input) {
if (input == null || input.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
for (char c : input.toCharArray()) {
if (isWideChar(c)) {
sb.append(" "); // 宽字符占2格
} else {
sb.append(" "); // 窄字符占1格
}
}
return sb.toString();
}
/**
* 获取字符串的视觉显示长度
* (中文算2英文算1)
*
* @param input 输入字符串
* @return 视觉长度
*/
public static int getVisualLength(String input) {
if (input == null || input.isEmpty()) {
return 0;
}
int length = 0;
for (char c : input.toCharArray()) {
length += isWideChar(c) ? 2 : 1;
}
return length;
}
/**
* 判断字符是否为宽字符占2个显示宽度
* 逻辑 ASCII 字符 (code > 255) 视为宽字符
*/
private static boolean isWideChar(char c) {
// 0-255 包括了数字英文大小写英文标点符号 (半角)
// 大于 255 的涵盖了汉字全角符号日韩文等
return c > 255;
}
}

View File

@ -18,9 +18,11 @@ import cn.axzo.workflow.common.model.dto.UploadFieldDTO;
import cn.axzo.workflow.common.model.dto.VariableObjectDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.engine.cmd.CustomGetProcessInstanceVariablesToObjectCmd;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.springframework.stereotype.Component;
@ -28,6 +30,8 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
@ -41,7 +45,9 @@ import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCE
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC;
import static cn.axzo.workflow.common.model.dto.VariableObjectDTO.Type.img;
import static cn.axzo.workflow.common.model.dto.VariableObjectDTO.Type.signatureAndAdvice;
import static cn.axzo.workflow.common.model.dto.VariableObjectDTO.Type.text;
/**
* WPS 工具类
@ -50,6 +56,7 @@ import static cn.axzo.workflow.common.model.dto.VariableObjectDTO.Type.signature
* @since 2025-04-09 20:41
*/
@Component
@Slf4j
public class WpsUtil {
public static final List<FileTypeEnum> WPS_SUPPORTED_FILE_TYPES = Lists.newArrayList(FileTypeEnum.WORD, FileTypeEnum.EXCEL);
@Resource
@ -58,6 +65,71 @@ public class WpsUtil {
private ServerFileServiceApi serverFileServiceApi;
@Resource
private OrganizationalNodeUserQueryApi organizationalNodeUserQueryApi;
private static final String TRANSPARENT_IMAGE_URL = "https://axzo-obs-public.obs.cn-north-4.myhuaweicloud.com:443/obs-public/obs-public/62F92618016840F89D8810CD1816E04D.png";
public static void main(String[] args) {
List<String> strList = new ArrayList<>();
strList.add("[中文]");
strList.add("[中文English,]");
}
/**
* 调用 wps 文件变量替换接口
*
* @param fileCode 基于该模板进行变量替换
* @param fileKey 如果有值则变量替换完成后直接将文档覆盖该 fileKey 对应的文件
* @return 返回替换变量后的文件oss fileKey
*/
public String wpsFileVariableReplace(List<VariableObjectDTO> wpsVariables,
String fileCode, String fileKey, String fileName, Boolean autoReplaceVariables) {
List<VariableObjectDTO> variables = wpsVariables.stream()
.filter(i -> Objects.equals(i.getType().name(), VariableObjectDTO.Type.img.name()) || Objects.equals(i.getType().name(), VariableObjectDTO.Type.text.name()))
.filter(i -> {
if (Objects.equals(Boolean.TRUE, autoReplaceVariables)) {
return true;
} else {
return Objects.nonNull(i.getValue()) && checkNotEmptyColl(i);
}
}).collect(Collectors.toList());
List<FileReplaceContent> fileReplaceContents = BeanMapper.copyList(variables, FileReplaceContent.class, (s, t) -> {
t.setKey(s.getDesc());
if (Objects.equals(s.getType().name(), VariableObjectDTO.Type.img.name())) {
List<UploadFieldDTO> uploadFieldDTOS = new ArrayList<>();
if (isJson(s.getValue().toString())) {
uploadFieldDTOS.addAll(JSON.parseArray(s.getValue().toString(), UploadFieldDTO.class));
} else {
uploadFieldDTOS.addAll(JSON.parseArray(JSON.toJSONString(s.getValue()), UploadFieldDTO.class));
}
t.setContent(CollectionUtils.isEmpty(uploadFieldDTOS) ?
// 强制设置为透明图片
TRANSPARENT_IMAGE_URL : uploadFieldDTOS.get(0).getFileUrl());
} else {
t.setContent(Objects.nonNull(s.getValue()) ? s.getValue().toString() : "");
}
t.setType(s.getType().name());
t.setPrefix("[");
t.setSuffix("]");
});
// 处理空值
if (Objects.equals(Boolean.TRUE, autoReplaceVariables)) {
fileReplaceContents.stream()
.filter(i -> !StringUtils.hasText(i.getContent()))
.forEach(i -> {
if (Objects.equals(i.getType(), img.name())) {
//强制设置为透明图片
i.setContent(TRANSPARENT_IMAGE_URL);
} else if (Objects.equals(i.getType(), text.name())) {
// 将变量替换未空串
String equalLengthSpace = VisualStringUtils.toEqualLengthSpace(i.getPrefix() + i.getKey() + i.getSuffix());
i.setContent(equalLengthSpace);
}
});
}
return replaceWordText(fileCode, fileKey, fileName, fileReplaceContents);
}
/**
* 调用 wps 文件变量替换接口
@ -69,18 +141,19 @@ public class WpsUtil {
public String wpsFileVariableReplace(List<VariableObjectDTO> wpsVariables,
String fileCode, String fileKey, String fileName) {
List<FileReplaceContent> fileReplaceContents = BeanMapper.copyList(wpsVariables.stream()
.filter(i -> Objects.nonNull(i.getValue()))
.filter(i -> !(Objects.equals(i.getType().name(), "img") && !StringUtils.hasText(i.getValue().toString())))
/*List<FileReplaceContent> fileReplaceContents = BeanMapper.copyList(wpsVariables.stream()
.filter(i -> Objects.nonNull(i.getValue()) && checkNotEmptyColl(i))
.filter(i -> Objects.equals(i.getType().name(), "img") || Objects.equals(i.getType().name(), "text"))
.collect(Collectors.toList()), FileReplaceContent.class, (s, t) -> {
t.setKey(s.getDesc());
if (Objects.equals(s.getType().name(), "img")) {
List<UploadFieldDTO> uploadFieldDTOS;
if (isJson(s.getValue().toString())) {
t.setContent(JSON.parseArray(s.getValue().toString(), UploadFieldDTO.class).get(0).getFileUrl());
uploadFieldDTOS = JSON.parseArray(s.getValue().toString(), UploadFieldDTO.class);
} else {
t.setContent(JSON.parseArray(JSON.toJSONString(s.getValue()), UploadFieldDTO.class).get(0).getFileUrl());
uploadFieldDTOS = JSON.parseArray(JSON.toJSONString(s.getValue()), UploadFieldDTO.class);
}
t.setContent(CollectionUtils.isEmpty(uploadFieldDTOS) ? "" : uploadFieldDTOS.get(0).getFileUrl());
} else {
t.setContent(Objects.nonNull(s.getValue()) ? s.getValue().toString() : "");
}
@ -88,6 +161,11 @@ public class WpsUtil {
t.setPrefix("[");
t.setSuffix("]");
});
return replaceWordText(fileCode, fileKey, fileName, fileReplaceContents);*/
return wpsFileVariableReplace(wpsVariables, fileCode, fileKey, fileName, false);
}
private String replaceWordText(String fileCode, String fileKey, String fileName, List<FileReplaceContent> fileReplaceContents) {
if (StringUtils.hasText(fileCode)) {
FileTemplateReplaceRequest request = new FileTemplateReplaceRequest();
request.setFileCode(fileCode);
@ -103,6 +181,14 @@ public class WpsUtil {
}
}
private static boolean checkNotEmptyColl(VariableObjectDTO i) {
if (!(i.getValue() instanceof Collection)) {
return true;
}
return !CollectionUtils.isEmpty((Collection<?>) i.getValue());
}
public List<VariableObjectDTO> getWpsReplaceVariables(ProcessEngineConfigurationImpl processEngineConfiguration, String processInstanceId) {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
List<VariableObjectDTO> wpsVariables =
@ -111,15 +197,28 @@ public class WpsUtil {
List<VariableObjectDTO> signatureAndAdvices = Lists.newArrayList();
wpsVariables.stream().filter(i -> Objects.equals(signatureAndAdvice, i.getType())).forEach(variableObjectDTO -> {
List<SignatureDTO.SignDetail> signDetails = (List<SignatureDTO.SignDetail>) variableObjectDTO.getValue();
log.info("getWpsReplaceVariables signDetails: {}", JSON.toJSONString(signDetails));
if (!CollectionUtils.isEmpty(signDetails)) {
SignatureDTO.SignDetail signDetail = signDetails.get(0);
signatureAndAdvices.add(VariableObjectDTO.builder()
.key(variableObjectDTO.getKey() + "_approverName")
.desc(variableObjectDTO.getDesc() + "姓名")
.value(signDetail.getApproverName())
.type(VariableObjectDTO.Type.text)
.build());
signatureAndAdvices.add(VariableObjectDTO.builder()
.key(variableObjectDTO.getKey() + "_activityResult")
.desc(variableObjectDTO.getDesc() + "审批结果")
.value(signDetail.getResult())
.type(VariableObjectDTO.Type.text)
.build());
ApiSignUrlDownloadRequest request = ApiSignUrlDownloadRequest.builder()
.fileKeys(Lists.newArrayList(signDetail.getSignature())).build();
List<ApiSignUrlDownloadResponse> signUrl = RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(request), "获取手写签图片地址", request);
signatureAndAdvices.add(VariableObjectDTO.builder()
.key(variableObjectDTO.getKey() + "_signature")
.desc(variableObjectDTO.getDesc() + "电子签名")
.value(Lists.newArrayList(UploadFieldDTO.builder().fileKey(signDetail.getSignature()).fileUrl(signUrl.get(0).getSignUrl()).build()))
.value(CollectionUtils.isEmpty(signUrl) ? Lists.newArrayList() : Lists.newArrayList(UploadFieldDTO.builder().fileKey(signDetail.getSignature()).fileUrl(CollectionUtils.isEmpty(signUrl) ? null : signUrl.get(0).getSignUrl()).build()))
.type(VariableObjectDTO.Type.img)
.build());
signatureAndAdvices.add(VariableObjectDTO.builder()
@ -128,6 +227,12 @@ public class WpsUtil {
.value(signDetail.getAdvice())
.type(VariableObjectDTO.Type.text)
.build());
signatureAndAdvices.add(VariableObjectDTO.builder()
.key(variableObjectDTO.getKey() + "_activityOperationTime")
.desc(variableObjectDTO.getDesc() + "日期")
.value(DateUtil.format(signDetail.getOperationTime(), "yyyy.MM.dd"))
.type(VariableObjectDTO.Type.text)
.build());
}
});
wpsVariables.addAll(signatureAndAdvices);
@ -152,6 +257,7 @@ public class WpsUtil {
.build());
});
log.info("wpsVariables: {}", JSON.toJSONString(wpsVariables));
return wpsVariables;
}

View File

@ -7,6 +7,7 @@ import cn.axzo.karma.client.model.response.PersonProfileResp;
import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssignerV2Resp;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.CarbonCopyObjectType;
import cn.axzo.workflow.common.exception.WorkflowApproverCalcException;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
@ -45,6 +46,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.CALC_TASK_ASSIGNEE_ERROR;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.COOPERATION_NOT_EXIST_WITH_NODE;
import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_META_DATA_FORMAT_ERROR;
import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR;
import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_PARAM_ERROR;
@ -122,6 +124,40 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign
return Collections.emptyList();
}
protected final <T> T parseFoundationApiResultWithErrCode(Supplier<cn.axzo.foundation.result.ApiResult<T>> supplier, String operatorDesc,
String extInfo, Object... param) {
StopWatch stopWatch = new StopWatch(operatorDesc);
log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param));
stopWatch.start();
cn.axzo.foundation.result.ApiResult<T> result = supplier.get();
stopWatch.stop();
log.info("{}-Cost:{}, Result: {}", operatorDesc,
"API StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " 's",
JSONUtil.toJsonStr(result));
try {
if (stopWatch.getTotalTimeSeconds() > refreshProperties.getApiTimeout() && Boolean.TRUE.equals(refreshProperties.getSendDingTalk())) {
DingTalkUtils.sendDingTalkForSlowUrl(applicationContext.getEnvironment()
.getProperty("spring.profiles.active"),
stopWatch.getTotalTimeSeconds(),
extInfo,
param,
result);
}
} catch (Exception e) {
// ignore
}
Assert.notNull(result, "服务调用异常");
if (Objects.equals(30022100, result.getCode())) {
// 特殊需求组织不存在时抛得的异常码
throw new WorkflowApproverCalcException(COOPERATION_NOT_EXIST_WITH_NODE);
}
// 200自定义处理
if (HttpStatus.HTTP_OK != result.getCode()) {
throw new WorkflowEngineException(CALC_TASK_ASSIGNEE_ERROR, "[API:" + extInfo + "]" + result.getMsg());
}
return result.getData();
}
protected final <T> T parseFoundationApiResult(Supplier<cn.axzo.foundation.result.ApiResult<T>> supplier, String operatorDesc,
String extInfo, Object... param) {
StopWatch stopWatch = new StopWatch(operatorDesc);

View File

@ -9,9 +9,11 @@ import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssignerV2Resp;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyRangeEnum;
import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum;
import cn.axzo.workflow.common.exception.WorkflowApproverCalcException;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
@ -21,6 +23,7 @@ import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@ -33,12 +36,14 @@ import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TA
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN;
import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.transferToAdmin;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyRange;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyRangeOrgLimit;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyValueV2;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getAreaFilterEnable;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getOnlyInProjectEnable;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSpecialtyFilterEnable;
/**
@ -76,6 +81,7 @@ public class BasedIdentityV2TaskAssigneeSelector extends AbstractBpmnTaskAssigne
.initiatorPersonId(initiator.parsePersonId())
.areaCodes(getAreaFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeAreaCodes())) : Sets.newHashSet())
.specialtyCodes(getSpecialtyFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeSpecialtyCodes())) : Sets.newHashSet())
.projectIds(getOnlyInProjectEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getProjectIds())) : Sets.newHashSet())
.querySupervisorWhileMissMatched(getApproverEmptyHandleType(flowElement).filter(type -> Objects.equals(type, transferToAdmin)).isPresent());
switch (optRange.get()) {
case within_the_project:
@ -118,8 +124,14 @@ public class BasedIdentityV2TaskAssigneeSelector extends AbstractBpmnTaskAssigne
break;
}
FlowTaskAssignerV2Req request = v2ReqBuilder.build();
List<FlowTaskAssignerV2Resp> apiResultUsers = parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询身份下的人" + execution.getProcessInstanceId() + flowElement.getId(),
"cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request);
List<FlowTaskAssignerV2Resp> apiResultUsers = new ArrayList<>();
try {
apiResultUsers.addAll(parseFoundationApiResultWithErrCode(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询身份下的人" + execution.getProcessInstanceId() + flowElement.getId(),
"cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request));
} catch (WorkflowApproverCalcException e) {
log.warn("组织节点不存在, 入参:{}; 错误信息:{}", JSONUtil.toJsonStr(request), e.getMessage(), e);
execution.setVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + flowElement.getId(), true);
}
return convertApprover(apiResultUsers);
}

View File

@ -7,8 +7,10 @@ import cn.axzo.orggateway.api.nodeuser.req.FlowTaskAssignerV2Req;
import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssignerV2Resp;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum;
import cn.axzo.workflow.common.exception.WorkflowApproverCalcException;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
@ -17,17 +19,20 @@ import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN;
import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.transferToAdmin;
import static cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum.in_project;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getAreaFilterEnable;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getInitiatorLeaderRangeUnit;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getOnlyInProjectEnable;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSpecialtyFilterEnable;
/**
@ -69,11 +74,18 @@ public class BasedInitiatorLeaderV2TaskAssigneeSelector extends AbstractBpmnTask
.initiatorPersonId(initiator.parsePersonId())
.areaCodes(getAreaFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeAreaCodes())) : Sets.newHashSet())
.specialtyCodes(getSpecialtyFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeSpecialtyCodes())) : Sets.newHashSet())
.projectIds(getOnlyInProjectEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getProjectIds())) : Sets.newHashSet())
.querySupervisorWhileMissMatched(getApproverEmptyHandleType(flowElement).filter(type -> Objects.equals(type, transferToAdmin)).isPresent());
FlowTaskAssignerV2Req request = v2ReqBuilder.build();
List<FlowTaskAssignerV2Resp> apiResultUsers = parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询发起人主管的审批人" + execution.getProcessInstanceId() + flowElement.getId(),
"cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request);
List<FlowTaskAssignerV2Resp> apiResultUsers = new ArrayList<>();
try {
apiResultUsers.addAll(parseFoundationApiResultWithErrCode(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询发起人主管的审批人" + execution.getProcessInstanceId() + flowElement.getId(),
"cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request));
} catch (WorkflowApproverCalcException e) {
log.warn("组织节点不存在, 入参:{}; 错误信息:{}", JSONUtil.toJsonStr(request), e.getMessage(), e);
execution.setVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + flowElement.getId(), true);
}
return convertApprover(apiResultUsers);
}

View File

@ -10,9 +10,11 @@ import cn.axzo.workflow.common.enums.ApproverSpecifyRangeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum;
import cn.axzo.workflow.common.enums.CooperateShipTypeEnum;
import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum;
import cn.axzo.workflow.common.exception.WorkflowApproverCalcException;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
@ -22,6 +24,7 @@ import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@ -33,6 +36,7 @@ import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_POSITIO
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN;
import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.transferToAdmin;
import static cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum.in_project;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType;
@ -42,6 +46,7 @@ import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprove
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyValueV2;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getAreaFilterEnable;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCooperateShipType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getOnlyInProjectEnable;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSpecialtyFilterEnable;
/**
@ -80,6 +85,7 @@ public class BasedPositionV2TaskAssigneeSelector extends AbstractBpmnTaskAssigne
.initiatorPersonId(initiator.parsePersonId())
.areaCodes(getAreaFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeAreaCodes())) : Sets.newHashSet())
.specialtyCodes(getSpecialtyFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeSpecialtyCodes())) : Sets.newHashSet())
.projectIds(getOnlyInProjectEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getProjectIds())) : Sets.newHashSet())
.querySupervisorWhileMissMatched(getApproverEmptyHandleType(flowElement).filter(type -> Objects.equals(type, transferToAdmin)).isPresent());
switch (optRange.get()) {
case within_the_project:
@ -141,8 +147,14 @@ public class BasedPositionV2TaskAssigneeSelector extends AbstractBpmnTaskAssigne
break;
}
FlowTaskAssignerV2Req request = v2ReqBuilder.build();
List<FlowTaskAssignerV2Resp> apiResultUsers = parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询岗位下的人" + execution.getProcessInstanceId() + flowElement.getId(),
"cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request);
List<FlowTaskAssignerV2Resp> apiResultUsers = new ArrayList<>();
try {
apiResultUsers.addAll(parseFoundationApiResultWithErrCode(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询岗位下的人" + execution.getProcessInstanceId() + flowElement.getId(),
"cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request));
} catch (WorkflowApproverCalcException e) {
log.warn("组织节点不存在, 入参:{}; 错误信息:{}", JSONUtil.toJsonStr(request), e.getMessage(), e);
execution.setVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + flowElement.getId(), true);
}
return convertApprover(apiResultUsers);
}

View File

@ -10,9 +10,11 @@ import cn.axzo.workflow.common.enums.ApproverSpecifyRangeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum;
import cn.axzo.workflow.common.enums.CooperateShipTypeEnum;
import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum;
import cn.axzo.workflow.common.exception.WorkflowApproverCalcException;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.CooperationOrgDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
@ -23,6 +25,7 @@ import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@ -34,6 +37,7 @@ import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_ROLE_V2
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN;
import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.transferToAdmin;
import static cn.axzo.workflow.common.enums.ApproverSpecifyRangeUnitEnum.in_project;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType;
@ -43,6 +47,7 @@ import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprove
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecifyValueV2;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getAreaFilterEnable;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCooperateShipType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getOnlyInProjectEnable;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSpecialtyFilterEnable;
/**
@ -82,6 +87,7 @@ public class BasedRoleV2TaskAssigneeSelector extends AbstractBpmnTaskAssigneeSel
.initiatorPersonId(initiator.parsePersonId())
.areaCodes(getAreaFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeAreaCodes())) : Sets.newHashSet())
.specialtyCodes(getSpecialtyFilterEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getIncludeSpecialtyCodes())) : Sets.newHashSet())
.projectIds(getOnlyInProjectEnable(flowElement) ? Sets.newHashSet(ListUtils.emptyIfNull(orgDTO.getProjectIds())) : Sets.newHashSet())
.querySupervisorWhileMissMatched(getApproverEmptyHandleType(flowElement).filter(type -> Objects.equals(type, transferToAdmin)).isPresent());
switch (optRange.get()) {
case within_the_project:
@ -144,8 +150,14 @@ public class BasedRoleV2TaskAssigneeSelector extends AbstractBpmnTaskAssigneeSel
break;
}
FlowTaskAssignerV2Req request = v2ReqBuilder.build();
List<FlowTaskAssignerV2Resp> apiResultUsers = parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询角色下的人" + execution.getProcessInstanceId() + flowElement.getId(),
"cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request);
List<FlowTaskAssignerV2Resp> apiResultUsers = new ArrayList<>();
try {
apiResultUsers.addAll(parseFoundationApiResultWithErrCode(() -> orgNodeUserApi.listFlowTaskAssignerV2(request), "新版查询角色下的人" + execution.getProcessInstanceId() + flowElement.getId(),
"cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.listFlowTaskAssignerV2", request));
} catch (WorkflowApproverCalcException e) {
log.warn("组织节点不存在, 入参:{}; 错误信息:{}", JSONUtil.toJsonStr(request), e.getMessage(), e);
execution.setVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + flowElement.getId(), true);
}
return convertApprover(apiResultUsers);
}

View File

@ -63,6 +63,7 @@ public class OperationFileArchiveActivityEvent_101_Listener extends AbstractBpmn
List<VariableObjectDTO> wpsReplaceVariables = wpsUtil.getWpsReplaceVariables(processEngineConfiguration, execution.getProcessInstanceId());
processSign.getFileArchive().stream().filter(i -> Objects.equals(i.getFileType(), FileTypeEnum.WORD)
|| Objects.equals(i.getFileType(), FileTypeEnum.EXCEL))
.filter(i -> Boolean.TRUE.equals(i.getNeedReplaceVariables()))
.forEach(docBaseVO -> {
String newFileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, null, docBaseVO.getFileKey(), docBaseVO.getTemplateName() + docBaseVO.getFileType().getSuffix());
docBaseVO.setFileKey(newFileKey);

View File

@ -178,7 +178,7 @@ public class RocketMqBpmActivityEvent_100_Listener extends AbstractBpmnEventList
if (Objects.nonNull(processInstance)) {
dto.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
dto.setBusinessKey(processInstance.getBusinessKey());
dto.setVariables(processInstance.getProcessVariables());
dto.setVariables(removeBpmnConstantsVariables(processInstance.getProcessVariables()));
dto.setWorkflowEngineVersion(String.valueOf(processInstance.getProcessVariables()
.getOrDefault(WORKFLOW_ENGINE_VERSION, FLOW_SERVER_VERSION_121)));
} else {

View File

@ -23,10 +23,10 @@ import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.beans.factory.annotation.Value;
@ -45,7 +45,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
@ -109,6 +108,8 @@ public class RocketMqMessagePushEventListener extends AbstractBpmnEventListener<
@Resource
private HistoryService historyService;
@Resource
private RuntimeService runtimeService;
@Resource
private TaskService taskService;
@Value("${sendMq:true}")
private Boolean sendMQ;
@ -365,11 +366,8 @@ public class RocketMqMessagePushEventListener extends AbstractBpmnEventListener<
private Map<String, Object> collectionVariable(MessagePushEvent event) {
Map<String, Object> variables = new HashMap<>();
Map<String, Object> originVariables = runtimeService.getVariables(event.getProcessInstanceId());
BpmnProcessInstanceVO processInstance = getContext().getInstanceVO(() -> getBpmnProcessInstanceVO(event));
Map<String, Object> originVariables = processInstance.getVariables().entrySet().stream()
.filter(e -> Objects.nonNull(e.getKey()) && Objects.nonNull(e.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(originVariables.get(INTERNAL_INITIATOR));
if (Objects.isNull(initiator)) {
initiator = new BpmnTaskDelegateAssigner();
@ -451,7 +449,7 @@ public class RocketMqMessagePushEventListener extends AbstractBpmnEventListener<
.setTemplateId(templateId)
.setTaskId(event.getTaskId())
.setReceivePersons(event.getAssigners())
.setVariables(variables)
.setVariables(removeBpmnConstantsVariables(variables))
.setProcessApproveConf(event.getProcessApproveConfig())
.setActivitySignature(activitySignature)
.setTerminalType(terminalType);

View File

@ -4,12 +4,14 @@ import cn.axzo.workflow.common.enums.FileTypeEnum;
import cn.axzo.workflow.common.model.dto.SignFileDTO;
import cn.axzo.workflow.common.model.dto.VariableObjectDTO;
import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf;
import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import cn.axzo.workflow.core.common.context.ProcessOperationContext;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.listener.AbstractBpmnEventListener;
import cn.axzo.workflow.core.listener.BpmnProcessEventListener;
import cn.axzo.workflow.core.repository.entity.ExtAxDocContent;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign;
import cn.axzo.workflow.core.service.CategoryService;
import cn.axzo.workflow.core.service.ExtAxDocContentService;
import cn.axzo.workflow.core.service.ExtAxModelDocService;
import cn.axzo.workflow.core.service.ExtAxProcessSignService;
@ -35,6 +37,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
/**
* 签署业务审批完成后进行文件归档并对所有工人发送业务待办
*
@ -55,6 +59,8 @@ public class FileArchiveProcessEventListener extends AbstractBpmnEventListener<P
@Resource
private ExtAxDocContentService extAxDocContentService;
@Resource
private CategoryService categoryService;
@Resource
private WpsUtil wpsUtil;
@ -71,18 +77,21 @@ public class FileArchiveProcessEventListener extends AbstractBpmnEventListener<P
HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId).includeProcessVariables().singleResult();
CategoryItemVO category = categoryService.get(BPM_MODEL_CATEGORY, mainProcess.getId()).orElse(new CategoryItemVO());
// 文件归档将审批过程中产生的数据全部替换文档模板变量
archiveFinalDocs(instance);
archiveFinalDocs(instance, category.getAutoReplaceVariables());
}
private void archiveFinalDocs(HistoricProcessInstance instance) {
private void archiveFinalDocs(HistoricProcessInstance instance, Boolean autoReplaceVariables) {
ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) getEngineConfiguration();
ExtAxProcessSign processSign = extAxProcessSignService.findByProcessInstanceId(instance.getId());
List<VariableObjectDTO> wpsReplaceVariables = wpsUtil.getWpsReplaceVariables(processEngineConfiguration, instance.getId());
processSign.getFileArchive().stream().filter(i -> Objects.equals(i.getFileType(), FileTypeEnum.WORD)
|| Objects.equals(i.getFileType(), FileTypeEnum.EXCEL))
.filter(i -> Boolean.TRUE.equals(i.getNeedReplaceVariables()))
.forEach(docBaseVO -> {
String newFileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, null, docBaseVO.getFileKey(), docBaseVO.getTemplateName() + docBaseVO.getFileType().getSuffix());
String newFileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, null, docBaseVO.getFileKey(), docBaseVO.getTemplateName() + docBaseVO.getFileType().getSuffix(), autoReplaceVariables);
docBaseVO.setFileKey(newFileKey);
});
// 删除非 WPS 的临时文档
@ -115,6 +124,6 @@ public class FileArchiveProcessEventListener extends AbstractBpmnEventListener<P
@Override
public int getOrder() {
return Integer.MIN_VALUE + 3;
return Integer.MIN_VALUE + 99;
}
}

View File

@ -81,7 +81,7 @@ public class RocketMqBpmnProcessEventListener extends AbstractBpmnEventListener<
.setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey())
.setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion())
.setInitiator(initiator)
.setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables())
.setVariables(removeBpmnConstantsVariables(((ExecutionEntityImpl) event.getEntity()).getVariables()))
.setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime())
.setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId())
.setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getBusinessKey())
@ -117,7 +117,7 @@ public class RocketMqBpmnProcessEventListener extends AbstractBpmnEventListener<
.setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey())
.setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion())
.setInitiator(initiator)
.setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables())
.setVariables(removeBpmnConstantsVariables(((ExecutionEntityImpl) event.getEntity()).getVariables()))
.setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime())
.setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId())
.setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getProcessInstance().getBusinessKey())
@ -151,7 +151,7 @@ public class RocketMqBpmnProcessEventListener extends AbstractBpmnEventListener<
.setInitiator(initiator)
.setLastOperationAssigner(getContext().getLastOperationAssigner(() -> BpmnTaskDelegateAssigner.toObjectCompatible(
runtimeService.getVariable(event.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, BpmnTaskDelegateAssigner.class))))
.setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables())
.setVariables(removeBpmnConstantsVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables()))
.setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime())
.setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId())
.setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey())
@ -192,7 +192,7 @@ public class RocketMqBpmnProcessEventListener extends AbstractBpmnEventListener<
.setInitiator(initiator)
.setLastOperationAssigner(getContext().getLastOperationAssigner(() -> BpmnTaskDelegateAssigner.toObjectCompatible(
runtimeService.getVariable(event.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, BpmnTaskDelegateAssigner.class))))
.setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables())
.setVariables(removeBpmnConstantsVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables()))
.setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime())
.setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId())
.setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey())
@ -228,7 +228,7 @@ public class RocketMqBpmnProcessEventListener extends AbstractBpmnEventListener<
.setInitiator(initiator)
.setLastOperationAssigner(getContext().getLastOperationAssigner(() -> BpmnTaskDelegateAssigner.toObjectCompatible(
runtimeService.getVariable(event.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, BpmnTaskDelegateAssigner.class))))
.setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables())
.setVariables(removeBpmnConstantsVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables()))
.setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime())
.setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId())
.setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey())
@ -264,7 +264,7 @@ public class RocketMqBpmnProcessEventListener extends AbstractBpmnEventListener<
.setInitiator(initiator)
.setLastOperationAssigner(getContext().getLastOperationAssigner(() -> BpmnTaskDelegateAssigner.toObjectCompatible(
runtimeService.getVariable(event.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, BpmnTaskDelegateAssigner.class))))
.setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables())
.setVariables(removeBpmnConstantsVariables(((ExecutionEntityImpl) event.getEntity()).getVariables()))
.setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime())
.setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId())
.setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceBusinessKey())

View File

@ -3,6 +3,7 @@ package cn.axzo.workflow.server.controller.listener.task;
import cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi;
import cn.axzo.nanopart.doc.api.index.request.CopyNodeRequest;
import cn.axzo.workflow.common.enums.FileTypeEnum;
import cn.axzo.workflow.common.model.dto.CustomDocDTO;
import cn.axzo.workflow.common.model.dto.SignFileDTO;
import cn.axzo.workflow.common.model.dto.VariableObjectDTO;
import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf;
@ -23,6 +24,7 @@ import cn.axzo.workflow.server.common.util.WpsUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
@ -35,10 +37,17 @@ import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_BASED_FILE_TAG_ORDER;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOCS;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
/**
@ -90,17 +99,59 @@ public class FirstCopyTemplateFileTaskEvent_105_Listener extends AbstractBpmnEve
processSign.setProcessInstanceId(processInstanceId);
processSign.setSignType(signConfig.get().getSignType().getType());
processSign.setPendingMessageId(signConfig.get().getSignPendingProperty().getPendingMessageId());
// 业务自定义文档
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
List<CustomDocDTO> customDocs = Optional.ofNullable(
runtimeService.getVariable(processInstanceId, SIGN_BIZ_CUSTOM_DOCS, List.class))
.orElse(Collections.emptyList());
if (CollectionUtils.isEmpty(docs)) {
processSign.setDocTemplate(Collections.emptyList());
processSign.setFileArchive(Collections.emptyList());
} else {
// 复制基础模板
List<SignFileDTO> docTemplates = copyTempTemplate(docs);
processSign.setDocTemplate(docTemplates);
List<SignFileDTO> archives = replaceTemplateVariable(docTemplates, processEngineConfiguration, processInstanceId);
processSign.setFileArchive(archives);
}
if (CollectionUtils.isEmpty(customDocs)) {
processSign.setFileArchive(Collections.emptyList());
}
// 复制基础模板
List<SignFileDTO> docTemplates = copyTempTemplate(docs);
List<SignFileDTO> customDocTemplates = new ArrayList<>();
for (CustomDocDTO customDoc : customDocs) {
customDocTemplates.add(SignFileDTO.builder()
.id(customDoc.getId())
.fileName(customDoc.getFileName())
.templateName(customDoc.getFileName())
.fileTag(customDoc.getFileTag())
.fileCode(customDoc.getFileCode())
.fileKey(customDoc.getFileKey())
.fileType(customDoc.getFileType())
.needReplaceVariables(Boolean.TRUE.equals(customDoc.getNeedReplaceVariables()))
.build());
}
List<String> basedFileTagOrder = runtimeService.getVariable(processInstanceId, SIGN_BIZ_BASED_FILE_TAG_ORDER, List.class);
if (Objects.nonNull(basedFileTagOrder)) {
docTemplates.addAll(customDocTemplates);
// 基于 fileTag 排序
Map<String, Integer> fileTagOrderMap = IntStream.range(0, basedFileTagOrder.size())
.boxed()
.collect(Collectors.toMap(basedFileTagOrder::get, i -> i, (a, b) -> a));
docTemplates.sort(Comparator.comparing(d -> fileTagOrderMap.getOrDefault(d.getFileTag(), Integer.MAX_VALUE)));
docTemplates = docTemplates.stream().sorted(Comparator.comparingInt(d -> fileTagOrderMap.getOrDefault(d.getFileTag(), Integer.MAX_VALUE)))
.collect(Collectors.toList());
} else {
// 基于前插还是后插排序
String customAddType = runtimeService.getVariable(processInstanceId, SIGN_BIZ_CUSTOM_DOC_ADD_ORDER_TYPE, String.class);
if (Objects.equals("last", customAddType)) {
docTemplates.addAll(customDocTemplates);
} else {
docTemplates.addAll(0, customDocTemplates);
}
}
processSign.setDocTemplate(docTemplates);
List<SignFileDTO> archives = replaceTemplateVariable(docTemplates, processEngineConfiguration, processInstanceId);
processSign.setFileArchive(archives);
// 没有可用的文档但仍然记录库表
extAxProcessSignService.save(processSign);
}
@ -108,20 +159,22 @@ public class FirstCopyTemplateFileTaskEvent_105_Listener extends AbstractBpmnEve
private List<SignFileDTO> replaceTemplateVariable(List<SignFileDTO> docTemplates, ProcessEngineConfigurationImpl processEngineConfiguration, String processInstanceId) {
List<SignFileDTO> archives = new ArrayList<>();
List<VariableObjectDTO> wpsReplaceVariables = wpsUtil.getWpsReplaceVariables(processEngineConfiguration, processInstanceId);
docTemplates.forEach(template -> {
SignFileDTO signFileDTO = new SignFileDTO();
signFileDTO.setId(template.getId());
signFileDTO.setFileName(template.getFileName());
signFileDTO.setTemplateName(template.getTemplateName());
signFileDTO.setFileTag(template.getFileTag());
signFileDTO.setFileType(template.getFileType());
signFileDTO.setFileCode(template.getFileCode());
if (Objects.equals(template.getFileType(), FileTypeEnum.WORD) || Objects.equals(template.getFileType(), FileTypeEnum.EXCEL)) {
String fileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, template.getFileCode(), null, template.getTemplateName() + template.getFileType().getSuffix());
signFileDTO.setFileKey(fileKey);
}
archives.add(signFileDTO);
});
docTemplates.stream().filter(i -> Boolean.TRUE.equals(i.getNeedReplaceVariables()))
.forEach(template -> {
SignFileDTO signFileDTO = new SignFileDTO();
signFileDTO.setId(template.getId());
signFileDTO.setFileName(template.getFileName());
signFileDTO.setTemplateName(template.getTemplateName());
signFileDTO.setFileTag(template.getFileTag());
signFileDTO.setFileType(template.getFileType());
signFileDTO.setFileCode(template.getFileCode());
signFileDTO.setNeedReplaceVariables(template.getNeedReplaceVariables());
if (Objects.equals(template.getFileType(), FileTypeEnum.WORD) || Objects.equals(template.getFileType(), FileTypeEnum.EXCEL)) {
String fileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, template.getFileCode(), template.getFileKey(), template.getTemplateName() + template.getFileType().getSuffix());
signFileDTO.setFileKey(fileKey);
}
archives.add(signFileDTO);
});
return archives;
}
@ -152,6 +205,7 @@ public class FirstCopyTemplateFileTaskEvent_105_Listener extends AbstractBpmnEve
default:
break;
}
signFileDTO.setNeedReplaceVariables(true);
files.add(signFileDTO);
});
return files;

View File

@ -154,7 +154,7 @@ public class RocketMqBpmnTaskEvent_102_Listener extends AbstractBpmnEventListene
.setInitiator(BpmnTaskDelegateAssigner.toObjectCompatible(delegateTask.getVariable(INTERNAL_INITIATOR)))
.setApprover(BpmnTaskDelegateAssigner.toObjectCompatible(
delegateTask.getVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId())))
.setVariables(delegateTask.getVariables())
.setVariables(removeBpmnConstantsVariables(delegateTask.getVariables()))
.setStartTime(delegateTask.getCreateTime())
.setTenantId(delegateTask.getTenantId())
.setBusinessKey(processInstance.getBusinessKey())

View File

@ -0,0 +1,240 @@
package cn.axzo.workflow.server.controller.web;
import cn.axzo.framework.domain.data.AssertUtil;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.BpmnProcessTaskService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessInstanceController;
import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessJobController;
import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessTaskController;
import cn.axzo.workflow.server.service.AuthCodeService;
import cn.axzo.workflow.server.xxljob.DangerSuperOperationJobHandler;
import cn.azxo.framework.common.model.CommonResponse;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Objects;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.StarterConstants.K8S_POD_NAME_SPACE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
/**
* 用于临时处理审批的一些问题
*
* @author wangli
* @since 2025-11-18 15:37
*/
@Slf4j
@Controller
public class DangerOperationController {
@Resource
private RuntimeService runtimeService;
@Resource
private BpmnProcessInstanceController instanceController;
@Resource
private BpmnProcessTaskController taskController;
@Resource
private BpmnProcessTaskService taskService;
@Resource
private BpmnProcessJobController jobController;
@Resource
private ExtAxProcessLogService processLogService;
@Resource
private AuthCodeService authCodeService;
@Resource
private Environment environment;
// 显示表单页面
@GetMapping("/web/process/form")
public String showProcessForm(HttpSession session, Model model) {
// 检查session中是否已验证授权码
Boolean isAuthenticated = (Boolean) session.getAttribute("isAuthenticated");
model.addAttribute("isAuthenticated", isAuthenticated != null && isAuthenticated);
String myPodNamespace = environment.getProperty(K8S_POD_NAME_SPACE);
model.addAttribute("apiBaseUrl", StringUtils.hasText(myPodNamespace) ? "/workflow-engine" : "");
// 可以在这里添加需要传递到页面的数据
return "form"; // 对应templates目录下的form.html
}
/**
* 获取授权码
*
* @param password
* @return
*/
@PostMapping("/web/process/get-auth-code")
@ResponseBody
public CommonResponse<String> getAuthCode(@RequestParam String password) {
if (Objects.equals("WANG+lI648438", password)) {
String authCode = authCodeService.generateAuthCode();
return CommonResponse.success(authCode);
}
return CommonResponse.error("密码错误");
}
/**
* 验证用户输入的授权码
*/
@PostMapping("/web/process/validate-auth")
public String validateAuthCode(@RequestParam String authCode, HttpSession session, Model model) {
if (Objects.equals("WANG+lI648438", authCode) || authCodeService.validateAuthCode(authCode)) {
// 验证通过在session中标记
session.setAttribute("isAuthenticated", true);
model.addAttribute("isAuthenticated", true);
} else {
// 验证失败提示错误
model.addAttribute("isAuthenticated", false);
model.addAttribute("authError", "授权码无效或已过期,请重新输入");
}
String myPodNamespace = environment.getProperty(K8S_POD_NAME_SPACE);
model.addAttribute("apiBaseUrl", StringUtils.hasText(myPodNamespace) ? "/workflow-engine" : "");
return "form"; // 重新显示授权码输入框
}
// 处理表单提交
@PostMapping(value = "/web/process/handle")
@ResponseBody
public CommonResponse<String> handleProcess(@Validated @RequestBody DangerSuperOperationJobHandler.DangerOperationJobParam jobParam, Model model) {
// 处理表单提交的逻辑
log.info("请求参入: {}", JSON.toJSONString(jobParam));
try {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(jobParam.getProcessInstanceId()).singleResult();
AssertUtil.notNull(processInstance, PROCESS_INSTANCE_NOT_EXISTS);
// 这里可以添加实际的业务逻辑如调用流程引擎API等
switch (jobParam.getOperationType()) {
case CANCEL:
cancelProcessInstance(jobParam);
break;
case APPROVE:
approveTask(jobParam);
break;
case REJECT:
rejectTask(jobParam);
break;
case ABORT:
abortProcessInstance(jobParam);
break;
case RESUMER_DEADLINE_JOB:
resumerDeadlineJob(jobParam);
break;
default:
break;
}
// 可以将处理结果添加到模型中返回给页面
model.addAttribute("message", "操作已成功提交");
return CommonResponse.success("操作已成功提交");
} catch (Exception e) {
model.addAttribute("message", "操作失败: " + e.getMessage());
return CommonResponse.error("操作失败: " + e.getMessage());
}
}
private void resumerDeadlineJob(DangerSuperOperationJobHandler.DangerOperationJobParam jobParam) {
jobController.executeDeadLetterJobAction("", jobParam.getProcessInstanceId());
}
private void abortProcessInstance(DangerSuperOperationJobHandler.DangerOperationJobParam jobParam) {
instanceController.abortProcessInstance(BpmnProcessInstanceAbortDTO.builder()
.processInstanceId(jobParam.getProcessInstanceId())
.advice(jobParam.getComment())
.build());
}
private void rejectTask(DangerSuperOperationJobHandler.DangerOperationJobParam jobParam) {
String personId = jobParam.getPersonId();
if (!StringUtils.hasText(personId)) {
log.warn("缺少 personId 参数,无法驳回任务");
}
String taskId = null;
if (!Objects.equals("0", personId)) {
taskId = taskService.findTaskIdByInstanceIdAndPersonId(jobParam.getProcessInstanceId(), jobParam.getPersonId());
}
ExtAxProcessLog query = new ExtAxProcessLog();
query.setProcessInstanceId(jobParam.getProcessInstanceId());
query.setTaskId(taskId);
query.setStatus(PROCESSING.getStatus());
List<ExtAxProcessLog> logs = processLogService.genericQuery(query);
if (CollectionUtils.isEmpty(logs)) {
log.warn("未找到可操作的任务日志无法驳回任务processInstanceId={}, personId={}", jobParam.getProcessInstanceId(), jobParam.getPersonId());
return;
}
taskController.rejectTask(BpmnTaskAuditDTO.builder()
.processInstanceId(jobParam.getProcessInstanceId())
.advice(jobParam.getComment())
.approver(logs.get(0).getAssigneeFull().get(0))
.async(false)
.build());
}
private void approveTask(DangerSuperOperationJobHandler.DangerOperationJobParam jobParam) {
String personId = jobParam.getPersonId();
if (!StringUtils.hasText(personId)) {
log.warn("缺少 personId 参数,无法驳回任务");
}
String taskId = null;
if (!Objects.equals("0", personId)) {
taskId = taskService.findTaskIdByInstanceIdAndPersonId(jobParam.getProcessInstanceId(), jobParam.getPersonId());
}
ExtAxProcessLog query = new ExtAxProcessLog();
query.setProcessInstanceId(jobParam.getProcessInstanceId());
query.setTaskId(taskId);
query.setStatus(PROCESSING.getStatus());
List<ExtAxProcessLog> logs = processLogService.genericQuery(query);
if (CollectionUtils.isEmpty(logs)) {
log.warn("未找到可操作的任务日志无法驳回任务processInstanceId={}, personId={}", jobParam.getProcessInstanceId(), jobParam.getPersonId());
return;
}
taskController.approveTask(BpmnTaskAuditDTO.builder()
.processInstanceId(jobParam.getProcessInstanceId())
.advice(jobParam.getComment())
.approver(logs.get(0).getAssigneeFull().get(0))
.async(false)
.build());
}
private void cancelProcessInstance(DangerSuperOperationJobHandler.DangerOperationJobParam jobParam) {
String processInstanceId = jobParam.getProcessInstanceId();
BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(runtimeService.getVariable(processInstanceId, INTERNAL_INITIATOR));
BpmnProcessInstanceCancelDTO cancelDTO = new BpmnProcessInstanceCancelDTO();
cancelDTO.setProcessInstanceId(processInstanceId);
cancelDTO.setInitiator(assigner);
cancelDTO.setReason(jobParam.getComment());
cancelDTO.setAsync(false);
instanceController.cancelProcessInstance(cancelDTO);
log.info("撤回操作完成");
}
}

View File

@ -1,6 +1,9 @@
package cn.axzo.workflow.server.controller.web;
import cn.axzo.framework.domain.ServiceException;
import cn.axzo.oss.http.api.ServerFileServiceSdk;
import cn.axzo.oss.http.model.ServerFileUploadSdkRequest;
import cn.axzo.oss.http.model.ServerFileUploadSdkResponse;
import cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi;
import cn.axzo.workflow.common.model.dto.VariableObjectDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO;
@ -14,6 +17,7 @@ import cn.axzo.workflow.core.engine.cmd.CustomGetProcessInstanceVariablesToObjec
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.service.BpmnProcessInstanceService;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.support.ExpressionConditionCmd;
import cn.axzo.workflow.core.service.support.FlowNodeForecastService;
import cn.axzo.workflow.form.service.FormDefinitionService;
import cn.axzo.workflow.server.common.annotation.RepeatSubmit;
@ -23,6 +27,7 @@ import cn.axzo.workflow.server.xxljob.SpecifyProcessInstanceSyncEsJobHandler;
import cn.azxo.framework.common.model.CommonResponse;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
@ -54,6 +59,8 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Date;
import java.util.List;
import java.util.Map;
@ -113,6 +120,8 @@ public class TestController {
private TaskService taskService;
@Resource
private SupportRefreshProperties refreshProperties;
@Resource
private ServerFileServiceSdk serverFileServiceSdk;
@RepeatSubmit
@GetMapping("/test")
@ -375,6 +384,14 @@ public class TestController {
return CommonResponse.success(value);
}
@GetMapping("/process/expression/testing")
public CommonResponse<String> parseProcessExpression(@RequestParam String processInstanceId, @RequestParam String expression) {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
// 自定义命令执行表达式
Object value = commandExecutor.execute(new ExpressionConditionCmd(runtimeService, processEngineConfiguration, processInstanceId, expression));
return CommonResponse.success(Objects.toString(value, ""));
}
@GetMapping("/es/index")
public CommonResponse<String> esIndex(@RequestParam String str) {
esIndexOperationJobHandler.execute(str);
@ -405,5 +422,16 @@ public class TestController {
public CommonResponse<String> refreshProperties() {
return CommonResponse.success(JSON.toJSONString(refreshProperties));
}
@PostMapping("/server/file/upload")
@SneakyThrows
public CommonResponse<String> serverFileUpload(@RequestBody ServerFileUploadSdkRequest request) {
String filePath = "/Users/wangli/Downloads/锦绣碧湖B区B-1工程模板施工专项方案4-30.docx";
byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
request.setFileContent(fileBytes);
ServerFileUploadSdkResponse serverFileUploadSdkResponse = serverFileServiceSdk.uploadFile(request);
return CommonResponse.success(JSON.toJSONString(serverFileUploadSdkResponse));
}
}

View File

@ -7,6 +7,7 @@ import cn.axzo.oss.http.model.ApiSignUrlDownloadResponse;
import cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.CustomDocDTO;
import cn.axzo.workflow.common.model.dto.SignFileDTO;
import cn.axzo.workflow.common.model.dto.SimpleDocDTO;
import cn.axzo.workflow.common.model.request.bpmn.log.LogApproveSearchDTO;
@ -21,6 +22,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCre
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceVariablesUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO;
@ -28,6 +30,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverRead
import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.form.ConditionPermissionMetaInfo;
import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO;
@ -66,6 +69,7 @@ import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.beans.BeanUtils;
@ -95,6 +99,7 @@ import java.util.stream.Stream;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_DOC_ID_NOT_IN_MODEL;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_DOC_READ_PARAM_ERROR;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_EXT_LOG_PARAM_ERROR;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_BIZ_CUSTOM_DOCS;
import static cn.azxo.framework.common.model.CommonResponse.success;
/**
@ -306,8 +311,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
@PutMapping("/status/update")
@RepeatSubmit
@Override
public CommonResponse<Boolean> updateProcessStatus(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId,
@NotNull(message = "状态不能为空") @RequestParam Integer status) {
public CommonResponse<Boolean> updateProcessStatus(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId, @NotNull(message = "状态不能为空") @RequestParam Integer status) {
log.info("获得流程实例 updateProcessStatus===>>>参数:{},{}", processDefinitionId, status);
Boolean result = bpmnProcessInstanceService.updateProcessStatus(processDefinitionId, status);
return success(result);
@ -323,8 +327,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
@Operation(summary = "获取审批流程实例的运行图")
@GetMapping("/graphical")
@Override
public CommonResponse<ObjectNode> processInstanceGraphical(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId,
@Nullable @RequestParam(required = false) String tenantId) {
public CommonResponse<ObjectNode> processInstanceGraphical(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId) {
return success(bpmnProcessInstanceService.getProcessInstanceGraphical(processInstanceId, tenantId));
}
@ -338,8 +341,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
@Operation(summary = "获取指定实例的全节点执行顺序推算")
@GetMapping("/node/forecasting")
@Override
public CommonResponse<List<ProcessNodeDetailVO>> processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId,
@Nullable String tenantId) {
public CommonResponse<List<ProcessNodeDetailVO>> processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable String tenantId) {
return success(bpmnProcessInstanceService.getProcessInstanceNodeForecast(processInstanceId, tenantId));
}
@ -356,10 +358,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
@Operation(summary = "推断指定流程实例的过滤掉部分节点执行顺序")
@GetMapping("/node/filter/forecasting")
@Override
public CommonResponse<List<ProcessNodeDetailVO>> processInstanceFilterNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId,
@Nullable @RequestParam(required = false) String tenantId,
@RequestParam(required = false, defaultValue = "false") Boolean allNode,
@Nullable @RequestParam(required = false) List<String> nodeDefinitionKeys) {
public CommonResponse<List<ProcessNodeDetailVO>> processInstanceFilterNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId, @Nullable @RequestParam(required = false) String tenantId, @RequestParam(required = false, defaultValue = "false") Boolean allNode, @Nullable @RequestParam(required = false) List<String> nodeDefinitionKeys) {
if (allNode) {
return success(bpmnProcessInstanceService.getProcessInstanceNodeForecast(processInstanceId, tenantId));
} else {
@ -377,13 +376,20 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
@Operation(summary = "获取指定流程实例的流程变量")
@GetMapping("/cooperation-org")
@Override
public CommonResponse<Map<String, Object>> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId,
@Nullable String tenantId) {
HistoricProcessInstance processInstance = bpmnProcessInstanceService.getProcessInstance(processInstanceId,
tenantId, true);
public CommonResponse<Map<String, Object>> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable String tenantId) {
HistoricProcessInstance processInstance = bpmnProcessInstanceService.getProcessInstance(processInstanceId, tenantId, true);
return success(processInstance.getProcessVariables());
}
@Override
@Operation(summary = "更新流程实例中的业务自定义变量集合")
@PostMapping("/biz/custom/variables/update")
public CommonResponse<Boolean> updateProcessBizCustomVariables(@Validated @RequestBody BpmnProcessInstanceVariablesUpdateDTO dto) {
log.info("更新流程实例中的业务自定义变量集合 updateProcessBizCustomVariables===>>>参数:{}", JSONUtil.toJsonStr(dto));
bpmnProcessInstanceService.overrideProcessVariables(dto);
return success(true);
}
/**
* 枢智业务(审批台账专用)
*
@ -466,18 +472,10 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
}
private void parseSignatureUrl(BpmnProcessInstanceLogVO log) {
List<String> signUrls = log.getTaskDetails().stream()
.filter(i -> StringUtils.hasText(i.getTaskId()))
.map(BpmnTaskInstanceLogVO::getSignatureUrl)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
List<String> signUrls = log.getTaskDetails().stream().filter(i -> StringUtils.hasText(i.getTaskId())).map(BpmnTaskInstanceLogVO::getSignatureUrl).filter(Objects::nonNull).distinct().collect(Collectors.toList());
if (!CollectionUtils.isEmpty(signUrls)) {
Map<String, String> ossUrlMap = ListUtils.emptyIfNull(getSignPrivateUrl(signUrls)).stream()
.collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, ApiSignUrlDownloadResponse::getSignUrl, (s, t) -> s));
log.getTaskDetails().stream()
.filter(i -> StringUtils.hasText(i.getTaskId()))
.forEach(i -> i.setSignatureUrl(ossUrlMap.getOrDefault(i.getSignatureUrl(), null)));
Map<String, String> ossUrlMap = ListUtils.emptyIfNull(getSignPrivateUrl(signUrls)).stream().collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, ApiSignUrlDownloadResponse::getSignUrl, (s, t) -> s));
log.getTaskDetails().stream().filter(i -> StringUtils.hasText(i.getTaskId())).forEach(i -> i.setSignatureUrl(ossUrlMap.getOrDefault(i.getSignatureUrl(), null)));
}
}
@ -509,8 +507,7 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
}
ApiSignUrlDownloadRequest ossRequest = new ApiSignUrlDownloadRequest();
ossRequest.setFileKeys(fileKeys);
Map</*fileKey*/String, /*ossUrl*/ApiSignUrlDownloadResponse> ossUrlMap = RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(ossRequest), "批量获取文件 OSS 地址", ossRequest)
.stream().collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, Function.identity(), (s, t) -> s));
Map</*fileKey*/String, /*ossUrl*/ApiSignUrlDownloadResponse> ossUrlMap = RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(ossRequest), "批量获取文件 OSS 地址", ossRequest).stream().collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, Function.identity(), (s, t) -> s));
docs.forEach(i -> {
if (StringUtils.hasText(i.getFileKey())) {
ApiSignUrlDownloadResponse ossFileInfo = ossUrlMap.getOrDefault(i.getFileKey(), null);
@ -542,10 +539,20 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
}
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
List<DocBaseVO> relationDocs = commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(), true, extAxModelDocMapper, extAxReModelService));
relationDocs.stream().filter(i -> Objects.equals(i.getId(), dto.getDocId()))
.findAny().orElseThrow(() -> new WorkflowEngineException(PROCESS_DOC_ID_NOT_IN_MODEL));
return success(readRecordService.changeReadStatus(dto));
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
List<CustomDocDTO> customDocs = ListUtils.emptyIfNull(runtimeService.getVariable(dto.getProcessInstanceId(), SIGN_BIZ_CUSTOM_DOCS, List.class));
// 检查 relationDocs 中是否存在具有指定 id 的对象
boolean existsInRelationDocs = relationDocs.stream().anyMatch(doc -> Objects.equals(doc.getId(), dto.getDocId()));
// 检查 customDocs 中是否存在具有指定 id 的对象
boolean existsInCustomDocs = customDocs.stream().anyMatch(doc -> Objects.equals(doc.getId(), dto.getDocId()));
if (!existsInRelationDocs && !existsInCustomDocs) {
throw new WorkflowEngineException(PROCESS_DOC_ID_NOT_IN_MODEL);
}
return success(readRecordService.changeReadStatus(dto, customDocs));
}
@Override
@ -602,4 +609,17 @@ public class BpmnProcessInstanceController extends BasicPopulateAvatarController
return logVO;
}).collect(Collectors.toList()));
}
/**
* 获取流程实例的条件字段信息
*
* @param processInstanceId
* @return
*/
@Operation(summary = "获取流程实例的条件字段信息, 仅用于同意抽屉展示")
@GetMapping("/conditions")
@Override
public CommonResponse<List<ConditionPermissionMetaInfo>> getConditions(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId) {
return success(bpmnProcessInstanceService.getConditions(processInstanceId));
}
}

View File

@ -36,6 +36,7 @@ import com.alibaba.fastjson.JSON;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
import org.flowable.form.api.FormInfo;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@ -54,6 +55,7 @@ import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_OPERATION_PARAM_INVALID;
@ -112,6 +114,10 @@ public class BpmnProcessTaskController extends BasicPopulateAvatarController imp
if (!StringUtils.hasText(dto.getTaskId())) {
dto.setTaskId(bpmnProcessTaskService.findTaskIdByInstanceIdAndPersonId(dto.getProcessInstanceId(), dto.getApprover().getPersonId()));
}
// 移除 variable value null key
MapUtils.emptyIfNull(dto.getVariables()).entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
List<AttachmentDTO> tempAttachments = ListUtils.defaultIfNull(dto.getAttachmentList(), new ArrayList<>());
if (StringUtils.hasText(dto.getSignatureUrl())) {
AttachmentDTO signature = new AttachmentDTO();
@ -138,6 +144,10 @@ public class BpmnProcessTaskController extends BasicPopulateAvatarController imp
if (!StringUtils.hasText(dto.getTaskId())) {
dto.setTaskId(bpmnProcessTaskService.findTaskIdByInstanceIdAndPersonId(dto.getProcessInstanceId(), dto.getApprover().getPersonId()));
}
// 移除 variable value null key
MapUtils.emptyIfNull(dto.getVariables()).entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
List<AttachmentDTO> tempAttachments = ListUtils.defaultIfNull(dto.getAttachmentList(), new ArrayList<>());
if (StringUtils.hasText(dto.getSignatureUrl())) {
AttachmentDTO signature = new AttachmentDTO();

View File

@ -1,47 +1,79 @@
package cn.axzo.workflow.server.controller.web.manage;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.maokai.api.client.OrganizationalNodeUserQueryApi;
import cn.axzo.maokai.api.vo.request.OrgNodeUserBriefInfoListReq;
import cn.axzo.maokai.api.vo.response.OrgNodeUserBriefInfoResp;
import cn.axzo.nanopart.doc.api.conversion.DocConversionApi;
import cn.axzo.nanopart.doc.api.conversion.req.QueryConversionTaskRequestV2;
import cn.axzo.nanopart.doc.api.conversion.req.SubmitConversionTaskRequest;
import cn.axzo.nanopart.doc.api.conversion.res.FileConvertResultResp;
import cn.axzo.nanopart.doc.api.enums.DocConversionTypeEnum;
import cn.axzo.oss.http.api.ServerFileServiceApi;
import cn.axzo.oss.http.model.ApiSignUrlDownloadRequest;
import cn.axzo.oss.http.model.ApiSignUrlDownloadResponse;
import cn.axzo.workflow.client.feign.manage.PrintAdminApi;
import cn.axzo.workflow.common.constant.VariableConstants;
import cn.axzo.workflow.common.enums.AttachmentTypeEnum;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.enums.VarTypeEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.print.FieldAttributeDTO;
import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.Print4ProcessLogDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.PrintFieldQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.PrintProcessLogPdfDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.QueryProcessLogPdfDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.category.CategoryGroupVarSearchDto;
import cn.axzo.workflow.common.model.response.ProcessLogItemDTO;
import cn.axzo.workflow.common.model.response.TableItemDTO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO;
import cn.axzo.workflow.common.model.response.bpmn.process.PrintData4LogVO;
import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo;
import cn.axzo.workflow.common.model.response.print.ProcessLogPdfResultDTO;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.conf.SupportRefreshProperties;
import cn.axzo.workflow.core.engine.cmd.CustomGetFormInstanceLatestValuesCmd;
import cn.axzo.workflow.core.engine.cmd.CustomGetFormModelByProcessInstanceIdCmd;
import cn.axzo.workflow.core.engine.cmd.CustomGetProcessInstanceVariablesCmd;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessInstanceService;
import cn.axzo.workflow.core.service.BpmnProcessModelService;
import cn.axzo.workflow.core.service.CategoryGroupService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import cn.axzo.workflow.server.common.annotation.ErrorReporter;
import cn.axzo.workflow.server.common.util.RpcExternalUtil;
import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessInstanceController;
import cn.azxo.framework.common.model.CommonResponse;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Attachment;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.model.FormContainer;
import org.flowable.form.model.FormField;
import org.flowable.form.model.FormFieldTypes;
import org.flowable.form.model.Option;
import org.flowable.form.model.OptionFormField;
import org.flowable.form.model.SimpleFormModel;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.slf4j.Logger;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@ -66,6 +98,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS;
import static cn.axzo.workflow.common.code.OtherRespCode.CANT_GENERATE_PROCESS_LOG_PDF;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_IMAGE;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_INPUT;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY;
@ -79,16 +112,24 @@ import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCE
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_UNIT;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_UNIT_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOGS;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOGS_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_NODE_TYPE;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ADVICE;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ADVICE_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_APPROVER_NAME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_OPERATION;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_OPERATION_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_OPERATION_TIME_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_POSITION;
@ -97,12 +138,15 @@ import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCE
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_UNIT;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_UNIT_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_NAME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_RESULT;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME_DESC;
import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.form;
import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.sign;
import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.signature;
import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.system;
import static cn.axzo.workflow.core.service.impl.BpmnProcessInstanceServiceImpl.getAttachmentByType;
import static cn.azxo.framework.common.model.CommonResponse.success;
/**
@ -111,13 +155,13 @@ import static cn.azxo.framework.common.model.CommonResponse.success;
* @author wangli
* @since 2025-01-16 17:48
*/
@Slf4j
@RequestMapping({"/web/v1/api/print/admin", "/api/print/admin"})
@RestController
@ErrorReporter
@Validated
public class PrintAdminController implements PrintAdminApi {
private static final Logger log = org.slf4j.LoggerFactory.getLogger(PrintAdminController.class);
@Resource
private FormRepositoryService formRepositoryService;
@Resource
@ -134,6 +178,18 @@ public class PrintAdminController implements PrintAdminApi {
private CategoryGroupService categoryGroupService;
@Resource
private BpmnProcessModelService bpmnProcessModelService;
@Resource
private ExtAxProcessLogService processLogService;
@Resource
private TaskService taskService;
@Resource
private ServerFileServiceApi serverFileServiceApi;
@Resource
private DocConversionApi docConversionApi;
@Resource
private SupportRefreshProperties refreshProperties;
@Resource
private RuntimeService runtimeService;
/**
* 查询指定流程实例是否能使用打印
@ -162,6 +218,7 @@ public class PrintAdminController implements PrintAdminApi {
bpmnProcessModelService.printTemplateConfig(dto);
return success();
}
/**
* 获取打印模板中可打印的字段, 或者是 WPS 模板中可配置的变量字段
*
@ -180,17 +237,29 @@ public class PrintAdminController implements PrintAdminApi {
List<FormField> formFields = ((SimpleFormModel) formModel.getFormModel()).getFields();
formFields.forEach(formField -> {
FormContainer formContainer = (FormContainer) formField;
printFields.addAll(formContainer.getFields().get(0).stream().map(field -> {
PrintFieldDTO printFieldDTO = new PrintFieldDTO()
.setName(field.getName())
.setCode(field.getId())
.setFieldCategoryType(form)
.setFieldFormType(field.getType());
switchAmount(field, printFieldDTO);
switchFormContainer(field, printFieldDTO);
return printFieldDTO;
}
).collect(Collectors.toList()));
printFields.addAll(formContainer.getFields().get(0).stream()
.filter(field -> {
// if (Objects.equals(Boolean.TRUE, dto.getFilterEnablePrint())) {
if (!CollectionUtils.isEmpty(field.getParams())) {
Optional<Object> optEnablePrint = Optional.ofNullable(field.getParam("enablePrint"));
return optEnablePrint.map(obj -> Boolean.parseBoolean(obj.toString())).orElse(Boolean.TRUE);
}
return true;
// } else {
// return false;
// }
})
.map(field -> {
PrintFieldDTO printFieldDTO = new PrintFieldDTO()
.setName(field.getName())
.setCode(field.getId())
.setFieldCategoryType(form)
.setFieldFormType(field.getType());
switchAmount(field, printFieldDTO);
switchFormContainer(field, printFieldDTO);
return printFieldDTO;
}
).collect(Collectors.toList()));
});
} catch (FlowableObjectNotFoundException e) {
log.warn("can't found form model");
@ -305,8 +374,13 @@ public class PrintAdminController implements PrintAdminApi {
.setCode(activity.getId())
.setFieldCategoryType(signature)
.setFieldFormType("signature")
.setAttributes(Lists.newArrayList(new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_SIGNATURE).setName(PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT),
new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ADVICE).setName(PRINT_VAR_PROCESS_LOG_ADVICE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT))))
.setAttributes(Lists.newArrayList(
new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_APPROVER_NAME).setName(PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT),
new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT).setName(PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT),
new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ADVICE).setName(PRINT_VAR_PROCESS_LOG_ADVICE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT),
new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_SIGNATURE).setName(PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT),
new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME).setName(PRINT_VAR_PROCESS_LOG_ACTIVITY_OPERATION_TIME_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT)
)))
.collect(Collectors.toList())
);
@ -335,21 +409,30 @@ public class PrintAdminController implements PrintAdminApi {
// 生成系统字段的变量
generateSystemFieldVariables(processInstanceId, result);
FormInfo formInfo = commandExecutor.execute(new CustomGetFormModelByProcessInstanceIdCmd(processInstanceId));
// 将所有变量都转换成 JSON 对象返回
convertValueToObject(result);
convertValueToObject(result, formInfo);
return success(result);
}
private void convertValueToObject(Map<String, Object> result) {
if (CollectionUtils.isEmpty(result)) {
private void convertValueToObject(Map<String, Object> result, FormInfo formInfo) {
if (CollectionUtils.isEmpty(result) || Objects.isNull(formInfo)) {
return;
}
Map<String, FormField> stringFormFieldMap = ((SimpleFormModel) formInfo.getFormModel()).allFieldsAsMap();
result.forEach((key, value) -> {
if (!(value instanceof String)) {
return;
}
if (((String) value).startsWith("[")) {
if (stringFormFieldMap.containsKey(key) && stringFormFieldMap.get(key) instanceof OptionFormField) {
OptionFormField optionFormField = (OptionFormField) stringFormFieldMap.get(key);
if (Objects.equals("checkbox", optionFormField.getType())) {
result.put(key, buildOptionFieldValue(optionFormField, JSONObject.parseArray((String) value, String.class)));
} else if (Objects.equals("radio", optionFormField.getType())) {
result.put(key, buildOptionFieldValue(optionFormField, Lists.newArrayList((String) value)));
}
} else if (((String) value).startsWith("[")) {
result.put(key, JSONArray.parseArray((String) value));
} else if (((String) value).startsWith("{")) {
result.put(key, JSONObject.parseObject((String) value));
@ -357,6 +440,16 @@ public class PrintAdminController implements PrintAdminApi {
});
}
private String buildOptionFieldValue(OptionFormField optionFormField, List<String> selectedValues) {
if (CollectionUtils.isEmpty(optionFormField.getOptions())) {
return "";
}
return optionFormField.getOptions().stream()
.filter(i -> selectedValues.contains(i.getId()))
.map(Option::getName)
.collect(Collectors.joining(""));
}
private void generateSystemFieldVariables(String processInstanceId, Map<String, Object> result) {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
Map<String, Object> variables = commandExecutor.execute(new CustomGetProcessInstanceVariablesCmd(processInstanceId));
@ -368,6 +461,7 @@ public class PrintAdminController implements PrintAdminApi {
variables.put(PRINT_VAR_PROCESS_INITIATOR_NAME, user.isPresent() ? user.get().getRealName() : "");
variables.put(PRINT_VAR_PROCESS_INITIATOR_POSITION, user.isPresent() && Objects.nonNull(user.get().getJob()) ? user.get().getJob().getName() : "");
variables.put(PRINT_VAR_PROCESS_INITIATOR_PHONE, user.isPresent() ? user.get().getProfile().getPhone() : "");
variables.put(PRINT_VAR_PROCESS_INITIATOR_UNIT, user.isPresent() ? user.get().getOrganizationalUnitName() : "");
variables.remove(PRINT_VAR_PROCESS_INITIATOR);
}
// 填充审批日志
@ -383,11 +477,14 @@ public class PrintAdminController implements PrintAdminApi {
.forEach(taskLog -> {
Map<String, Object> taskLogMap = new HashMap<>();
taskLogMap.put(PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME, taskLog.getName());
taskLogMap.put(PRINT_VAR_PROCESS_LOG_ACTIVITY_NODE_TYPE, taskLog.getNodeType().getType());
Optional<OrgNodeUserBriefInfoResp> user = getUserInfo(taskLog.getAssigneeSnapshot());
taskLogMap.put(PRINT_VAR_PROCESS_LOG_APPROVER_NAME, user.isPresent() ? user.get().getRealName() : "");
taskLogMap.put(PRINT_VAR_PROCESS_LOG_UNIT, user.isPresent() ? user.get().getOrganizationalUnitName() : "");
taskLogMap.put(PRINT_VAR_PROCESS_LOG_POSITION, user.isPresent() && Objects.nonNull(user.get().getJob()) ? user.get().getJob().getName() : "");
taskLogMap.put(PRINT_VAR_PROCESS_LOG_ADVICE, taskLog.getAdvice());
taskLogMap.put(PRINT_VAR_PROCESS_LOG_OPERATION, taskLog.getOperationDesc());
taskLogMap.put(PRINT_VAR_PROCESS_LOG_ACTIVITY_RESULT, taskLog.getResult().getStatus());
taskLogMap.put(PRINT_VAR_PROCESS_LOG_OPERATION_TIME, sdf.format(taskLog.getCreateTime()));
taskLogMap.put(PRINT_VAR_PROCESS_LOG_SIGNATURE, taskLog.getSignatureUrl());
taskLogs.add(taskLogMap);
@ -427,4 +524,130 @@ public class PrintAdminController implements PrintAdminApi {
return users.stream().sorted(Comparator.comparing(OrgNodeUserBriefInfoResp::getExited).reversed())
.collect(Collectors.toList()).stream().findFirst();
}
@Operation(summary = "获取用于打印审批日志公共模板的数据")
@PostMapping("/process/log/data/v2")
@Override
public CommonResponse<PrintData4LogVO> getPrintDataForProcessLog(@Validated @RequestBody Print4ProcessLogDTO dto) {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
Map<String, Object> variables = commandExecutor.execute(new CustomGetProcessInstanceVariablesCmd(dto.getProcessInstanceId()));
PrintData4LogVO vo = new PrintData4LogVO();
vo.setProcessName((String) variables.getOrDefault(PRINT_VAR_PROCESS_NAME, ""));
vo.setTenantName(String.valueOf(variables.get(VariableConstants.PRINT_VAR_PROCESS_BELONG_TENANT_ID)));
vo.setCreateAt(String.valueOf(variables.get(VariableConstants.PRINT_VAR_PROCESS_START_TIME)));
vo.setResult((BpmnProcessInstanceResultEnum) variables.get(PRINT_VAR_PROCESS_RESULT));
List<TableItemDTO> systemVarItems = new ArrayList<>();
systemVarItems.add(TableItemDTO.builder()
.label(PRINT_VAR_PROCESS_INSTANCE_ID_DESC)
.code(PRINT_VAR_PROCESS_INSTANCE_ID)
.type(FORM_FIELD_TYPE_INPUT)
.value(variables.get(PRINT_VAR_PROCESS_INSTANCE_ID))
.build());
// 解析发起人
BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(variables.getOrDefault(PRINT_VAR_PROCESS_INITIATOR, null));
if (Objects.nonNull(initiator)) {
Optional<OrgNodeUserBriefInfoResp> user = getUserInfo(initiator);
systemVarItems.add(TableItemDTO.builder()
.label(PRINT_VAR_PROCESS_INITIATOR_NAME_DESC)
.code(PRINT_VAR_PROCESS_INITIATOR_NAME)
.type(FORM_FIELD_TYPE_INPUT)
.value(user.isPresent() ? user.get().getRealName() : "")
.build());
systemVarItems.add(TableItemDTO.builder()
.label(PRINT_VAR_PROCESS_INITIATOR_UNIT_DESC)
.code(PRINT_VAR_PROCESS_INITIATOR_UNIT)
.type(FORM_FIELD_TYPE_INPUT)
.value(user.isPresent() ? user.get().getOrganizationalUnitName() : "")
.build());
}
vo.setSystemVarItems(systemVarItems);
// 审批日志
List<ProcessLogItemDTO> logItems = new ArrayList<>();
Map<String, List<Attachment>> attachmentByTaskMap =
taskService.getProcessInstanceAttachments(dto.getProcessInstanceId()).stream()
.collect(Collectors.groupingBy(Attachment::getTaskId));
ExtAxProcessLog query = new ExtAxProcessLog();
query.setProcessInstanceId(dto.getProcessInstanceId());
// TODO 这里可能需要结合节点隐藏来过滤
processLogService.genericQuery(query).stream()
.sorted(Comparator.comparing(ExtAxProcessLog::getEndTime, Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.toList())
.forEach(log -> {
logItems.add(ProcessLogItemDTO.builder()
.advice(log.getAdvice())
.activityName(log.getActivityName())
.operationDesc(log.getOperationDesc())
.imageList(getAttachmentByType(attachmentByTaskMap, log.getTaskId(), AttachmentTypeEnum.image))
.fileList(getAttachmentByType(attachmentByTaskMap, log.getTaskId(), AttachmentTypeEnum.file))
.signatureUrl(getAttachmentByType(attachmentByTaskMap, log.getTaskId(), AttachmentTypeEnum.signature).stream().findFirst().orElse(new AttachmentDTO()).getUrl())
.operationTime(DateUtil.format(log.getEndTime(), "yyyy.MM.dd HH:mm:ss"))
.result(log.getStatus())
.build());
});
parseSignatureUrl(logItems);
vo.setLogItems(logItems);
return success(vo);
}
private void parseSignatureUrl(List<ProcessLogItemDTO> logItems) {
List<String> signUrls = logItems.stream()
.map(ProcessLogItemDTO::getSignatureUrl)
.filter(StringUtils::hasText)
.distinct()
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(signUrls)) {
Map<String, String> ossUrlMap = ListUtils.emptyIfNull(getSignPrivateUrl(signUrls)).stream()
.collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, ApiSignUrlDownloadResponse::getSignUrl, (s, t) -> s));
logItems.stream()
.filter(i -> StringUtils.hasText(i.getSignatureUrl()))
.forEach(i -> i.setSignatureUrl(ossUrlMap.getOrDefault(i.getSignatureUrl(), null)));
}
}
private List<ApiSignUrlDownloadResponse> getSignPrivateUrl(List<String> signUrls) {
ApiSignUrlDownloadRequest request = new ApiSignUrlDownloadRequest();
request.setFileKeys(signUrls);
return RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(request), "批量获取手写签私有访问地址", request);
}
@Operation(summary = "后端请求指定流程日志 PDF 文件生成")
@PostMapping("/process/log/pdf")
@Override
public CommonResponse<String> createProcessLogPdf(@Validated @RequestBody PrintProcessLogPdfDTO dto) {
if (!StringUtils.hasText(dto.getBizCode())) {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult();
if (Objects.nonNull(processInstance)) {
throw new WorkflowEngineException(CANT_GENERATE_PROCESS_LOG_PDF);
}
}
SubmitConversionTaskRequest request = new SubmitConversionTaskRequest();
request.setBizCode(StringUtils.hasText(dto.getBizCode()) ? dto.getBizCode() : "workflow-process-log");
request.setBizKey(StringUtils.hasText(dto.getBizKey()) ? dto.getBizKey() : dto.getProcessInstanceId() + ":" + dto.getPersonId());
request.setConversionType(DocConversionTypeEnum.HTML_URL_TO_PDF);
request.setFileName(String.format(refreshProperties.getProcessLogHtmlUrl(), dto.getProcessInstanceId(), dto.getPersonId()));
String taskId = RpcExternalUtil.rpcApiResultProcessor(() -> docConversionApi.submitConvertTask(request), "创建网页转 PDF 的异步任务", request);
return CommonResponse.success(taskId);
}
@Operation(summary = "后端查询指定审批日志 PDF 文件的生成结果")
@PostMapping("/process/log/pdf/result")
@Override
public CommonResponse<ProcessLogPdfResultDTO> queryProcessLogPdfResult(@Validated @RequestBody QueryProcessLogPdfDTO dto) {
QueryConversionTaskRequestV2 request = new QueryConversionTaskRequestV2();
request.setBizCode(StringUtils.hasText(dto.getBizCode()) ? dto.getBizCode() : "workflow-process-log");
request.setBizKeys(Lists.newArrayList(StringUtils.hasText(dto.getBizKey()) ? dto.getBizKey() : dto.getProcessInstanceId() + ":" + dto.getPersonId()));
List<FileConvertResultResp> taskConvertResults = RpcExternalUtil.rpcApiResultProcessor(() -> docConversionApi.queryConvertResultByBiz(request), "查询流程日志转 PDF 的结果", request);
List<ProcessLogPdfResultDTO> results = BeanMapper.copyList(taskConvertResults, ProcessLogPdfResultDTO.class, (s, t) -> {
t.setPdfFileKey(s.getResultFileFileKey());
t.setStatus(s.getStatus().name());
});
return CommonResponse.success(results.isEmpty() ? null : results.get(0));
}
}

View File

@ -41,6 +41,7 @@ import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -52,18 +53,24 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_BACK_COUNTERSIGN;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_FORWARD_COUNTERSIGN;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE;
import static cn.axzo.workflow.common.constant.BpmnConstants.SUPPORT_UPGRADE_VARIABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_LOG_NODE_HAS_BEEN_HIDDEN;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.nobody;
import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.BACK_COUNTERSIGN;
import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.FORWARD_COUNTERSIGN;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.AND;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.GENERAL;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.OR;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.HIDDEN;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.HANDLING;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature;
@ -152,9 +159,17 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
} else {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
@SuppressWarnings("unchecked")
List<BpmnTaskDelegateAssigner> assigneeList = runtimeService.getVariable(taskEntity.getProcessInstanceId(),
INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(), List.class);
List<BpmnTaskDelegateAssigner> assigneeList = new ArrayList<>();
if (Objects.equals(FORWARD_COUNTERSIGN.getType(), taskEntity.getScopeType())) {
assigneeList.addAll(runtimeService.getVariable(taskEntity.getProcessInstanceId(),
INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + INTERNAL_ACTIVITY_FORWARD_COUNTERSIGN + taskEntity.getParentTaskId(), List.class));
} else if (Objects.equals(BACK_COUNTERSIGN.getType(), taskEntity.getScopeType())) {
assigneeList.addAll(runtimeService.getVariable(taskEntity.getProcessInstanceId(),
INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + INTERNAL_ACTIVITY_BACK_COUNTERSIGN + taskEntity.getParentTaskId(), List.class));
} else {
assigneeList.addAll(runtimeService.getVariable(taskEntity.getProcessInstanceId(),
INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(), List.class));
}
ListUtils.emptyIfNull(assigneeList).stream().filter(e -> Objects.equals(e.buildAssigneeId(), taskEntity.getAssignee())).findAny()
.ifPresent(assignee -> {
ExtAxProcessLog queryLog = new ExtAxProcessLog();
@ -283,12 +298,18 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
String completionType = taskEntity.getVariable(TASK_COMPLETE_OPERATION_TYPE + taskEntity.getId(), String.class);
if (StringUtils.hasText(completionType) && !Objects.equals(DELETED.getStatus(), completionType)) {
update.setStatus(completionType);
Boolean hiddenLog = taskEntity.getVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + taskEntity.getTaskDefinitionKey(), Boolean.class);
if (Objects.equals(hiddenLog, Boolean.TRUE)) {
update.setStatus(HIDDEN.getStatus());
}
} else {
// 多实例除操作人以外的任务直接删除日志 例如一个节点有两个人或签A 人驳回了那么 B 人不再需要操作任务自动删除而会签也同理
update.setStatus(DELETED.getStatus());// delete标志着是多实例删除
needDelete = true;
}
}
// 重置日志隐藏的标识为 false
taskEntity.removeVariable(TASK_LOG_NODE_HAS_BEEN_HIDDEN + taskEntity.getTaskDefinitionKey());
update.setEndTime(new Date());
// 判断是否抄送节点如果是的话需要将抄送人集合放入对应字段

View File

@ -0,0 +1,125 @@
package cn.axzo.workflow.server.initializer;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.core.engine.cmd.helper.CustomBpmnModelHelper;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
/**
* 在框架提供 Web 服务前恢复动态前后加签的流程实例的内存中的模型
*
* @author wangli
* @since 2025-10-17 17:43
*/
@Slf4j
@Component
public class RecoverDynamicCounterSignProcess implements SmartLifecycle {
private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService;
private final SpringProcessEngineConfiguration springProcessEngineConfiguration;
private boolean isRunning = false;
public RecoverDynamicCounterSignProcess(ExtAxDynamicSignRecordService extAxDynamicSignRecordService,
SpringProcessEngineConfiguration springProcessEngineConfiguration) {
this.extAxDynamicSignRecordService = extAxDynamicSignRecordService;
this.springProcessEngineConfiguration = springProcessEngineConfiguration;
}
@Override
public void start() {
// 恢复动态会签中的流程实例
log.info("executing before web server start");
// 查询有过加签的审批实例
List<ExtAxDynamicSignRecord> list = extAxDynamicSignRecordService.list();
if (CollectionUtils.isEmpty(list)) {
return;
}
RuntimeService runtimeService = springProcessEngineConfiguration.getRuntimeService();
// 获取还在审批中的实例
Set<String> processInstanceIds = list.stream().map(ExtAxDynamicSignRecord::getProcessInstanceId).collect(Collectors.toSet());
Set<String> runningProcessIds = runtimeService.createProcessInstanceQuery()
.processInstanceIds(processInstanceIds)
.active()
.list().stream().map(ProcessInstance::getProcessInstanceId).collect(Collectors.toSet());
if (CollectionUtils.isEmpty(runningProcessIds)) {
return;
}
Map<String, List<ExtAxDynamicSignRecord>> grouped = list.stream()
.collect(Collectors.groupingBy(
ExtAxDynamicSignRecord::getProcessInstanceId,
Collectors.collectingAndThen(
Collectors.toList(),
l -> {
l.sort(Comparator.comparing(
ExtAxDynamicSignRecord::getCreateAt,
Comparator.nullsFirst(Comparator.naturalOrder())
));
return l;
}
)
));
RepositoryService repositoryService = springProcessEngineConfiguration.getRepositoryService();
grouped.forEach((k, v) -> {
if (runningProcessIds.contains(k)) {
log.info("recover dynamic counter sign process instance id: {}", k);
BpmnModel bpmnModel = repositoryService.getBpmnModel(v.get(0).getProcessDefinitionId());
Process process = bpmnModel.getMainProcess();
v.forEach(sign -> {
UserTask originalUserTask = (UserTask) process.getFlowElement(sign.getOriginalActivityId());
BpmnFlowNodeMode nodeMode = Objects.equals(originalUserTask.getLoopCharacteristics().getCompletionCondition(), AND_SIGN_EXPRESSION) ? BpmnFlowNodeMode.AND : BpmnFlowNodeMode.OR;
if (StringUtils.hasText(sign.getTargetNodeMode())) {
nodeMode = BpmnFlowNodeMode.valueOfType(sign.getTargetNodeMode());
}
// 创建加签节点
UserTask newUserTask = CustomBpmnModelHelper.createUserTask(springProcessEngineConfiguration, originalUserTask, sign.getTargetActivityId(), nodeMode, sign.getAssignerList());
process.addFlowElement(newUserTask);
CustomBpmnModelHelper.rewireSequenceFlows(process, originalUserTask, newUserTask, Objects.requireNonNull(BpmnCountersignTypeEnum.valueOfType(sign.getCounterSignType())));
});
}
});
isRunning = true;
}
@Override
public void stop() {
isRunning = false;
}
@Override
public boolean isRunning() {
return isRunning;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE - 2; // 确保在Web服务器启动前执行
}
}

View File

@ -0,0 +1,46 @@
package cn.axzo.workflow.server.service;
import cn.axzo.workflow.server.common.util.RedisUtils;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Objects;
import java.util.UUID;
/**
* form.html 页面授权码
*
* @author wangli
* @since 2025-11-19 10:51
*/
@Service
public class AuthCodeService {
private static final String AUTH_CODE_KEY_PREFIX = "we:auth_code";
// 授权码有效期1小时可自定义
private static final long EXPIRE_HOURS = 1;
/**
* 生成授权码仅管理员可调用
*/
public String generateAuthCode() {
// 生成随机授权码UUID简化
String authCode = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
RedisUtils.setCacheObject(AUTH_CODE_KEY_PREFIX, authCode, Duration.ofMinutes(1));
return authCode;
}
/**
* 验证授权码是否有效
*/
public boolean validateAuthCode(String authCode) {
if (authCode == null || authCode.isEmpty()) {
return false;
}
String key = RedisUtils.getCacheObject(AUTH_CODE_KEY_PREFIX);
if (key == null || !Objects.equals(key, authCode)) {
return false;
}
RedisUtils.deleteObject(AUTH_CODE_KEY_PREFIX);
return true;
}
}

View File

@ -0,0 +1,190 @@
package cn.axzo.workflow.server.xxljob;
import cn.axzo.infra.xxl220to250.XxlJobLogger;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.BpmnProcessTaskService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessInstanceController;
import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessJobController;
import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessTaskController;
import com.alibaba.fastjson.JSON;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
/**
* 危险操作操作审批中的行为
*
* @author wangli
* @since 2025-11-18 14:08
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class DangerSuperOperationJobHandler extends IJobHandler {
@Resource
private RuntimeService runtimeService;
@Resource
private BpmnProcessInstanceController instanceController;
@Resource
private BpmnProcessTaskController taskController;
@Resource
private BpmnProcessTaskService taskService;
@Resource
private BpmnProcessJobController jobController;
@Resource
private ExtAxProcessLogService processLogService;
@Override
@XxlJob("DangerSuperOperationJobHandler")
public void execute() throws Exception {
String paramStr = XxlJobHelper.getJobParam();
log.info("#DangerSuperOperationJobHandler#param_({})", paramStr);
XxlJobLogger.log("#DangerSuperOperationJobHandler#param_({})", paramStr);
DangerOperationJobParam dangerOperationJobParam = JSON.parseObject(paramStr, DangerOperationJobParam.class);
String processInstanceId = dangerOperationJobParam.getProcessInstanceId();
if (!StringUtils.hasText(processInstanceId)) {
log.warn("缺少 processInstanceId 参数,无法撤回流程实例");
XxlJobLogger.log("缺少 processInstanceId 参数,无法撤回流程实例");
return;
}
switch (dangerOperationJobParam.getOperationType()) {
case CANCEL:
cancelProcessInstance(dangerOperationJobParam);
break;
case APPROVE:
approveTask(dangerOperationJobParam);
break;
case REJECT:
rejectTask(dangerOperationJobParam);
break;
case ABORT:
abortProcessInstance(dangerOperationJobParam);
break;
case RESUMER_DEADLINE_JOB:
resumerDeadlineJob(dangerOperationJobParam);
break;
default:
break;
}
}
private void resumerDeadlineJob(DangerOperationJobParam dangerOperationJobParam) {
jobController.executeDeadLetterJobAction("", dangerOperationJobParam.getProcessInstanceId());
}
private void abortProcessInstance(DangerOperationJobParam dangerOperationJobParam) {
instanceController.abortProcessInstance(BpmnProcessInstanceAbortDTO.builder()
.processInstanceId(dangerOperationJobParam.getProcessInstanceId())
.advice(dangerOperationJobParam.getComment())
.build());
}
private void rejectTask(DangerOperationJobParam dangerOperationJobParam) {
String personId = dangerOperationJobParam.getPersonId();
if (!StringUtils.hasText(personId)) {
log.warn("缺少 personId 参数,无法驳回任务");
XxlJobLogger.log("缺少 personId 参数,无法驳回任务");
}
String taskId = taskService.findTaskIdByInstanceIdAndPersonId(dangerOperationJobParam.getProcessInstanceId(), dangerOperationJobParam.getPersonId());
ExtAxProcessLog query = new ExtAxProcessLog();
query.setProcessInstanceId(dangerOperationJobParam.getProcessInstanceId());
query.setTaskId(taskId);
query.setStatus(PROCESSING.getStatus());
List<ExtAxProcessLog> logs = processLogService.genericQuery(query);
if (CollectionUtils.isEmpty(logs)) {
log.warn("未找到可操作的任务日志无法驳回任务processInstanceId={}, personId={}", dangerOperationJobParam.getProcessInstanceId(), dangerOperationJobParam.getPersonId());
XxlJobLogger.log("未找到可操作的任务日志无法驳回任务processInstanceId={}, personId={}", dangerOperationJobParam.getProcessInstanceId(), dangerOperationJobParam.getPersonId());
return;
}
taskController.rejectTask(BpmnTaskAuditDTO.builder()
.processInstanceId(dangerOperationJobParam.getProcessInstanceId())
.advice(dangerOperationJobParam.getComment())
.approver(logs.get(0).getAssigneeFull().get(0))
.async(false)
.build());
}
private void approveTask(DangerOperationJobParam dangerOperationJobParam) {
String personId = dangerOperationJobParam.getPersonId();
if (!StringUtils.hasText(personId)) {
log.warn("缺少 personId 参数,无法驳回任务");
XxlJobLogger.log("缺少 personId 参数,无法驳回任务");
}
String taskId = taskService.findTaskIdByInstanceIdAndPersonId(dangerOperationJobParam.getProcessInstanceId(), dangerOperationJobParam.getPersonId());
ExtAxProcessLog query = new ExtAxProcessLog();
query.setProcessInstanceId(dangerOperationJobParam.getProcessInstanceId());
query.setTaskId(taskId);
query.setStatus(PROCESSING.getStatus());
List<ExtAxProcessLog> logs = processLogService.genericQuery(query);
if (CollectionUtils.isEmpty(logs)) {
log.warn("未找到可操作的任务日志无法驳回任务processInstanceId={}, personId={}", dangerOperationJobParam.getProcessInstanceId(), dangerOperationJobParam.getPersonId());
XxlJobLogger.log("未找到可操作的任务日志无法驳回任务processInstanceId={}, personId={}", dangerOperationJobParam.getProcessInstanceId(), dangerOperationJobParam.getPersonId());
return;
}
taskController.approveTask(BpmnTaskAuditDTO.builder()
.processInstanceId(dangerOperationJobParam.getProcessInstanceId())
.advice(dangerOperationJobParam.getComment())
.approver(logs.get(0).getAssigneeFull().get(0))
.async(false)
.build());
}
private void cancelProcessInstance(DangerOperationJobParam dangerOperationJobParam) {
String processInstanceId = dangerOperationJobParam.getProcessInstanceId();
BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(runtimeService.getVariable(processInstanceId, INTERNAL_INITIATOR));
BpmnProcessInstanceCancelDTO cancelDTO = new BpmnProcessInstanceCancelDTO();
cancelDTO.setProcessInstanceId(processInstanceId);
cancelDTO.setInitiator(assigner);
cancelDTO.setReason(dangerOperationJobParam.getComment());
cancelDTO.setAsync(false);
instanceController.cancelProcessInstance(cancelDTO);
log.info("撤回操作完成");
XxlJobLogger.log("撤回操作完成");
}
@Data
public static class DangerOperationJobParam implements Serializable {
@NotNull(message = "操作类型不能为空")
private OperationEnumType operationType;
@NotBlank(message = "流程实例ID不能为空")
private String processInstanceId;
private String personId;
private String comment;
}
public static enum OperationEnumType {
CANCEL, // 撤销任务
APPROVE, // 同意任务
REJECT, // 拒绝任务
ABORT, // 中止实例
RESUMER_DEADLINE_JOB, // 一般是因为二方计算人异常导致卡节点
}
}

View File

@ -2,3 +2,10 @@ arthas:
app-name: ${spring.application.name}
agent-id: ${ARTHAS_AGENT_ID:${spring.profiles.active}-${spring.application.name}}
tunnel-server: ${ARTHAS_TUNNEL_SERVER:ws://localhost:7777/ws}
spring:
thymeleaf:
mode: LEGACYHTML5
cache: false
encoding: UTF-8
prefix: classpath:/templates/
suffix: .html

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

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