已修改85个文件
已删除11个文件
已重命名8个文件
已添加42个文件
6713 ■■■■ 文件已修改
iailab-cloud/iailab-gateway/src/main/resources/application.yaml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/collection/CollectionUtils.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/object/BeanUtils.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/DictTypeConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/ErrorCodeConstants.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmBoundaryEventType.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmFieldPermissionEnum.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelFormTypeEnum.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelTypeEnum.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerType.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerValueType.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModeConditionType.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModelNodeType.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/message/BpmMessageEnum.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmDeleteReasonEnum.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmReasonEnum.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmTaskStatusEnum.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/package-info.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmCategoryController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessExpressionController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmActivityController.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.http 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmTaskController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmProcessDefinitionConvert.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmActivityConvert.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmProcessInstanceConvert.java 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmCategoryDO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmFormDO.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmFormMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java 107 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormSDeptLeaderStrategy.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmConstants.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java 508 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java 686 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryServiceImpl.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelService.java 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityService.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityServiceImpl.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceService.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskService.java 182 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java 783 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/db/mysql/tenant.sql 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/channel/http/collector/ihdb/HttpCollectorForIhd.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/collection/PointCollector.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/collection/handler/MeasureHandle.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/dao/DaPointCollectStatusDao.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/dto/DaPointDTO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/entity/DaPointCollectStatusEntity.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/service/DaPointCollectStatusService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/service/impl/DaPointCollectStatusServiceImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/resources/application.yaml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/resources/mapper/point/DaPointDao.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/api/McsApiImpl.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/dao/MmModelArithSettingsDao.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmItemOutputService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmModelArithSettingsService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmItemOutputServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmModelArithSettingsServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/dao/StScheduleModelSettingDao.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleModelSettingService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleModelSettingServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/resources/application-dev.yaml 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/MmModelArithSettings.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/StScheduleModelSettingDao.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-gateway/src/main/resources/application.yaml
@@ -122,6 +122,20 @@
            - Path=/admin-api/shasteel/**
          filters:
            - RewritePath=/admin-api/shasteel/v3/api-docs, /v3/api-docs
        ## xmcpms 服务
        - id: xmcpms-admin-api # 路由的编号
          uri: grayLb://xmcpms-server
          predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
            - Path=/admin-api/xmcpms/**
          filters:
            - RewritePath=/admin-api/xmcpms/v3/api-docs, /v3/api-docs
        ## xmcsms 服务
        - id: xmcsms-admin-api # 路由的编号
          uri: grayLb://xmcsms-server
          predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
            - Path=/admin-api/xmcsms/**
          filters:
            - RewritePath=/admin-api/xmcsms/v3/api-docs, /v3/api-docs
      x-forwarded:
        prefix-enabled: true # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java
@@ -21,6 +21,7 @@
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import java.util.HashMap;
import java.util.HashSet;
@@ -119,7 +120,7 @@
        // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
        if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
            && Boolean.FALSE.equals(deptDataPermission.getSelf())) {
                && Boolean.FALSE.equals(deptDataPermission.getSelf())) {
            return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
        }
@@ -141,7 +142,7 @@
            return deptExpression;
        }
        // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
        return new Parenthesis(new OrExpression(deptExpression, userExpression));
        return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression));
    }
    private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
@@ -156,8 +157,8 @@
        }
        // 拼接条件
        return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
//                new Parenthesis(new ExpressionList<>(CollectionUtils.convertList(deptIds, LongValue::new))));
                new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
                // Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号
                new ParenthesedExpressionList(new ExpressionList<LongValue>(CollectionUtils.convertList(deptIds, LongValue::new))));
    }
    private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
@@ -181,7 +182,7 @@
    public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {
        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
       addDeptColumn(tableName, columnName);
        addDeptColumn(tableName, columnName);
    }
    public void addDeptColumn(String tableName, String columnName) {
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/collection/CollectionUtils.java
@@ -4,6 +4,7 @@
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import com.google.common.collect.ImmutableMap;
import com.iailab.framework.common.pojo.PageResult;
import java.util.*;
import java.util.function.*;
@@ -71,6 +72,13 @@
            return new ArrayList<>();
        }
        return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
    }
    public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) {
        if (ArrayUtil.isEmpty(from)) {
            return new PageResult<>(from.getTotal());
        }
        return new PageResult<>(convertList(from.getList(), func), from.getTotal());
    }
    public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
@@ -290,7 +298,15 @@
        return valueFunc.apply(t);
    }
    public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
    public static <T, V extends Comparable<? super V>> T getMinObject(List<T> from, Function<T, V> valueFunc) {
        if (CollUtil.isEmpty(from)) {
            return null;
        }
        assert from.size() > 0; // 断言,避免告警
        return from.stream().min(Comparator.comparing(valueFunc)).get();
    }
    public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
                                                                     BinaryOperator<V> accumulator) {
        return getSumValue(from, valueFunc, accumulator, null);
    }
@@ -316,7 +332,7 @@
    }
    public static <T> List<T> newArrayList(List<List<T>> list) {
        return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
        return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
    }
}
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/object/BeanUtils.java
@@ -59,4 +59,11 @@
        return new PageResult<>(list, source.getTotal());
    }
    public static void copyProperties(Object source, Object target) {
        if (source == null || target == null) {
            return;
        }
        BeanUtil.copyProperties(source, target, false);
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/DictTypeConstants.java
@@ -7,7 +7,5 @@
 */
public interface DictTypeConstants {
//    String TASK_ASSIGN_RULE_TYPE = "bpm_task_assign_rule_type"; // 任务分配规则类型
//    String TASK_ASSIGN_SCRIPT = "bpm_task_assign_script"; // 任务分配自定义脚本
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/ErrorCodeConstants.java
@@ -23,6 +23,7 @@
            "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
    ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件");
    ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在");
    ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员");
    // ========== 流程定义 1-009-003-000 ==========
    ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
@@ -34,15 +35,16 @@
    ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
    ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
    ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
    ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置");
    ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在");
    ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "任务({})的候选人未配置");
    ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
    ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
    // ========== 流程任务 1-009-005-000 ==========
    ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
    ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
    ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作");
    ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在");
    ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
    ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退回任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
    ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
    ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
    ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在");
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmBoundaryEventType.java
对比新文件
@@ -0,0 +1,25 @@
package com.iailab.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * BPM 边界事件 (boundary event) 自定义类型枚举
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmBoundaryEventType {
    USER_TASK_TIMEOUT(1,"用户任务超时");
    private final Integer type;
    private final String name;
    public static BpmBoundaryEventType typeOf(Integer type) {
        return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmFieldPermissionEnum.java
对比新文件
@@ -0,0 +1,33 @@
package com.iailab.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * BPM 表单权限的枚举
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmFieldPermissionEnum {
    READ(1, "只读"),
    WRITE(2, "可编辑"),
    NONE(3, "隐藏");
    /**
     * 权限
     */
    private final Integer permission;
    /**
     * 名字
     */
    private final String name;
    public static BpmFieldPermissionEnum valueOf(Integer permission) {
        return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values());
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelFormTypeEnum.java
@@ -9,7 +9,7 @@
/**
 * BPM 模型的表单类型的枚举
 *
 * @author iailab
 * @author hou
 */
@Getter
@AllArgsConstructor
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelTypeEnum.java
对比新文件
@@ -0,0 +1,31 @@
package com.iailab.module.bpm.enums.definition;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * BPM 模型的类型的枚举
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmModelTypeEnum implements IntArrayValuable {
    BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/
    SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉、飞书工作流的设计器
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmModelTypeEnum::getType).toArray();
    private final Integer type;
    private final String name;
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerType.java
@@ -6,7 +6,7 @@
/**
 * BPM 流程监听器的类型
 *
 * @author iailab
 * @author hou
 */
@Getter
@AllArgsConstructor
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerValueType.java
@@ -6,7 +6,7 @@
/**
 * BPM 流程监听器的值类型
 *
 * @author iailab
 * @author hou
 */
@Getter
@AllArgsConstructor
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModeConditionType.java
对比新文件
@@ -0,0 +1,36 @@
package com.iailab.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * 仿钉钉的流程器设计器条件节点的条件类型
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmSimpleModeConditionType implements IntArrayValuable {
    EXPRESSION(1, "条件表达式"),
    RULE(2, "条件规则");
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModeConditionType::getType).toArray();
    private final Integer type;
    private final String name;
    public static BpmSimpleModeConditionType valueOf(Integer type) {
        return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
    }
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModelNodeType.java
对比新文件
@@ -0,0 +1,62 @@
package com.iailab.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
/**
 * 仿钉钉的流程器设计器的模型节点类型
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmSimpleModelNodeType implements IntArrayValuable {
    // 0 ~ 1 开始和结束
    START_NODE(0, "开始", "startEvent"),
    END_NODE(1, "结束", "endEvent"),
    // 10 ~ 49 各种节点
    START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定
    APPROVE_NODE(11, "审批人", "userTask"),
    COPY_NODE(12, "抄送人", "serviceTask"),
    // 50 ~ 条件分支
    CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
    CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
    PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"),
    INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"),
    ;
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
    private final Integer type;
    private final String name;
    private final String bpmnType;
    /**
     * 判断是否为分支节点
     *
     * @param type 节点类型
     */
    public static boolean isBranchNode(Integer type) {
        return Objects.equals(CONDITION_BRANCH_NODE.getType(), type)
                || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type)
                || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type);
    }
    public static BpmSimpleModelNodeType valueOf(Integer type) {
        return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
    }
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java
对比新文件
@@ -0,0 +1,47 @@
package com.iailab.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * BPM 多人审批方式的枚举
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable {
    RANDOM(1, "随机挑选一人审批", null),
    RATIO(2, "多人会签(按通过比例)", "${ nrOfCompletedInstances/nrOfInstances >= %s}"), // 会签(按通过比例)
    ANY(3, "多人或签(一人通过或拒绝)", "${ nrOfCompletedInstances > 0 }"), // 或签(通过只需一人,拒绝只需一人)
    SEQUENTIAL(4, "依次审批", "${ nrOfCompletedInstances >= nrOfInstances }"); // 依次审批
    /**
     * 审批方式
     */
    private final Integer method;
    /**
     * 名字
     */
    private final String name;
    /**
     * 完成表达式
     */
    private final String completionCondition;
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray();
    public static BpmUserTaskApproveMethodEnum valueOf(Integer method) {
        return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values());
    }
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java
对比新文件
@@ -0,0 +1,31 @@
package com.iailab.module.bpm.enums.definition;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * 用户任务的审批类型枚举
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmUserTaskApproveTypeEnum implements IntArrayValuable {
    USER(1), // 人工审批
    AUTO_APPROVE(2), // 自动通过
    AUTO_REJECT(3); // 自动拒绝
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveTypeEnum::getType).toArray();
    private final Integer type;
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java
对比新文件
@@ -0,0 +1,33 @@
package com.iailab.module.bpm.enums.definition;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
 * BPM 用户任务的审批人为空时,处理类型枚举
 *
 * @author hou
 */
@RequiredArgsConstructor
@Getter
public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable {
    APPROVE(1), // 自动通过
    REJECT(2), // 自动拒绝
    ASSIGN_USER(3), // 指定人员审批
    ASSIGN_ADMIN(4), // 转交给流程管理员
    ;
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray();
    private final Integer type;
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java
对比新文件
@@ -0,0 +1,31 @@
package com.iailab.module.bpm.enums.definition;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
 * BPM 用户任务的审批人与发起人相同时,处理类型枚举
 *
 * @author hou
 */
@RequiredArgsConstructor
@Getter
public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable {
    START_USER_AUDIT(1), // 由发起人对自己审批
    SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过
    TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray();
    private final Integer type;
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java
对比新文件
@@ -0,0 +1,35 @@
package com.iailab.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * BPM 用户任务拒绝处理类型枚举
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmUserTaskRejectHandlerType implements IntArrayValuable {
    FINISH_PROCESS_INSTANCE(1, "终止流程"),
    RETURN_USER_TASK(2, "驳回到指定任务节点");
    private final Integer type;
    private final String name;
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskRejectHandlerType::getType).toArray();
    public static BpmUserTaskRejectHandlerType typeOf(Integer type) {
        return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
    }
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java
对比新文件
@@ -0,0 +1,32 @@
package com.iailab.module.bpm.enums.definition;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * 用户任务超时处理类型枚举
 *
 * @author hou
 */
@Getter
@AllArgsConstructor
public enum BpmUserTaskTimeoutHandlerTypeEnum implements IntArrayValuable {
    REMINDER(1,"自动提醒"),
    APPROVE(2, "自动同意"),
    REJECT(3, "自动拒绝");
    private final Integer type;
    private final String name;
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray();
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/message/BpmMessageEnum.java
@@ -14,7 +14,8 @@
    PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人
    PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人
    TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人
    TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人
    TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人
    /**
     * 短信模板的标识
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmDeleteReasonEnum.java
文件已删除
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java
@@ -1,6 +1,7 @@
package com.iailab.module.bpm.enums.task;
import com.iailab.framework.common.core.IntArrayValuable;
import com.iailab.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -15,6 +16,7 @@
@AllArgsConstructor
public enum BpmProcessInstanceStatusEnum implements IntArrayValuable {
    NOT_START(-1, "未开始"),
    RUNNING(1, "审批中"),
    APPROVE(2, "审批通过"),
    REJECT(3, "审批不通过"),
@@ -36,4 +38,14 @@
        return ARRAYS;
    }
    public static boolean isRejectStatus(Integer status) {
        return REJECT.getStatus().equals(status);
    }
    public static boolean isProcessEndStatus(Integer status) {
        return ObjectUtils.equalsAny(status,
                APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus());
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmReasonEnum.java
对比新文件
@@ -0,0 +1,49 @@
package com.iailab.module.bpm.enums.task;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * 流程实例/任务的的处理原因枚举
 *
 * @author iailab
 */
@Getter
@AllArgsConstructor
public enum BpmReasonEnum {
    // ========== 流程实例的独有原因 ==========
    REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法
    CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程
    CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程
    // ========== 流程任务的独有原因 ==========
    CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等
    TIMEOUT_APPROVE("审批超时,系统自动通过"),
    TIMEOUT_REJECT("审批超时,系统自动不通过"),
    ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
    ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
    ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
    ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
    ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),
    ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"),
    APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
    APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
    ;
    private final String reason;
    /**
     * 格式化理由
     *
     * @param args 参数
     * @return 理由
     */
    public String format(Object... args) {
        return StrUtil.format(reason, args);
    }
}
iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmTaskStatusEnum.java
@@ -1,5 +1,6 @@
package com.iailab.module.bpm.enums.task;
import cn.hutool.core.util.ObjUtil;
import com.iailab.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -13,13 +14,13 @@
@AllArgsConstructor
public enum BpmTaskStatusEnum {
    NOT_START(-1, "未开始"),
    RUNNING(1, "审批中"),
    APPROVE(2, "审批通过"),
    REJECT(3, "审批不通过"),
    CANCEL(4, "已取消"),
    RETURN(5, "已退回"),
    DELEGATE(6, "委派中"),
    /**
     * 使用场景:
@@ -44,6 +45,10 @@
     */
    private final String name;
    public static boolean isRejectStatus(Integer status) {
        return REJECT.getStatus().equals(status);
    }
    /**
     * 判断该状态是否已经处于 End 最终状态
     * <p>
@@ -58,4 +63,8 @@
                RETURN.getStatus(), APPROVING.getStatus());
    }
    public static boolean isCancelStatus(Integer status) {
        return ObjUtil.equal(status, CANCEL.getStatus());
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/package-info.java
对比新文件
@@ -0,0 +1,4 @@
/**
 * 基础包,放一些通用的 VO 类
 */
package com.iailab.module.bpm.controller.admin.base;
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java
对比新文件
@@ -0,0 +1,22 @@
package com.iailab.module.bpm.controller.admin.base.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户精简信息 VO")
@Data
public class UserSimpleBaseVO {
    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long id;
    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
    private String nickname;
    @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
    private String avatar;
    @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long deptId;
    @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
    private String deptName;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmCategoryController.java
@@ -48,6 +48,15 @@
        return success(true);
    }
    @PutMapping("/update-sort-batch")
    @Operation(summary = "批量更新流程分类的排序")
    @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3")
    @PreAuthorize("@ss.hasPermission('bpm:category:update')")
    public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) {
        categoryService.updateCategorySortBatch(ids);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除流程分类")
    @Parameter(name = "id", description = "编号", required = true)
@@ -78,8 +87,6 @@
    @Operation(summary = "获取流程分类的精简信息列表", description = "只包含被开启的分类,主要用于前端的下拉选项")
    public CommonResult<List<BpmCategoryRespVO>> getCategorySimpleList() {
        List<BpmCategoryDO> list = categoryService.getCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus());
        BpmCategoryRespVO bpmCategoryRespVO = new BpmCategoryRespVO();
        BpmCategoryRespVO bpmCategoryRespVO1 = bpmCategoryRespVO.setName("123");
        list.sort(Comparator.comparingInt(BpmCategoryDO::getSort));
        return success(convertList(list, category -> new BpmCategoryRespVO().setId(category.getId())
                .setName(category.getName()).setCode(category.getCode())));
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java
@@ -2,12 +2,9 @@
import cn.hutool.core.collection.CollUtil;
import com.iailab.framework.common.pojo.CommonResult;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.collection.CollectionUtils;
import com.iailab.framework.common.util.io.IoUtils;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.module.bpm.controller.admin.definition.vo.model.*;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
import com.iailab.module.bpm.convert.definition.BpmModelConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
@@ -16,6 +13,8 @@
import com.iailab.module.bpm.service.definition.BpmModelService;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -28,15 +27,12 @@
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Stream;
import static com.iailab.framework.common.pojo.CommonResult.success;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
import static com.iailab.framework.common.util.collection.CollectionUtils.*;
import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程模型")
@RestController
@@ -53,32 +49,42 @@
    @Resource
    private BpmProcessDefinitionService processDefinitionService;
    @GetMapping("/page")
    @Resource
    private AdminUserApi adminUserApi;
    @GetMapping("/list")
    @Operation(summary = "获得模型分页")
    public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) {
        PageResult<Model> pageResult = modelService.getModelPage(pageVO);
        if (CollUtil.isEmpty(pageResult.getList())) {
            return success(PageResult.empty(pageResult.getTotal()));
    @Parameter(name = "name", description = "模型名称", example = "芋艿")
    public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) {
        List<Model> list = modelService.getModelList(name);
        if (CollUtil.isEmpty(list)) {
            return success(Collections.emptyList());
        }
        // 拼接数据
        // 获得 Form 表单
        Set<Long> formIds = convertSet(pageResult.getList(), model -> {
            BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
        Set<Long> formIds = convertSet(list, model -> {
            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
            return metaInfo != null ? metaInfo.getFormId() : null;
        });
        Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
        // 获得 Category Map
        Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
                convertSet(pageResult.getList(), Model::getCategory));
                convertSet(list, Model::getCategory));
        // 获得 Deployment Map
        Set<String> deploymentIds = new HashSet<>();
        pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
        Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
        Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
                convertSet(list, Model::getDeploymentId));
        // 获得 ProcessDefinition Map
        List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
        List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
                deploymentMap.keySet());
        Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
        return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap));
        // 获得 User Map
        Set<Long> userIds = convertSetByFlatMap(list, model -> {
            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
            return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
        });
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
        return success(BpmModelConvert.INSTANCE.buildModelList(list,
                formMap, categoryMap, deploymentMap, processDefinitionMap, userMap));
    }
    @GetMapping("/get")
@@ -97,26 +103,25 @@
    @PostMapping("/create")
    @Operation(summary = "新建模型")
    @PreAuthorize("@ss.hasPermission('bpm:model:create')")
    public CommonResult<String> createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) {
        return success(modelService.createModel(createRetVO, null));
    public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) {
        return success(modelService.createModel(createRetVO));
    }
    @PutMapping("/update")
    @Operation(summary = "修改模型")
    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
    public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) {
        modelService.updateModel(modelVO);
    public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) {
        modelService.updateModel(getLoginUserId(), modelVO);
        return success(true);
    }
    @PostMapping("/import")
    @Operation(summary = "导入模型")
    @PreAuthorize("@ss.hasPermission('bpm:model:import')")
    public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException {
        BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class);
        // 读取文件
        String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false);
        return success(modelService.createModel(createReqVO, bpmnXml));
    @PutMapping("/update-sort-batch")
    @Operation(summary = "批量修改模型排序")
    @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3")
    public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) {
        modelService.updateModelSortBatch(getLoginUserId(), ids);
        return success(true);
    }
    @PostMapping("/deploy")
@@ -124,7 +129,7 @@
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('bpm:model:deploy')")
    public CommonResult<Boolean> deployModel(@RequestParam("id") String id) {
        modelService.deployModel(id);
        modelService.deployModel(getLoginUserId(), id);
        return success(true);
    }
@@ -132,7 +137,15 @@
    @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态")
    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
    public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
        modelService.updateModelState(reqVO.getId(), reqVO.getState());
        modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState());
        return success(true);
    }
    @PutMapping("/update-bpmn")
    @Operation(summary = "修改模型的 BPMN")
    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
    public CommonResult<Boolean> updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) {
        modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml());
        return success(true);
    }
@@ -141,8 +154,26 @@
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('bpm:model:delete')")
    public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) {
        modelService.deleteModel(id);
        modelService.deleteModel(getLoginUserId(), id);
        return success(true);
    }
    // ========== 仿钉钉/飞书的精简模型 =========
    @GetMapping("/simple/get")
    @Operation(summary = "获得仿钉钉流程设计模型")
    @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a")
    public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){
        return success(modelService.getSimpleModel(modelId));
    }
    @PostMapping("/simple/update")
    @Operation(summary = "保存仿钉钉流程设计模型")
    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
    public CommonResult<Boolean> updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) {
        modelService.updateSimpleModel(getLoginUserId(), reqVO);
        return success(Boolean.TRUE);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
@@ -9,7 +9,6 @@
import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy;
import com.iailab.module.bpm.service.definition.BpmCategoryService;
import com.iailab.module.bpm.service.definition.BpmFormService;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
@@ -34,6 +33,7 @@
import static com.iailab.framework.common.pojo.CommonResult.success;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程定义")
@RestController
@@ -69,7 +69,7 @@
                convertSet(pageResult.getList(), ProcessDefinition::getId));
        // 获得 Form Map
        Map<Long, BpmFormDO> formMap = formService.getFormMap(
               convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfoDO::getFormId));
                convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfoDO::getFormId));
        return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionPage(
                pageResult, deploymentMap, processDefinitionMap, formMap, categoryMap));
    }
@@ -78,15 +78,24 @@
    @Operation(summary = "获得流程定义列表")
    @Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举
    public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList(
            @RequestParam("suspensionState") Integer suspensionState) {
        List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState);
            @RequestParam("suspensionState") Integer suspensionState, @RequestParam(value = "categoryId", required = false) String categoryId) {
        // 1.1 获得开启的流程定义
        List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState, categoryId);
        if (CollUtil.isEmpty(list)) {
            return success(Collections.emptyList());
        }
        // 获得 BpmProcessDefinitionInfoDO Map
        // 1.2 移除不可见的流程定义
        Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
                convertSet(list, ProcessDefinition::getId));
        Long userId = getLoginUserId();
        list.removeIf(processDefinition -> {
            BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId());
            return processDefinitionInfo == null // 不存在
                    || Boolean.FALSE.equals(processDefinitionInfo.getVisible()) // visible 不可见
                    || !processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId); // 无权限发起
        });
        // 2. 拼接 VO 返回
        return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList(
                list, null, processDefinitionMap, null, null));
    }
@@ -105,9 +114,8 @@
        }
        BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());
        BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
        List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
        return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
                processDefinition, null, processDefinitionInfo, null, null, bpmnModel, userTaskList));
                processDefinition, null, processDefinitionInfo, null, null, bpmnModel));
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessExpressionController.java
@@ -71,4 +71,5 @@
        return success(BeanUtils.toBean(pageResult, BpmProcessExpressionRespVO.class));
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java
对比新文件
@@ -0,0 +1,20 @@
package com.iailab.module.bpm.controller.admin.definition.vo.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO")
@Data
public class BpmModeUpdateBpmnReqVO {
    @Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotEmpty(message = "流程编号不能为空")
    private String id;
    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "BPMN XML 不能为空")
    private String bpmnXml;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
对比新文件
@@ -0,0 +1,65 @@
package com.iailab.module.bpm.controller.admin.definition.vo.model;
import com.iailab.framework.common.validation.InEnum;
import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
 * BPM 流程 MetaInfo Response DTO
 * 主要用于 { Model#setMetaInfo(String)} 的存储
 *
 * 最终,它的字段和
 * {@link com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO}
 * 是一致的
 *
 * @author iailab
 */
@Data
public class BpmModelMetaInfoVO {
    @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
    @NotEmpty(message = "流程图标不能为空")
    @URL(message = "流程图标格式不正确")
    private String icon;
    @Schema(description = "流程描述", example = "我是描述")
    private String description;
    @Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
    @InEnum(BpmModelTypeEnum.class)
    @NotNull(message = "流程类型不能为空")
    private Integer type;
    @Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
    @InEnum(BpmModelFormTypeEnum.class)
    @NotNull(message = "表单类型不能为空")
    private Integer formType;
    @Schema(description = "表单编号", example = "1024")
    private Long formId; // formType 为 NORMAL 使用,必须非空
    @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
    private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
    @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
    private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空
    @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
    @NotNull(message = "是否可见不能为空")
    private Boolean visible;
    @Schema(description = "可发起用户编号数组", example = "[1,2,3]")
    private List<Long> startUserIds;
    @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]")
    @NotEmpty(message = "可管理用户编号数组不能为空")
    private List<Long> managerUserIds;
    @Schema(description = "排序", example = "1")
    private Long sort; // 创建时,后端自动生成
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
@@ -1,47 +1,36 @@
package com.iailab.module.bpm.controller.admin.definition.vo.model;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 流程模型 Response VO")
@Data
public class BpmModelRespVO {
public class BpmModelRespVO extends BpmModelMetaInfoVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private String id;
    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_iailab")
    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao")
    private String key;
    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
    private String name;
    @Schema(description = "流程图标", example = "https://www.baidu.com/iailab.jpg")
    @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
    private String icon;
    @Schema(description = "流程描述", example = "我是描述")
    private String description;
    @Schema(description = "流程分类编码", example = "1")
    private String category;
    @Schema(description = "流程分类名字", example = "请假")
    private String categoryName;
    @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
    private Integer formType;
    @Schema(description = "表单编号", example = "1024")
    private Long formId; // 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
    @Schema(description = "表单名字", example = "请假表单")
    private String formName;
    @Schema(description = "自定义表单的提交路径", example = "/bpm/oa/leave/create")
    private String formCustomCreatePath; // 使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
    @Schema(description = "自定义表单的查看路径", example = "/bpm/oa/leave/view")
    private String formCustomViewPath; // ,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;
@@ -49,6 +38,9 @@
    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
    private String bpmnXml;
    @Schema(description = "可发起的用户数组")
    private List<UserSimpleBaseVO> startUsers;
    /**
     * 最新部署的流程定义
     */
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java
对比新文件
@@ -0,0 +1,26 @@
package com.iailab.module.bpm.controller.admin.definition.vo.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@Schema(description = "管理后台 - 流程模型的保存 Request VO")
@Data
public class BpmModelSaveReqVO extends BpmModelMetaInfoVO {
    @Schema(description = "编号", example = "1024")
    private String id;
    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao")
    @NotEmpty(message = "流程标识不能为空")
    private String key;
    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
    @NotEmpty(message = "流程名称不能为空")
    private String name;
    @Schema(description = "流程分类", example = "1")
    private String category;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
对比新文件
@@ -0,0 +1,212 @@
package com.iailab.module.bpm.controller.admin.definition.vo.model.simple;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.iailab.framework.common.validation.InEnum;
import com.iailab.module.bpm.enums.definition.*;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BpmSimpleModelNodeVO {
    @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1")
    @NotEmpty(message = "模型节点编号不能为空")
    private String id;
    @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "模型节点类型不能为空")
    @InEnum(BpmSimpleModelNodeType.class)
    private Integer type;
    @Schema(description = "模型节点名称", example = "领导审批")
    private String name;
    @Schema(description = "节点展示内容", example = "指定成员: 芋道源码")
    private String showText;
    @Schema(description = "子节点")
    private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个
    @Schema(description = "条件节点")
    private List<BpmSimpleModelNodeVO> conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用
    @Schema(description = "条件类型", example = "1")
    @InEnum(BpmSimpleModeConditionType.class)
    private Integer conditionType; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
    @Schema(description = "条件表达式", example = "${day>3}")
    private String conditionExpression; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
    @Schema(description = "是否默认条件", example = "true")
    private Boolean defaultFlow; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
    /**
     * 条件组
     */
    private ConditionGroups conditionGroups; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
    @Schema(description = "候选人策略", example = "30")
    @InEnum(BpmTaskCandidateStrategyEnum.class)
    private Integer candidateStrategy; // 用于审批,抄送节点
    @Schema(description = "候选人参数")
    private String candidateParam; // 用于审批,抄送节点
    @Schema(description = "审批节点类型", example = "1")
    @InEnum(BpmUserTaskApproveTypeEnum.class)
    private Integer approveType; // 用于审批节点
    @Schema(description = "多人审批方式", example = "1")
    @InEnum(BpmUserTaskApproveMethodEnum.class)
    private Integer approveMethod; // 用于审批节点
    @Schema(description = "通过比例", example = "100")
    private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置
    @Schema(description = "表单权限", example = "[]")
    private List<Map<String, String>> fieldsPermission;
    @Schema(description = "操作按钮设置", example = "[]")
    private List<OperationButtonSetting> buttonsSetting;  // 用于审批节点
    /**
     * 审批节点拒绝处理
     */
    private RejectHandler rejectHandler;
    /**
     * 审批节点超时处理
     */
    private TimeoutHandler timeoutHandler;
    @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1")
    @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class)
    private Integer assignStartUserHandlerType;
    /**
     * 空处理策略
     */
    private AssignEmptyHandler assignEmptyHandler;
    @Schema(description = "审批节点拒绝处理策略")
    @Data
    public static class RejectHandler {
        @Schema(description = "拒绝处理类型", example = "1")
        @InEnum(BpmUserTaskRejectHandlerType.class)
        private Integer type;
        @Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1")
        private String returnNodeId;
    }
    @Schema(description = "审批节点超时处理策略")
    @Valid
    @Data
    public static class TimeoutHandler {
        @Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
        @NotNull(message = "是否开启超时处理不能为空")
        private Boolean enable;
        @Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @NotNull(message = "任务超时未处理的行为不能为空")
        @InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class)
        private Integer type;
        @Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H")
        @NotEmpty(message = "超时时间不能为空")
        private String timeDuration;
        @Schema(description = "最大提醒次数", example = "1")
        private Integer maxRemindCount;
    }
    @Schema(description = "空处理策略")
    @Data
    @Valid
    public static class AssignEmptyHandler {
        @Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @NotNull(message = "空处理类型不能为空")
        @InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class)
        private Integer type;
        @Schema(description = "指定人员审批的用户编号数组", example = "1")
        private List<Long> userIds;
    }
    @Schema(description = "操作按钮设置")
    @Data
    @Valid
    public static class OperationButtonSetting {
        // TODO @jason:是不是按钮的标识?id 会和数据库的 id 自增有点模糊,key 标识会更合理一点点哈。
        @Schema(description = "按钮 Id", example = "1")
        private Integer id;
        @Schema(description = "显示名称", example = "审批")
        private String displayName;
        @Schema(description = "是否启用", example = "true")
        private Boolean enable;
    }
    @Schema(description = "条件组")
    @Data
    @Valid
    public static class ConditionGroups {
        @Schema(description = "条件组下的条件关系是否为与关系", example = "true")
        @NotNull(message = "条件关系不能为空")
        private Boolean and;
        @Schema(description = "条件组下的条件", example = "[]")
        @NotEmpty(message = "条件不能为空")
        private List<Condition> conditions;
    }
    @Schema(description = "条件")
    @Data
    @Valid
    public static class Condition {
        @Schema(description = "条件下的规则关系是否为与关系", example = "true")
        @NotNull(message = "规则关系不能为空")
        private Boolean and;
        @Schema(description = "条件下的规则", example = "[]")
        @NotEmpty(message = "规则不能为空")
        private List<ConditionRule> rules;
    }
    @Schema(description = "条件规则")
    @Data
    @Valid
    public static class ConditionRule {
        @Schema(description = "运行符号", example = "==")
        @NotEmpty(message = "运行符号不能为空")
        private String opCode;
        @Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId")
        @NotEmpty(message = "运算符左边的值不能为空")
        private String leftSide;
        @Schema(description = "运算符右边的值", example = "1")
        @NotEmpty(message = "运算符右边的值不能为空")
        private String rightSide;
    }
    // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java
对比新文件
@@ -0,0 +1,24 @@
package com.iailab.module.bpm.controller.admin.definition.vo.model.simple;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
// TODO @jason:需要考虑,如果某个节点的配置不正确,需要有提示;具体怎么实现,可以讨论下;
@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO")
@Data
public class BpmSimpleModelUpdateReqVO {
    @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotEmpty(message = "流程模型编号不能为空")
    private String id; // 对应 Flowable act_re_model 表 ID_ 字段
    @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "仿钉钉流程设计模型对象不能为空")
    @Valid
    private BpmSimpleModelNodeVO simpleModel;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
@@ -16,13 +16,13 @@
    @Schema(description = "版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Integer version;
    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
    private String name;
    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iailab")
    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
    private String key;
    @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.baidu.com/iailab.jpg")
    @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
    private String icon;
    @Schema(description = "流程描述", example = "我是描述")
@@ -32,6 +32,9 @@
    private String category;
    @Schema(description = "流程分类名字", example = "请假")
    private String categoryName;
    @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
    private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
    @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
    private Integer formType;
@@ -59,9 +62,12 @@
    @Schema(description = "BPMN XML")
    private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回
    @Schema(description = "发起用户需要选择审批人的任务数组")
    private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回
    @Schema(description = "SIMPLE 设计器模型数据 json 格式")
    private String simpleModel; // 非必须返回
    @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private Long sort;
    @Schema(description = "BPMN UserTask 用户任务")
    @Data
    public static class UserTask {
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmActivityController.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.http
对比新文件
@@ -0,0 +1,16 @@
### 请求 /bpm/process-instance/get-bpmn 接口 => 成功
GET {{baseUrl}}/bpm/process-instance/get-bpmn-model-view?id=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
Content-Type: application/json
tenant-id: 1
Authorization: Bearer {{token}}
### 请求 /bpm/process-instance/get-bpmn 接口 => 失败
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b
GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b
Content-Type: application/json
tenant-id: 1
Authorization: Bearer {{token}}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.java
@@ -4,14 +4,10 @@
import com.iailab.framework.common.pojo.CommonResult;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.number.NumberUtils;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.*;
import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.service.definition.BpmCategoryService;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import com.iailab.module.bpm.service.task.BpmProcessInstanceService;
@@ -39,6 +35,7 @@
import static com.iailab.framework.common.util.collection.CollectionUtils.convertList;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请”
@RestController
@@ -131,15 +128,13 @@
                processInstance.getProcessDefinitionId());
        BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(
                processInstance.getProcessDefinitionId());
        String bpmnXml = BpmnModelUtils.getBpmnXml(
                processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()));
        AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData();
        DeptRespDTO dept = null;
        if (startUser != null) {
        if (startUser != null && startUser.getDeptId() != null) {
            dept = deptApi.getDept(startUser.getDeptId()).getCheckedData();
        }
        return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance,
                processDefinition, processDefinitionInfo, bpmnXml, startUser, dept));
                processDefinition, processDefinitionInfo, startUser, dept));
    }
    @DeleteMapping("/cancel-by-start-user")
@@ -160,4 +155,19 @@
        return success(true);
    }
    @GetMapping("/get-approval-detail")
    @Operation(summary = "获得审批详情")
    @Parameter(name = "id", description = "流程实例的编号", required = true)
    @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
    public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) {
        return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
    }
    @GetMapping("/get-bpmn-model-view")
    @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
    @Parameter(name = "id", description = "流程实例的编号", required = true)
    public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) {
        return success(processInstanceService.getProcessInstanceBpmnModelView(id));
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java
@@ -6,12 +6,12 @@
import com.iailab.framework.common.util.collection.MapUtils;
import com.iailab.framework.common.util.date.DateUtils;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
import com.iailab.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import com.iailab.module.bpm.service.task.BpmProcessInstanceCopyService;
import com.iailab.module.bpm.service.task.BpmProcessInstanceService;
import com.iailab.module.bpm.service.task.BpmTaskService;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
@@ -29,9 +29,9 @@
import java.util.stream.Stream;
import static com.iailab.framework.common.pojo.CommonResult.success;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
import static com.iailab.framework.common.util.collection.CollectionUtils.*;
import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程实例抄送")
@RestController
@@ -43,8 +43,6 @@
    private BpmProcessInstanceCopyService processInstanceCopyService;
    @Resource
    private BpmProcessInstanceService processInstanceService;
    @Resource
    private BpmTaskService taskService;
    @Resource
    private AdminUserApi adminUserApi;
@@ -61,18 +59,19 @@
        }
        // 拼接返回
        Map<String, String> taskNameMap = taskService.getTaskNameByTaskIds(
                convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getTaskId));
        Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
                convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId));
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
                copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator()))));
        return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> {
            MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname()));
            MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname()));
            MapUtils.findAndThen(taskNameMap, copyVO.getTaskId(), copyVO::setTaskName);
        return success(convertPage(pageResult, copy -> {
            BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class);
            MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()),
                    user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
            MapUtils.findAndThen(userMap, copy.getStartUserId(),
                    user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
            MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(),
                    processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime())));
            return copyVO;
        }));
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmTaskController.java
@@ -36,7 +36,8 @@
import static com.iailab.framework.common.pojo.CommonResult.success;
import static com.iailab.framework.common.util.collection.CollectionUtils.*;
import static com.iailab.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程任务实例")
@RestController
@@ -117,24 +118,21 @@
    @PreAuthorize("@ss.hasPermission('bpm:task:query')")
    public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
            @RequestParam("processInstanceId") String processInstanceId) {
        List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId);
        List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true);
        if (CollUtil.isEmpty(taskList)) {
            return success(Collections.emptyList());
        }
        // 拼接数据
        HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
        // 获得 User 和 Dept Map
        Set<Long> userIds = convertSetByFlatMap(taskList, task ->
                Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
        userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
                convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
        // 获得 Form Map
        Map<Long, BpmFormDO> formMap = formService.getFormMap(
                convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey())));
        return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance,
        return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList,
                formMap, userMap, deptMap));
    }
@@ -155,7 +153,7 @@
    }
    @GetMapping("/list-by-return")
    @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮")
    @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮")
    @Parameter(name = "taskId", description = "当前任务ID", required = true)
    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
    public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) {
@@ -165,7 +163,7 @@
    }
    @PutMapping("/return")
    @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮")
    @Operation(summary = "退回任务", description = "用于【流程详情】的【退回】按钮")
    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
    public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
        taskService.returnTask(getLoginUserId(), reqVO);
@@ -204,6 +202,14 @@
        return success(true);
    }
    @PutMapping("/copy")
    @Operation(summary = "抄送任务")
    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
    public CommonResult<Boolean> copyTask(@Valid @RequestBody BpmTaskCopyReqVO reqVO) {
        taskService.copyTask(getLoginUserId(), reqVO);
        return success(true);
    }
    @GetMapping("/list-by-parent-task-id")
    @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
    @Parameter(name = "parentTaskId", description = "父级任务编号", required = true)
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java
@@ -1,5 +1,6 @@
package com.iailab.module.bpm.controller.admin.task.vo.cc;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -12,29 +13,31 @@
    @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private Long id;
    @Schema(description = "发起人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
    private Long startUserId;
    @Schema(description = "发起人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
    private String startUserName;
    @Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED)
    private UserSimpleBaseVO startUser;
    @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233")
    private String processInstanceId;
    @Schema(description = "流程实例的名称")
    @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试")
    private String processInstanceName;
    @Schema(description = "流程实例的发起时间")
    @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime processInstanceStartTime;
    @Schema(description = "发起抄送的任务编号")
    @Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED)
    private String activityId;
    @Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED)
    private String activityName;
    @Schema(description = "流程活动的编号")
    private String taskId;
    @Schema(description = "发起抄送的任务名称")
    private String taskName;
    @Schema(description = "抄送人")
    private String creator;
    @Schema(description = "抄送人昵称")
    private String creatorName;
    @Schema(description = "抄送人意见")
    private String reason;
    @Schema(description = "抄送时间")
    @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
    private UserSimpleBaseVO createUser;
    @Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime createTime;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
对比新文件
@@ -0,0 +1,37 @@
package com.iailab.module.bpm.controller.admin.task.vo.instance;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
import java.util.Map;
@Schema(description = "管理后台 - 审批详情 Request VO")
@Data
public class BpmApprovalDetailReqVO {
    @Schema(description = "流程定义的编号", example = "1024")
    private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID
    @Schema(description = "流程变量")
    private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测
    @Schema(description = "流程实例的编号", example = "1024")
    private String processInstanceId;  // 使用场景:流程已发起时候传流程实例 ID
    // TODO @芋艿:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。
    @Schema(description = "流程活动编号", example = "StartUserNode")
    private String activityId; // 用于获取表单权限。1)发起流程时,传“发起人节点” activityId 可获取发起人的表单权限;2)从抄送列表界面进来时,传抄送的 activityId 可获取抄送人的表单权限;
    @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
    private String taskId; // 用于获取表单权限。1)从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限
    @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
    @JsonIgnore
    public boolean isValidProcessParam() {
        return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
对比新文件
@@ -0,0 +1,106 @@
package com.iailab.module.bpm.controller.admin.task.vo.instance;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - 审批详情 Response VO")
@Data
public class BpmApprovalDetailRespVO {
    @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
    @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<ActivityNode> activityNodes;
    @Schema(description = "表单字段权限")
    private Map<String, String> formFieldsPermission;
    @Schema(description = "待办任务")
    private BpmTaskRespVO todoTask;
    /**
     * 所属流程定义信息
     */
    private BpmProcessDefinitionRespVO processDefinition;
    /**
     * 所属流程实例信息
     */
    private BpmProcessInstanceRespVO processInstance;
    @Schema(description = "活动节点信息")
    @Data
    public static class ActivityNode {
        @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode")
        private String id;
        @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人")
        private String name;
        @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举
        @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
        private Integer status; // 参见 BpmTaskStatusEnum 枚举
        @Schema(description = "节点的开始时间")
        private LocalDateTime startTime;
        @Schema(description = "节点的结束时间")
        private LocalDateTime endTime;
        @Schema(description = "审批节点的任务信息")
        private List<ActivityNodeTask> tasks;
        @Schema(description = "候选人策略", example = "35")
        private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选
        @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
        @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers
        private List<Long> candidateUserIds;
        @Schema(description = "候选人用户列表")
        private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
    }
    @Schema(description = "活动节点的任务信息")
    @Data
    public static class ActivityNodeTask {
        @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        private String id;
        @Schema(description = "任务所属人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
        @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
        private Long owner;
        @Schema(description = "任务所属人", example = "1024")
        private UserSimpleBaseVO ownerUser;
        @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
        @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
        private Long assignee;
        @Schema(description = "任务分配人", example = "2048")
        private UserSimpleBaseVO assigneeUser;
        @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        private Integer status;  // 参见 BpmTaskStatusEnum 枚举
        @Schema(description = "审批意见", example = "同意")
        private String reason;
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java
对比新文件
@@ -0,0 +1,43 @@
package com.iailab.module.bpm.controller.admin.task.vo.instance;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
import java.util.Set;
@Schema(description = "管理后台 - 流程示例的 BPMN 视图 Response VO")
@Data
public class BpmProcessInstanceBpmnModelViewRespVO {
    // ========== 基本信息 ==========
    @Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED)
    private BpmProcessInstanceRespVO processInstance;
    @Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<BpmTaskRespVO> tasks;
    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
    private String bpmnXml;
    @Schema(description = "SIMPLE 模型")
    private BpmSimpleModelNodeVO simpleModel;
    // ========== 进度信息 ==========
    @Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
    private Set<String> unfinishedTaskActivityIds; // 只包括 UserTask
    @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
    private Set<String> finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow
    @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
    private Set<String> finishedSequenceFlowActivityIds; // 只包括 SequenceFlow
    @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
    private Set<String> rejectedTaskActivityIds; // 只包括 UserTask
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java
@@ -9,11 +9,12 @@
import static com.iailab.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 流程实例抄送的分页 Request VO")
@Data
public class BpmProcessInstanceCopyPageReqVO extends PageParam {
    @Schema(description = "流程名称", example = "平台")
    @Schema(description = "流程名称", example = "芋道")
    private String processInstanceName;
    @Schema(description = "创建时间")
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java
@@ -11,15 +11,16 @@
import static com.iailab.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 流程实例分页 Request VO")
@Data
public class BpmProcessInstancePageReqVO extends PageParam {
    @Schema(description = "流程名称", example = "平台")
    @Schema(description = "流程名称", example = "芋道")
    private String name;
    @Schema(description = "流程定义的编号", example = "2048")
    private String processDefinitionId;
    @Schema(description = "流程定义的标识", example = "2048")
    private String processDefinitionKey; // 精准匹配
    @Schema(description = "流程实例的状态", example = "1")
    @InEnum(BpmProcessInstanceStatusEnum.class)
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
@@ -1,5 +1,6 @@
package com.iailab.module.bpm.controller.admin.task.vo.instance;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -15,7 +16,7 @@
    @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private String id;
    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
    private String name;
    @Schema(description = "流程分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@@ -44,7 +45,7 @@
    /**
     * 发起流程的用户
     */
    private User startUser;
    private UserSimpleBaseVO startUser;
    @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
    private String processDefinitionId;
@@ -58,22 +59,6 @@
     */
    private List<Task> tasks; // 仅在流程实例分页才返回
    @Schema(description = "用户信息")
    @Data
    public static class User {
        @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        private Long id;
        @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "iailab")
        private String nickname;
        @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        private Long deptId;
        @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
        private String deptName;
    }
    @Schema(description = "流程任务")
    @Data
    public static class Task {
@@ -81,7 +66,7 @@
        @Schema(description = "流程任务的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
        private String id;
        @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
        @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
        private String name;
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java
对比新文件
@@ -0,0 +1,23 @@
package com.iailab.module.bpm.controller.admin.task.vo.task;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.Collection;
@Schema(description = "管理后台 - 抄送流程任务的 Request VO")
@Data
public class BpmTaskCopyReqVO {
    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotEmpty(message = "任务编号不能为空")
    private String id;
    @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]")
    @NotEmpty(message = "抄送用户不能为空")
    private Collection<Long> copyUserIds;
    @Schema(description = "抄送意见", example = "帮忙看看!")
    private String reason;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
@@ -1,5 +1,7 @@
package com.iailab.module.bpm.controller.admin.task.vo.task;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -15,7 +17,7 @@
    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private String id;
    @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
    @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
    private String name;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@@ -33,14 +35,21 @@
    @Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
    private String reason;
    @Schema(description = "任务负责人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
    @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
    private Long owner;
    /**
     * 负责人的用户信息
     */
    private BpmProcessInstanceRespVO.User ownerUser;
    private UserSimpleBaseVO ownerUser;
    @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
    @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
    private Long assignee;
    /**
     * 审核的用户信息
     */
    private BpmProcessInstanceRespVO.User assigneeUser;
    private UserSimpleBaseVO assigneeUser;
    @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
    private String taskDefinitionKey;
@@ -55,18 +64,20 @@
    @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    private String parentTaskId;
    @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask")
    private List<BpmTaskRespVO> children;
    private List<BpmTaskRespVO> children; // 由加签生成,包含多层子任务
    @Schema(description = "表单编号", example = "1024")
    private Long formId;
    @Schema(description = "表单名字", example = "请假表单")
    private String formName;
    @Schema(description = "表单的配置-JSON 字符串")
    @Schema(description = "表单的配置,JSON 字符串")
    private String formConf;
    @Schema(description = "表单项的数组")
    private List<String> formFields;
    @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED)
    private Map<String, Object> formVariables;
    @Schema(description = "操作按钮设置值")
    private Map<Integer, OperationButtonSetting> buttonsSetting;
    @Data
    @Schema(description = "流程实例")
@@ -75,7 +86,7 @@
        @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
        private String id;
        @Schema(description = "流程实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台")
        @Schema(description = "流程实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
        private String name;
        @Schema(description = "提交时间", requiredMode = Schema.RequiredMode.REQUIRED)
@@ -87,8 +98,20 @@
        /**
         * 发起人的用户信息
         */
        private BpmProcessInstanceRespVO.User startUser;
        private UserSimpleBaseVO startUser;
    }
    @Data
    @Schema(description = "操作按钮设置")
    public static class OperationButtonSetting {
        @Schema(description = "显示名称", example = "审批")
        private String displayName;
        @Schema(description = "是否启用", example = "true")
        private Boolean enable;
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java
@@ -1,19 +1,18 @@
package com.iailab.module.bpm.convert.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.collection.CollectionUtils;
import com.iailab.framework.common.util.date.DateUtils;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
@@ -21,60 +20,64 @@
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertList;
/**
 * 流程模型 Convert
 *
 * @author yunlongn
 * @author hou
 */
@Mapper
public interface BpmModelConvert {
    BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class);
    default PageResult<BpmModelRespVO> buildModelPage(PageResult<Model> pageResult,
                                                      Map<Long, BpmFormDO> formMap,
                                                      Map<String, BpmCategoryDO> categoryMap, Map<String, Deployment> deploymentMap,
                                                      Map<String, ProcessDefinition> processDefinitionMap) {
        List<BpmModelRespVO> list = CollectionUtils.convertList(pageResult.getList(), model -> {
            BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model);
    default List<BpmModelRespVO> buildModelList(List<Model> list,
                                                Map<Long, BpmFormDO> formMap,
                                                Map<String, BpmCategoryDO> categoryMap,
                                                Map<String, Deployment> deploymentMap,
                                                Map<String, ProcessDefinition> processDefinitionMap,
                                                Map<Long, AdminUserRespDTO> userMap) {
        List<BpmModelRespVO> result = convertList(list, model -> {
            BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
            BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
            BpmCategoryDO category = categoryMap.get(model.getCategory());
            Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null;
            ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null;
            return buildModel0(model, metaInfo, form, category, deployment, processDefinition);
            ProcessDefinition processDefinition = model.getDeploymentId() != null ?
                    processDefinitionMap.get(model.getDeploymentId()) : null;
            List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null;
            return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers);
        });
        return new PageResult<>(list, pageResult.getTotal());
        // 排序
        result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort));
        return result;
    }
    default BpmModelRespVO buildModel(Model model,
                                     byte[] bpmnBytes) {
        BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model);
        BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null);
    default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) {
        BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
        BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null);
        if (ArrayUtil.isNotEmpty(bpmnBytes)) {
            modelVO.setBpmnXml(new String(bpmnBytes));
            modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes));
        }
        return modelVO;
    }
    default BpmModelRespVO buildModel0(Model model,
                                      BpmModelMetaInfoRespDTO metaInfo, BpmFormDO form, BpmCategoryDO category,
                                      Deployment deployment, ProcessDefinition processDefinition) {
                                       BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category,
                                       Deployment deployment, ProcessDefinition processDefinition,
                                       List<AdminUserRespDTO> startUsers) {
        BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName())
                .setKey(model.getKey()).setCategory(model.getCategory())
                .setCreateTime(DateUtils.of(model.getCreateTime()));
        // Form
        if (metaInfo != null) {
            modelRespVO.setFormType(metaInfo.getFormType()).setFormId(metaInfo.getFormId())
                    .setFormCustomCreatePath(metaInfo.getFormCustomCreatePath())
                    .setFormCustomViewPath(metaInfo.getFormCustomViewPath());
            modelRespVO.setIcon(metaInfo.getIcon()).setDescription(metaInfo.getDescription());
        }
        BeanUtils.copyProperties(metaInfo, modelRespVO);
        if (form != null) {
            modelRespVO.setFormId(form.getId()).setFormName(form.getName());
            modelRespVO.setFormName(form.getName());
        }
        // Category
        if (category != null) {
@@ -89,49 +92,34 @@
                modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime()));
            }
        }
        // User
        modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class));
        return modelRespVO;
    }
    default void copyToCreateModel(Model model, BpmModelCreateReqVO bean) {
        model.setName(bean.getName());
        model.setKey(bean.getKey());
        model.setMetaInfo(buildMetaInfoStr(null,
                null, bean.getDescription(),
                null, null, null, null));
    default void copyToModel(Model model, BpmModelSaveReqVO reqVO) {
        model.setName(reqVO.getName());
        model.setKey(reqVO.getKey());
        model.setCategory(reqVO.getCategory());
        model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class)));
    }
    default void copyToUpdateModel(Model model, BpmModelUpdateReqVO bean) {
        model.setName(bean.getName());
        model.setCategory(bean.getCategory());
        model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model),
                bean.getIcon(), bean.getDescription(),
                bean.getFormType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath()));
    }
    default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo,
                                    String icon, String description,
                                    Integer formType, Long formId, String formCustomCreatePath, String formCustomViewPath) {
        if (metaInfo == null) {
            metaInfo = new BpmModelMetaInfoRespDTO();
    default BpmModelMetaInfoVO parseMetaInfo(Model model) {
        BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class);
        if (vo == null) {
            return null;
        }
        // 只有非空,才进行设置,避免更新时的覆盖
        if (StrUtil.isNotEmpty(icon)) {
            metaInfo.setIcon(icon);
        if (vo.getManagerUserIds() == null) {
            vo.setManagerUserIds(Collections.emptyList());
        }
        if (StrUtil.isNotEmpty(description)) {
            metaInfo.setDescription(description);
        if (vo.getStartUserIds() == null) {
            vo.setStartUserIds(Collections.emptyList());
        }
        if (Objects.nonNull(formType)) {
            metaInfo.setFormType(formType);
            metaInfo.setFormId(formId);
            metaInfo.setFormCustomCreatePath(formCustomCreatePath);
            metaInfo.setFormCustomViewPath(formCustomViewPath);
        // 如果为空,兜底处理,使用 createTime 创建时间
        if (vo.getSort() == null) {
            vo.setSort(model.getCreateTime().getTime());
        }
        return JsonUtils.toJsonString(metaInfo);
    }
    default BpmModelMetaInfoRespDTO buildMetaInfo(Model model) {
        return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
        return vo;
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
@@ -11,7 +11,6 @@
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
@@ -20,6 +19,7 @@
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -47,7 +47,7 @@
                                                                        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap,
                                                                        Map<Long, BpmFormDO> formMap,
                                                                        Map<String, BpmCategoryDO> categoryMap) {
        return CollectionUtils.convertList(list, definition -> {
        List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> {
            Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class);
            BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class);
            BpmFormDO form = null;
@@ -55,8 +55,11 @@
                form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class);
            }
            BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class);
            return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null);
            return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null);
        });
        // 排序
        result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort));
        return result;
    }
    default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition,
@@ -64,8 +67,7 @@
                                                              BpmProcessDefinitionInfoDO processDefinitionInfo,
                                                              BpmFormDO form,
                                                              BpmCategoryDO category,
                                                              BpmnModel bpmnModel,
                                                              List<UserTask> startUserSelectUserTaskList) {
                                                              BpmnModel bpmnModel) {
        BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class);
        respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
        // Deployment
@@ -87,7 +89,6 @@
        // BpmnModel
        if (bpmnModel != null) {
            respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
            respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionRespVO.UserTask.class));
        }
        return respVO;
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmActivityConvert.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmProcessInstanceConvert.java
@@ -1,35 +1,54 @@
package com.iailab.module.bpm.convert.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.collection.MapUtils;
import com.iailab.framework.common.util.collection.SetUtils;
import com.iailab.framework.common.util.number.NumberUtils;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import com.iailab.module.bpm.convert.definition.BpmProcessDefinitionConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.iailab.module.bpm.event.BpmProcessInstanceStatusEvent;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertList;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
/**
 * 流程实例 Convert
 *
 * @author iailab
 * @author 芋道源码
 */
@Mapper
public interface BpmProcessInstanceConvert {
@@ -55,7 +74,7 @@
            if (userMap != null) {
                AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId()));
                if (startUser != null) {
                    respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
                    respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
                    MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName()));
                }
            }
@@ -65,20 +84,18 @@
    default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance,
                                                          ProcessDefinition processDefinition,
                                                          BpmProcessDefinitionInfoDO processDefinitionExt,
                                                          String bpmnXml,
                                                          BpmProcessDefinitionInfoDO processDefinitionInfo,
                                                          AdminUserRespDTO startUser,
                                                          DeptRespDTO dept) {
        BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class);
        respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance));
        respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
        respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))
                .setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
        // definition
        respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
        copyTo(processDefinitionExt, respVO.getProcessDefinition());
        respVO.getProcessDefinition().setBpmnXml(bpmnXml);
        copyTo(processDefinitionInfo, respVO.getProcessDefinition());
        // user
        if (startUser != null) {
            respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
            respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
            if (dept != null) {
                respVO.getStartUser().setDeptName(dept.getName());
            }
@@ -89,12 +106,7 @@
    @Mapping(source = "from.id", target = "to.id", ignore = true)
    void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to);
    default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, HistoricProcessInstance instance, Integer status) {
        return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status)
                .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey());
    }
    default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {;
    default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {
        return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status)
                .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey());
    }
@@ -108,10 +120,162 @@
    default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) {
        return new BpmMessageSendWhenProcessInstanceRejectReqDTO()
                .setProcessInstanceName(instance.getName())
                .setProcessInstanceId(instance.getId())
                .setReason(reason)
                .setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
            .setProcessInstanceName(instance.getName())
            .setProcessInstanceId(instance.getId())
            .setReason(reason)
            .setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
    }
    default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance,
                                                                                    List<HistoricTaskInstance> taskInstances,
                                                                                    BpmnModel bpmnModel,
                                                                                    BpmSimpleModelNodeVO simpleModel,
                                                                                    Set<String> unfinishedTaskActivityIds,
                                                                                    Set<String> finishedTaskActivityIds,
                                                                                    Set<String> finishedSequenceFlowActivityIds,
                                                                                    Set<String> rejectTaskActivityIds,
                                                                                    Map<Long, AdminUserRespDTO> userMap,
                                                                                    Map<Long, DeptRespDTO> deptMap) {
        BpmProcessInstanceBpmnModelViewRespVO respVO = new BpmProcessInstanceBpmnModelViewRespVO();
        // 基本信息
        respVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class, o -> o
                        .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)))
                        .setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap)));
        respVO.setTasks(convertList(taskInstances, task -> BeanUtils.toBean(task, BpmTaskRespVO.class)
                .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task))
                .setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap))
                .setOwnerUser(buildUser(task.getOwner(), userMap, deptMap))));
        respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
        respVO.setSimpleModel(simpleModel);
        // 进度信息
        respVO.setUnfinishedTaskActivityIds(unfinishedTaskActivityIds)
                .setFinishedTaskActivityIds(finishedTaskActivityIds)
                .setFinishedSequenceFlowActivityIds(finishedSequenceFlowActivityIds)
                .setRejectedTaskActivityIds(rejectTaskActivityIds);
        return respVO;
    }
    default UserSimpleBaseVO buildUser(String userIdStr,
                                       Map<Long, AdminUserRespDTO> userMap,
                                       Map<Long, DeptRespDTO> deptMap) {
        if (StrUtil.isEmpty(userIdStr)) {
            return null;
        }
        Long userId = NumberUtils.parseLong(userIdStr);
        return buildUser(userId, userMap, deptMap);
    }
    default UserSimpleBaseVO buildUser(Long userId,
                                                    Map<Long, AdminUserRespDTO> userMap,
                                                    Map<Long, DeptRespDTO> deptMap) {
        if (userId == null) {
            return null;
        }
        AdminUserRespDTO user = userMap.get(userId);
        if (user == null) {
            return null;
        }
        UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class);
        DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null;
        if (dept != null) {
            userVO.setDeptName(dept.getName());
        }
        return userVO;
    }
    default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) {
        if (task == null) {
            return null;
        }
        return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class)
                .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
    }
    default Set<Long> parseUserIds(HistoricProcessInstance processInstance,
                                   List<BpmApprovalDetailRespVO.ActivityNode> activityNodes,
                                   BpmTaskRespVO todoTask) {
        Set<Long> userIds = new HashSet<>();
        if (processInstance != null) {
            userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
        }
        for (BpmApprovalDetailRespVO.ActivityNode activityNode : activityNodes) {
            CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getAssignee));
            CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getOwner));
            CollUtil.addAll(userIds, activityNode.getCandidateUserIds());
        }
        if (todoTask != null) {
            CollUtil.addIfAbsent(userIds, todoTask.getAssignee());
            CollUtil.addIfAbsent(userIds, todoTask.getOwner());
            if (CollUtil.isNotEmpty(todoTask.getChildren())) {
                CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getAssignee));
                CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getOwner));
            }
        }
        return userIds;
    }
    default Set<Long> parseUserIds02(HistoricProcessInstance processInstance,
                                     List<HistoricTaskInstance> tasks) {
        Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
        tasks.forEach(task -> {
            CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee())));
            CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner())));
        });
        return userIds;
    }
    default BpmApprovalDetailRespVO buildApprovalDetail(BpmnModel bpmnModel,
                                                        ProcessDefinition processDefinition,
                                                        BpmProcessDefinitionInfoDO processDefinitionInfo,
                                                        HistoricProcessInstance processInstance,
                                                        Integer processInstanceStatus,
                                                        List<BpmApprovalDetailRespVO.ActivityNode> activityNodes,
                                                        BpmTaskRespVO todoTask,
                                                        Map<String, String> formFieldsPermission,
                                                        Map<Long, AdminUserRespDTO> userMap,
                                                        Map<Long, DeptRespDTO> deptMap) {
        // 1.1 流程实例
        BpmProcessInstanceRespVO processInstanceResp = null;
        if (processInstance != null) {
            AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
            DeptRespDTO dept = startUser != null ? deptMap.get(startUser.getDeptId()) : null;
            processInstanceResp = buildProcessInstance(processInstance, null, null, startUser, dept);
        }
        // 1.2 流程定义
        BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
                processDefinition, null, processDefinitionInfo, null, null, bpmnModel);
        // 1.3 流程节点
        activityNodes.forEach(approveNode -> {
            if (approveNode.getTasks() != null) {
                approveNode.getTasks().forEach(task -> {
                    task.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap));
                    task.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap));
                });
            }
            approveNode.setCandidateUsers(convertList(approveNode.getCandidateUserIds(), userId -> buildUser(userId, userMap, deptMap)));
        });
        // 1.4 待办任务
        if (todoTask != null) {
            todoTask.setAssigneeUser(buildUser(todoTask.getAssignee(), userMap, deptMap));
            todoTask.setOwnerUser(buildUser(todoTask.getOwner(), userMap, deptMap));
            if (CollUtil.isNotEmpty(todoTask.getChildren())) {
                todoTask.getChildren().forEach(childTask -> {
                    childTask.setAssigneeUser(buildUser(childTask.getAssignee(), userMap, deptMap));
                    childTask.setOwnerUser(buildUser(childTask.getOwner(), userMap, deptMap));
                });
            }
        }
        // 2. 拼接起来
        return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus)
                .setProcessDefinition(definitionResp)
                .setProcessInstance(processInstanceResp)
                .setFormFieldsPermission(formFieldsPermission)
                .setTodoTask(todoTask)
                .setActivityNodes(activityNodes);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java
@@ -1,14 +1,15 @@
package com.iailab.module.bpm.convert.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.collection.CollectionUtils;
import com.iailab.framework.common.util.number.NumberUtils;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
@@ -25,13 +26,14 @@
import java.util.List;
import java.util.Map;
import static com.iailab.framework.common.util.collection.CollectionUtils.*;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertList;
import static com.iailab.framework.common.util.collection.MapUtils.findAndThen;
/**
 * Bpm 任务 Convert
 *
 * @author iailab
 * @author 芋道源码
 */
@Mapper
public interface BpmTaskConvert {
@@ -48,7 +50,7 @@
            }
            taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
            AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
            taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
            taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
        });
    }
@@ -56,13 +58,13 @@
                                                    Map<String, HistoricProcessInstance> processInstanceMap,
                                                    Map<Long, AdminUserRespDTO> userMap,
                                                    Map<Long, DeptRespDTO> deptMap) {
        List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(pageResult.getList(), task -> {
        List<BpmTaskRespVO> taskVOList = convertList(pageResult.getList(), task -> {
            BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
            taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
            // 用户信息
            AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
            if (assignUser != null) {
                taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
                taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
                findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName()));
            }
            // 流程实例
@@ -70,7 +72,7 @@
            if (processInstance != null) {
                AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
                taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
                taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
                taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
            }
            return taskVO;
        });
@@ -78,17 +80,17 @@
    }
    default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList,
                                                                 HistoricProcessInstance processInstance,
                                                                 Map<Long, BpmFormDO> formMap,
                                                                 Map<Long, AdminUserRespDTO> userMap,
                                                                 Map<Long, DeptRespDTO> deptMap) {
        List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(taskList, task -> {
        return convertList(taskList, task -> {
            // 特殊:已取消的任务,不返回
            BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
            taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
            // 流程实例
            AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
            taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
            taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
            Integer taskStatus = FlowableUtils.getTaskStatus(task);
            if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) {
                return null;
            }
            taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task));
            // 表单信息
            BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class);
            if (form != null) {
@@ -96,27 +98,10 @@
                        .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task));
            }
            // 用户信息
            AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
            if (assignUser != null) {
                taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
                findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName()));
            }
            AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
            if (ownerUser != null) {
                taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class));
                findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
            }
            buildTaskAssignee(taskVO, task.getAssignee(), userMap, deptMap);
            buildTaskOwner(taskVO, task.getOwner(), userMap, deptMap);
            return taskVO;
        });
        // 拼接父子关系
        Map<String, List<BpmTaskRespVO>> childrenTaskMap = convertMultiMap(
                filterList(taskVOList, r -> StrUtil.isNotEmpty(r.getParentTaskId())),
                BpmTaskRespVO::getParentTaskId);
        for (BpmTaskRespVO taskVO : taskVOList) {
            taskVO.setChildren(childrenTaskMap.get(taskVO.getId()));
        }
        return filterList(taskVOList, r -> StrUtil.isEmpty(r.getParentTaskId()));
    }
    default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList,
@@ -125,7 +110,7 @@
        return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> {
            AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
            if (assignUser != null) {
                taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
                taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
                DeptRespDTO dept = deptMap.get(assignUser.getDeptId());
                if (dept != null) {
                    taskVO.getAssigneeUser().setDeptName(dept.getName());
@@ -133,10 +118,19 @@
            }
            AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
            if (ownerUser != null) {
                taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class));
                taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
                findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
            }
        }));
    }
    default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks,
                                              Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) {
        return BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
                .setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask))
                .setButtonsSetting(buttonsSetting)
                .setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class)
                        .setStatus(FlowableUtils.getTaskStatus(childTask))));
    }
    default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
@@ -149,14 +143,50 @@
        return reqDTO;
    }
    default void buildTaskOwner(BpmTaskRespVO task, String taskOwner,
                                Map<Long, AdminUserRespDTO> userMap,
                                Map<Long, DeptRespDTO> deptMap) {
        AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner));
        if (ownerUser != null) {
            task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
            findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName()));
        }
    }
    default void buildTaskChildren(BpmTaskRespVO task, Map<String, List<Task>> childrenTaskMap,
                                   Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
        List<Task> childTasks = childrenTaskMap.get(task.getId());
        if (CollUtil.isNotEmpty(childTasks)) {
            task.setChildren(
                    convertList(childTasks, childTask -> {
                        BpmTaskRespVO childTaskVO = BeanUtils.toBean(childTask, BpmTaskRespVO.class);
                        childTaskVO.setStatus(FlowableUtils.getTaskStatus(childTask));
                        buildTaskOwner(childTaskVO, childTask.getOwner(), userMap, deptMap);
                        buildTaskAssignee(childTaskVO, childTask.getAssignee(), userMap, deptMap);
                        return childTaskVO;
                    })
            );
        }
    }
    default void buildTaskAssignee(BpmTaskRespVO task, String taskAssignee,
                                   Map<Long, AdminUserRespDTO> userMap,
                                   Map<Long, DeptRespDTO> deptMap) {
        AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee));
        if (assignUser != null) {
            task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
            findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName()));
        }
    }
    /**
     * 将父任务的属性,拷贝到子任务(加签任务)
     *
     * <p>
     * 为什么不使用 mapstruct 映射?因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的。
     * 使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题。
     *
     * @param parentTask 父任务
     * @param childTask 加签任务
     * @param childTask  加签任务
     */
    default void copyTo(TaskEntityImpl parentTask, TaskEntityImpl childTask) {
        childTask.setName(parentTask.getName());
@@ -165,7 +195,6 @@
        childTask.setParentTaskId(parentTask.getId());
        childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId());
        childTask.setProcessInstanceId(parentTask.getProcessInstanceId());
//        childTask.setExecutionId(parentTask.getExecutionId()); // TODO iailab:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错)
        childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
        childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId());
        childTask.setPriority(parentTask.getPriority());
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmCategoryDO.java
@@ -1,9 +1,9 @@
package com.iailab.module.bpm.dal.dataobject.definition;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmFormDO.java
@@ -1,10 +1,11 @@
package com.iailab.module.bpm.dal.dataobject.definition;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -19,6 +20,7 @@
 * @author iailab
 */
@TableName(value = "bpm_form", autoResultMap = true)
@KeySequence("bpm_form_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor
@@ -45,8 +47,6 @@
    /**
     * 表单项的数组
     *
     * 目前直接将 https://github.com/JakHuang/form-generator 生成的 JSON 串,直接保存
     * 定义:https://github.com/JakHuang/form-generator/issues/46
     */
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<String> fields;
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
@@ -1,25 +1,33 @@
package com.iailab.module.bpm.dal.dataobject.definition;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import com.iailab.framework.mybatis.core.type.LongListTypeHandler;
import com.iailab.framework.mybatis.core.type.StringListTypeHandler;
import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import java.util.List;
/**
 * BPM 流程定义的拓信息
 * 主要解决 Flowable {@link org.flowable.engine.repository.ProcessDefinition} 不支持拓展字段,所以新建该表
 * 主要解决 Flowable {@link ProcessDefinition} 不支持拓展字段,所以新建该表
 *
 * @author iailab
 */
@TableName(value = "bpm_process_definition_info", autoResultMap = true)
@KeySequence("bpm_process_definition_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor
@@ -34,15 +42,21 @@
    /**
     * 流程定义的编号
     *
     * 关联 ProcessDefinition 的 id 属性
     * 关联 {@link ProcessDefinition#getId()} 属性
     */
    private String processDefinitionId;
    /**
     * 流程模型的编号
     *
     * 关联 Model 的 id 属性
     * 关联 {@link Model#getId()} 属性
     */
    private String modelId;
    /**
     * 流程模型的类型
     *
     * 枚举 {@link BpmModelTypeEnum}
     */
    private Integer modelType;
    /**
     * 图标
@@ -56,11 +70,12 @@
    /**
     * 表单类型
     *
     * 关联 {@link BpmModelFormTypeEnum}
     * 枚举 {@link BpmModelFormTypeEnum}
     */
    private Integer formType;
    /**
     * 动态表单编号
     *
     * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
     *
     * 关联 {@link BpmFormDO#getId()}
@@ -68,6 +83,7 @@
    private Long formId;
    /**
     * 表单的配置
     *
     * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
     *
     * 冗余 {@link BpmFormDO#getConf()}
@@ -75,21 +91,63 @@
    private String formConf;
    /**
     * 表单项的数组
     *
     * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
     *
     * 冗余 {@link BpmFormDO#getFields()} ()}
     * 冗余 {@link BpmFormDO#getFields()}
     */
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<String> formFields;
    /**
     * 自定义表单的提交路径,使用 Vue 的路由地址
     *
     * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
     */
    private String formCustomCreatePath;
    /**
     * 自定义表单的查看路径,使用 Vue 的路由地址
     *
     * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
     */
    private String formCustomViewPath;
    /**
     * SIMPLE 设计器模型数据 json 格式
     *
     * 目的:当使用仿钉钉设计器时。流程模型发布的时候,需要保存流程模型设计器的快照数据。
     */
    private String simpleModel;
    /**
     * 是否可见
     *
     * 目的:如果 false 不可见,则不展示在“发起流程”的列表里
     */
    private Boolean visible;
    /**
     * 排序值
     */
    private Long sort;
    /**
     * 可发起用户编号数组
     *
     * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
     *
     * 如果为空,则表示“全部可以发起”!
     *
     * 它和 {@link #visible} 的区别在于:
     * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起
     * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的
     */
    @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
    private List<Long> startUserIds;
    /**
     * 可管理用户编号数组
     *
     * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
     */
    @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
    private List<Long> managerUserIds;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java
@@ -1,9 +1,10 @@
package com.iailab.module.bpm.dal.dataobject.definition;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import com.sun.xml.bind.v2.TODO;
import lombok.*;
/**
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java
@@ -1,8 +1,9 @@
package com.iailab.module.bpm.dal.dataobject.definition;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -16,6 +17,7 @@
 * @author iailab
 */
@TableName(value = "bpm_process_listener")
@KeySequence("bpm_process_listener_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor
@@ -42,8 +44,6 @@
     *
     * 枚举 {@link com.iailab.module.bpm.enums.definition.BpmProcessListenerType}
     *
     * 1. execution:ExecutionListener <a href="https://tkjohn.github.io/flowable-userguide/#executionListeners">执行监听器</a>
     * 2. task:TaskListener <a href="https://tkjohn.github.io/flowable-userguide/#taskListeners">任务监听器</a>
     */
    private String type;
    /**
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java
@@ -1,11 +1,12 @@
package com.iailab.module.bpm.dal.dataobject.definition;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.iailab.framework.common.enums.CommonStatusEnum;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.iailab.framework.common.enums.CommonStatusEnum;
import com.iailab.framework.mybatis.core.dataobject.BaseDO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -19,6 +20,7 @@
 * @author iailab
 */
@TableName(value = "bpm_user_group", autoResultMap = true)
@KeySequence("bpm_user_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@Builder
@NoArgsConstructor
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java
@@ -7,6 +7,8 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.task.api.history.HistoricTaskInstance;
/**
 * 流程抄送 DO
@@ -51,19 +53,26 @@
     * 冗余 ProcessInstance 的 category 字段
     */
    private String category;
    /**
     * 任务主键
     * 流程活动的编号
     * <p/>
     *
     * 关联 Task 的 id 属性
     * 冗余 {@link FlowNode#getId()},对应 BPMN XML 节点编号
     * 原因:用于查询抄送节点的表单字段权限。因为仿钉钉/飞书的抄送节点 (ServiceTask),没有 taskId,只有 activityId
     */
    private String activityId;
    /**
     * 流程活动的名字
     *
     * 冗余 {@link FlowNode#getName()}
     */
    private String activityName;
    /**
     * 流程活动的编号
     *
     * 关联 {@link HistoricTaskInstance#getId()}
     */
    private String taskId;
    /**
     * 任务名称
     *
     * 冗余 Task 的 name 属性
     */
    private String taskName;
    /**
     * 用户编号(被抄送的用户编号)
@@ -72,4 +81,9 @@
     */
    private Long userId;
    /**
     * 抄送意见
     */
    private String reason;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmFormMapper.java
@@ -11,7 +11,7 @@
/**
 * 动态表单 Mapper
 *
 * @author 风里雾里
 * @author hou
 */
@Mapper
public interface BpmFormMapper extends BaseMapperX<BpmFormDO> {
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
@@ -1,6 +1,7 @@
package com.iailab.module.bpm.dal.mysql.definition;
import com.iailab.framework.mybatis.core.mapper.BaseMapperX;
import com.iailab.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import org.apache.ibatis.annotations.Mapper;
@@ -18,4 +19,9 @@
        return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId);
    }
    default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) {
        update(updateObj,
                new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId));
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java
@@ -6,6 +6,7 @@
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
import com.iailab.module.system.api.user.AdminUserApi;
import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
@@ -56,12 +57,15 @@
    @Bean
    public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer(
            ObjectProvider<FlowableEventListener> listeners,
            ObjectProvider<FlowableFunctionDelegate> customFlowableFunctionDelegates,
            BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
        return configuration -> {
            // 注册监听器,例如说 BpmActivityEventListener
            configuration.setEventListeners(ListUtil.toList(listeners.iterator()));
            // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义
            configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
            // 设置自定义的函数
            configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator()));
        };
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
@@ -1,5 +1,7 @@
package com.iailab.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
import com.iailab.framework.common.util.collection.SetUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter;
@@ -48,9 +50,20 @@
        super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
        // 第二步,获取任务的所有处理人
        Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
        execution.setVariable(super.collectionVariable, assigneeUserIds);
        @SuppressWarnings("unchecked")
        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
        if (assigneeUserIds == null) {
            assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
            execution.setVariable(super.collectionVariable, assigneeUserIds);
            if (CollUtil.isEmpty(assigneeUserIds)) {
                // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
                // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
                // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
                assigneeUserIds = SetUtils.asSet((Long) null);
            }
        }
        return assigneeUserIds.size();
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
@@ -1,5 +1,7 @@
package com.iailab.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
import com.iailab.framework.common.util.collection.SetUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter;
@@ -42,9 +44,18 @@
        super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
        // 第二步,获取任务的所有处理人
        Set<Long> assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!!
        execution.setVariable(super.collectionVariable, assigneeUserIds);
        @SuppressWarnings("unchecked")
        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
        if (assigneeUserIds == null) {
            assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
            execution.setVariable(super.collectionVariable, assigneeUserIds);
            if (CollUtil.isEmpty(assigneeUserIds)) {
                // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
                // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
                // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
                assigneeUserIds = SetUtils.asSet((Long) null);
            }
        }
        return assigneeUserIds.size();
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
@@ -14,6 +14,7 @@
import org.flowable.engine.impl.util.TaskHelper;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
@@ -36,14 +37,16 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    protected void handleAssignments(TaskService taskService, String assignee, String owner,
        List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
        DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
                                     List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
                                     DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
        // 第一步,获得任务的候选用户
        Long assigneeUserId = calculateTaskCandidateUsers(execution);
        Assert.notNull(assigneeUserId, "任务处理人不能为空");
        // 第二步,设置作为负责人
        TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
        if (assigneeUserId != null) {
            TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
        }
    }
    private Long calculateTaskCandidateUsers(DelegateExecution execution) {
@@ -55,7 +58,10 @@
        // 情况二,如果非多实例的任务,则计算任务处理人
        // 第一步,先计算可处理该任务的处理人们
        Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsers(execution);
        Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
        if (CollUtil.isEmpty(candidateUserIds)) {
            return null;
        }
        // 第二步,后随机选择一个任务的处理人
        // 疑问:为什么一定要选择一个任务处理人?
        // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
@@ -2,23 +2,28 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.iailab.framework.common.enums.CommonStatusEnum;
import com.iailab.framework.common.util.object.ObjectUtils;
import com.iailab.framework.datapermission.core.annotation.DataPermission;
import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.service.task.BpmProcessInstanceService;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG;
@@ -57,7 +62,14 @@
        List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
        // 遍历所有的 UserTask,校验审批人配置
        userTaskList.forEach(userTask -> {
            // 1. 非空校验
            // 1.1 非人工审批,无需校验审批人配置
            Integer approveType = BpmnModelUtils.parseApproveType(userTask);
            if (ObjectUtils.equalsAny(approveType,
                    BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
                    BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
                return;
            }
            // 1.2 非空校验
            Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask);
            String param = BpmnModelUtils.parseCandidateParam(userTask);
            if (strategy == null) {
@@ -79,20 +91,66 @@
     * @return 用户编号集合
     */
    @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人
    public Set<Long> calculateUsers(DelegateExecution execution) {
        Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
        String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
    public Set<Long> calculateUsersByTask(DelegateExecution execution) {
        // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
        FlowElement flowElement = execution.getCurrentFlowElement();
        Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
        if (ObjectUtils.equalsAny(approveType,
                BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
                BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
            return new HashSet<>();
        }
        // 1.1 计算任务的候选人
        Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
        Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement);
        String param = BpmnModelUtils.parseCandidateParam(flowElement);
        Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param);
        // 1.2 移除被禁用的用户
        removeDisableUsers(userIds);
        // 2. 校验是否有候选人
        // 2. 候选人为空时,根据“审批人为空”的配置补充
        if (CollUtil.isEmpty(userIds)) {
            log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(),
                    execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param);
            throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
            userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
                    .calculateUsersByTask(execution, param);
            // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
        }
        // 3. 移除发起人的用户
        ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class)
                .getProcessInstance(execution.getProcessInstanceId());
        Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId());
        removeStartUserIfSkip(userIds, flowElement, Long.valueOf(processInstance.getStartUserId()));
        return userIds;
    }
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
                                              Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
        FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
        Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
        if (ObjectUtils.equalsAny(approveType,
                BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
                BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
            return new HashSet<>();
        }
        // 1.1 计算任务的候选人
        Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement);
        String param = BpmnModelUtils.parseCandidateParam(flowElement);
        Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByActivity(bpmnModel, activityId, param,
                startUserId, processDefinitionId, processVariables);
        // 1.2 移除被禁用的用户
        removeDisableUsers(userIds);
        // 2. 候选人为空时,根据“审批人为空”的配置补充
        if (CollUtil.isEmpty(userIds)) {
            userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
                    .calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables);
            // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
        }
        // 3. 移除发起人的用户
        removeStartUserIfSkip(userIds, flowElement, startUserId);
        return userIds;
    }
@@ -104,10 +162,31 @@
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
        assigneeUserIds.removeIf(id -> {
            AdminUserRespDTO user = userMap.get(id);
            return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
            return user == null || CommonStatusEnum.isDisable(user.getStatus());
        });
    }
    /**
     * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人
     *
     * 注意:如果只有一个候选人,则不处理,避免无法审批
     *
     * @param assigneeUserIds 当前分配的候选人
     * @param flowElement 当前节点
     * @param startUserId 发起人
     */
    @VisibleForTesting
    void removeStartUserIfSkip(Set<Long> assigneeUserIds, FlowElement flowElement, Long startUserId) {
        if (CollUtil.size(assigneeUserIds) <= 1) {
            return;
        }
        Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement);
        if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
            return;
        }
        assigneeUserIds.remove(startUserId);
    }
    private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
        BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
        Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java
@@ -1,16 +1,18 @@
package com.iailab.module.bpm.framework.flowable.core.candidate;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import java.util.Map;
import java.util.Set;
/**
 * BPM 任务的候选人的策略接口
 *
 * <p>
 * 例如说:分配审批人
 *
 * @author iailab
 * @author hou
 */
public interface BpmTaskCandidateStrategy {
@@ -29,14 +31,6 @@
    void validateParam(String param);
    /**
     * 基于执行任务,获得任务的候选用户们
     *
     * @param execution 执行任务
     * @return 用户编号集合
     */
    Set<Long> calculateUsers(DelegateExecution execution, String param);
    /**
     * 是否一定要输入参数
     *
     * @return 是否
@@ -45,4 +39,47 @@
        return true;
    }
    /**
     * 基于候选人参数,获得任务的候选用户们
     *
     * 注意:实现 calculateUsers 系列方法时,有两种选择:
     * 1. 只重写 calculateUsers 默认方法
     * 2. 都重写 calculateUsersByTask 和 calculateUsersByActivity 两个方法
     *
     * @param param 执行任务
     * @return 用户编号集合
     */
    default Set<Long> calculateUsers(String param) {
        throw new UnsupportedOperationException("该分配方法未实现,请检查!");
    }
    /**
     * 基于【执行任务】,获得任务的候选用户们
     *
     * @param execution 执行任务
     * @return 用户编号集合
     */
    default Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        return calculateUsers(param);
    }
    /**
     * 基于【流程活动】,获得任务的候选用户们
     * <p>
     * 目的:用于获取未执行节点的候选用户们
     *
     * @param bpmnModel 流程图
     * @param activityId 活动 ID (对应 Bpmn XML id)
     * @param param     节点的参数
     * @param startUserId  流程发起人编号
     * @param processDefinitionId 流程定义编号
     * @param processVariables 流程变量
     * @return 用户编号集合
     */
    @SuppressWarnings("unused")
    default Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                               Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        return calculateUsers(param);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java
对比新文件
@@ -0,0 +1,94 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.system.api.dept.DeptApi;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
 * 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类
 *
 * @author hou
 */
public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
    @Resource
    protected DeptApi deptApi;
    @Resource
    protected AdminUserApi adminUserApi;
    /**
     * 获得指定层级的部门负责人,只有第 level 的负责人
     *
     * @param dept 指定部门
     * @param level 第几级
     * @return 部门负责人的编号
     */
    protected Long getAssignLevelDeptLeaderId(DeptRespDTO dept, Integer level) {
        Assert.isTrue(level > 0, "level 必须大于 0");
        if (dept == null) {
            return null;
        }
        DeptRespDTO currentDept = dept;
        for (int i = 1; i < level; i++) {
            DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId()).getCheckedData();
            if (parentDept == null) { // 找不到父级部门,到了最高级。返回最高级的部门负责人
                break;
            }
            currentDept = parentDept;
        }
        return currentDept.getLeaderUserId();
    }
    /**
     * 获得连续层级的部门负责人,包含 [1, level] 的负责人
     *
     * @param deptIds 指定部门编号数组
     * @param level 最大层级
     * @return 连续部门负责人 Id
     */
    protected Set<Long> getMultiLevelDeptLeaderIds(List<Long> deptIds, Integer level) {
        Assert.isTrue(level > 0, "level 必须大于 0");
        if (CollUtil.isEmpty(deptIds)) {
            return new HashSet<>();
        }
        Set<Long> deptLeaderIds = new LinkedHashSet<>(); // 保证有序
        for (Long deptId : deptIds) {
            DeptRespDTO dept = deptApi.getDept(deptId).getCheckedData();
            for (int i = 0; i < level; i++) {
                if (dept.getLeaderUserId() != null) {
                    deptLeaderIds.add(dept.getLeaderUserId());
                }
                DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()).getCheckedData();
                if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了
                    break;
                }
                dept = parentDept;
            }
        }
        return deptLeaderIds;
    }
    /**
     * 获取发起人的部门
     *
     * @param startUserId 发起人 Id
     */
    protected DeptRespDTO getStartUserDept(Long startUserId) {
        AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData();
        if (startUser.getDeptId() == null) { // 找不到部门
            return null;
        }
        return deptApi.getDept(startUser.getDeptId()).getCheckedData();
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java
对比新文件
@@ -0,0 +1,45 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.lang.Assert;
import com.iailab.framework.common.util.string.StrUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
/**
 * 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author hou
 */
@Component
public class BpmTaskCandidateDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
    @Override
    public BpmTaskCandidateStrategyEnum getStrategy() {
        return BpmTaskCandidateStrategyEnum.MULTI_DEPT_LEADER_MULTI;
    }
    @Override
    public void validateParam(String param) {
        // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级
        String[] params = param.split("\\|");
        Assert.isTrue(params.length == 2, "参数格式不匹配");
        List<Long> deptIds = StrUtils.splitToLong(params[0], ",");
        int level = Integer.parseInt(params[1]);
        // 校验部门存在
        deptApi.validateDeptList(deptIds).checkError();
        Assert.isTrue(level > 0, "部门层级必须大于 0");
    }
    @Override
    public Set<Long> calculateUsers(String param) {
        String[] params = param.split("\\|");
        List<Long> deptIds = StrUtils.splitToLong(params[0], ",");
        int level = Integer.parseInt(params[1]);
        return super.getMultiLevelDeptLeaderIds(deptIds, level);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java 修改
@@ -1,11 +1,10 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy;
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept;
import com.iailab.framework.common.util.string.StrUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.system.api.dept.DeptApi;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@@ -17,7 +16,7 @@
/**
 * 部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author kyle
 * @author hou
 */
@Component
public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
@@ -33,11 +32,11 @@
    @Override
    public void validateParam(String param) {
        Set<Long> deptIds = StrUtils.splitToLongSet(param);
        deptApi.validateDeptList(deptIds);
        deptApi.validateDeptList(deptIds).checkError();
    }
    @Override
    public Set<Long> calculateUsers(DelegateExecution execution, String param) {
    public Set<Long> calculateUsers(String param) {
        Set<Long> deptIds = StrUtils.splitToLongSet(param);
        List<DeptRespDTO> depts = deptApi.getDeptList(deptIds).getCheckedData();
        return convertSet(depts, DeptRespDTO::getLeaderUserId);
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java 修改
@@ -1,4 +1,4 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy;
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept;
import com.iailab.framework.common.util.string.StrUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
@@ -6,7 +6,6 @@
import com.iailab.module.system.api.dept.DeptApi;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@@ -15,10 +14,11 @@
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
/**
 * 部门的成员 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author kyle
 * @author hou
 */
@Component
public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy {
@@ -36,11 +36,11 @@
    @Override
    public void validateParam(String param) {
        Set<Long> deptIds = StrUtils.splitToLongSet(param);
        deptApi.validateDeptList(deptIds);
        deptApi.validateDeptList(deptIds).checkError();
    }
    @Override
    public Set<Long> calculateUsers(DelegateExecution execution, String param) {
    public Set<Long> calculateUsers(String param) {
        Set<Long> deptIds = StrUtils.splitToLongSet(param);
        List<AdminUserRespDTO> users = adminUserApi.getUserListByDeptIds(deptIds).getCheckedData();
        return convertSet(users, AdminUserRespDTO::getId);
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java
对比新文件
@@ -0,0 +1,70 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.lang.Assert;
import com.iailab.framework.common.util.number.NumberUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.service.task.BpmProcessInstanceService;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static cn.hutool.core.collection.ListUtil.toList;
/**
 * 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author hou
 */
@Component
public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
    @Resource
    @Lazy
    private BpmProcessInstanceService processInstanceService;
    @Override
    public BpmTaskCandidateStrategyEnum getStrategy() {
        return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI;
    }
    @Override
    public void validateParam(String param) {
        int level = Integer.parseInt(param); // 参数是部门的层级
        Assert.isTrue(level > 0, "部门的层级必须大于 0");
    }
    @Override
    public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        int level = Integer.parseInt(param); // 参数是部门的层级
        // 获得流程发起人
        ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
        Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
        // 获取发起人的 multi 部门负责人
        DeptRespDTO dept = super.getStartUserDept(startUserId);
        if (dept == null) {
            return new HashSet<>();
        }
        return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level);
    }
    @Override
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                              Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        int level = Integer.parseInt(param); // 参数是部门的层级
        DeptRespDTO dept = super.getStartUserDept(startUserId);
        if (dept == null) {
            return new HashSet<>();
        }
        return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java
对比新文件
@@ -0,0 +1,71 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.lang.Assert;
import com.iailab.framework.common.util.number.NumberUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.service.task.BpmProcessInstanceService;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static com.iailab.framework.common.util.collection.SetUtils.asSet;
/**
 * 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author hou
 */
@Component
public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
    @Resource
    @Lazy // 避免循环依赖
    private BpmProcessInstanceService processInstanceService;
    @Override
    public BpmTaskCandidateStrategyEnum getStrategy() {
        return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER;
    }
    @Override
    public void validateParam(String param) {
        // 参数是部门的层级
        Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0");
    }
    @Override
    public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        // 获得流程发起人
        ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
        Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
        // 获取发起人的部门负责人
        return getStartUserDeptLeader(startUserId, param);
    }
    @Override
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                              Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        // 获取发起人的部门负责人
        return getStartUserDeptLeader(startUserId, param);
    }
    private Set<Long> getStartUserDeptLeader(Long startUserId, String param) {
        int level = Integer.parseInt(param); // 参数是部门的层级
        DeptRespDTO dept = super.getStartUserDept(startUserId);
        if (dept == null) {
            return new HashSet<>();
        }
        Long deptLeaderId = super.getAssignLevelDeptLeaderId(dept, level);
        return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>();
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java
对比新文件
@@ -0,0 +1,97 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.google.common.collect.Sets;
import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.service.task.BpmProcessInstanceService;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.Task;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
/**
 * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类
 *
 * @author hou
 */
@Component
public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
    @Resource
    @Lazy // 延迟加载,避免循环依赖
    private BpmProcessInstanceService processInstanceService;
    @Override
    public BpmTaskCandidateStrategyEnum getStrategy() {
        return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
    }
    @Override
    public void validateParam(String param) {}
    @Override
    public boolean isParamRequired() {
        return false;
    }
    @Override
    public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
        Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
        Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
        Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空",
                execution.getProcessInstanceId());
        // 获得审批人
        List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
        return new LinkedHashSet<>(assignees);
    }
    @Override
    public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                                        Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        if (processVariables == null) {
            return Sets.newLinkedHashSet();
        }
        Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables);
        if (startUserSelectAssignees == null) {
            return Sets.newLinkedHashSet();
        }
        // 获得审批人
        List<Long> assignees = startUserSelectAssignees.get(activityId);
        return new LinkedHashSet<>(assignees);
    }
    /**
     * 获得发起人自选审批人或抄送人的 Task 列表
     *
     * @param bpmnModel BPMN 模型
     * @return Task 列表
     */
    public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) {
        if (bpmnModel == null) {
            return Collections.emptyList();
        }
        List<Task> tasks = new ArrayList<>();
        tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class));
        tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class));
        if (CollUtil.isEmpty(tasks)) {
            return Collections.emptyList();
        }
        tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task),
                BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));
        return tasks;
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormSDeptLeaderStrategy.java
对比新文件
@@ -0,0 +1,56 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.form;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.AbstractBpmTaskCandidateDeptLeaderStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
/**
 * 表单内部门负责人 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author hou
 */
@Component
public class BpmTaskCandidateFormSDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
    @Override
    public BpmTaskCandidateStrategyEnum getStrategy() {
        return BpmTaskCandidateStrategyEnum.FORM_DEPT_LEADER;
    }
    @Override
    public void validateParam(String param) {
        // 参数格式: | 分隔:1)左边为表单内部门字段。2)右边为部门层级
        String[] params = param.split("\\|");
        Assert.isTrue(params.length == 2, "参数格式不匹配");
        Assert.notEmpty(param, "表单内部门字段不能为空");
        int level = Integer.parseInt(params[1]);
        Assert.isTrue(level > 0, "部门层级必须大于 0");
    }
    @Override
    public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        String[] params = param.split("\\|");
        Object result = execution.getVariable(params[0]);
        int level = Integer.parseInt(params[1]);
        return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level);
    }
    @Override
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
                                              String param, Long startUserId, String processDefinitionId,
                                              Map<String, Object> processVariables) {
        String[] params = param.split("\\|");
        Object result = processVariables == null ? null : processVariables.get(params[0]);
        int level = Integer.parseInt(params[1]);
        return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java
对比新文件
@@ -0,0 +1,47 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.form;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
/**
 * 表单内用户字段 {@link BpmTaskCandidateUserStrategy} 实现类
 *
 * @author hou
 */
@Component
public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrategy {
    @Override
    public BpmTaskCandidateStrategyEnum getStrategy() {
        return BpmTaskCandidateStrategyEnum.FORM_USER;
    }
    @Override
    public void validateParam(String param) {
        Assert.notEmpty(param, "表单内用户字段不能为空");
    }
    @Override
    public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        Object result = execution.getVariable(param);
        return Convert.toSet(Long.class, result);
    }
    @Override
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
                                              String param, Long startUserId, String processDefinitionId,
                                              Map<String, Object> processVariables) {
        Object result = processVariables == null ? null : processVariables.get(param);
        return Convert.toSet(Long.class, result);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java
对比新文件
@@ -0,0 +1,73 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.other;
import cn.hutool.core.lang.Assert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
 * 审批人为空 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author hou
 */
@Component
public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy {
    @Resource
    @Lazy // 延迟加载,避免循环依赖
    private BpmProcessDefinitionService processDefinitionService;
    @Override
    public BpmTaskCandidateStrategyEnum getStrategy() {
        return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY;
    }
    @Override
    public void validateParam(String param) {
    }
    @Override
    public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        return getCandidateUsers(execution.getProcessDefinitionId(), execution.getCurrentFlowElement());
    }
    @Override
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                              Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
        return getCandidateUsers(processDefinitionId, flowElement);
    }
    private Set<Long> getCandidateUsers(String processDefinitionId, FlowElement flowElement) {
        // 情况一:指定人员审批
        Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(flowElement);
        if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) {
            return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(flowElement));
        }
        // 情况二:流程管理员
        if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) {
            BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processDefinitionId);
            Assert.notNull(processDefinition, "流程定义({})不存在", processDefinitionId);
            return new HashSet<>(processDefinition.getManagerUserIds());
        }
        // 都不满足,还是返回空
        return new HashSet<>();
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java 修改
@@ -1,12 +1,14 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy;
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.other;
import cn.hutool.core.convert.Convert;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
/**
@@ -28,9 +30,16 @@
    }
    @Override
    public Set<Long> calculateUsers(DelegateExecution execution, String param) {
    public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        Object result = FlowableUtils.getExpressionValue(execution, param);
        return Convert.toSet(Long.class, result);
    }
    @Override
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                              Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        Object result = FlowableUtils.getExpressionValue(processVariables, param);
        return Convert.toSet(Long.class, result);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java 修改
@@ -1,11 +1,10 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy;
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user;
import com.iailab.framework.common.util.string.StrUtils;
import com.iailab.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.service.definition.BpmUserGroupService;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@@ -15,10 +14,11 @@
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
/**
 * 用户组 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author kyle
 * @author hou
 */
@Component
public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy {
@@ -34,11 +34,11 @@
    @Override
    public void validateParam(String param) {
        Set<Long> groupIds = StrUtils.splitToLongSet(param);
        userGroupService.getUserGroupList(groupIds);
        userGroupService.validUserGroups(groupIds);
    }
    @Override
    public Set<Long> calculateUsers(DelegateExecution execution, String param) {
    public Set<Long> calculateUsers(String param) {
        Set<Long> groupIds = StrUtils.splitToLongSet(param);
        List<BpmUserGroupDO> groups = userGroupService.getUserGroupList(groupIds);
        return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream);
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java 修改
@@ -1,4 +1,4 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy;
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user;
import com.iailab.framework.common.util.string.StrUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
@@ -6,7 +6,6 @@
import com.iailab.module.system.api.dept.PostApi;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@@ -15,10 +14,11 @@
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet;
/**
 * 岗位 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author kyle
 * @author hou
 */
@Component
public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy {
@@ -40,7 +40,7 @@
    }
    @Override
    public Set<Long> calculateUsers(DelegateExecution execution, String param) {
    public Set<Long> calculateUsers(String param) {
        Set<Long> postIds = StrUtils.splitToLongSet(param);
        List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(postIds).getCheckedData();
        return convertSet(users, AdminUserRespDTO::getId);
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java 修改
@@ -1,11 +1,10 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy;
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user;
import com.iailab.framework.common.util.string.StrUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.system.api.permission.PermissionApi;
import com.iailab.module.system.api.permission.RoleApi;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@@ -14,7 +13,7 @@
/**
 * 角色 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author kyle
 * @author hou
 */
@Component
public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy {
@@ -36,7 +35,7 @@
    }
    @Override
    public Set<Long> calculateUsers(DelegateExecution execution, String param) {
    public Set<Long> calculateUsers(String param) {
        Set<Long> roleIds = StrUtils.splitToLongSet(param);
        return permissionApi.getUserRoleIdListByRoleIds(roleIds).getCheckedData();
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java
对比新文件
@@ -0,0 +1,57 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user;
import com.iailab.framework.common.util.collection.SetUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.service.task.BpmProcessInstanceService;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
/**
 * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类
 * <p>
 * 适合场景:用于需要发起人信息复核等场景
 *
 * @author hou
 */
@Component
public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy {
    @Resource
    @Lazy // 延迟加载,避免循环依赖
    private BpmProcessInstanceService processInstanceService;
    @Override
    public BpmTaskCandidateStrategyEnum getStrategy() {
        return BpmTaskCandidateStrategyEnum.START_USER;
    }
    @Override
    public void validateParam(String param) {
    }
    @Override
    public boolean isParamRequired() {
        return false;
    }
    @Override
    public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
        ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
        return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
    }
    @Override
    public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                              Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
        return SetUtils.asSet(startUserId);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java
文件名从 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java 修改
@@ -1,19 +1,19 @@
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy;
package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.hutool.core.text.StrPool;
import com.iailab.framework.common.util.string.StrUtils;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.system.api.user.AdminUserApi;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
import java.util.LinkedHashSet;
/**
 * 用户 {@link BpmTaskCandidateStrategy} 实现类
 *
 * @author kyle
 * @author hou
 */
@Component
public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy {
@@ -28,12 +28,12 @@
    @Override
    public void validateParam(String param) {
        adminUserApi.validateUserList(StrUtils.splitToLongSet(param));
        adminUserApi.validateUserList(StrUtils.splitToLongSet(param)).checkError();
    }
    @Override
    public Set<Long> calculateUsers(DelegateExecution execution, String param) {
        return StrUtils.splitToLongSet(param);
    public LinkedHashSet<Long> calculateUsers(String param) {
        return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA));
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmConstants.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
@@ -1,8 +1,11 @@
package com.iailab.module.bpm.framework.flowable.core.enums;
import cn.hutool.core.util.ArrayUtil;
import com.iailab.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * BPM 任务的候选人策略枚举
@@ -13,17 +16,26 @@
 */
@Getter
@AllArgsConstructor
public enum BpmTaskCandidateStrategyEnum {
public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable {
    ROLE(10, "角色"),
    DEPT_MEMBER(20, "部门的成员"), // 包括负责人
    DEPT_LEADER(21, "部门的负责人"),
    MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
    POST(22, "岗位"),
    USER(30, "用户"),
    START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人
    START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景
    START_USER_DEPT_LEADER(37, "发起人部门负责人"),
    START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"),
    USER_GROUP(40, "用户组"),
    FORM_USER(50, "表单内用户字段"),
    FORM_DEPT_LEADER(51, "表单内部门负责人"),
    EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager
    ASSIGN_EMPTY(1, "审批人为空"),
    ;
    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmTaskCandidateStrategyEnum::getStrategy).toArray();
    /**
     * 类型
@@ -38,4 +50,9 @@
        return ArrayUtil.firstMatch(o -> o.getStrategy().equals(strategy), values());
    }
    @Override
    public int[] array() {
        return ARRAYS;
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
@@ -23,4 +23,91 @@
     */
    String USER_TASK_CANDIDATE_PARAM = "candidateParam";
    /**
     * BPMN ExtensionElement 的扩展属性,用于标记边界事件类型
     */
    String BOUNDARY_EVENT_TYPE = "boundaryEventType";
    /**
     * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作
     */
    String USER_TASK_TIMEOUT_HANDLER_TYPE = "timeoutHandlerType";
    /**
     * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型
     */
    String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType";
    /**
     * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理类型
     */
    String USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE = "assignEmptyHandlerType";
    /**
     * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理的指定用户编号数组
     */
    String USER_TASK_ASSIGN_USER_IDS = "assignEmptyUserIds";
    /**
     * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型
     */
    String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType";
    /**
     * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的退回的任务 Id
     */
    String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId";
    /**
     * BPMN UserTask 的扩展属性,用于标记用户任务的审批类型
     */
    String USER_TASK_APPROVE_TYPE = "approveType";
    /**
     * BPMN UserTask 的扩展属性,用于标记用户任务的审批方式
     */
    String USER_TASK_APPROVE_METHOD = "approveMethod";
    /**
     * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
     */
    String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission";
    /**
     * BPMN ExtensionElement Attribute, 用于标记表单字段
     */
    String FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE = "field";
    /**
     * BPMN ExtensionElement Attribute, 用于标记表单权限
     */
    String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission";
    /**
     * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置
     */
    String BUTTON_SETTING_ELEMENT = "buttonsSetting";
    /**
     * BPMN ExtensionElement Attribute, 用于标记按钮编号
     */
    String BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE = "id";
    /**
     * BPMN ExtensionElement Attribute, 用于标记按钮显示名称
     */
    String BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE = "displayName";
    /**
     * BPMN ExtensionElement Attribute, 用于标记按钮是否启用
     */
    String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable";
    /**
     * BPMN Start Event Node Id
     */
    String START_EVENT_NODE_ID = "StartEvent";
    /**
     * 发起人节点 ID
     */
    String START_USER_NODE_ID = "StartUserNode";
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
对比新文件
@@ -0,0 +1,62 @@
package com.iailab.module.bpm.framework.flowable.core.enums;
import org.flowable.engine.runtime.ProcessInstance;
/**
 * BPM Variable 通用常量
 *
 * @author iailab
 */
public class BpmnVariableConstants {
    /**
     * 流程实例的变量 - 状态
     *
     * @see ProcessInstance#getProcessVariables()
     */
    public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS";
    /**
     * 流程实例的变量 - 理由
     *
     * 例如说:审批不通过的理由(目前审核通过暂时不会记录)
     *
     * @see ProcessInstance#getProcessVariables()
     */
    public static final String PROCESS_INSTANCE_VARIABLE_REASON = "PROCESS_REASON";
    /**
     * 流程实例的变量 - 发起用户选择的审批人 Map
     *
     * @see ProcessInstance#getProcessVariables()
     */
    public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
    /**
     * 流程实例的变量 - 发起用户 ID
     *
     * @see ProcessInstance#getProcessVariables()
     */
    public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID";
    /**
     * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id}
     *
     * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过
     *
     * @see ProcessInstance#getProcessVariables()
     */
    public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s";
    /**
     * 任务的变量 - 状态
     *
     * @see org.flowable.task.api.Task#getTaskLocalVariables()
     */
    public static final String TASK_VARIABLE_STATUS = "TASK_STATUS";
    /**
     * 任务的变量 - 理由
     *
     * 例如说:审批通过、不通过的理由
     *
     * @see org.flowable.task.api.Task#getTaskLocalVariables()
     */
    public static final String TASK_VARIABLE_REASON = "TASK_REASON";
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java
对比新文件
@@ -0,0 +1,45 @@
package com.iailab.module.bpm.framework.flowable.core.listener;
import cn.hutool.core.collection.CollUtil;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import com.iailab.module.bpm.service.task.BpmProcessInstanceCopyService;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
/**
 * 处理抄送用户的 {@link JavaDelegate} 的实现类
 * <p>
 * 目前只有仿钉钉/飞书模式的【抄送节点】使用
 *
 * @author hou
 */
@Component(BpmCopyTaskDelegate.BEAN_NAME)
public class BpmCopyTaskDelegate implements JavaDelegate {
    public static final String BEAN_NAME = "bpmCopyTaskDelegate";
    @Resource
    private BpmTaskCandidateInvoker taskCandidateInvoker;
    @Resource
    private BpmProcessInstanceCopyService processInstanceCopyService;
    @Override
    public void execute(DelegateExecution execution) {
        // 1. 获得抄送人
        Set<Long> userIds = taskCandidateInvoker.calculateUsersByTask(execution);
        if (CollUtil.isEmpty(userIds)) {
            return;
        }
        // 2. 执行抄送
        FlowElement currentFlowElement = execution.getCurrentFlowElement();
        processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(),
                currentFlowElement.getId(), currentFlowElement.getName(), null);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
@@ -21,27 +21,20 @@
@Component
public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
    @Resource
    @Lazy
    private BpmProcessInstanceService processInstanceService;
    public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
                     .add(FlowableEngineEventType.PROCESS_CANCELLED)
                     .add(FlowableEngineEventType.PROCESS_COMPLETED)
                     .build();
            .add(FlowableEngineEventType.PROCESS_COMPLETED)
            .build();
    @Resource
    @Lazy // 延迟加载,避免循环依赖
    private BpmProcessInstanceService processInstanceService;
    public BpmProcessInstanceEventListener(){
        super(PROCESS_INSTANCE_EVENTS);
    }
    @Override
    protected void processCancelled(FlowableCancelledEvent event) {
        processInstanceService.updateProcessInstanceWhenCancel(event);
    }
    @Override
    protected void processCompleted(FlowableEngineEntityEvent event) {
        processInstanceService.updateProcessInstanceWhenApprove((ProcessInstance)event.getEntity());
        processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity());
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
@@ -1,16 +1,25 @@
package com.iailab.module.bpm.framework.flowable.core.listener;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.module.bpm.service.task.BpmActivityService;
import com.iailab.framework.common.util.number.NumberUtils;
import com.iailab.module.bpm.enums.definition.BpmBoundaryEventType;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.service.definition.BpmModelService;
import com.iailab.module.bpm.service.task.BpmTaskService;
import com.google.common.collect.ImmutableSet;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BoundaryEvent;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.job.api.Job;
import org.flowable.task.api.Task;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@@ -29,36 +38,37 @@
public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
    @Resource
    @Lazy // 解决循环依赖
    private BpmTaskService taskService;
    @Lazy // 延迟加载,避免循环依赖
    private BpmModelService modelService;
    @Resource
    @Lazy // 解决循环依赖
    private BpmActivityService activityService;
    private BpmTaskService taskService;
    public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
            .add(FlowableEngineEventType.TASK_CREATED)
            .add(FlowableEngineEventType.TASK_ASSIGNED)
//            .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。
            .add(FlowableEngineEventType.ACTIVITY_CANCELLED)
            .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时
            .build();
    public BpmTaskEventListener(){
    public BpmTaskEventListener() {
        super(TASK_EVENTS);
    }
    @Override
    protected void taskCreated(FlowableEngineEntityEvent event) {
        taskService.updateTaskStatusWhenCreated((Task) event.getEntity());
        taskService.processTaskCreated((Task) event.getEntity());
    }
    @Override
    protected void taskAssigned(FlowableEngineEntityEvent event) {
        taskService.updateTaskExtAssign((Task)event.getEntity());
        taskService.processTaskAssigned((Task) event.getEntity());
    }
    @Override
    protected void activityCancelled(FlowableActivityCancelledEvent event) {
        List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId());
        List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId());
        if (CollUtil.isEmpty(activityList)) {
            log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId());
            return;
@@ -68,8 +78,34 @@
            if (StrUtil.isEmpty(activity.getTaskId())) {
                return;
            }
            taskService.updateTaskStatusWhenCanceled(activity.getTaskId());
            taskService.processTaskCanceled(activity.getTaskId());
        });
    }
    @Override
    @SuppressWarnings("PatternVariableCanBeUsed")
    protected void timerFired(FlowableEngineEntityEvent event) {
        // 1.1 只处理 BoundaryEvent 边界计时时间
        String processDefinitionId = event.getProcessDefinitionId();
        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
        Job entity = (Job) event.getEntity();
        FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
        if (!(element instanceof BoundaryEvent)) {
            return;
        }
        // 1.2 判断是否为超时处理
        BoundaryEvent boundaryEvent = (BoundaryEvent) element;
        String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent,
                BpmnModelConstants.BOUNDARY_EVENT_TYPE);
        BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType));
        if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) {
            return;
        }
        // 2. 处理超时
        String timeoutHandlerType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent,
                BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_TYPE);
        String taskKey = boundaryEvent.getAttachedToRefId();
        taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutHandlerType));
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -1,33 +1,353 @@
package com.iailab.module.bpm.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.iailab.framework.common.util.collection.CollectionUtils;
import com.iailab.framework.common.util.number.NumberUtils;
import com.iailab.framework.common.util.string.StrUtils;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
/**
 * 流程模型转操作工具类
 * BPMN Model 操作工具类。目前分成三部分:
 *
 * 1. BPMN 修改 + 解析元素相关的方法
 * 2. BPMN 简单查找相关的方法
 * 3. BPMN 复杂遍历相关的方法
 * 4. BPMN 流程预测相关的方法
 *
 * @author iailab
 */
@Slf4j
public class BpmnModelUtils {
    public static Integer parseCandidateStrategy(FlowElement userTask) {
        return NumberUtils.parseInt(userTask.getAttributeValue(
                BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
    // ========== BPMN 修改 + 解析元素相关的方法 ==========
    public static void addExtensionElement(FlowElement element, String name, String value) {
        if (value == null) {
            return;
        }
        ExtensionElement extensionElement = new ExtensionElement();
        extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
        extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
        extensionElement.setElementText(value);
        extensionElement.setName(name);
        element.addExtensionElement(extensionElement);
    }
    public static String parseCandidateParam(FlowElement userTask) {
        return userTask.getAttributeValue(
                BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
    public static void addExtensionElement(FlowElement element, String name, Integer value) {
        if (value == null) {
            return;
        }
        addExtensionElement(element, name, String.valueOf(value));
    }
    public static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) {
        if (attributes == null) {
            return;
        }
        ExtensionElement extensionElement = new ExtensionElement();
        extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
        extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
        extensionElement.setName(name);
        attributes.forEach((key, value) -> {
            ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
            extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
            extensionElement.addAttribute(extensionAttribute);
        });
        element.addExtensionElement(extensionElement);
    }
    /**
     * 解析扩展元素
     *
     * @param flowElement 节点
     * @param elementName 元素名称
     * @return 扩展元素
     */
    public static String parseExtensionElement(FlowElement flowElement, String elementName) {
        if (flowElement == null) {
            return null;
        }
        ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
        return element != null ? element.getElementText() : null;
    }
    /**
     * 给节点添加候选人元素
     *
     * @param candidateStrategy 候选人策略
     * @param candidateParam 候选人参数,允许空
     * @param flowElement 节点
     */
    public static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
        addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
                candidateStrategy == null ? null : candidateStrategy.toString());
        addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
    }
    /**
     * 解析候选人策略
     *
     * @param userTask 任务节点
     * @return 候选人策略
     */
    public static Integer parseCandidateStrategy(FlowElement userTask) {
        Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue(
                BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
        // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限
        if (candidateStrategy == null) {
            ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
            candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null;
        }
        return candidateStrategy;
    }
    /**
     * 解析候选人参数
     *
     * @param userTask 任务节点
     * @return 候选人参数
     */
    public static String parseCandidateParam(FlowElement userTask) {
        String candidateParam = userTask.getAttributeValue(
                BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
        if (candidateParam == null) {
            ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
            candidateParam = element != null ? element.getElementText() : null;
        }
        return candidateParam;
    }
    /**
     * 解析审批类型
     *
     * @see BpmUserTaskApproveTypeEnum
     * @param userTask 任务节点
     * @return 审批类型
     */
    public static Integer parseApproveType(FlowElement userTask) {
        return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE));
    }
    /**
     * 添加任务拒绝处理元素
     *
     * @param rejectHandler 任务拒绝处理
     * @param userTask 任务节点
     */
    public static void addTaskRejectElements(BpmSimpleModelNodeVO.RejectHandler rejectHandler, UserTask userTask) {
        if (rejectHandler == null) {
            return;
        }
        addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
        addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
    }
    /**
     * 解析任务拒绝处理类型
     *
     * @param userTask 任务节点
     * @return 任务拒绝处理类型
     */
    public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
        Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
        return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
    }
    /**
     * 解析任务拒绝返回任务节点 ID
     *
     * @param flowElement 任务节点
     * @return 任务拒绝返回任务节点 ID
     */
    public static String parseReturnTaskId(FlowElement flowElement) {
        return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
    }
    /**
     * 给节点添加用户任务的审批人与发起人相同时,处理类型枚举
     *
     * @see BpmUserTaskAssignStartUserHandlerTypeEnum
     * @param assignStartUserHandlerType 发起人处理类型
     * @param userTask 任务节点
     */
    public static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) {
        if (assignStartUserHandlerType == null) {
            return;
        }
        addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
    }
    /**
     * 给节点添加用户任务的审批人为空时,处理类型枚举
     *
     * @see BpmUserTaskAssignEmptyHandlerTypeEnum
     * @param emptyHandler 空处理
     * @param userTask 任务节点
     */
    public static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) {
        if (emptyHandler == null) {
            return;
        }
        addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType()));
        addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds()));
    }
    /**
     * 解析用户任务的审批人与发起人相同时,处理类型枚举
     *
     * @param userTask 任务节点
     * @return 处理类型枚举
     */
    public static Integer parseAssignStartUserHandlerType(FlowElement userTask) {
        return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE));
    }
    /**
     * 解析用户任务的审批人为空时,处理类型枚举
     *
     * @param userTask 任务节点
     * @return 处理类型枚举
     */
    public static Integer parseAssignEmptyHandlerType(FlowElement userTask) {
        return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE));
    }
    /**
     * 解析用户任务的审批人为空时,处理用户 ID 数组
     *
     * @param userTask 任务节点
     * @return 处理用户 ID 数组
     */
    public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) {
        return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ",");
    }
    /**
     * 给节点添加表单字段权限元素
     *
     * @param fieldsPermissions 表单字段权限
     * @param flowElement 节点
     */
    public static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) {
        if (CollUtil.isNotEmpty(fieldsPermissions)) {
            fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item));
        }
    }
    /**
     * 解析表单字段权限
     *
     * @param bpmnModel bpmnModel 对象
     * @param flowElementId 元素 ID
     * @return 表单字段权限
     */
    public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) {
        if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) {
            return null;
        }
        FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
        if (flowElement == null) {
            return null;
        }
        List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT);
        if (CollUtil.isEmpty(extensionElements)) {
            return null;
        }
        Map<String, String> fieldsPermission = MapUtil.newHashMap();
        extensionElements.forEach(element -> {
            String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
            String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
            if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) {
                fieldsPermission.put(field, permission);
            }
        });
        return fieldsPermission;
    }
    /**
     * 给节点添加操作按钮设置元素
     */
    public static void addButtonsSetting(List<BpmSimpleModelNodeVO.OperationButtonSetting> buttonsSetting, UserTask userTask) {
        if (CollUtil.isNotEmpty(buttonsSetting)) {
            List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> {
                Map<String, String> settingMap = Maps.newHashMapWithExpectedSize(3);
                settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId()));
                settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName());
                settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable()));
                return settingMap;
            });
            list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item));
        }
    }
    /**
     * 解析操作按钮设置
     *
     * @param bpmnModel bpmnModel 对象
     * @param flowElementId 元素 ID
     * @return 操作按钮设置
     */
    public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) {
        FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
        if (flowElement == null) {
            return null;
        }
        List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(BUTTON_SETTING_ELEMENT);
        if (CollUtil.isEmpty(extensionElements)) {
            return null;
        }
        Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size());
        extensionElements.forEach(element -> {
            String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
            String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
            String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
            if (StrUtil.isNotEmpty(id)) {
                BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting();
                buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable)));
            }
        });
        return buttonSettings;
    }
    /**
     * 解析边界事件扩展元素
     *
     * @param boundaryEvent 边界事件
     * @param customElement 元素
     * @return 扩展元素
     */
    public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
        if (boundaryEvent == null) {
            return null;
        }
        ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
        return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
    }
    // ========== BPM 简单查找相关的方法 ==========
    /**
     * 根据节点,获取入口连线
@@ -74,15 +394,14 @@
     * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
     * @return 元素们
     */
    @SuppressWarnings("unchecked")
    public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
        List<T> result = new ArrayList<>();
        model.getProcesses().forEach(process -> {
            process.getFlowElements().forEach(flowElement -> {
                if (flowElement.getClass().isAssignableFrom(clazz)) {
                    result.add((T) flowElement);
                }
            });
        });
        model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> {
            if (flowElement.getClass().isAssignableFrom(clazz)) {
                result.add((T) flowElement);
            }
        }));
        return result;
    }
@@ -95,6 +414,12 @@
        }
        // 从 flowElementList 找
        return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent);
    }
    public static EndEvent getEndEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        // 从 flowElementList 找 endEvent
        return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent);
    }
    public static BpmnModel getBpmnModel(byte[] bpmnBytes) {
@@ -111,10 +436,17 @@
            return null;
        }
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return new String(converter.convertToXML(model));
        return StrUtil.utf8Str(converter.convertToXML(model));
    }
    // ========== 遍历相关的方法 ==========
    public static String getBpmnXml(byte[] bpmnBytes) {
        if (ArrayUtil.isEmpty(bpmnBytes)) {
            return null;
        }
        return StrUtil.utf8Str(bpmnBytes);
    }
    // ========== BPMN 复杂遍历相关的方法 ==========
    /**
     * 找到 source 节点之前的所有用户任务节点
@@ -209,16 +541,16 @@
        return userTaskList;
    }
    /**
     * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
     * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
     * 不存在直接退回到子流程中的情况,但存在从子流程出去到父流程情况
     *
     * @param source          起始节点
     * @param target          目标节点
     * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复
     * @return 结果
     */
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
        visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
        // 不能是开始事件和子流程
@@ -329,4 +661,136 @@
        return userTaskList;
    }
    // ========== BPMN 流程预测相关的方法 ==========
    /**
     * 流程预测,返回 StartEvent、UserTask、ServiceTask、EndEvent 节点元素,最终是 List 串行结果
     *
     * @param bpmnModel BPMN 图
     * @param variables 变量
     * @return 节点元素数组
     */
    public static List<FlowElement> simulateProcess(BpmnModel bpmnModel, Map<String, Object> variables) {
        List<FlowElement> resultElements = new ArrayList<>();
        Set<FlowElement> visitElements = new HashSet<>();
        // 从 StartEvent 开始遍历
        StartEvent startEvent = getStartEvent(bpmnModel);
        simulateNextFlowElements(startEvent, variables, resultElements, visitElements);
        // 将 EndEvent 放在末尾。原因是,DFS 遍历,可能 EndEvent 在 resultElements 中
        List<FlowElement> endEvents = CollUtil.removeWithAddIf(resultElements,
                flowElement -> flowElement instanceof EndEvent);
        resultElements.addAll(endEvents);
        return resultElements;
    }
    @SuppressWarnings("PatternVariableCanBeUsed")
    private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables,
                                                 List<FlowElement> resultElements, Set<FlowElement> visitElements) {
        // 如果为空,或者已经遍历过,则直接结束
        if (currentElement == null) {
            return;
        }
        if (visitElements.contains(currentElement)) {
            return;
        }
        visitElements.add(currentElement);
        // 情况:StartEvent/EndEvent/UserTask/ServiceTask
        if (currentElement instanceof StartEvent
            || currentElement instanceof EndEvent
            || currentElement instanceof UserTask
            || currentElement instanceof ServiceTask) {
            // 添加元素
            FlowNode flowNode = (FlowNode) currentElement;
            resultElements.add(flowNode);
            // 遍历子节点
            flowNode.getOutgoingFlows().forEach(
                    nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
            return;
        }
        // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的
        if (currentElement instanceof ExclusiveGateway) {
            // 查找满足条件的 SequenceFlow 路径
            Gateway gateway = (Gateway) currentElement;
            SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                    flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
                            && evalConditionExpress(variables, flow.getConditionExpression()));
            if (matchSequenceFlow == null) {
                matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
                        flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
                // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
                if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
                    matchSequenceFlow = gateway.getOutgoingFlows().get(0);
                }
            }
            // 遍历满足条件的 SequenceFlow 路径
            if (matchSequenceFlow != null) {
                simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
            }
            return;
        }
        // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
        if (currentElement instanceof InclusiveGateway) {
            // 查找满足条件的 SequenceFlow 路径
            Gateway gateway = (Gateway) currentElement;
            Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
                    flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
                            && evalConditionExpress(variables, flow.getConditionExpression()));
            if (CollUtil.isEmpty(matchSequenceFlows)) {
                matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
                        flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
                // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
                if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
                    matchSequenceFlows = gateway.getOutgoingFlows();
                }
            }
            // 遍历满足条件的 SequenceFlow 路径
            matchSequenceFlows.forEach(
                    flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
        }
        // 情况:ParallelGateway 并行,都满足,都走
        if (currentElement instanceof ParallelGateway) {
            Gateway gateway = (Gateway) currentElement;
            // 遍历子节点
            gateway.getOutgoingFlows().forEach(
                    nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
            return;
        }
    }
    /**
     * 计算条件表达式是否为 true 满足条件
     *
     * @param variables 流程实例
     * @param express 条件表达式
     * @return 是否满足条件
     */
    public static boolean evalConditionExpress(Map<String, Object> variables, String express) {
        if (express == null) {
            return Boolean.FALSE;
        }
        try {
            Object result = FlowableUtils.getExpressionValue(variables, express);
            return Boolean.TRUE.equals(result);
        } catch (FlowableException ex) {
            log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错", express, variables, ex);
            return Boolean.FALSE;
        }
    }
    @SuppressWarnings("PatternVariableCanBeUsed")
    public static boolean isSequentialUserTask(FlowElement flowElement) {
        if (!(flowElement instanceof UserTask)) {
            return false;
        }
        UserTask userTask = (UserTask) flowElement;
        MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics();
        return loopCharacteristics != null && loopCharacteristics.isSequential();
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java
@@ -1,11 +1,16 @@
package com.iailab.module.bpm.framework.flowable.core.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.iailab.framework.tenant.core.context.TenantContextHolder;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants;
import com.iailab.framework.tenant.core.util.TenantUtils;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.variable.MapDelegateVariableContainer;
import org.flowable.engine.ManagementService;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
@@ -16,11 +21,12 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
 * Flowable 相关的工具方法
 *
 * @author iailab
 * @author 芋道源码
 */
public class FlowableUtils {
@@ -37,6 +43,16 @@
    public static String getTenantId() {
        Long tenantId = TenantContextHolder.getTenantId();
        return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID;
    }
    public static void execute(String tenantIdStr, Runnable runnable) {
        if (ObjectUtil.isEmpty(tenantIdStr)
                || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) {
            runnable.run();
        } else {
            Long tenantId = Long.valueOf(tenantIdStr);
            TenantUtils.execute(tenantId, runnable);
        }
    }
    // ========== Execution 相关的工具方法 ==========
@@ -78,7 +94,28 @@
     * @return 状态
     */
    private static Integer getProcessInstanceStatus(Map<String, Object> processVariables) {
        return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
        return (Integer) processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
    }
    /**
     * 获得流程实例的审批原因
     *
     * @param processInstance 流程实例
     * @return 审批原因
     */
    public static String getProcessInstanceReason(HistoricProcessInstance processInstance) {
        return (String) processInstance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON);
    }
    /**
     * 获得流程实例的表单
     *
     * @param processInstance 流程实例
     * @return 表单
     */
    public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) {
        Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables());
        return filterProcessInstanceFormVariable(processVariables);
    }
    /**
@@ -88,9 +125,8 @@
     * @return 表单
     */
    public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) {
        Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables());
        filterProcessInstanceFormVariable(formVariables);
        return formVariables;
        Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables());
        return filterProcessInstanceFormVariable(processVariables);
    }
    /**
@@ -102,7 +138,7 @@
     * @return 过滤后的表单
     */
    public static Map<String, Object> filterProcessInstanceFormVariable(Map<String, Object> processVariables) {
        processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
        processVariables.remove(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
        return processVariables;
    }
@@ -112,10 +148,23 @@
     * @param processInstance 流程实例
     * @return 发起用户选择的审批人 Map
     */
    @SuppressWarnings("unchecked")
    public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) {
        return (Map<String, List<Long>>) processInstance.getProcessVariables().get(
                BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
        return processInstance != null ? getStartUserSelectAssignees(processInstance.getProcessVariables()) : null;
    }
    /**
     * 获得流程实例的发起用户选择的审批人 Map
     *
     * @param processVariables 流程变量
     * @return 发起用户选择的审批人 Map
     */
    @SuppressWarnings("unchecked")
    public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) {
        if (processVariables == null) {
            return null;
        }
        return (Map<String, List<Long>>) processVariables.get(
                BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
    }
    // ========== Task 相关的工具方法 ==========
@@ -127,7 +176,7 @@
     * @return 状态
     */
    public static Integer getTaskStatus(TaskInfo task) {
        return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
        return (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS);
    }
    /**
@@ -137,7 +186,7 @@
     * @return 审批原因
     */
    public static String getTaskReason(TaskInfo task) {
        return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON);
        return (String) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_REASON);
    }
    /**
@@ -161,20 +210,37 @@
     * @return 过滤后的表单
     */
    public static Map<String, Object> filterTaskFormVariable(Map<String, Object> taskLocalVariables) {
        taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS);
        taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON);
        taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_STATUS);
        taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_REASON);
        return taskLocalVariables;
    }
    // ========== Expression 相关的工具方法 ==========
    public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
        assert processEngineConfiguration != null;
    private static Object getExpressionValue(VariableContainer variableContainer, String expressionString,
                                             ProcessEngineConfigurationImpl processEngineConfiguration) {
        assert processEngineConfiguration!= null;
        ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
        assert expressionManager != null;
        assert expressionManager!= null;
        Expression expression = expressionManager.createExpression(expressionString);
        return expression.getValue(variableContainer);
    }
    public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
        if (processEngineConfiguration != null) {
            return getExpressionValue(variableContainer, expressionString, processEngineConfiguration);
        }
        // 如果 ProcessEngineConfigurationImpl 获取不到,则需要通过 ManagementService 来获取
        ManagementService managementService = SpringUtil.getBean(ManagementService.class);
        assert managementService != null;
        return managementService.executeCommand(context ->
                getExpressionValue(variableContainer, expressionString, CommandContextUtil.getProcessEngineConfiguration()));
    }
    public static Object getExpressionValue(Map<String, Object> variable, String expressionString) {
        VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty());
        return getExpressionValue(variableContainer, expressionString);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
对比新文件
@@ -0,0 +1,686 @@
package com.iailab.module.bpm.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import com.iailab.framework.common.util.collection.CollectionUtils;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.enums.definition.*;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import com.iailab.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import java.util.*;
import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
import static com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
import static java.util.Arrays.asList;
/**
 * 仿钉钉/飞书的模型相关的工具方法
 * <p>
 * 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法
 * 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类
 *
 * @author jason
 */
public class SimpleModelUtils {
    private static final Map<BpmSimpleModelNodeType, NodeConvert> NODE_CONVERTS = MapUtil.newHashMap();
    static {
        List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
                new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(),
                new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert());
        converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
    }
    /**
     * 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model
     * <p>
     * 整体逻辑如下:
     * 1. 创建:BpmnModel、Process 对象
     * 2. 转换:将 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素
     * 3. 连接:构建并添加节点之间的连线 Sequence Flow
     *
     * @param processId       流程标识
     * @param processName     流程名称
     * @param simpleModelNode 仿钉钉流程设计模型数据结构
     * @return Bpmn Model
     */
    public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
        // 1. 创建 BpmnModel
        BpmnModel bpmnModel = new BpmnModel();
        bpmnModel.setTargetNamespace(BpmnXMLConstants.BPMN2_NAMESPACE); // 设置命名空间。不加这个,解析 Message 会报 NPE 异常
        // 创建 Process 对象
        Process process = new Process();
        process.setId(processId);
        process.setName(processName);
        process.setExecutable(Boolean.TRUE);
        bpmnModel.addProcess(process);
        // 2.1 创建 StartNode 节点
        // 原因是:目前前端的第一个节点是“发起人节点”,所以这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点
        BpmSimpleModelNodeVO startNode = buildStartNode();
        startNode.setChildNode(simpleModelNode);
        // 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中
        traverseNodeToBuildFlowNode(startNode, process);
        // 3. 构建并添加节点之间的连线 Sequence Flow
        EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
        traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
        // 4. 自动布局
        new BpmnAutoLayout(bpmnModel).execute();
        return bpmnModel;
    }
    private static BpmSimpleModelNodeVO buildStartNode() {
        return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID)
                .setName(BpmSimpleModelNodeType.START_USER_NODE.getName())
                .setType(BpmSimpleModelNodeType.START_NODE.getType());
    }
    /**
     * 遍历节点,构建 FlowNode 元素
     *
     * @param node SIMPLE 节点
     * @param process BPMN 流程
     */
    private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) {
        // 1. 判断是否有效节点
        if (!isValidNode(node)) {
            return;
        }
        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
        Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType());
        // 2. 处理当前节点
        NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType);
        Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType());
        List<? extends FlowElement> flowElements = nodeConvert.convertList(node);
        flowElements.forEach(process::addFlowElement);
        // 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点
        if (BpmSimpleModelNodeType.isBranchNode(node.getType())
                && CollUtil.isNotEmpty(node.getConditionNodes())) {
            // 注意:这里的 item.getChildNode() 处理的是每个条件的子节点,不是处理条件
            node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process));
        }
        // 3.2 情况二:如果有“子”节点,则递归处理子节点
        traverseNodeToBuildFlowNode(node.getChildNode(), process);
    }
    /**
     * 遍历节点,构建 SequenceFlow 元素
     *
     * @param process Bpmn 流程
     * @param node 当前节点
     * @param targetNodeId 目标节点 ID
     */
    private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
        // 1.1 无效节点返回
        if (!isValidNode(node)) {
            return;
        }
        // 1.2 END_NODE 直接返回
        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
        Assert.notNull(nodeType, "模型节点类型不支持");
        if (nodeType == BpmSimpleModelNodeType.END_NODE) {
            return;
        }
        // 2.1 情况一:普通节点
        if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) {
            traverseNormalNodeToBuildSequenceFlow(process, node, targetNodeId);
        } else {
            // 2.2 情况二:分支节点
            traverseBranchNodeToBuildSequenceFlow(process, node, targetNodeId);
        }
    }
    /**
     * 遍历普通(非条件)节点,构建 SequenceFlow 元素
     *
     * @param process Bpmn 流程
     * @param node 当前节点
     * @param targetNodeId 目标节点 ID
     */
    private static void traverseNormalNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
        BpmSimpleModelNodeVO childNode = node.getChildNode();
        boolean isChildNodeValid = isValidNode(childNode);
        // 情况一:有“子”节点,则建立连线
        // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点
        String finalTargetNodeId = isChildNodeValid? childNode.getId() : targetNodeId;
        SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId);
        process.addFlowElement(sequenceFlow);
        // 因为有子节点,递归调用后续子节点
        if (isChildNodeValid) {
            traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
        }
    }
    /**
     * 遍历条件节点,构建 SequenceFlow 元素
     *
     * @param process Bpmn 流程
     * @param node 当前节点
     * @param targetNodeId 目标节点 ID
     */
    private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
        BpmSimpleModelNodeVO childNode = node.getChildNode();
        List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
        Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空");
        // 分支终点节点 ID
        String branchEndNodeId = null;
        if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { // 条件分支
            // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID
            branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
        } else if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE
                || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) {  // 并行分支或包容分支
            // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。
            branchEndNodeId = buildGatewayJoinId(node.getId());
        }
        Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空");
        // 3. 遍历分支节点
        // 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点
        for (BpmSimpleModelNodeVO item : conditionNodes) {
            Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeType.CONDITION_NODE.getType()),
                    "条件节点类型({})不符合", item.getType());
            BpmSimpleModelNodeVO conditionChildNode = item.getChildNode();
            // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况
            if (isValidNode(conditionChildNode)) {
                // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线
                SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item);
                process.addFlowElement(sequenceFlow);
                // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线
                traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId);
            } else {
                // 3.2 分支没有后续节点。例如说,建立 A->D 的连线
                SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item);
                process.addFlowElement(sequenceFlow);
            }
        }
        // 4. 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线
        if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE
                || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE ) {
            String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
            SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId);
            process.addFlowElement(sequenceFlow);
        }
        // 5. 递归调用后续节点 继续递归。例如说,建立 D->E 的连线
        traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
    }
    private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId) {
        return buildBpmnSequenceFlow(sourceId, targetId, null, null, null);
    }
    private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId,
                                                      String sequenceFlowId, String sequenceFlowName,
                                                      String conditionExpression) {
        Assert.notEmpty(sourceId, "sourceId 不能为空");
        Assert.notEmpty(targetId, "targetId 不能为空");
        // TODO @jason:如果 sequenceFlowId 不存在的时候,是不是要生成一个默认的 sequenceFlowId? @芋艿: 貌似不需要,Flowable 会默认生成;TODO @jason:建议还是搞一个,主要是后续好排查问题。
        // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧;TODO @jason:建议还是搞一个,主要是后续好排查问题。
        SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
        if (StrUtil.isNotEmpty(sequenceFlowId)) {
            sequenceFlow.setId(sequenceFlowId);
        }
        if (StrUtil.isNotEmpty(sequenceFlowName)) {
            sequenceFlow.setName(sequenceFlowName);
        }
        if (StrUtil.isNotEmpty(conditionExpression)) {
            sequenceFlow.setConditionExpression(conditionExpression);
        }
        return sequenceFlow;
    }
    public static boolean isValidNode(BpmSimpleModelNodeVO node) {
        return node != null && node.getId() != null;
    }
    public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) {
        return BpmSimpleModelNodeType.APPROVE_NODE.getType().equals(node.getType())
                && BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod());
    }
    // ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ==========
    private interface NodeConvert {
        default List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
            return Collections.singletonList(convert(node));
        }
        default FlowElement convert(BpmSimpleModelNodeVO node) {
            throw new UnsupportedOperationException("请实现该方法");
        }
        BpmSimpleModelNodeType getType();
    }
    private static class StartNodeConvert implements NodeConvert {
        @Override
        public StartEvent convert(BpmSimpleModelNodeVO node) {
            StartEvent startEvent = new StartEvent();
            startEvent.setId(node.getId());
            startEvent.setName(node.getName());
            return startEvent;
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.START_NODE;
        }
    }
    private static class EndNodeConvert implements NodeConvert {
        @Override
        public EndEvent convert(BpmSimpleModelNodeVO node) {
            EndEvent endEvent = new EndEvent();
            endEvent.setId(node.getId());
            endEvent.setName(node.getName());
            // TODO @芋艿 + jason:要不要加一个终止定义?
            return endEvent;
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.END_NODE;
        }
    }
    private static class StartUserNodeConvert implements NodeConvert {
        @Override
        public UserTask convert(BpmSimpleModelNodeVO node) {
            UserTask userTask = new UserTask();
            userTask.setId(node.getId());
            userTask.setName(node.getName());
            // 人工审批
            addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
            // 候选人策略为发起人自己
            addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
            // 添加表单字段权限属性元素
            addFormFieldsPermission(node.getFieldsPermission(), userTask);
            // 添加操作按钮配置属性元素
            addButtonsSetting(node.getButtonsSetting(), userTask);
            // 使用自动通过策略
            // TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过;
            addAssignStartUserHandlerType(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType(), userTask);
            return userTask;
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.START_USER_NODE;
        }
    }
    private static class ApproveNodeConvert implements NodeConvert {
        @Override
        public List<FlowElement> convertList(BpmSimpleModelNodeVO node) {
            List<FlowElement> flowElements = new ArrayList<>(2);
            // 1. 构建用户任务
            UserTask userTask = buildBpmnUserTask(node);
            flowElements.add(userTask);
            // 2. 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
            if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
                BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler());
                flowElements.add(boundaryEvent);
            }
            return flowElements;
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.APPROVE_NODE;
        }
        /**
         * 添加 UserTask 用户的审批超时 BoundaryEvent 事件
         *
         * @param userTask       审批任务
         * @param timeoutHandler 超时处理器
         * @return BoundaryEvent 超时事件
         */
        private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask,
                                                                BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) {
            // 1.1 定时器边界事件
            BoundaryEvent boundaryEvent = new BoundaryEvent();
            boundaryEvent.setId("Event-" + IdUtil.fastUUID());
            boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
            boundaryEvent.setAttachedToRef(userTask);
            // 1.2 定义超时时间、最大提醒次数
            TimerEventDefinition eventDefinition = new TimerEventDefinition();
            eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
            if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) &&
                    timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
                eventDefinition.setTimeCycle(String.format("R%d/%s",
                        timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
            }
            boundaryEvent.addEventDefinition(eventDefinition);
            // 2.1 添加定时器边界事件类型
            addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType());
            // 2.2 添加超时执行动作元素
            addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType());
            return boundaryEvent;
        }
        private UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
            UserTask userTask = new UserTask();
            userTask.setId(node.getId());
            userTask.setName(node.getName());
            // 如果不是审批人节点,则直接返回
            addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, node.getApproveType());
            if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) {
                return userTask;
            }
            // 添加候选人元素
            addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
            // 添加表单字段权限属性元素
            addFormFieldsPermission(node.getFieldsPermission(), userTask);
            // 添加操作按钮配置属性元素
            addButtonsSetting(node.getButtonsSetting(), userTask);
            // 处理多实例(审批方式)
            processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask);
            // 添加任务被拒绝的处理元素
            addTaskRejectElements(node.getRejectHandler(), userTask);
            // 添加用户任务的审批人与发起人相同时的处理元素
            addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
            // 添加用户任务的空处理元素
            addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask);
            //  设置审批任务的截止时间
            if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
                userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
            }
            return userTask;
        }
        private void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
            BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
            Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum);
            // 添加审批方式的扩展属性
            addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod);
            if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
                // 随机审批,不需要设置多实例属性
                return;
            }
            // 处理多实例审批方式
            MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
            // 设置 collectionVariable。本系统用不到,仅仅为了 Flowable 校验不报错
            multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
            if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) {
                multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition());
                multiInstanceCharacteristics.setSequential(false);
            } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) {
                multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition());
                multiInstanceCharacteristics.setSequential(true);
                multiInstanceCharacteristics.setLoopCardinality("1");
            } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) {
                Assert.notNull(approveRatio, "通过比例不能为空");
                multiInstanceCharacteristics.setCompletionCondition(
                        String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D)));
                multiInstanceCharacteristics.setSequential(false);
            }
            userTask.setLoopCharacteristics(multiInstanceCharacteristics);
        }
    }
    private static class CopyNodeConvert implements NodeConvert {
        @Override
        public ServiceTask convert(BpmSimpleModelNodeVO node) {
            ServiceTask serviceTask = new ServiceTask();
            serviceTask.setId(node.getId());
            serviceTask.setName(node.getName());
            serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
            serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}");
            // 添加抄送候选人元素
            addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask);
            // 添加表单字段权限属性元素
            addFormFieldsPermission(node.getFieldsPermission(), serviceTask);
            return serviceTask;
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.COPY_NODE;
        }
    }
    private static class ConditionBranchNodeConvert implements NodeConvert {
        @Override
        public ExclusiveGateway convert(BpmSimpleModelNodeVO node) {
            ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
            exclusiveGateway.setId(node.getId());
            // TODO @jason:setName
            // 设置默认的序列流(条件)
            BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
                    item -> BooleanUtil.isTrue(item.getDefaultFlow()));
            Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId());
            exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
            return exclusiveGateway;
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.CONDITION_BRANCH_NODE;
        }
    }
    private static class ParallelBranchNodeConvert implements NodeConvert {
        @Override
        public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) {
            ParallelGateway parallelGateway = new ParallelGateway();
            parallelGateway.setId(node.getId());
            // TODO @jason:setName
            // 并行聚合网关由程序创建,前端不需要传入
            ParallelGateway joinParallelGateway = new ParallelGateway();
            joinParallelGateway.setId(buildGatewayJoinId(node.getId()));
            // TODO @jason:setName
            return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE;
        }
    }
    private static class InclusiveBranchNodeConvert implements NodeConvert {
        @Override
        public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) {
            InclusiveGateway inclusiveGateway = new InclusiveGateway();
            inclusiveGateway.setId(node.getId());
            // 设置默认的序列流(条件)
            BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
                    item -> BooleanUtil.isTrue(item.getDefaultFlow()));
            Assert.notNull(defaultSeqFlow, "包容分支节点({})的默认序列流不能为空", node.getId());
            inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
            // TODO @jason:setName
            // 并行聚合网关由程序创建,前端不需要传入
            InclusiveGateway joinInclusiveGateway = new InclusiveGateway();
            joinInclusiveGateway.setId(buildGatewayJoinId(node.getId()));
            // TODO @jason:setName
            return CollUtil.newArrayList(inclusiveGateway, joinInclusiveGateway);
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE;
        }
    }
    public static class ConditionNodeConvert implements NodeConvert {
        @Override
        public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
            // 原因是:正常情况下,它不会被调用到
            throw new UnsupportedOperationException("条件节点不支持转换");
        }
        @Override
        public BpmSimpleModelNodeType getType() {
            return BpmSimpleModelNodeType.CONDITION_NODE;
        }
        public static SequenceFlow buildSequenceFlow(String sourceId, String targetId,
                                                     BpmSimpleModelNodeVO node) {
            String conditionExpression = buildConditionExpression(node);
            return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression);
        }
        /**
         * 构造条件表达式
         *
         * @param node 条件节点
         */
        public static String buildConditionExpression(BpmSimpleModelNodeVO node) {
            BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(node.getConditionType());
            if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) {
                return node.getConditionExpression();
            }
            if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) {
                BpmSimpleModelNodeVO.ConditionGroups conditionGroups = node.getConditionGroups();
                if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) {
                    return null;
                }
                List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> {
                    if (CollUtil.isEmpty(item.getRules())) {
                        return "";
                    }
                    // 构造规则表达式
                    List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> {
                        String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide()
                                : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号
                        return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide);
                    });
                    // 构造条件组的表达式
                    Boolean and = item.getAnd();
                    return "(" + CollUtil.join(list, and ? " && " : " || ") + ")";
                });
                return String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || "));
            }
            return null;
        }
    }
    private static String buildGatewayJoinId(String id) {
        return id + "_join";
    }
    // ========== SIMPLE 流程预测相关的方法 ==========
    public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) {
        List<BpmSimpleModelNodeVO> resultNodes = new ArrayList<>();
        // 从头开始遍历
        simulateNextNode(rootNode, variables, resultNodes);
        return resultNodes;
    }
    private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables,
                                  List<BpmSimpleModelNodeVO> resultNodes) {
        // 如果不合法(包括为空),则直接结束
        if (!isValidNode(currentNode)) {
            return;
        }
        BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentNode.getType());
        Assert.notNull(nodeType, "模型节点类型不支持");
        // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE
        if (nodeType == BpmSimpleModelNodeType.START_NODE
            || nodeType == BpmSimpleModelNodeType.START_USER_NODE
            || nodeType == BpmSimpleModelNodeType.APPROVE_NODE
            || nodeType == BpmSimpleModelNodeType.COPY_NODE
            || nodeType == BpmSimpleModelNodeType.END_NODE) {
            // 添加元素
            resultNodes.add(currentNode);
        }
        // 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的
        if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) {
            // 查找满足条件的 BpmSimpleModelNodeVO 节点
            BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
                    conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
                        && evalConditionExpress(variables, conditionNode));
            if (matchConditionNode == null) {
                matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
                        conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
            }
            Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode);
            // 遍历满足条件的 BpmSimpleModelNodeVO 节点
            simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes);
        }
        // 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的
        if (nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) {
            // 查找满足条件的 BpmSimpleModelNodeVO 节点
            Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
                    conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
                            && evalConditionExpress(variables, conditionNode));
            if (CollUtil.isEmpty(matchConditionNodes)) {
                matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
                        conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
            }
            Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode);
            // 遍历满足条件的 BpmSimpleModelNodeVO 节点
            matchConditionNodes.forEach(matchConditionNode ->
                    simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes));
        }
        // 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走
        if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE) {
            // 遍历所有 BpmSimpleModelNodeVO 节点
            currentNode.getConditionNodes().forEach(matchConditionNode ->
                    simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes));
        }
        // 遍历子节点
        simulateNextNode(currentNode.getChildNode(), variables, resultNodes);
    }
    public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO conditionNode) {
        return BpmnModelUtils.evalConditionExpress(variables, ConditionNodeConvert.buildConditionExpression(conditionNode));
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryService.java
@@ -82,4 +82,11 @@
     */
    List<BpmCategoryDO> getCategoryListByStatus(Integer status);
    /**
     * 批量更新流程分类的排序:每个分类的 sort 值,从 0 开始递增
     *
     * @param ids 分类编号列表
     */
    void updateCategorySortBatch(List<Long> ids);
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryServiceImpl.java
@@ -9,12 +9,15 @@
import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.iailab.module.bpm.dal.mysql.category.BpmCategoryMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.module.bpm.enums.ErrorCodeConstants.*;
@@ -56,7 +59,7 @@
    private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) {
        BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName());
        if (category == null
            || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
                || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
            return;
        }
        throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName());
@@ -65,7 +68,7 @@
    private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) {
        BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode());
        if (category == null
            || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
                || ObjUtil.equal(category.getId(), updateReqVO.getId())) {
            return;
        }
        throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode());
@@ -108,4 +111,20 @@
        return bpmCategoryMapper.selectListByStatus(status);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCategorySortBatch(List<Long> ids) {
        // 校验分类都存在
        List<BpmCategoryDO> categories = bpmCategoryMapper.selectBatchIds(ids);
        if (categories.size() != ids.size()) {
            throw exception(CATEGORY_NOT_EXISTS);
        }
        // 批量更新排序
        List<BpmCategoryDO> updateList = IntStream.range(0, ids.size())
                .mapToObj(index -> new BpmCategoryDO().setId(ids.get(index)).setSort(index))
                .collect(Collectors.toList());
        bpmCategoryMapper.updateBatch(updateList);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelService.java
@@ -1,13 +1,13 @@
package com.iailab.module.bpm.service.definition;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.repository.Model;
import javax.validation.Valid;
import java.util.List;
/**
 * Flowable流程模型接口
@@ -17,21 +17,20 @@
public interface BpmModelService {
    /**
     * 获得流程模型分页
     * 获得流程模型列表
     *
     * @param pageVO 分页查询
     * @return 流程模型分页
     * @param name 模型名称
     * @return 流程模型列表
     */
    PageResult<Model> getModelPage(BpmModelPageReqVO pageVO);
    List<Model> getModelList(String name);
    /**
     * 创建流程模型
     *
     * @param modelVO 创建信息
     * @param bpmnXml BPMN XML
     * @return 创建的流程模型的编号
     */
    String createModel(@Valid BpmModelCreateReqVO modelVO, String bpmnXml);
    String createModel(@Valid BpmModelSaveReqVO modelVO);
    /**
     * 获得流程模块
@@ -50,33 +49,53 @@
    byte[] getModelBpmnXML(String id);
    /**
     * 修改流程模型的 BPMN XML
     *
     * @param id      编号
     * @param bpmnXml BPMN XML
     */
    void updateModelBpmnXml(String id, String bpmnXml);
    /**
     * 修改流程模型
     *
     * @param userId 用户编号
     * @param updateReqVO 更新信息
     */
    void updateModel(@Valid BpmModelUpdateReqVO updateReqVO);
    void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO);
    /**
     * 批量更新模型排序
     *
     * @param userId 用户编号
     * @param ids 编号列表
     */
    void updateModelSortBatch(Long userId, List<String> ids);
    /**
     * 将流程模型,部署成一个流程定义
     *
     * @param userId 用户编号
     * @param id 编号
     */
    void deployModel(String id);
    void deployModel(Long userId, String id);
    /**
     * 删除模型
     *
     * @param userId  用户编号
     * @param id 编号
     */
    void deleteModel(String id);
    void deleteModel(Long userId, String id);
    /**
     * 修改模型的状态,实际更新的部署的流程定义的状态
     *
     * @param userId 用户编号
     * @param id    编号
     * @param state 状态
     */
    void updateModelState(String id, Integer state);
    void updateModelState(Long userId, String id, Integer state);
    /**
     * 获得流程定义编号对应的 BPMN Model
@@ -86,4 +105,22 @@
     */
    BpmnModel getBpmnModelByDefinitionId(String processDefinitionId);
    // ========== 仿钉钉/飞书的精简模型 =========
    /**
     * 获取仿钉钉流程设计模型结构
     *
     * @param modelId 流程模型编号
     * @return 仿钉钉流程设计模型结构
     */
    BpmSimpleModelNodeVO getSimpleModel(String modelId);
    /**
     * 更新仿钉钉流程设计模型
     *
     * @param userId 用户编号
     * @param reqVO 请求信息
     */
    void updateSimpleModel(Long userId, @Valid BpmSimpleModelUpdateReqVO reqVO);
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java
@@ -1,19 +1,21 @@
package com.iailab.module.bpm.service.definition;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.framework.common.util.object.PageUtils;
import com.iailab.framework.common.util.validation.ValidationUtils;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
import com.iailab.module.bpm.convert.definition.BpmModelConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
@@ -32,18 +34,18 @@
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap;
import static com.iailab.module.bpm.enums.ErrorCodeConstants.*;
/**
 * Flowable流程模型实现
 * 主要进行 Flowable {@link Model} 的维护
 *
 * @author yunlongn
 * @author iailab
 * @author jason
 */
@Service
@Validated
@@ -61,91 +63,118 @@
    private BpmTaskCandidateInvoker taskCandidateInvoker;
    @Override
    public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) {
    public List<Model> getModelList(String name) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        if (StrUtil.isNotBlank(pageVO.getKey())) {
            modelQuery.modelKey(pageVO.getKey());
        if (StrUtil.isNotEmpty(name)) {
            modelQuery.modelNameLike(name);
        }
        if (StrUtil.isNotBlank(pageVO.getName())) {
            modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配
        }
        if (StrUtil.isNotBlank(pageVO.getCategory())) {
            modelQuery.modelCategory(pageVO.getCategory());
        }
        // 执行查询
        long count = modelQuery.count();
        if (count == 0) {
            return PageResult.empty(count);
        }
        // 关闭多租户查询,不添加tenantId条件
        if (StrUtil.isNotBlank(FlowableUtils.getTenantId())) {
            modelQuery.modelTenantId(FlowableUtils.getTenantId());
        }
        List<Model> models = modelQuery
                .orderByCreateTime().desc()
                .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
        return new PageResult<>(models, count);
        return modelQuery.list();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createModel(@Valid BpmModelCreateReqVO createReqVO, String bpmnXml) {
    public String createModel(@Valid BpmModelSaveReqVO createReqVO) {
        if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) {
            throw exception(MODEL_KEY_VALID);
        }
        // 校验流程标识已经存在
        // 1. 校验流程标识已经存在
        Model keyModel = getModelByKey(createReqVO.getKey());
        if (keyModel != null) {
            throw exception(MODEL_KEY_EXISTS, createReqVO.getKey());
        }
        // 创建流程定义
        // 2.1 创建流程定义
        createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序
        Model model = repositoryService.newModel();
        BpmModelConvert.INSTANCE.copyToCreateModel(model, createReqVO);
        BpmModelConvert.INSTANCE.copyToModel(model, createReqVO);
        model.setTenantId(FlowableUtils.getTenantId());
        // 保存流程定义
        // 2.2 保存流程定义
        repositoryService.saveModel(model);
        // 保存 BPMN XML
        saveModelBpmnXml(model, bpmnXml);
        return model.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
    public void updateModel(@Valid BpmModelUpdateReqVO updateReqVO) {
        // 校验流程模型存在
        Model model = getModel(updateReqVO.getId());
    public void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO) {
        // 1. 校验流程模型存在
        Model model = validateModelManager(updateReqVO.getId(), userId);
        // 修改流程定义
        BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO);
        // 更新模型
        repositoryService.saveModel(model);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateModelSortBatch(Long userId, List<String> ids) {
        // 1.1 校验流程模型存在
        List<Model> models = repositoryService.createModelQuery()
                .modelTenantId(FlowableUtils.getTenantId()).list();
        models.removeIf(model ->!ids.contains(model.getId()));
        if (ids.size() != models.size()) {
            throw exception(MODEL_NOT_EXISTS);
        }
        Map<String, Model> modelMap = convertMap(models, Model::getId);
        // 1.2 校验是否为管理员
        ids.forEach(id -> validateModelManager(id, userId));
        // 保存排序
        long sort = System.currentTimeMillis(); // 使用时间戳 - i 作为排序
        for (int i = ids.size() - 1; i > 0; i--) {
            Model model = modelMap.get(ids.get(i));
            // 更新模型
            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model).setSort(sort);
            model.setMetaInfo(JsonUtils.toJsonString(metaInfo));
            repositoryService.saveModel(model);
            // 更新排序
            processDefinitionService.updateProcessDefinitionSortByModelId(model.getId(), sort);
            sort--;
        }
    }
    private Model validateModelExists(String id) {
        Model model = repositoryService.getModel(id);
        if (model == null) {
            throw exception(MODEL_NOT_EXISTS);
        }
        return model;
    }
        // 修改流程定义
        BpmModelConvert.INSTANCE.copyToUpdateModel(model, updateReqVO);
        // 更新模型
        repositoryService.saveModel(model);
        // 更新 BPMN XML
        saveModelBpmnXml(model, updateReqVO.getBpmnXml());
    /**
     * 校验是否有流程模型的管理权限
     *
     * @param id     流程模型编号
     * @param userId 用户编号
     * @return 流程模型
     */
    private Model validateModelManager(String id, Long userId) {
        Model model = validateModelExists(id);
        BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
        if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) {
            throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER, model.getName());
        }
        return model;
    }
    @Override
    @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
    public void deployModel(String id) {
    public void deployModel(Long userId, String id) {
        // 1.1 校验流程模型存在
        Model model = getModel(id);
        if (ObjectUtils.isEmpty(model)) {
            throw exception(MODEL_NOT_EXISTS);
        }
        Model model = validateModelManager(id, userId);
        // 1.2 校验流程图
        byte[] bpmnBytes = getModelBpmnXML(model.getId());
        validateBpmnXml(bpmnBytes);
        // 1.3 校验表单已配
        BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
        BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
        BpmFormDO form = validateFormConfig(metaInfo);
        // 1.4 校验任务分配规则已配置
        taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
        // 1.5 获取仿钉钉流程设计器模型数据
        String simpleJson = getModelSimpleJson(model.getId());
        // 2.1 创建流程定义
        String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form);
        String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form);
        // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
        updateProcessDefinitionSuspended(model.getDeploymentId());
@@ -177,12 +206,10 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteModel(String id) {
    public void deleteModel(Long userId, String id) {
        // 校验流程模型存在
        Model model = getModel(id);
        if (model == null) {
            throw exception(MODEL_NOT_EXISTS);
        }
        Model model = validateModelManager(id, userId);
        // 执行删除
        repositoryService.deleteModel(id);
        // 禁用流程定义
@@ -190,12 +217,9 @@
    }
    @Override
    public void updateModelState(String id, Integer state) {
    public void updateModelState(Long userId, String id, Integer state) {
        // 1.1 校验流程模型存在
        Model model = getModel(id);
        if (model == null) {
            throw exception(MODEL_NOT_EXISTS);
        }
        Model model = validateModelManager(id, userId);
        // 1.2 校验流程定义存在
        ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
        if (definition == null) {
@@ -211,13 +235,34 @@
        return repositoryService.getBpmnModel(processDefinitionId);
    }
    @Override
    public BpmSimpleModelNodeVO getSimpleModel(String modelId) {
        Model model = validateModelExists(modelId);
        // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据
        String json = getModelSimpleJson(model.getId());
        return JsonUtils.parseObject(json, BpmSimpleModelNodeVO.class);
    }
    @Override
    public void updateSimpleModel(Long userId, BpmSimpleModelUpdateReqVO reqVO) {
        // 1. 校验流程模型存在
        Model model = validateModelManager(reqVO.getId(), userId);
        // 2.1 JSON 转换成 bpmnModel
        BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel());
        // 2.2 保存 Bpmn XML
        updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
        // 2.3 保存 JSON 数据
        updateModelSimpleJson(model.getId(), reqVO.getSimpleModel());
    }
    /**
     * 校验流程表单已配置
     *
     * @param metaInfo 流程模型元数据
     * @return 表单配置
     */
    private BpmFormDO validateFormConfig(BpmModelMetaInfoRespDTO  metaInfo) {
    private BpmFormDO validateFormConfig(BpmModelMetaInfoVO metaInfo) {
        if (metaInfo == null || metaInfo.getFormType() == null) {
            throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
        }
@@ -239,16 +284,34 @@
        }
    }
    private void saveModelBpmnXml(Model model, String bpmnXml) {
    @Override
    public void updateModelBpmnXml(String id, String bpmnXml) {
        if (StrUtil.isEmpty(bpmnXml)) {
            return;
        }
        repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
        repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml));
    }
    @SuppressWarnings("JavaExistingMethodCanBeUsed")
    private String getModelSimpleJson(String id) {
        byte[] bytes = repositoryService.getModelEditorSourceExtra(id);
        if (ArrayUtil.isEmpty(bytes)) {
            return null;
        }
        return StrUtil.utf8Str(bytes);
    }
    private void updateModelSimpleJson(String id, BpmSimpleModelNodeVO node) {
        if (node == null) {
            return;
        }
        byte[] bytes = JsonUtils.toJsonByte(node);
        repositoryService.addModelEditorSourceExtra(id, bytes);
    }
    /**
     * 挂起 deploymentId 对应的流程定义
     *
     * <p>
     * 注意:这里一个 deploymentId 只关联一个流程定义
     *
     * @param deploymentId 流程发布Id
@@ -265,7 +328,9 @@
    }
    private Model getModelByKey(String key) {
        return repositoryService.createModelQuery().modelKey(key).singleResult();
        return repositoryService.createModelQuery()
                .modelTenantId(FlowableUtils.getTenantId())
                .modelKey(key).singleResult();
    }
    @Override
@@ -277,5 +342,4 @@
    public byte[] getModelBpmnXML(String id) {
        return repositoryService.getModelEditorSource(id);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionService.java
@@ -1,6 +1,7 @@
package com.iailab.module.bpm.service.definition;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
@@ -40,7 +41,7 @@
     * @param suspensionState 中断状态
     * @return 流程定义列表
     */
    List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState);
    List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState, String categoryId);
    /**
     * 基于流程模型,创建流程定义
@@ -48,10 +49,12 @@
     * @param model 流程模型
     * @param modelMetaInfo 流程模型元信息
     * @param bpmnBytes BPMN XML 字节数组
     * @param simpleJson SIMPLE Model JSON
     * @param form 表单
     * @return 流程编号
     */
    String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, byte[] bpmnBytes, BpmFormDO form);
    String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo,
                                   byte[] bpmnBytes, String simpleJson, BpmFormDO form);
    /**
     * 更新流程定义状态
@@ -60,6 +63,14 @@
     * @param state 状态
     */
    void updateProcessDefinitionState(String id, Integer state);
    /**
     * 更新模型编号
     *
     * @param modelId 流程定义编号
     * @param sort 排序
     */
    void updateProcessDefinitionSortByModelId(String modelId, Long sort);
    /**
     * 获得流程定义对应的 BPMN
@@ -134,6 +145,15 @@
    ProcessDefinition getActiveProcessDefinition(String key);
    /**
     * 判断用户是否可以使用该流程定义,进行流程的发起
     *
     * @param processDefinition 流程定义
     * @param userId 用户编号
     * @return 是否可以发起流程
     */
    boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId);
    /**
     * 获得 ids 对应的 Deployment Map
     *
     * @param ids 部署编号的数组
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
@@ -5,6 +5,7 @@
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.framework.common.util.object.PageUtils;
import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
@@ -13,6 +14,7 @@
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.RepositoryService;
@@ -36,8 +38,6 @@
 * 流程定义实现
 * 主要进行 Flowable {@link ProcessDefinition} 和 {@link Deployment} 的维护
 *
 * @author yunlongn
 * @author ZJQ
 * @author iailab
 */
@Service
@@ -79,7 +79,22 @@
    @Override
    public ProcessDefinition getActiveProcessDefinition(String key) {
        return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult();
        return repositoryService.createProcessDefinitionQuery()
                .processDefinitionTenantId(FlowableUtils.getTenantId())
                .processDefinitionKey(key).active().singleResult();
    }
    @Override
    public boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId) {
        if (processDefinition == null) {
            return false;
        }
        // 为空,则所有人都可以发起
        if (CollUtil.isEmpty(processDefinition.getStartUserIds())) {
            return true;
        }
        // 不为空,则需要存在里面
        return processDefinition.getStartUserIds().contains(userId);
    }
    @Override
@@ -103,8 +118,8 @@
    }
    @Override
    public String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo,
                                          byte[] bpmnBytes, BpmFormDO form) {
    public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo,
                                          byte[] bpmnBytes, String simpleJson, BpmFormDO form) {
        // 创建 Deployment 部署
        Deployment deploy = repositoryService.createDeployment()
                .key(model.getKey()).name(model.getName()).category(model.getCategory())
@@ -129,7 +144,9 @@
        // 插入拓展表
        BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class)
                .setModelId(model.getId()).setProcessDefinitionId(definition.getId());
                .setModelId(model.getId()).setProcessDefinitionId(definition.getId())
                .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson);
        if (form != null) {
            definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf());
        }
@@ -155,6 +172,11 @@
    }
    @Override
    public void updateProcessDefinitionSortByModelId(String modelId, Long sort) {
        processDefinitionMapper.updateByModelId(modelId, new BpmProcessDefinitionInfoDO().setSort(sort));
    }
    @Override
    public BpmnModel getProcessDefinitionBpmnModel(String id) {
        return repositoryService.getBpmnModel(id);
    }
@@ -172,6 +194,7 @@
    @Override
    public PageResult<ProcessDefinition> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) {
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        query.processDefinitionTenantId(FlowableUtils.getTenantId());
        if (StrUtil.isNotBlank(pageVO.getKey())) {
            query.processDefinitionKey(pageVO.getKey());
        }
@@ -186,7 +209,7 @@
    }
    @Override
    public List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState) {
    public List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState, String categoryId) {
        // 拼接查询条件
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), suspensionState)) {
@@ -194,13 +217,11 @@
        } else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), suspensionState)) {
            query.active();
        }
        // 执行查询
        // 关闭多租户查询,不添加tenantId条件
        if (StrUtil.isNotBlank(FlowableUtils.getTenantId())) {
            query.processDefinitionTenantId(FlowableUtils.getTenantId());
        } else {
            query.processDefinitionWithoutTenantId();
        if(ObjectUtils.isNotEmpty(categoryId)) {
            query.processDefinitionCategory(categoryId);
        }
        // 执行查询
        query.processDefinitionTenantId(FlowableUtils.getTenantId());
        return query.list();
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageService.java
@@ -3,6 +3,7 @@
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
import javax.validation.Valid;
@@ -36,4 +37,11 @@
     */
    void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO);
    /**
     * 发送任务审批超时的消息
     *
     * @param reqDTO 发送信息
     */
    void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO);
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageServiceImpl.java
@@ -6,6 +6,7 @@
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
import com.iailab.module.system.api.sms.SmsSendApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -61,6 +62,16 @@
                BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams));
    }
    @Override
    public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) {
        Map<String, Object> templateParams = new HashMap<>();
        templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
        templateParams.put("taskName", reqDTO.getTaskName());
        templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
        smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(),
                BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams)).checkError();
    }
    private String getProcessInstanceDetailUrl(String taskId) {
        return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId;
    }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java
对比新文件
@@ -0,0 +1,42 @@
package com.iailab.module.bpm.service.message.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
 * BPM 发送任务审批超时 Request DTO
 */
@Data
public class BpmMessageSendWhenTaskTimeoutReqDTO {
    /**
     * 流程实例的编号
     */
    @NotEmpty(message = "流程实例的编号不能为空")
    private String processInstanceId;
    /**
     * 流程实例的名字
     */
    @NotEmpty(message = "流程实例的名字不能为空")
    private String processInstanceName;
    /**
     * 流程任务的编号
     */
    @NotEmpty(message = "流程任务的编号不能为空")
    private String taskId;
    /**
     * 流程任务的名字
     */
    @NotEmpty(message = "流程任务的名字不能为空")
    private String taskName;
    /**
     * 审批人的用户编号
     */
    @NotNull(message = "审批人的用户编号不能为空")
    private Long assigneeUserId;
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityService.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityServiceImpl.java
文件已删除
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyService.java
@@ -3,7 +3,9 @@
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
import com.iailab.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import org.flowable.bpmn.model.FlowNode;
import javax.validation.constraints.NotEmpty;
import java.util.Collection;
/**
@@ -14,12 +16,29 @@
public interface BpmProcessInstanceCopyService {
    /**
     * 流程实例的抄送
     * 【管理员】流程实例的抄送
     *
     * @param userIds 抄送的用户编号
     * @param reason 抄送意见
     * @param taskId 流程任务编号
     */
    void createProcessInstanceCopy(Collection<Long> userIds, String taskId);
    void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId);
    /**
     * 【自动抄送】流程实例的抄送
     *
     * @param userIds 抄送的用户编号
     * @param reason 抄送意见
     * @param processInstanceId 流程编号
     * @param activityId 流程活动编号(对应 {@link FlowNode#getId()})
     * @param activityName 任务编号(对应 {@link FlowNode#getName()})
     * @param taskId 任务编号,允许空
     */
    void createProcessInstanceCopy(Collection<Long> userIds, String reason,
                                   @NotEmpty(message = "流程实例编号不能为空") String processInstanceId,
                                   @NotEmpty(message = "流程活动编号不能为空") String activityId,
                                   @NotEmpty(message = "流程活动名字不能为空") String activityName,
                                   String taskId);
    /**
     * 获得抄送的流程的分页
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
@@ -22,6 +22,7 @@
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.framework.common.util.collection.CollectionUtils.convertList;
/**
 * 流程抄送 Service 实现类
 *
@@ -47,19 +48,25 @@
    private BpmProcessDefinitionService processDefinitionService;
    @Override
    public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) {
        // 1.1 校验任务存在
    public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId) {
        Task task = taskService.getTask(taskId);
        if (ObjectUtil.isNull(task)) {
            throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
        }
        // 1.2 校验流程实例存在
        String processInstanceId = task.getProcessInstanceId();
        // 执行抄送
        createProcessInstanceCopy(userIds, reason,
                task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getId(), task.getName());
    }
    @Override
    public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String processInstanceId,
                                          String activityId, String activityName, String taskId) {
        // 1.1 校验流程实例存在
        ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
        if (processInstance == null) {
            throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
        }
        // 1.3 校验流程定义存在
        // 1.2 校验流程定义存在
        ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
                processInstance.getProcessDefinitionId());
        if (processDefinition == null) {
@@ -68,9 +75,10 @@
        // 2. 创建抄送流程
        List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO()
                .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId()))
                .setUserId(userId).setReason(reason).setStartUserId(Long.valueOf(processInstance.getStartUserId()))
                .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
                .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(task.getName()));
                .setCategory(processDefinition.getCategory()).setTaskId(taskId)
                .setActivityId(activityId).setActivityName(activityName));
        processInstanceCopyMapper.insertBatch(copyList);
    }
@@ -80,4 +88,5 @@
        return processInstanceCopyMapper.selectPage(userId, pageReqVO);
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceService.java
@@ -2,10 +2,7 @@
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import com.iailab.module.bpm.controller.admin.task.vo.instance.*;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
@@ -19,9 +16,11 @@
/**
 * 流程实例 Service 接口
 *
 * @author iailab
 * @author 芋道源码
 */
public interface BpmProcessInstanceService {
    // ========== Query 查询相关方法 ==========
    /**
     * 获得流程实例
@@ -85,6 +84,28 @@
    PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
                                                               @Valid BpmProcessInstancePageReqVO pageReqVO);
    // TODO @芋艿:重点在 review 下
    /**
     * 获取审批详情。
     * <p>
     * 可以是准备发起的流程、进行中的流程、已经结束的流程
     *
     * @param loginUserId  登录人的用户编号
     * @param reqVO 请求信息
     * @return 流程实例的进度
     */
    BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
    /**
     * 获取流程实例的 BPMN 模型视图
     *
     * @param id 流程实例的编号
     * @return BPMN 模型视图
     */
    BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id);
    // ========== Update 写入相关方法 ==========
    /**
     * 创建流程实例(提供给前端)
     *
@@ -114,31 +135,26 @@
    /**
     * 管理员取消流程实例
     *
     * @param userId           用户编号
     * @param userId      用户编号
     * @param cancelReqVO 取消信息
     */
    void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO);
    /**
     * 更新 ProcessInstance 拓展记录为取消
     * 更新 ProcessInstance 为不通过
     *
     * @param event 流程取消事件
     * @param processInstance 流程实例
     * @param reason          理由。例如说,审批不通过时,需要传递该值
     */
    void updateProcessInstanceWhenCancel(FlowableCancelledEvent event);
    void updateProcessInstanceReject(ProcessInstance processInstance, String reason);
    // ========== Event 事件相关方法 ==========
    /**
     * 更新 ProcessInstance 拓展记录为完成
     * 处理 ProcessInstance 完成事件,例如说:审批通过、不通过、取消
     *
     * @param instance 流程任务
     */
    void updateProcessInstanceWhenApprove(ProcessInstance instance);
    /**
     * 更新 ProcessInstance 拓展记录为不通过
     *
     * @param id     流程编号
     * @param reason 理由。例如说,审批不通过时,需要传递该值
     */
    void updateProcessInstanceReject(String id, String reason);
    void processProcessInstanceCompleted(ProcessInstance instance);
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -1 +1 @@
package com.iailab.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.object.PageUtils; import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert; import com.iailab.module.bpm.enums.task.BpmDeleteReasonEnum; import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants; import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.message.BpmMessageService; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; /**  * 流程实例 Service 实现类  *  * ProcessDefinition & ProcessInstance & Execution & Task 的关系:  *  1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" />  *  * HistoricProcessInstance & ProcessInstance 的关系:  *  1. <a href=" https://my.oschina.net/843294669/blog/71902" />  *  * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例  *  * @author iailab  */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {     @Resource     private RuntimeService runtimeService;     @Resource     private HistoryService historyService;     @Resource     private BpmProcessDefinitionService processDefinitionService;     @Resource     private BpmMessageService messageService;     @Resource     private AdminUserApi adminUserApi;     @Resource     private BpmProcessInstanceEventPublisher processInstanceEventPublisher;     @Override     public ProcessInstance getProcessInstance(String id) {         return runtimeService.createProcessInstanceQuery()                 .includeProcessVariables()                 .processInstanceId(id)                 .singleResult();     }     @Override     public List<ProcessInstance> getProcessInstances(Set<String> ids) {         return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();     }     @Override     public HistoricProcessInstance getHistoricProcessInstance(String id) {         return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();     }     @Override     public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {         return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();     }     @Override     public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,                                                                       BpmProcessInstancePageReqVO pageReqVO) {         // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页         HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()                 .includeProcessVariables()                 .processInstanceTenantId(FlowableUtils.getTenantId())                 .orderByProcessInstanceStartTime().desc();         if (userId != null) { // 【我的流程】菜单时,需要传递该字段             processInstanceQuery.startedBy(String.valueOf(userId));         }  else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段             processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId()));         }         if (StrUtil.isNotEmpty(pageReqVO.getName())) {             processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%");         }         if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) {             processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%");         }         if (StrUtil.isNotEmpty(pageReqVO.getCategory())) {             processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory());         }         if (pageReqVO.getStatus() != null) {             processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus());         }         if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) {             processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0]));             processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1]));         }         // 查询数量         long processInstanceCount = processInstanceQuery.count();         if (processInstanceCount == 0) {             return PageResult.empty(processInstanceCount);         }         // 查询列表         List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());         return new PageResult<>(processInstanceList, processInstanceCount);     }     @Override     @Transactional(rollbackFor = Exception.class)     public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {         // 获得流程定义         ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());         // 发起流程         return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,                 createReqVO.getStartUserSelectAssignees());     }     @Override     public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {         // 获得流程定义         ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());         // 发起流程         return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(),                 createReqDTO.getStartUserSelectAssignees());     }     private String createProcessInstance0(Long userId, ProcessDefinition definition,                                           Map<String, Object> variables, String businessKey,                                           Map<String, List<Long>> startUserSelectAssignees) {         // 1.1 校验流程定义         if (definition == null) {             throw exception(PROCESS_DEFINITION_NOT_EXISTS);         }         if (definition.isSuspended()) {             throw exception(PROCESS_DEFINITION_IS_SUSPENDED);         }         // 1.2 校验发起人自选审批人         validateStartUserSelectAssignees(definition, startUserSelectAssignees);         // 2. 创建流程实例         if (variables == null) {             variables = new HashMap<>();         }         FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用         variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中                 BpmProcessInstanceStatusEnum.RUNNING.getStatus());         if (CollUtil.isNotEmpty(startUserSelectAssignees)) {             variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);         }         ProcessInstance instance = runtimeService.createProcessInstanceBuilder()                 .processDefinitionId(definition.getId())                 .businessKey(businessKey)                 .name(definition.getName().trim())                 .variables(variables)                 .start();         return instance.getId();     }     private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) {         // 1. 获得发起人自选审批人的 UserTask 列表         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());         List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);         if (CollUtil.isEmpty(userTaskList)) {             return;         }         // 2. 校验发起人自选审批人的 UserTask 是否都配置了         userTaskList.forEach(userTask -> {             List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null;             if (CollUtil.isEmpty(assignees)) {                 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName());             }             Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees);             assignees.forEach(assignee -> {                 if (userMap.get(assignee) == null) {                     throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee);                 }             });         });     }     @Override     public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {         // 1.1 校验流程实例存在         ProcessInstance instance = getProcessInstance(cancelReqVO.getId());         if (instance == null) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);         }         // 1.2 只能取消自己的         if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);         }         // 2. 通过删除流程实例,实现流程实例的取消,         // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。         deleteProcessInstance(cancelReqVO.getId(),                 BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason()));         // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法     }     @Override     public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) {         // 1.1 校验流程实例存在         ProcessInstance instance = getProcessInstance(cancelReqVO.getId());         if (instance == null) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);         }         // 1.2 管理员取消,不用校验是否为自己的         AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData();         // 2. 通过删除流程实例,实现流程实例的取消,         // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。         deleteProcessInstance(cancelReqVO.getId(),                 BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason()));         // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法     }     @Override     public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) {         // 1. 判断是否为 Reject 不通过。如果是,则不进行更新.         // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了         if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) {             return;         }         // 2. 更新流程实例 status         runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,                 BpmProcessInstanceStatusEnum.CANCEL.getStatus());         // 3. 发送流程实例的状态事件         // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance         HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());         // 发送流程实例的状态事件         processInstanceEventPublisher.sendProcessInstanceResultEvent(                 BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus()));     }     @Override     public void updateProcessInstanceWhenApprove(ProcessInstance instance) {         // 1. 更新流程实例 status         runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,                 BpmProcessInstanceStatusEnum.APPROVE.getStatus());         // 2. 发送流程被【通过】的消息         messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));         // 3. 发送流程实例的状态事件         // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance         HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());         processInstanceEventPublisher.sendProcessInstanceResultEvent(                 BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus()));     }     @Override     @Transactional(rollbackFor = Exception.class)     public void updateProcessInstanceReject(String id, String reason) {         // 1. 更新流程实例 status         runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus());         // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程         ProcessInstance processInstance = getProcessInstance(id);         deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason)));         // 3. 发送流程被【不通过】的消息         messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason));         // 4. 发送流程实例的状态事件         processInstanceEventPublisher.sendProcessInstanceResultEvent(                 BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus()));     }     private void deleteProcessInstance(String id, String reason) {         runtimeService.deleteProcessInstance(id, reason);     } }
package com.iailab.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.json.JsonUtils; import com.iailab.framework.common.util.object.ObjectUtils; import com.iailab.framework.common.util.object.PageUtils; import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.*; import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import com.iailab.module.bpm.enums.ErrorCodeConstants; import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum; import com.iailab.module.bpm.enums.definition.BpmSimpleModelNodeType; import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import com.iailab.module.bpm.enums.task.BpmReasonEnum; import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.message.BpmMessageService; import com.iailab.module.system.api.dept.DeptApi; import com.iailab.module.system.api.dept.dto.DeptRespDTO; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.util.collection.CollectionUtils.*; import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /**  * 流程实例 Service 实现类  * <p>  * ProcessDefinition & ProcessInstance & Execution & Task 的关系:  * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" />  * <p>  * HistoricProcessInstance & ProcessInstance 的关系:  * 1. <a href=" https://my.oschina.net/843294669/blog/71902" />  * <p>  * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例  *  * @author 芋道源码  */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {     @Resource     private RuntimeService runtimeService;     @Resource     private HistoryService historyService;     @Resource     private BpmProcessDefinitionService processDefinitionService;     @Resource     @Lazy // 避免循环依赖     private BpmTaskService taskService;     @Resource     private BpmMessageService messageService;     @Resource     private AdminUserApi adminUserApi;     @Resource     private DeptApi deptApi;     @Resource     private BpmProcessInstanceEventPublisher processInstanceEventPublisher;     @Resource     private BpmTaskCandidateInvoker taskCandidateInvoker;     // ========== Query 查询相关方法 ==========     @Override     public ProcessInstance getProcessInstance(String id) {         return runtimeService.createProcessInstanceQuery()                 .includeProcessVariables()                 .processInstanceId(id)                 .singleResult();     }     @Override     public List<ProcessInstance> getProcessInstances(Set<String> ids) {         return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();     }     @Override     public HistoricProcessInstance getHistoricProcessInstance(String id) {         return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();     }     @Override     public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {         return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();     }     @Override     public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,                                                                       BpmProcessInstancePageReqVO pageReqVO) {         // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页         HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()                 .includeProcessVariables()                 .processInstanceTenantId(FlowableUtils.getTenantId())                 .orderByProcessInstanceStartTime().desc();         if (userId != null) { // 【我的流程】菜单时,需要传递该字段             processInstanceQuery.startedBy(String.valueOf(userId));         } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段             processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId()));         }         if (StrUtil.isNotEmpty(pageReqVO.getName())) {             processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%");         }         if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) {             processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey());         }         if (StrUtil.isNotEmpty(pageReqVO.getCategory())) {             processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory());         }         if (pageReqVO.getStatus() != null) {             processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus());         }         if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) {             processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0]));             processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1]));         }         // 查询数量         long processInstanceCount = processInstanceQuery.count();         if (processInstanceCount == 0) {             return PageResult.empty(processInstanceCount);         }         // 查询列表         List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());         return new PageResult<>(processInstanceList, processInstanceCount);     }     private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel,                                                         String activityId, String taskId) {         // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id         if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) {             activityId = Optional.ofNullable(taskService.getHistoricTask(taskId))                     .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null);         }         if (StrUtil.isEmpty(activityId)) {             return null;         }         // 2. 从 BpmnModel 中解析表单字段权限         return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId);     }     @Override     public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) {         // 1.1 从 reqVO 中,读取公共变量         Long startUserId = loginUserId; // 流程发起人         HistoricProcessInstance historicProcessInstance = null; // 流程实例         Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态         Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量         // 1.2 如果是流程已发起的场景,则使用流程实例的数据         if (reqVO.getProcessInstanceId() != null) {             historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId());             if (historicProcessInstance == null) {                 throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);             }             startUserId = Long.valueOf(historicProcessInstance.getStartUserId());             processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);             processVariables = historicProcessInstance.getProcessVariables();         }         // 1.3 读取其它相关数据         ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(                 historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId());         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());         // 2.1 已结束 + 进行中的活动节点         List<ActivityNode> endActivityNodes = null; // 已结束的审批信息         List<ActivityNode> runActivityNodes = null; // 进行中的审批信息         List<HistoricActivityInstance> activities = null; // 流程实例列表         if (reqVO.getProcessInstanceId() != null) {             activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId());             List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true);             endActivityNodes = getEndActivityNodeList(startUserId, bpmnModel, processDefinitionInfo,                     historicProcessInstance, processInstanceStatus, activities, tasks);             runActivityNodes = getRunApproveNodeList(startUserId, bpmnModel, processDefinition, processVariables, activities, tasks);         }         // 2.2 流程已经结束,直接 return,无需预测         if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) {             return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,                     processInstanceStatus, endActivityNodes, runActivityNodes, null, null);         }         // 3.1 计算当前登录用户的待办任务         // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。         BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId());         // 3.2 预测未运行节点的审批信息         List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo,                 processVariables, activities);         // 4. 拼接最终数据         return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,                 processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask);     }     /**      * 拼接审批详情的最终数据      * <p>      * 主要是,拼接审批人的用户信息、部门信息      */     private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO,                                                         BpmnModel bpmnModel,                                                         ProcessDefinition processDefinition,                                                         BpmProcessDefinitionInfoDO processDefinitionInfo,                                                         HistoricProcessInstance processInstance,                                                         Integer processInstanceStatus,                                                         List<ActivityNode> endApprovalNodeInfos,                                                         List<ActivityNode> runningApprovalNodeInfos,                                                         List<ActivityNode> simulateApprovalNodeInfos,                                                         BpmTaskRespVO todoTask) {         // 1. 获取所有需要读取用户信息的 userIds         List<ActivityNode> approveNodes = newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos));         Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes, todoTask);         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));         // 2. 表单权限         Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), reqVO.getTaskId());         // 3. 拼接数据         return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance,                 processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap);     }     /**      * 获得【已结束】的活动节点们      */     private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,                                                       BpmProcessDefinitionInfoDO processDefinitionInfo,                                                       HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,                                                       List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {         // 遍历 tasks 列表,只处理已结束的 UserTask         // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点         List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);         List<ActivityNode> approvalNodes = convertList(endTasks, task -> {             FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());             ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName())                     .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ?                             BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType())                     .setStatus(FlowableUtils.getTaskStatus(task))                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))                     .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))                     .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));             // 如果是取消状态,则跳过             if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) {                 return null;             }             return activityNode;         });         // 遍历 activities,只处理已结束的 StartEvent、EndEvent         List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null                 && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END)));         endActivities.forEach(activity -> {             // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点             if (ELEMENT_EVENT_START.equals(activity.getActivityType())                     && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) {                 ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)                         .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());                 ActivityNode startNode = new ActivityNode().setId(startTask.getId())                         .setName(BpmSimpleModelNodeType.START_USER_NODE.getName())                         .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType())                         .setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask))                         .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime()));                 approvalNodes.add(0, startNode);                 return;             }             // EndEvent             if (ELEMENT_EVENT_END.equals(activity.getActivityType())) {                 if (BpmProcessInstanceStatusEnum.isRejectStatus(processInstanceStatus)) {                     // 拒绝情况下,不需要展示 EndEvent 结束节点。原因是:前端已经展示 x 效果,无需重复展示                     return;                 }                 ActivityNode endNode = new ActivityNode().setId(activity.getId())                         .setName(BpmSimpleModelNodeType.END_NODE.getName())                         .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus)                         .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime()));                 String reason = FlowableUtils.getProcessInstanceReason(historicProcessInstance);                 if (StrUtil.isNotEmpty(reason)) {                     endNode.setTasks(singletonList(new ActivityNodeTask().setId(endNode.getId())                             .setStatus(endNode.getStatus()).setReason(reason)));                 }                 approvalNodes.add(endNode);             }         });         return approvalNodes;     }     /**      * 获得【进行中】的活动节点们      */     private List<ActivityNode> getRunApproveNodeList(Long startUserId,                                                      BpmnModel bpmnModel,                                                      ProcessDefinition processDefinition,                                                      Map<String, Object> processVariables,                                                      List<HistoricActivityInstance> activities,                                                      List<HistoricTaskInstance> tasks) {         // 构建运行中的任务,基于 activityId 分组         List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null                 && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER)));         Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId);         // 按照 activityId 分组,构建 ApprovalNodeInfo 节点         Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId);         return convertList(runningTaskMap.entrySet(), entry -> {             String activityId = entry.getKey();             List<HistoricActivityInstance> taskActivities = entry.getValue();             // 构建活动节点             FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);             HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同             ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName())                     .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus())                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))                     .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime()))                     .setTasks(new ArrayList<>());             // 处理每个任务的 tasks 属性             for (HistoricActivityInstance activity : taskActivities) {                 HistoricTaskInstance task = taskMap.get(activity.getTaskId());                 activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task));                 // 加签子任务,需要过滤掉已经完成的加签子任务                 List<HistoricTaskInstance> childrenTasks = filterList(                         taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks),                         childTask -> childTask.getEndTime() == null);                 if (CollUtil.isNotEmpty(childrenTasks)) {                     activityNode.getTasks().addAll(convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo));                 }             }             // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task             if (BpmnModelUtils.isSequentialUserTask(flowNode)) {                 List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, flowNode.getId(),                         startUserId, processDefinition.getId(), processVariables);                 // 截取当前审批人位置后面的候选人,不包含当前审批人                 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks());                 Assert.notNull(approvalTaskInfo, "任务不能为空");                 int index = CollUtil.indexOf(candidateUserIds, userId -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(),                         approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner                 activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size()));             }             return activityNode;         });     }     /**      * 获得【预测(未来)】的活动节点们      */     private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel,                                                           BpmProcessDefinitionInfoDO processDefinitionInfo,                                                           Map<String, Object> processVariables,                                                           List<HistoricActivityInstance> activities) {         // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录         Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);         // 情况一:BPMN 设计器         if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {             List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);             return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel,                     processDefinitionInfo, processVariables, flowElement, runActivityIds));         }         // 情况二:SIMPLE 设计器         if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) {             BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class);             List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);             return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel,                     processDefinitionInfo, processVariables, simpleNode, runActivityIds));         }         throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());     }     private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,                                                          BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,                                                          BpmSimpleModelNodeVO node, Set<String> runActivityIds) {         // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录         if (runActivityIds.contains(node.getId())) {             return null;         }         ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())                 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())                 .setStatus(BpmTaskStatusEnum.NOT_START.getStatus());         // 1. 开始节点/审批节点         if (ObjectUtils.equalsAny(node.getType(),                 BpmSimpleModelNodeType.START_USER_NODE.getType(),                 BpmSimpleModelNodeType.APPROVE_NODE.getType())) {             List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),                     startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);             activityNode.setCandidateUserIds(candidateUserIds);             return activityNode;         }         // 2. 结束节点         if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) {             return activityNode;         }         // 3. 抄送节点         if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人                 BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) {             return activityNode;         }         return null;     }     private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,                                                        BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,                                                        FlowElement node, Set<String> runActivityIds) {         if (runActivityIds.contains(node.getId())) {             return null;         }         ActivityNode activityNode = new ActivityNode().setId(node.getId()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus());         // 1. 开始节点         if (node instanceof StartEvent) {             return activityNode.setName(BpmSimpleModelNodeType.START_USER_NODE.getName())                     .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType());         }         // 2. 审批节点         if (node instanceof UserTask) {             List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),                     startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);             return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType())                     .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))                     .setCandidateUserIds(candidateUserIds);         }         // 3. 结束节点         if (node instanceof EndEvent) {             return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName())                     .setNodeType(BpmSimpleModelNodeType.END_NODE.getType());         }         return null;     }     private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId,                                                 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {         Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,                 startUserId, processDefinitionId, processVariables);         return new ArrayList<>(userIds);     }     @Override     public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) {         // 1.1 获得流程实例         HistoricProcessInstance processInstance = getHistoricProcessInstance(id);         if (processInstance == null) {             return null;         }         // 1.2 获得流程定义         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId());         if (bpmnModel == null) {             return null;         }         BpmSimpleModelNodeVO simpleModel = null;         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(                 processInstance.getProcessDefinitionId());         if (processDefinitionInfo != null && BpmModelTypeEnum.SIMPLE.getType().equals(processDefinitionInfo.getModelType())) {             simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class);         }         // 1.3 获得流程实例对应的活动实例列表 + 任务列表         List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id);         List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true);         // 2.1 拼接进度信息         Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() == null);         Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() != null                         && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));         Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId,                 activityInstance -> activityInstance.getEndTime() != null                         && ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW));         // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉         unfinishedTaskActivityIds.removeAll(finishedTaskActivityIds);         // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。         // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点         Set<String> rejectTaskActivityIds = CollUtil.newHashSet();         if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) {             tasks.stream()                     .filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task)))                     .max(Comparator.comparing(HistoricTaskInstance::getEndTime))                     .ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey()));             finishedTaskActivityIds.removeAll(rejectTaskActivityIds);         }         // 2.2 拼接基础信息         Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks);         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));         return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel,                 unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds,                 userMap, deptMap);     }     // ========== Update 写入相关方法 ==========     @Override     @Transactional(rollbackFor = Exception.class)     public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {         // 获得流程定义         ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());         // 发起流程         return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,                 createReqVO.getStartUserSelectAssignees());     }     @Override     public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {         // 获得流程定义         ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());         // 发起流程         return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(),                 createReqDTO.getStartUserSelectAssignees());     }     private String createProcessInstance0(Long userId, ProcessDefinition definition,                                           Map<String, Object> variables, String businessKey,                                           Map<String, List<Long>> startUserSelectAssignees) {         // 1.1 校验流程定义         if (definition == null) {             throw exception(PROCESS_DEFINITION_NOT_EXISTS);         }         if (definition.isSuspended()) {             throw exception(PROCESS_DEFINITION_IS_SUSPENDED);         }         BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId());         if (processDefinitionInfo == null) {             throw exception(PROCESS_DEFINITION_NOT_EXISTS);         }         // 1.2 校验是否能够发起         if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) {             throw exception(PROCESS_INSTANCE_START_USER_CAN_START);         }         // 1.3 校验发起人自选审批人         validateStartUserSelectAssignees(definition, startUserSelectAssignees);         // 2. 创建流程实例         if (variables == null) {             variables = new HashMap<>();         }         FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用         variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID         variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中                 BpmProcessInstanceStatusEnum.RUNNING.getStatus());         if (CollUtil.isNotEmpty(startUserSelectAssignees)) {             variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);         }         ProcessInstance instance = runtimeService.createProcessInstanceBuilder()                 .processDefinitionId(definition.getId())                 .businessKey(businessKey)                 .name(definition.getName().trim())                 .variables(variables)                 .start();         return instance.getId();     }     private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) {         // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表         BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());         List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel);         if (CollUtil.isEmpty(tasks)) {             return;         }         // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了         tasks.forEach(task -> {             List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;             if (CollUtil.isEmpty(assignees)) {                 throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName());             }             Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees);             assignees.forEach(assignee -> {                 if (userMap.get(assignee) == null) {                     throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, task.getName(), assignee);                 }             });         });     }     @Override     public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {         // 1.1 校验流程实例存在         ProcessInstance instance = getProcessInstance(cancelReqVO.getId());         if (instance == null) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);         }         // 1.2 只能取消自己的         if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);         }         // 2. 取消流程         updateProcessInstanceCancel(cancelReqVO.getId(),                 BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason()));     }     @Override     public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) {         // 1.1 校验流程实例存在         ProcessInstance instance = getProcessInstance(cancelReqVO.getId());         if (instance == null) {             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);         }         // 2. 取消流程         AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData();         updateProcessInstanceCancel(cancelReqVO.getId(),                 BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason()));     }     private void updateProcessInstanceCancel(String id, String reason) {         // 1. 更新流程实例 status         runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,                 BpmProcessInstanceStatusEnum.CANCEL.getStatus());         runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason);         // 2. 结束流程         taskService.moveTaskToEnd(id);     }     @Override     public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) {         runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,                 BpmProcessInstanceStatusEnum.REJECT.getStatus());         runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON,                 BpmReasonEnum.REJECT_TASK.format(reason));     }     // ========== Event 事件相关方法 ==========     @Override     public void processProcessInstanceCompleted(ProcessInstance instance) {         // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号         FlowableUtils.execute(instance.getTenantId(), () -> {             // 1.1 获取当前状态             Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);             String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON);             // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态             // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了             if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) {                 status = BpmProcessInstanceStatusEnum.APPROVE.getStatus();                 runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status);             }             // 2. 发送对应的消息通知             if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {                 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));             } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) {                 messageService.sendMessageWhenProcessInstanceReject(                         BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason));             }             // 3. 发送流程实例的状态事件             processInstanceEventPublisher.sendProcessInstanceResultEvent(                     BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status));         });     } }
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskService.java
@@ -3,8 +3,11 @@
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.collection.CollectionUtils;
import com.iailab.module.bpm.controller.admin.task.vo.task.*;
import com.iailab.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import javax.validation.Valid;
@@ -16,9 +19,11 @@
 * 流程任务实例 Service 接口
 *
 * @author jason
 * @author iailab
 * @author 芋道源码
 */
public interface BpmTaskService {
    // ========== Query 查询相关方法 ==========
    /**
     * 获得待办的流程任务分页
@@ -28,6 +33,15 @@
     * @return 流程任务分页
     */
    PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO);
    /**
     * 获得用户在指定流程下,首个需要处理(待办)的任务
     *
     * @param userId 用户编号
     * @param processInstanceId 流程实例编号
     * @return 待办任务
     */
    BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId);
    /**
     * 获得已办的流程任务分页
@@ -70,9 +84,88 @@
     * 获得指定流程实例的流程任务列表,包括所有状态的
     *
     * @param processInstanceId 流程实例的编号
     * @param asc               是否升序
     * @return 流程任务列表
     */
    List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId);
    List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc);
    /**
     * 获取任务
     *
     * @param id 任务编号
     * @return 任务
     */
    Task getTask(String id);
    /**
     * 获取历史任务
     *
     * @param id 任务编号
     * @return 历史任务
     */
    HistoricTaskInstance getHistoricTask(String id);
    /**
     * 获取历史任务列表
     *
     * @param taskIds 任务编号集合
     * @return 历史任务列表
     */
    List<HistoricTaskInstance> getHistoricTasks(Collection<String> taskIds);
    /**
     * 根据条件查询正在进行中的任务
     *
     * @param processInstanceId 流程实例编号,不允许为空
     * @param assigned          是否分配了审批人,允许空
     * @param taskDefineKey     任务定义 Key,允许空
     */
    List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId,
                                                     Boolean assigned,
                                                     String taskDefineKey);
    /**
     * 获取当前任务的可退回的 UserTask 集合
     *
     * @param id 当前的任务 ID
     * @return 可以退回的节点列表
     */
    List<UserTask> getUserTaskListByReturn(String id);
    /**
     * 获取指定任务的子任务列表(多层)
     *
     * @param parentTaskId 父任务 ID
     * @param tasks 任务列表
     * @return 子任务列表
     */
    <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks);
    /**
     * 获取指定任务的子任务列表
     *
     * @param parentTaskId 父任务ID
     * @return 子任务列表
     */
    List<Task> getTaskListByParentTaskId(String parentTaskId);
    /**
     * 获得指定流程实例的活动实例列表
     *
     * @param processInstanceId 流程实例的编号
     * @return 活动实例列表
     */
    List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId);
    /**
     * 获得执行编号对应的活动实例
     *
     * @param executionId 执行编号
     * @return 活动实例
     */
    List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId);
    // ========== Update 写入相关方法 ==========
    /**
     * 通过任务
@@ -99,47 +192,17 @@
    void transferTask(Long userId, BpmTaskTransferReqVO reqVO);
    /**
     * 更新 Task 状态,在创建时
     * 将指定流程实例的、进行中的流程任务,移动到结束节点
     *
     * @param task 任务实体
     * @param processInstanceId 流程编号
     */
    void updateTaskStatusWhenCreated(Task task);
    void moveTaskToEnd(String processInstanceId);
    /**
     * 更新 Task 状态,在取消时
     *
     * @param taskId 任务的编号
     */
    void updateTaskStatusWhenCanceled(String taskId);
    /**
     * 更新 Task 拓展记录,并发送通知
     *
     * @param task 任务实体
     */
    void updateTaskExtAssign(Task task);
    /**
     * 获取任务
     *
     * @param id 任务编号
     * @return 任务
     */
    Task getTask(String id);
    /**
     * 获取当前任务的可回退的 UserTask 集合
     *
     * @param id 当前的任务 ID
     * @return 可以回退的节点列表
     */
    List<UserTask> getUserTaskListByReturn(String id);
    /**
     * 将任务回退到指定的 targetDefinitionKey 位置
     * 将任务退回到指定的 targetDefinitionKey 位置
     *
     * @param userId 用户编号
     * @param reqVO  回退的任务key和当前所在的任务ID
     * @param reqVO  退回的任务key和当前所在的任务ID
     */
    void returnTask(Long userId, BpmTaskReturnReqVO reqVO);
@@ -168,19 +231,48 @@
    void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO);
    /**
     * 获取指定任务的子任务列表
     * 抄送任务
     *
     * @param parentTaskId 父任务ID
     * @return 子任务列表
     * @param userId 用户编号
     * @param reqVO  通过请求
     */
    List<Task> getTaskListByParentTaskId(String parentTaskId);
    void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
    // ========== Event 事件相关方法 ==========
    /**
     * 通过任务 ID,查询任务名 Map
     * 处理 Task 创建事件,目前是
     * <p>
     * 1. 更新它的状态为审批中
     * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过
     * <p>
     * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后
     *
     * @param taskIds 任务 ID
     * @return 任务 ID 与名字的 Map
     * @param task 任务实体
     */
    Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds);
    void processTaskCreated(Task task);
    /**
     * 处理 Task 取消事件,目前是更新它的状态为已取消
     *
     * @param taskId 任务的编号
     */
    void processTaskCanceled(String taskId);
    /**
     * 处理 Task 设置审批人事件,目前是发送审批消息
     *
     * @param task 任务实体
     */
    void processTaskAssigned(Task task);
    /**
     * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务
     *
     * @param processInstanceId 流程示例编号
     * @param taskDefineKey     任务 Key
     * @param handlerType       处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum}
     */
    void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType);
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -1,40 +1,47 @@
package com.iailab.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.CommonResult;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.date.DateUtils;
import com.iailab.framework.common.util.number.NumberUtils;
import com.iailab.framework.common.util.object.ObjectUtils;
import com.iailab.framework.common.util.object.PageUtils;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.framework.web.core.util.WebFrameworkUtils;
import com.iailab.module.bpm.controller.admin.task.vo.task.*;
import com.iailab.module.bpm.convert.task.BpmTaskConvert;
import com.iailab.module.bpm.enums.definition.*;
import com.iailab.module.bpm.enums.task.BpmCommentTypeEnum;
import com.iailab.module.bpm.enums.task.BpmDeleteReasonEnum;
import com.iailab.module.bpm.enums.task.BpmReasonEnum;
import com.iailab.module.bpm.enums.task.BpmTaskSignTypeEnum;
import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.service.definition.BpmModelService;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import com.iailab.module.bpm.service.message.BpmMessageService;
import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
import com.iailab.module.system.api.dept.DeptApi;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
@@ -44,7 +51,6 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import javax.validation.Valid;
@@ -54,11 +60,12 @@
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.framework.common.util.collection.CollectionUtils.*;
import static com.iailab.module.bpm.enums.ErrorCodeConstants.*;
import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;
/**
 * 流程任务实例 Service 实现类
 *
 * @author iailab
 * @author 芋道源码
 * @author jason
 */
@Slf4j
@@ -77,14 +84,20 @@
    @Resource
    private BpmProcessInstanceService processInstanceService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private BpmProcessInstanceCopyService processInstanceCopyService;
    @Resource
    private BpmModelService bpmModelService;
    private BpmModelService modelService;
    @Resource
    private BpmMessageService messageService;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private DeptApi deptApi;
    // ========== Query 查询相关方法 ==========
    @Override
    public PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageVO) {
@@ -98,7 +111,7 @@
        }
        if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
            taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
        }
        long count = taskQuery.count();
        if (count == 0) {
@@ -106,6 +119,41 @@
        }
        List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
        return new PageResult<>(tasks, count);
    }
    @Override
    public BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId) {
        if (processInstanceId == null) {
            return null;
        }
        // 1. 查询所有任务
        List<Task> tasks = taskService.createTaskQuery()
                .active()
                .processInstanceId(processInstanceId)
                .includeTaskLocalVariables()
                .includeProcessVariables()
                .orderByTaskCreateTime().asc() // 按创建时间升序
                .list();
        if (CollUtil.isEmpty(tasks)) {
            return null;
        }
        // 2.1 查询我的首个任务
        Task todoTask = CollUtil.findOne(tasks, task -> {
            return isAssignUserTask(userId, task) // 当前用户为审批人
                    || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签)
        });
        if (todoTask == null) {
            return null;
        }
        // 2.2 查询该任务的子任务
        List<Task> childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), tasks);
        // 3. 转换返回
        BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId());
        Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting(
                bpmnModel, todoTask.getTaskDefinitionKey());
        return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting);
    }
    @Override
@@ -120,7 +168,7 @@
        }
        if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
            taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
        }
        // 执行查询
        long count = taskQuery.count();
@@ -142,7 +190,7 @@
        }
        if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
            taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1]));
            taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
        }
        // 执行查询
        long count = taskQuery.count();
@@ -162,17 +210,246 @@
    }
    @Override
    public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId) {
        List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
    public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc) {
        HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
                .includeTaskLocalVariables()
                .processInstanceId(processInstanceId)
                .orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
                .list();
                .processInstanceId(processInstanceId);
        if (Boolean.TRUE.equals(asc)) {
            query.orderByHistoricTaskInstanceStartTime().asc();
        } else {
            query.orderByHistoricTaskInstanceStartTime().desc();
        }
        return query.list();
    }
    /**
     * 校验任务是否存在,并且是否是分配给自己的任务
     *
     * @param userId 用户 id
     * @param taskId task id
     */
    private Task validateTask(Long userId, String taskId) {
        Task task = validateTaskExist(taskId);
        // 为什么判断 assignee 非空的情况下?
        // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过
        if (StrUtil.isNotBlank(task.getAssignee())
                && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) {
            throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
        }
        return task;
    }
    private Task validateTaskExist(String id) {
        Task task = getTask(id);
        if (task == null) {
            throw exception(TASK_NOT_EXISTS);
        }
        return task;
    }
    @Override
    public Task getTask(String id) {
        return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult();
    }
    @Override
    public HistoricTaskInstance getHistoricTask(String id) {
        return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult();
    }
    @Override
    public List<HistoricTaskInstance> getHistoricTasks(Collection<String> taskIds) {
        return historyService.createHistoricTaskInstanceQuery().taskIds(taskIds).includeTaskLocalVariables().list();
    }
    @Override
    public List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String defineKey) {
        Assert.notNull(processInstanceId, "processInstanceId 不能为空");
        TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId).active()
                .includeTaskLocalVariables();
        if (BooleanUtil.isTrue(assigned)) {
            taskQuery.taskAssigned();
        } else if (BooleanUtil.isFalse(assigned)) {
            taskQuery.taskUnassigned();
        }
        if (StrUtil.isNotEmpty(defineKey)) {
            taskQuery.taskDefinitionKey(defineKey);
        }
        return taskQuery.list();
    }
    @Override
    public List<UserTask> getUserTaskListByReturn(String id) {
        // 1.1 校验当前任务 task 存在
        Task task = validateTaskExist(id);
        // 1.2 根据流程定义获取流程模型信息
        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
        FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        if (source == null) {
            throw exception(TASK_NOT_EXISTS);
        }
        // 2.1 查询该任务的前置任务节点的 key 集合
        List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null);
        if (CollUtil.isEmpty(previousUserList)) {
            return Collections.emptyList();
        }
        // 2.2 过滤:只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回
        previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
        return previousUserList;
    }
    @Override
    public <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks) {
        if (CollUtil.isEmpty(tasks)) {
            return Collections.emptyList();
        }
        return tasks;
        Map<String, List<T>> parentTaskMap = convertMultiMap(
                filterList(tasks, task -> StrUtil.isNotEmpty(task.getParentTaskId())), TaskInfo::getParentTaskId);
        if (CollUtil.isEmpty(parentTaskMap)) {
            return Collections.emptyList();
        }
        List<T> result = new ArrayList<>();
        // 1. 递归获取子级
        Stack<String> stack = new Stack<>();
        stack.push(parentTaskId);
        // 2. 递归遍历
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            if (stack.isEmpty()) {
                break;
            }
            // 2.1 获取子任务们
            String taskId = stack.pop();
            List<T> childTaskList = filterList(tasks, task -> StrUtil.equals(task.getParentTaskId(), taskId));
            // 2.2 如果非空,则添加到 stack 进一步递归
            if (CollUtil.isNotEmpty(childTaskList)) {
                stack.addAll(convertList(childTaskList, TaskInfo::getId));
                result.addAll(childTaskList);
            }
        }
        return result;
    }
    /**
     * 获得所有子任务列表
     *
     * @param parentTask 父任务
     * @return 所有子任务列表
     */
    private List<Task> getAllChildTaskList(Task parentTask) {
        List<Task> result = new ArrayList<>();
        // 1. 递归获取子级
        Stack<Task> stack = new Stack<>();
        stack.push(parentTask);
        // 2. 递归遍历
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            if (stack.isEmpty()) {
                break;
            }
            // 2.1 获取子任务们
            Task task = stack.pop();
            List<Task> childTaskList = getTaskListByParentTaskId(task.getId());
            // 2.2 如果非空,则添加到 stack 进一步递归
            if (CollUtil.isNotEmpty(childTaskList)) {
                stack.addAll(childTaskList);
                result.addAll(childTaskList);
            }
        }
        return result;
    }
    @Override
    public List<Task> getTaskListByParentTaskId(String parentTaskId) {
        String tableName = managementService.getTableName(TaskEntity.class);
        // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询
        String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}";
        return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list();
    }
    /**
     * 获取子任务个数
     *
     * @param parentTaskId 父任务 ID
     * @return 剩余子任务个数
     */
    private Long getTaskCountByParentTaskId(String parentTaskId) {
        String tableName = managementService.getTableName(TaskEntity.class);
        String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}";
        return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count();
    }
    /**
     * 获得任务根任务的父任务编号
     *
     * @param task 任务
     * @return 根任务的父任务编号
     */
    private String getTaskRootParentId(Task task) {
        if (task == null || task.getParentTaskId() == null) {
            return null;
        }
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            Task parentTask = getTask(task.getParentTaskId());
            if (parentTask == null) {
                return null;
            }
            if (parentTask.getParentTaskId() == null) {
                return parentTask.getId();
            }
            task = parentTask;
        }
        throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId()));
    }
    @Override
    public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) {
        return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceStartTime().asc().list();
    }
    @Override
    public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) {
        return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list();
    }
    /**
     * 判断指定用户,是否是当前任务的审批人
     *
     * @param userId 用户编号
     * @param task   任务
     * @return 是否
     */
    private boolean isAssignUserTask(Long userId, Task task) {
        Long assignee = NumberUtil.parseLong(task.getAssignee(), null);
        return ObjectUtil.equals(userId, assignee);
    }
    /**
     * 判断指定用户,是否是当前任务的拥有人
     *
     * @param userId 用户编号
     * @param task   任务
     * @return 是否
     */
    private boolean isOwnerUserTask(Long userId, Task task) {
        Long assignee = NumberUtil.parseLong(task.getOwner(), null);
        return ObjectUtil.equal(userId, assignee);
    }
    /**
     * 判断指定用户,是否是当前任务的加签人
     *
     * @param userId 用户 Id
     * @param task 任务
     * @return 是否
     */
    private boolean isAddSignUserTask(Long userId, Task task) {
        return (isAssignUserTask(userId, task) || isOwnerUserTask(userId, task))
                && BpmTaskSignTypeEnum.of(task.getScopeType()) != null;
    }
    // ========== Update 写入相关方法 ==========
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -183,11 +460,6 @@
        ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
        if (instance == null) {
            throw exception(PROCESS_INSTANCE_NOT_EXISTS);
        }
        // 2. 抄送用户
        if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) {
            processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId());
        }
        // 情况一:被委派的任务,不调用 complete 去完成任务
@@ -203,15 +475,17 @@
        }
        // 情况三:审批普通的任务。大多数情况下,都是这样
        // 3.1 更新 task 状态、原因
        // 2.1 更新 task 状态、原因
        updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason());
        // 3.2 添加评论
        // 2.2 添加评论
        taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
                BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
        // 3.3 调用 BPM complete 去完成任务
        // 2.3 调用 BPM complete 去完成任务
        // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
        if (CollUtil.isNotEmpty(reqVO.getVariables())) {
            Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
            // 修改表单的值需要存储到 ProcessInstance 变量
            runtimeService.setVariables(task.getProcessInstanceId(), variables);
            taskService.complete(task.getId(), variables, true);
        } else {
            taskService.complete(task.getId());
@@ -245,7 +519,7 @@
    /**
     * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务:
     *
     * <p>
     * 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批
     * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批
     *
@@ -278,11 +552,11 @@
            taskService.resolveTask(parentTaskId);
            // 3.1.2 更新流程任务 status
            updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus());
        // 3.2 情况二:处理向【向后】加签
            // 3.2 情况二:处理向【向后】加签
        } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) {
            // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成
            // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题
            Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
            Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS);
            if (ObjectUtil.notEqual(status, BpmTaskStatusEnum.APPROVING.getStatus())) {
                return;
            }
@@ -327,135 +601,59 @@
            throw exception(PROCESS_INSTANCE_NOT_EXISTS);
        }
        // 2.1 更新流程实例为不通过
        // 2.1 更新流程任务为不通过
        updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason());
        // 2.2 添加评论
        // 2.2 添加流程评论
        taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
                BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
        // 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过
        // 疑问:为什么要标记未通过呢?
        // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。
        if (task.getParentTaskId() != null) {
            String rootParentId = getTaskRootParentId(task);
            updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(),
                    BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过"));
            taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
                    BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过"));
        }
        // 3. 更新流程实例,审批不通过!
        processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason());
        // 3. 根据不同的 RejectHandler 处理策略
        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
        FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        // 3.1 情况一:驳回到指定的任务节点
        BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement);
        if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
            String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement);
            Assert.notNull(returnTaskId, "退回的节点不能为空");
            returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId())
                    .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason()));
            return;
        }
        // 3.2 情况二:直接结束,审批不通过
        processInstanceService.updateProcessInstanceReject(instance, reqVO.getReason()); // 标记不通过
        moveTaskToEnd(task.getProcessInstanceId()); // 结束流程
    }
    /**
     * 更新流程任务的 status 状态
     *
     * @param id    任务编号
     * @param id     任务编号
     * @param status 状态
     */
    private void updateTaskStatus(String id, Integer status) {
        taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_STATUS, status);
        taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_STATUS, status);
    }
    /**
     * 更新流程任务的 status 状态、reason 理由
     *
     * @param id 任务编号
     * @param id     任务编号
     * @param status 状态
     * @param reason 理由(审批通过、审批不通过的理由)
     */
    private void updateTaskStatusAndReason(String id, Integer status, String reason) {
        updateTaskStatus(id, status);
        taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_REASON, reason);
    }
    /**
     * 校验任务是否存在,并且是否是分配给自己的任务
     *
     * @param userId 用户 id
     * @param taskId task id
     */
    private Task validateTask(Long userId, String taskId) {
        Task task = validateTaskExist(taskId);
        if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) {
            throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
        }
        return task;
    }
    @Override
    public void updateTaskStatusWhenCreated(Task task) {
        Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
        if (status != null) {
            log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status);
            return;
        }
        updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus());
    }
    @Override
    public void updateTaskStatusWhenCanceled(String taskId) {
        Task task = getTask(taskId);
        // 1. 可能只是活动,不是任务,所以查询不到
        if (task == null) {
            log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId);
            return;
        }
        // 2. 更新 task 状态 + 原因
        Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
        if (BpmTaskStatusEnum.isEndStatus(status)) {
            log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status);
            return;
        }
        updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason());
        // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由
    }
    @Override
    public void updateTaskExtAssign(Task task) {
        // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                if (StrUtil.isEmpty(task.getAssignee())) {
                    return;
                }
                ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
                AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData();
                messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
            }
        });
    }
    private Task validateTaskExist(String id) {
        Task task = getTask(id);
        if (task == null) {
            throw exception(TASK_NOT_EXISTS);
        }
        return task;
    }
    @Override
    public Task getTask(String id) {
        return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult();
    }
    private HistoricTaskInstance getHistoricTask(String id) {
        return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult();
    }
    @Override
    public List<UserTask> getUserTaskListByReturn(String id) {
        // 1.1 校验当前任务 task 存在
        Task task = validateTaskExist(id);
        // 1.2 根据流程定义获取流程模型信息
        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
        FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        if (source == null) {
            throw exception(TASK_NOT_EXISTS);
        }
        // 2.1 查询该任务的前置任务节点的 key 集合
        List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null);
        if (CollUtil.isEmpty(previousUserList)) {
            return Collections.emptyList();
        }
        // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
        previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
        return previousUserList;
        taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_REASON, reason);
    }
    @Override
@@ -470,12 +668,12 @@
        FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(),
                reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
        // 2. 调用 Flowable 框架的回退逻辑
        // 2. 调用 Flowable 框架的退回逻辑
        returnTask(task, targetElement, reqVO);
    }
    /**
     * 回退流程节点时,校验目标任务节点是否可回退
     * 退回流程节点时,校验目标任务节点是否可退回
     *
     * @param sourceKey           当前任务节点 Key
     * @param targetKey           目标任务节点 key
@@ -484,7 +682,7 @@
     */
    private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {
        // 1.1 获取流程模型信息
        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId);
        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
        // 1.3 获取当前任务节点元素
        FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
        // 1.3 获取跳转的节点元素
@@ -493,7 +691,7 @@
            throw exception(TASK_TARGET_NODE_NOT_EXISTS);
        }
        // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回
        // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回
        if (!BpmnModelUtils.isSequentialReachable(source, target, null)) {
            throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR);
        }
@@ -501,10 +699,10 @@
    }
    /**
     * 执行回退逻辑
     * 执行退回逻辑
     *
     * @param currentTask   当前回退的任务
     * @param targetElement 需要回退到的目标任务
     * @param currentTask   当前退回的任务
     * @param targetElement 需要退回到的目标任务
     * @param reqVO         前端参数封装
     */
    public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
@@ -517,9 +715,9 @@
        List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
        List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId);
        // 2. 给当前要被回退的 task 数组,设置回退意见
        // 2. 给当前要被退回的 task 数组,设置退回意见
        taskList.forEach(task -> {
            // 需要排除掉,不需要设置回退意见的任务
            // 需要排除掉,不需要设置退回意见的任务
            if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
                return;
            }
@@ -530,7 +728,11 @@
            updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
        });
        // 3. 执行驳回
        // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过
        runtimeService.setVariable(currentTask.getProcessInstanceId(),
                String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE);
        // 4. 执行驳回
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(currentTask.getProcessInstanceId())
                .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
@@ -562,9 +764,7 @@
        taskService.setOwner(taskId, task.getAssignee());
        // 3.2 执行委派,将任务委派给 delegateUser
        taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
        // 3.3 更新 task 状态。
        // 为什么不更新原因?因为原因目前主要给审批通过、不通过时使用
        updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus());
        // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中
    }
    @Override
@@ -591,6 +791,35 @@
        // 3.2 执行转派(审批人),将任务转派给 assigneeUser
        // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!!
        taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString());
    }
    @Override
    public void moveTaskToEnd(String processInstanceId) {
        List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null);
        if (CollUtil.isEmpty(taskList)) {
            return;
        }
        // 1. 其它未结束的任务,直接取消
        // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢?
        // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景
        taskList.forEach(task -> {
            Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS);
            if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) {
                return;
            }
            processTaskCanceled(task.getId());
        });
        // 2. 终止流程
        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId());
        List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey));
        EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
        Assert.notNull(endEvent, "结束节点不能未空");
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(processInstanceId)
                .moveActivityIdsToSingleActivityId(activityIds, endEvent.getId())
                .changeState();
    }
    @Override
@@ -657,7 +886,7 @@
        List<Long> currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner
                Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
        if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) {
            List<AdminUserRespDTO> userList = adminUserApi.getUserList( CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())).getCheckedData();
            List<AdminUserRespDTO> userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())).getCheckedData();
            throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname)));
        }
        return taskEntity;
@@ -666,8 +895,8 @@
    /**
     * 创建加签子任务
     *
     * @param userIds 被加签的用户 ID
     * @param taskEntity        被加签的任务
     * @param userIds    被加签的用户 ID
     * @param taskEntity 被加签的任务
     */
    private void createSignTaskList(List<String> userIds, TaskEntityImpl taskEntity) {
        if (CollUtil.isEmpty(userIds)) {
@@ -696,7 +925,7 @@
        // 2.1 向前加签,设置审批人
        if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) {
            task.setAssignee(assignee);
        // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成
            // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成
        } else {
            task.setOwner(assignee);
        }
@@ -742,6 +971,11 @@
        handleParentTaskIfSign(task.getParentTaskId());
    }
    @Override
    public void copyTask(Long userId, BpmTaskCopyReqVO reqVO) {
        processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId());
    }
    /**
     * 校验任务是否能被减签
     *
@@ -763,61 +997,208 @@
        return task;
    }
    /**
     * 获得所有子任务列表
     *
     * @param parentTask 父任务
     * @return 所有子任务列表
     */
    private List<Task> getAllChildTaskList(Task parentTask) {
        List<Task> result = new ArrayList<>();
        // 1. 递归获取子级
        Stack<Task> stack = new Stack<>();
        stack.push(parentTask);
        // 2. 递归遍历
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            if (stack.isEmpty()) {
                break;
            }
            // 2.1 获取子任务们
            Task task = stack.pop();
            List<Task> childTaskList = getTaskListByParentTaskId(task.getId());
            // 2.2 如果非空,则添加到 stack 进一步递归
            if (CollUtil.isNotEmpty(childTaskList)) {
                stack.addAll(childTaskList);
                result.addAll(childTaskList);
            }
        }
        return result;
    }
    // ========== Event 事件相关方法 ==========
    @Override
    public List<Task> getTaskListByParentTaskId(String parentTaskId) {
        String tableName = managementService.getTableName(TaskEntity.class);
        // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询
        String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}";
        return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list();
    public void processTaskCreated(Task task) {
        // 1. 设置为待办中
        Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS);
        if (status != null) {
            log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status);
            return;
        }
        updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus());
        // 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过
        ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
        if (processInstance == null) {
            log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId());
            return;
        }
        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
        FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement);
        Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCompletion(int transactionStatus) {
                // 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以
                // 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时
                if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) {
                    return;
                }
                // TODO 芋艿:可以后续优化成 getSelf();
                // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝
                if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
                    // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝
                    if (!ObjectUtil.isAllEmpty(task.getAssignee(), task.getOwner())) {
                        return;
                    }
                    if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
                        SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
                                .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
                    } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) {
                        SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
                                .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason()));
                    }
                    // 特殊情况二:【自动审核】审批类型为自动通过、不通过
                } else {
                    if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) {
                        SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
                                .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason()));
                    } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
                        SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
                                .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason()));
                    }
                }
            }
        });
    }
    /**
     * 获取子任务个数
     *
     * @param parentTaskId 父任务 ID
     * @return 剩余子任务个数
     * 重要补充说明:该方法目前主要有两个情况会调用到:
     * <p>
     * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消
     * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消
     */
    private Long getTaskCountByParentTaskId(String parentTaskId) {
        String tableName = managementService.getTableName(TaskEntity.class);
        String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}";
        return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count();
    @Override
    public void processTaskCanceled(String taskId) {
        Task task = getTask(taskId);
        // 1. 可能只是活动,不是任务,所以查询不到
        if (task == null) {
            log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId);
            return;
        }
        // 2. 更新 task 状态 + 原因
        Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS);
        if (BpmTaskStatusEnum.isEndStatus(status)) {
            log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status);
            return;
        }
        updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason());
        // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由
    }
    @Override
    public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) {
        if (CollUtil.isEmpty(taskIds)) {
            return Collections.emptyMap();
    public void processTaskAssigned(Task task) {
        // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                if (StrUtil.isEmpty(task.getAssignee())) {
                    log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId());
                    return;
                }
                ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
                if (processInstance == null) {
                    log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId());
                    return;
                }
                // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
                if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
                    // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略
                    // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
                    Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
                            String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
                    if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
                        BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
                        if (bpmnModel == null) {
                            log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
                            return;
                        }
                        FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
                        Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
                        // 情况一:自动跳过
                        if (ObjectUtils.equalsAny(assignStartUserHandlerType,
                                BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
                            getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
                                    .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason()));
                            return;
                        }
                        // 情况二:转交给部门负责人审批
                        if (ObjectUtils.equalsAny(assignStartUserHandlerType,
                                BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) {
                            AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData();
                            Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId());
                            DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()).getCheckedData() : null;
                            Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId());
                            // 找不到部门负责人的情况下,自动审批通过
                            // noinspection DataFlowIssue
                            if (dept.getLeaderUserId() == null) {
                                getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
                                        .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason()));
                                return;
                            }
                            // 找得到部门负责人的情况下,修改负责人
                            if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) {
                                getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO()
                                        .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId())
                                        .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason()));
                                return;
                            }
                            // 如果部门负责人是自己,还是自己审批吧~
                        }
                    }
                }
                AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData();
                messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
            }
        });
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType) {
        ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
        if (processInstance == null) {
            log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId);
            return;
        }
        List<Task> tasks = taskService.createTaskQuery().taskIds(taskIds).list();
        return convertMap(tasks, Task::getId, Task::getName);
        List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey);
        // TODO 优化:未来需要考虑加签的情况
        if (CollUtil.isEmpty(taskList)) {
            log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey);
            return;
        }
        taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> {
            // 情况一:自动提醒
            if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType())) {
                messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO()
                        .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
                        .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee())));
                return;
            }
            // 情况二:自动同意
            if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.APPROVE.getType())) {
                approveTask(Long.parseLong(task.getAssignee()),
                        new BpmTaskApproveReqVO().setId(task.getId()).setReason(BpmReasonEnum.TIMEOUT_APPROVE.getReason()));
                return;
            }
            // 情况三:自动拒绝
            if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REJECT.getType())) {
                rejectTask(Long.parseLong(task.getAssignee()),
                        new BpmTaskRejectReqVO().setId(task.getId()).setReason(BpmReasonEnum.REJECT_TASK.getReason()));
            }
        }));
    }
    /**
     * 获得自身的代理对象,解决 AOP 生效问题
     *
     * @return 自己
     */
    private BpmTaskServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }
}
iailab-module-data/iailab-module-data-biz/db/mysql/tenant.sql
@@ -399,3 +399,13 @@
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT = '累计点表';
INSERT INTO `t_da_sequence_num` (`id`, `code`, `name`, `sequence_num`, `prefix`) VALUES ('8', 'POINT_L', '累计点编码', 100001, 'L');
INSERT INTO `iailab_plat_system`.`system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1673, 4, '累计点', 'CUMULATE', 'data_point_type', 0, '', '', '', '142', '2024-12-10 10:13:12', '142', '2024-12-10 10:13:12', b'0');
CREATE TABLE t_da_point_collect_status(
                           `id` VARCHAR(36) NOT NULL  COMMENT 'ID' ,
                           `point_no` VARCHAR(36) NOT NULL   COMMENT '测点编码',
                           `collect_value` VARCHAR(24)   COMMENT '采集值',
                           `collect_quality` VARCHAR(5)   COMMENT '采集质量',
                           `collect_time` DATETIME   COMMENT '采集时间' ,
                           PRIMARY KEY (id) USING BTREE,
                           UNIQUE KEY `uk_point_no` (`point_no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT = '测点采集状态表';
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/channel/http/collector/ihdb/HttpCollectorForIhd.java
@@ -12,6 +12,7 @@
import com.iailab.module.data.common.utils.HttpRequest;
import com.iailab.module.data.common.utils.TagUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
@@ -265,7 +266,12 @@
            StringBuilder tagSb = new StringBuilder();
            tagSb.append("[");
            for (int i = 0; i < params.size(); i++) {
                Map<String, Object> queryParams = new HashMap<>();
                if (StringUtils.isBlank(params.get(i)[1].toString()) ||
                        StringUtils.isBlank(params.get(i)[2].toString()) ||
                        StringUtils.isBlank(params.get(i)[3].toString())) {
                    continue;
                }
                Map<String, Object> queryParams = new HashMap<>(3);
                queryParams.put(N, params.get(i)[1]);
                queryParams.put(D, params.get(i)[2]);
                queryParams.put(P, params.get(i)[3]);
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/collection/PointCollector.java
@@ -3,10 +3,13 @@
import com.iailab.module.data.common.enums.DataSourceType;
import com.iailab.module.data.common.utils.R;
import com.iailab.module.data.channel.kio.collector.KingIOCollector;
import com.iailab.module.data.influxdb.pojo.InfluxPointValueDigPOJO;
import com.iailab.module.data.influxdb.pojo.InfluxPointValueSimPOJO;
import com.iailab.module.data.point.collection.handler.CalculateHandle;
import com.iailab.module.data.point.collection.handler.CumulateHandle;
import com.iailab.module.data.point.common.PointTypeEnum;
import com.iailab.module.data.point.dto.DaPointDTO;
import com.iailab.module.data.point.service.DaPointCollectStatusService;
import com.iailab.module.data.point.service.DaPointService;
import com.iailab.module.data.influxdb.pojo.InfluxPointValuePOJO;
import com.iailab.module.data.channel.modbus.collector.ModBusCollector;
@@ -16,7 +19,10 @@
import com.iailab.module.data.point.dto.DaPointWriteValueDTO;
import com.iailab.module.data.influxdb.service.InfluxDBService;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@@ -59,6 +65,9 @@
    @Resource
    private CumulateHandle cumulateHandle;
    @Autowired
    private DaPointCollectStatusService daPointCollectStatusService;
    /**
     * 采集
     *
@@ -89,13 +98,31 @@
            log.info("存入数据库");
            influxDBService.asyncWritePointValues(pointValues);
            log.info("更新采集状态");
            updateCollectStatus(pointValues, collectTime);
            log.info("采集完成");
        } catch (Exception ex)  {
        } catch (Exception ex) {
            log.info("采集异常!");
            ex.printStackTrace();
        }
    }
    private void updateCollectStatus(List<InfluxPointValuePOJO> pointValues, Date collectTime) {
        try {
            for (InfluxPointValuePOJO pointValue : pointValues) {
                if (pointValue instanceof InfluxPointValueSimPOJO) {
                    InfluxPointValueSimPOJO pvo = (InfluxPointValueSimPOJO) pointValue;
                    daPointCollectStatusService.recordStatus(pvo.getPoint(), pvo.getValue().toString(), collectTime);
                } else if (pointValue instanceof InfluxPointValueDigPOJO) {
                    InfluxPointValueDigPOJO pvo = (InfluxPointValueDigPOJO) pointValue;
                    daPointCollectStatusService.recordStatus(pvo.getPoint(), pvo.getValue().toString(), collectTime);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    public Map<String, Object> getCurrentValue(List<String> pointNos) {
        try {
            Map<String, Object> data = new HashMap<>();
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/collection/handler/MeasureHandle.java
@@ -133,9 +133,11 @@
    private Object handleData(DaPointDTO dto, Object value) {
        Object result = value;
        try {
            if (value == null) {
                return CommonConstant.BAD_VALUE;
            }
            if (DataTypeEnum.FLOAT.getCode().equals(dto.getDataType()) || DataTypeEnum.INT.getCode().equals(dto.getDataType())) {
                BigDecimal rawValue = new BigDecimal(value.toString());
                // 异常值处理
                if (rawValue.compareTo(maxValue) > 0 || rawValue.compareTo(minValue) < 0) {
                    rawValue = CommonConstant.BAD_VALUE;
@@ -157,7 +159,6 @@
            } else if (DataTypeEnum.BOOLEAN.getCode().equals(dto.getDataType())) {
                result = Boolean.parseBoolean(value.toString());
            }
        } catch (Exception ex) {
            log.warn("handleData异常,PointNo=" + dto.getPointNo());
            ex.printStackTrace();
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/dao/DaPointCollectStatusDao.java
对比新文件
@@ -0,0 +1,16 @@
package com.iailab.module.data.point.dao;
import com.iailab.framework.common.dao.BaseDao;
import com.iailab.framework.tenant.core.db.dynamic.TenantDS;
import com.iailab.module.data.point.entity.DaPointCollectStatusEntity;
import org.apache.ibatis.annotations.Mapper;
/**
 * @author PanZhibao
 * @Description
 * @createTime 2024年12月13日
 */
@TenantDS
@Mapper
public interface DaPointCollectStatusDao extends BaseDao<DaPointCollectStatusEntity> {
}
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/dto/DaPointDTO.java
@@ -1,5 +1,6 @@
package com.iailab.module.data.point.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.iailab.framework.common.validation.group.AddGroup;
import com.iailab.framework.common.validation.group.UpdateGroup;
import com.iailab.framework.excel.core.annotations.DictFormat;
@@ -127,4 +128,15 @@
    @Schema(description = "累计点")
    private DaCumulatePointDTO cumulatePoint;
    @Schema(description = "采集值")
    private String collectValue;
    @Schema(description = "采集质量")
    private String collectQuality;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Schema(description = "采集时间")
    private Date collectTime;
}
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/entity/DaPointCollectStatusEntity.java
对比新文件
@@ -0,0 +1,46 @@
package com.iailab.module.data.point.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * @author PanZhibao
 * @Description
 * @createTime 2024年12月13日
 */
@Data
@TableName("t_da_point_collect_status")
public class DaPointCollectStatusEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * id
     */
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    /**
     * 测点编码
     */
    private String pointNo;
    /**
     * 采集值
     */
    private String collectValue;
    /**
     * 采集质量
     */
    private String collectQuality;
    /**
     * 采集时间
     */
    private Date collectTime;
}
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/service/DaPointCollectStatusService.java
对比新文件
@@ -0,0 +1,18 @@
package com.iailab.module.data.point.service;
import com.iailab.framework.common.service.BaseService;
import com.iailab.module.data.point.entity.DaPointCollectStatusEntity;
import org.springframework.scheduling.annotation.Async;
import java.util.Date;
/**
 * @author PanZhibao
 * @Description
 * @createTime 2024年12月13日
 */
public interface DaPointCollectStatusService extends BaseService<DaPointCollectStatusEntity> {
    @Async
    void recordStatus(String pointNo, String collectValue, Date collectTime);
}
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/point/service/impl/DaPointCollectStatusServiceImpl.java
对比新文件
@@ -0,0 +1,43 @@
package com.iailab.module.data.point.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.iailab.framework.common.service.impl.BaseServiceImpl;
import com.iailab.module.data.common.enums.DataQualityEnum;
import com.iailab.module.data.point.dao.DaPointCollectStatusDao;
import com.iailab.module.data.point.entity.DaPointCollectStatusEntity;
import com.iailab.module.data.point.service.DaPointCollectStatusService;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.UUID;
/**
 * @author PanZhibao
 * @Description
 * @createTime 2024年12月13日
 */
@Service
public class DaPointCollectStatusServiceImpl extends BaseServiceImpl<DaPointCollectStatusDao, DaPointCollectStatusEntity>
        implements DaPointCollectStatusService {
    public void recordStatus(String pointNo, String collectValue, Date collectTime) {
        QueryWrapper<DaPointCollectStatusEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("point_no", pointNo);
        DaPointCollectStatusEntity entity = baseDao.selectOne(queryWrapper);
        if (entity == null) {
            entity = new DaPointCollectStatusEntity();
            entity.setId(UUID.randomUUID().toString());
            entity.setPointNo(pointNo);
            entity.setCollectValue(collectValue);
            entity.setCollectQuality(DataQualityEnum.getEumByValue(collectValue).getCode());
            entity.setCollectTime(collectTime);
            baseDao.insert(entity);
        } else {
            entity.setCollectValue(collectValue);
            entity.setCollectQuality(DataQualityEnum.getEumByValue(collectValue).getCode());
            entity.setCollectTime(collectTime);
            baseDao.updateById(entity);
        }
    }
}
iailab-module-data/iailab-module-data-biz/src/main/resources/application.yaml
@@ -184,6 +184,7 @@
      - t_plan_item_category
      - t_plan_item
      - t_da_cumulate_point
      - t_da_point_collect_status
  app:
    app-key: data
    app-secret: 85b0df7edc3df3611913df34ed695011
iailab-module-data/iailab-module-data-biz/src/main/resources/mapper/point/DaPointDao.xml
@@ -26,9 +26,13 @@
      t3.source_name,
      t2.tag_no,
      t2.dimension,
      t2.value_type
      t2.value_type,
      t6.collect_value,
      t6.collect_quality,
      t6.collect_time
      from t_da_point t1
      left join t_da_measure_point t2 on t2.point_id = t1.id
      left join t_da_point_collect_status t6 on t6.point_no = t1.point_no
      left join (
        select id source_id,server_name source_name
        from t_channel_opcua_device
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/api/McsApiImpl.java
@@ -303,9 +303,6 @@
            return result;
        }
        String resultIndex = chartParams.get(CommonConstant.RESULT_INDEX);
        if (resultIndex == null) {
            return result;
        }
        ItemVO predictItem = mmPredictItemService.getItemByItemNo(itemCode);
        if (predictItem == null || predictItem.getLastTime() == null) {
@@ -327,7 +324,7 @@
        List<String> categories = DateUtils.getTimeScale(startTime, endTime, predictItem.getGranularity(), timeFormat);
        List<String> legend = new ArrayList<>();
        MmItemOutputEntity outPut = mmItemOutputService.getByItemid(predictItem.getId(), resultStr, Integer.parseInt(resultIndex));
        MmItemOutputEntity outPut = mmItemOutputService.getByItemid(predictItem.getId(), resultStr, resultIndex);
        PreDataViewRespDTO dataView = new PreDataViewRespDTO();
        dataView.setItemId(predictItem.getId());
        dataView.setItemName(predictItem.getItemName());
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/dao/MmModelArithSettingsDao.java
@@ -4,6 +4,7 @@
import com.iailab.framework.tenant.core.db.dynamic.TenantDS;
import com.iailab.module.model.mcs.pre.entity.MmModelArithSettingsEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@@ -30,4 +31,6 @@
     * @param list
     */
    void insertList(List<MmModelArithSettingsEntity> list);
    void updatePyFile(@Param("likeValue") String likeValue,@Param("value") String value);
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmItemOutputService.java
@@ -22,7 +22,7 @@
    List<MmItemOutputEntity> getByItemid(String itemId);
    MmItemOutputEntity getByItemid(String itemid, String resultstr, Integer resultIndex);
    MmItemOutputEntity getByItemid(String itemid, String resultstr, String resultIndex);
    void deleteByItemId(String itemId);
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmModelArithSettingsService.java
@@ -13,4 +13,6 @@
    void saveList(List<MmModelArithSettingsEntity> list);
    List<MmModelArithSettingsEntity> getByModelId(String modelId);
    void updatePyFile(String pyModule, String fileName);
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmItemOutputServiceImpl.java
@@ -6,6 +6,7 @@
import com.iailab.module.model.mcs.pre.dto.MmItemOutputDTO;
import com.iailab.module.model.mcs.pre.entity.MmItemOutputEntity;
import com.iailab.module.model.mcs.pre.service.MmItemOutputService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@@ -74,11 +75,11 @@
    }
    @Override
    public MmItemOutputEntity getByItemid(String itemid, String resultstr, Integer resultIndex) {
    public MmItemOutputEntity getByItemid(String itemid, String resultstr, String resultIndex) {
        QueryWrapper<MmItemOutputEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("itemid", itemid)
                .eq("resultstr", resultstr)
                .eq("result_index", resultIndex);
                .eq(StringUtils.isNotBlank(resultIndex), "result_index", resultIndex);
        return mmItemOutputDao.selectOne(queryWrapper);
    }
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmModelArithSettingsServiceImpl.java
@@ -53,4 +53,9 @@
        modelIdMap.put(modelId, list);
        return list;
    }
    @Override
    public void updatePyFile(String pyModule, String fileName) {
        baseMapper.updatePyFile(pyModule + "." + fileName.substring(0,fileName.lastIndexOf("_")),pyModule + "." + fileName);
    }
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/dao/StScheduleModelSettingDao.java
@@ -4,6 +4,7 @@
import com.iailab.framework.tenant.core.db.dynamic.TenantDS;
import com.iailab.module.model.mcs.sche.entity.StScheduleModelSettingEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * @author PanZhibao
@@ -13,4 +14,5 @@
@TenantDS
@Mapper
public interface StScheduleModelSettingDao extends BaseMapperX<StScheduleModelSettingEntity> {
    void updatePyFile(@Param("likeValue") String likeValue, @Param("value") String value);
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleModelSettingService.java
@@ -18,4 +18,6 @@
    void deleteByModelId(String modelId);
    void saveList(String modelId, List<StScheduleModelSettingSaveReqVO> saveList);
    void updatePyFile(String pyModule, String fileName);
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleModelSettingServiceImpl.java
@@ -53,4 +53,9 @@
            baseDao.insert(entity);
        }
    }
    @Override
    public void updatePyFile(String pyModule, String fileName) {
        baseDao.updatePyFile(pyModule + "." + fileName.substring(0,fileName.lastIndexOf("_")),pyModule + "." + fileName);
    }
}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java
@@ -14,6 +14,8 @@
import com.iailab.framework.security.core.util.SecurityFrameworkUtils;
import com.iailab.framework.tenant.core.context.TenantContextHolder;
import com.iailab.module.infra.api.config.ConfigApi;
import com.iailab.module.model.mcs.pre.service.MmModelArithSettingsService;
import com.iailab.module.model.mcs.sche.service.StScheduleModelSettingService;
import com.iailab.module.model.mpk.common.MdkConstant;
import com.iailab.module.model.mpk.common.utils.CmdUtils;
import com.iailab.module.model.mpk.common.utils.DllUtils;
@@ -67,6 +69,12 @@
    @Autowired
    private ProjectPackageHistoryModelService projectPackageHistoryModelService;
    @Autowired
    private MmModelArithSettingsService mmModelArithSettingsService;
    @Autowired
    private StScheduleModelSettingService stScheduleModelSettingService;
    @Autowired
    private ConfigApi configApi;
@@ -204,6 +212,11 @@
            }
        }
        // 修改预测项pyFile参数
        mmModelArithSettingsService.updatePyFile(dto.getPyModule(),fileName);
        // 修改调度项pyFile参数
        stScheduleModelSettingService.updatePyFile(dto.getPyModule(),fileName);
        MpkFileEntity entity = ConvertUtils.sourceToTarget(dto, MpkFileEntity.class);
        entity.setUpdater(SecurityFrameworkUtils.getLoginUserId());
        entity.setUpdateDate(new Date());
iailab-module-model/iailab-module-model-biz/src/main/resources/application-dev.yaml
iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/MmModelArithSettings.xml
@@ -2,6 +2,9 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.iailab.module.model.mcs.pre.dao.MmModelArithSettingsDao">
    <update id="updatePyFile">
        update t_mm_model_arith_settings set value = #{value} where `key` = 'pyFile' and `value` like CONCAT(#{likeValue},'%')
    </update>
    <select id="getMmModelArithSettings" resultType="com.iailab.module.model.mcs.pre.entity.MmModelArithSettingsEntity"
            parameterType="map">
        SELECT
iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/StScheduleModelSettingDao.xml
对比新文件
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.iailab.module.model.mcs.sche.dao.StScheduleModelSettingDao">
    <update id="updatePyFile">
        update t_st_schedule_model_setting set value = #{value} where `key` = 'pyFile' and `value` like CONCAT(#{likeValue},'%')
    </update>
</mapper>
iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm
@@ -23,7 +23,6 @@
        jstring keyJString = env->NewStringUTF("pyFile");
        jobject javaValueObj = env->CallObjectMethod(settings, getMID, keyJString);
        const char* strValue = env->GetStringUTFChars((jstring)javaValueObj, NULL);
        cout << strValue << endl;
        PyObject* pModule = create_py_module(strValue);
        /*PyObject* pModule = create_py_module("${pyModule}.${pyName}");*/
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java
@@ -220,6 +220,7 @@
            LambdaQueryWrapperX<MenuDO> menuWrapper = new LambdaQueryWrapperX<>();
            menuWrapper.eq(MenuDO::getAppId, app.getId());
            menuWrapper.eq(MenuDO::getType, MenuTypeEnum.DIR.getType());
            menuWrapper.eq(MenuDO::getParentId, 0);
            MenuDO menu = menuMapper.selectOne(menuWrapper);
            TenantDO tenantDO = tenantMapper.selectById(app.getTenantId());
            if(ObjectUtils.isNotEmpty(menu) && ObjectUtils.isNotEmpty(tenantDO)) {