From fd97e608cbd2530b1babfe6f924e53b95b5e9d83 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Mon, 29 Sep 2025 09:36:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=88=86=20bpm=20=E5=88=B0=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E7=9A=84=E5=BA=93=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 75 + deployment.yaml | 362 +++ pom.xml | 258 ++ zt-module-bpm/pom.xml | 27 + zt-module-bpm/zt-module-bpm-api/pom.xml | 47 + .../bpm/api/definition/BpmCategoryApi.java | 55 + .../module/bpm/api/definition/BpmFormApi.java | 50 + .../bpm/api/definition/BpmUserGroupApi.java | 29 + .../definition/dto/BpmCategoryPageReqDTO.java | 32 + .../definition/dto/BpmCategoryRespDTO.java | 33 + .../definition/dto/BpmCategorySaveReqDTO.java | 37 + .../api/definition/dto/BpmFormPageReqDTO.java | 18 + .../api/definition/dto/BpmFormRespDTO.java | 33 + .../api/definition/dto/BpmFormSaveReqDTO.java | 32 + .../definition/dto/BpmUserGroupRespDTO.java | 31 + .../event/BpmProcessInstanceStatusEvent.java | 41 + ...BpmProcessInstanceStatusEventListener.java | 34 + .../zt/plat/module/bpm/api/package-info.java | 4 + .../bpm/api/task/BpmProcessInstanceApi.java | 67 + .../plat/module/bpm/api/task/BpmTaskApi.java | 53 + .../api/task/dto/BpmApprovalDetailReqDTO.java | 40 + .../task/dto/BpmApprovalDetailRespDTO.java | 110 + .../task/dto/BpmProcessDefinitionRespDTO.java | 55 + ...pmProcessInstanceBpmnModelViewRespDTO.java | 41 + .../dto/BpmProcessInstanceCancelReqDTO.java | 19 + .../dto/BpmProcessInstanceCreateReqDTO.java | 36 + .../dto/BpmProcessInstancePageReqDTO.java | 51 + .../task/dto/BpmProcessInstanceRespDTO.java | 92 + .../api/task/dto/BpmSimpleModelNodeDTO.java | 31 + .../api/task/dto/BpmTaskApproveReqDTO.java | 33 + .../bpm/api/task/dto/BpmTaskPageReqDTO.java | 29 + .../bpm/api/task/dto/BpmTaskRejectReqDTO.java | 18 + .../bpm/api/task/dto/BpmTaskRespDTO.java | 133 ++ .../bpm/api/task/dto/UserSimpleDTO.java | 25 + .../plat/module/bpm/enums/ApiConstants.java | 23 + .../module/bpm/enums/DictTypeConstants.java | 10 + .../module/bpm/enums/ErrorCodeConstants.java | 87 + .../definition/BpmAutoApproveTypeEnum.java | 32 + .../definition/BpmBoundaryEventTypeEnum.java | 27 + ...ildProcessMultiInstanceSourceTypeEnum.java | 37 + ...BpmChildProcessStartUserEmptyTypeEnum.java | 36 + .../BpmChildProcessStartUserTypeEnum.java | 35 + .../definition/BpmDelayTimerTypeEnum.java | 31 + .../definition/BpmFieldPermissionEnum.java | 33 + .../BpmHttpRequestParamTypeEnum.java | 31 + .../definition/BpmModelFormTypeEnum.java | 32 + .../enums/definition/BpmModelTypeEnum.java | 31 + .../BpmProcessListenerTypeEnum.java | 21 + .../BpmProcessListenerValueTypeEnum.java | 22 + .../BpmSimpleModeConditionTypeEnum.java | 36 + .../BpmSimpleModelNodeTypeEnum.java | 70 + .../enums/definition/BpmTriggerTypeEnum.java | 46 + .../BpmUserTaskApproveMethodEnum.java | 47 + .../BpmUserTaskApproveTypeEnum.java | 31 + ...BpmUserTaskAssignEmptyHandlerTypeEnum.java | 33 + ...serTaskAssignStartUserHandlerTypeEnum.java | 31 + .../BpmUserTaskRejectHandlerTypeEnum.java | 35 + .../BpmUserTaskTimeoutHandlerTypeEnum.java | 32 + .../bpm/enums/message/BpmMessageEnum.java | 27 + .../bpm/enums/task/BpmCommentTypeEnum.java | 46 + .../task/BpmProcessInstanceStatusEnum.java | 80 + .../module/bpm/enums/task/BpmReasonEnum.java | 52 + .../bpm/enums/task/BpmTaskSignTypeEnum.java | 47 + .../bpm/enums/task/BpmTaskStatusEnum.java | 100 + zt-module-bpm/zt-module-bpm-server/Dockerfile | 19 + zt-module-bpm/zt-module-bpm-server/pom.xml | 142 ++ .../druid/pool/DruidPooledStatement.java | 781 ++++++ .../plat/module/bpm/BpmServerApplication.java | 30 + .../api/definition/BpmCategoryApiImpl.java | 79 + .../bpm/api/definition/BpmFormApiImpl.java | 76 + .../api/definition/BpmUserGroupApiImpl.java | 41 + .../zt/plat/module/bpm/api/package-info.java | 4 + .../api/task/BpmProcessInstanceApiImpl.java | 150 ++ .../module/bpm/api/task/BpmTaskApiImpl.java | 203 ++ .../admin/base/dept/DeptSimpleBaseVO.java | 15 + .../controller/admin/base/package-info.java | 4 + .../admin/base/user/UserSimpleBaseVO.java | 22 + .../definition/BpmCategoryController.java | 95 + .../admin/definition/BpmFormController.java | 83 + .../admin/definition/BpmModelController.java | 200 ++ .../BpmProcessDefinitionController.java | 133 ++ .../BpmProcessExpressionController.java | 73 + .../BpmProcessListenerController.java | 73 + .../definition/BpmUserGroupController.java | 83 + .../vo/category/BpmCategoryPageReqVO.java | 32 + .../vo/category/BpmCategoryRespVO.java | 33 + .../vo/category/BpmCategorySaveReqVO.java | 37 + .../BpmProcessExpressionPageReqVO.java | 33 + .../BpmProcessExpressionRespVO.java | 30 + .../BpmProcessExpressionSaveReqVO.java | 27 + .../definition/vo/form/BpmFormFieldVO.java | 24 + .../definition/vo/form/BpmFormPageReqVO.java | 14 + .../definition/vo/form/BpmFormRespVO.java | 39 + .../definition/vo/form/BpmFormSaveReqVO.java | 35 + .../vo/group/BpmUserGroupPageReqVO.java | 28 + .../vo/group/BpmUserGroupRespVO.java | 31 + .../vo/group/BpmUserGroupSaveReqVO.java | 31 + .../listener/BpmProcessListenerPageReqVO.java | 30 + .../vo/listener/BpmProcessListenerRespVO.java | 36 + .../listener/BpmProcessListenerSaveReqVO.java | 39 + .../vo/model/BpmModeUpdateBpmnReqVO.java | 19 + .../vo/model/BpmModelMetaInfoVO.java | 180 ++ .../definition/vo/model/BpmModelRespVO.java | 57 + .../vo/model/BpmModelSaveReqVO.java | 34 + .../vo/model/BpmModelUpdateStateReqVO.java | 19 + .../vo/model/simple/BpmSimpleModelNodeVO.java | 526 +++++ .../simple/BpmSimpleModelUpdateReqVO.java | 23 + .../BpmProcessDefinitionPageReqVO.java | 14 + .../process/BpmProcessDefinitionRespVO.java | 71 + .../admin/oa/BpmOALeaveController.http | 12 + .../admin/oa/BpmOALeaveController.java | 62 + .../bpm/controller/admin/oa/package-info.java | 5 + .../admin/oa/vo/BpmOALeaveCreateReqVO.java | 43 + .../admin/oa/vo/BpmOALeavePageReqVO.java | 29 + .../admin/oa/vo/BpmOALeaveRespVO.java | 36 + .../task/BpmProcessInstanceController.http | 16 + .../task/BpmProcessInstanceController.java | 202 ++ .../BpmProcessInstanceCopyController.java | 89 + .../admin/task/BpmTaskController.java | 252 ++ .../task/vo/activity/BpmActivityRespVO.java | 25 + .../vo/cc/BpmProcessInstanceCopyRespVO.java | 48 + .../vo/instance/BpmApprovalDetailReqVO.java | 40 + .../vo/instance/BpmApprovalDetailRespVO.java | 112 + ...BpmProcessInstanceBpmnModelViewRespVO.java | 43 + .../BpmProcessInstanceCancelReqVO.java | 19 + .../BpmProcessInstanceCopyPageReqVO.java | 23 + .../BpmProcessInstanceCreateReqVO.java | 24 + .../instance/BpmProcessInstancePageReqVO.java | 45 + .../vo/instance/BpmProcessInstanceRespVO.java | 86 + .../task/vo/task/BpmTaskApproveReqVO.java | 33 + .../admin/task/vo/task/BpmTaskCopyReqVO.java | 23 + .../task/vo/task/BpmTaskDelegateReqVO.java | 24 + .../admin/task/vo/task/BpmTaskPageReqVO.java | 28 + .../task/vo/task/BpmTaskRejectReqVO.java | 18 + .../admin/task/vo/task/BpmTaskRespVO.java | 130 + .../task/vo/task/BpmTaskReturnReqVO.java | 23 + .../task/vo/task/BpmTaskSignCreateReqVO.java | 29 + .../task/vo/task/BpmTaskSignDeleteReqVO.java | 19 + .../task/vo/task/BpmTaskTransferReqVO.java | 24 + .../bpm/controller/app/package-info.java | 4 + .../module/bpm/controller/package-info.java | 6 + .../convert/definition/BpmModelConvert.java | 132 ++ .../BpmProcessDefinitionConvert.java | 99 + .../convert/message/BpmMessageConvert.java | 21 + .../plat/module/bpm/convert/package-info.java | 6 + .../task/BpmProcessInstanceConvert.java | 298 +++ .../task/BpmProcessInstanceDTOConvert.java | 67 + .../bpm/convert/task/BpmTaskConvert.java | 316 +++ ...道 Spring Boot 对象转换 MapStruct 入门》.md | 1 + .../dataobject/definition/BpmCategoryDO.java | 54 + .../dal/dataobject/definition/BpmFormDO.java | 57 + .../BpmProcessDefinitionInfoDO.java | 219 ++ .../definition/BpmProcessExpressionDO.java | 45 + .../definition/BpmProcessListenerDO.java | 74 + .../dataobject/definition/BpmUserGroupDO.java | 52 + .../bpm/dal/dataobject/oa/BpmOALeaveDO.java | 78 + .../task/BpmProcessInstanceCopyDO.java | 98 + .../dal/mysql/category/BpmCategoryMapper.java | 46 + .../dal/mysql/definition/BpmFormMapper.java | 25 + .../BpmProcessDefinitionInfoMapper.java | 27 + .../BpmProcessExpressionMapper.java | 26 + .../definition/BpmProcessListenerMapper.java | 27 + .../mysql/definition/BpmUserGroupMapper.java | 32 + .../bpm/dal/mysql/oa/BpmOALeaveMapper.java | 29 + .../task/BpmProcessInstanceCopyMapper.java | 25 + .../bpm/dal/redis/BpmProcessIdRedisDAO.java | 61 + .../bpm/dal/redis/RedisKeyConstants.java | 15 + .../config/BpmFlowableConfiguration.java | 95 + .../behavior/BpmActivityBehaviorFactory.java | 44 + .../BpmParallelMultiInstanceBehavior.java | 91 + .../BpmSequentialMultiInstanceBehavior.java | 95 + .../behavior/BpmUserTaskActivityBehavior.java | 86 + .../candidate/BpmTaskCandidateInvoker.java | 207 ++ .../candidate/BpmTaskCandidateStrategy.java | 85 + .../BpmTaskAssignLeaderExpression.java | 79 + .../BpmTaskAssignStartUserExpression.java | 36 + ...actBpmTaskCandidateDeptLeaderStrategy.java | 95 + ...askCandidateApproveUserSelectStrategy.java | 78 + ...mTaskCandidateDeptLeaderMultiStrategy.java | 45 + .../BpmTaskCandidateDeptLeaderStrategy.java | 45 + .../BpmTaskCandidateDeptMemberStrategy.java | 48 + ...idateStartUserDeptLeaderMultiStrategy.java | 70 + ...kCandidateStartUserDeptLeaderStrategy.java | 71 + ...mTaskCandidateStartUserSelectStrategy.java | 73 + ...pmTaskCandidateFormDeptLeaderStrategy.java | 56 + .../BpmTaskCandidateFormUserStrategy.java | 47 + .../BpmTaskCandidateAssignEmptyStrategy.java | 73 + .../BpmTaskCandidateExpressionStrategy.java | 58 + .../user/BpmTaskCandidateGroupStrategy.java | 46 + .../user/BpmTaskCandidatePostStrategy.java | 48 + .../user/BpmTaskCandidateRoleStrategy.java | 43 + .../BpmTaskCandidateStartUserStrategy.java | 57 + .../user/BpmTaskCandidateUserStrategy.java | 39 + ...riableConvertByTypeExpressionFunction.java | 32 + .../enums/BpmTaskCandidateStrategyEnum.java | 59 + .../core/enums/BpmnModelConstants.java | 146 ++ .../core/enums/BpmnVariableConstants.java | 99 + .../BpmProcessInstanceEventPublisher.java | 24 + .../core/listener/BpmCopyTaskDelegate.java | 47 + .../BpmProcessInstanceEventListener.java | 54 + .../core/listener/BpmTaskEventListener.java | 125 + .../core/listener/BpmTriggerTaskDelegate.java | 55 + .../DemoDelegateClassExecutionListener.java | 21 + ...moDelegateExpressionExecutionListener.java | 23 + ...DemoSpringExpressionExecutionListener.java | 21 + .../task/DemoDelegateClassTaskListener.java | 20 + .../DemoDelegateExpressionTaskListener.java | 22 + .../DemoSpringExpressionTaskListener.java | 20 + .../core/util/BpmHttpRequestUtils.java | 158 ++ .../flowable/core/util/BpmnModelUtils.java | 1025 ++++++++ .../flowable/core/util/FlowableUtils.java | 362 +++ .../flowable/core/util/SimpleModelUtils.java | 1007 ++++++++ .../module/bpm/framework/package-info.java | 6 + .../rpc/config/RpcConfiguration.java | 17 + .../bpm/framework/rpc/package-info.java | 4 + .../config/SecurityConfiguration.java | 40 + .../framework/security/core/package-info.java | 4 + .../web/config/BpmWebConfiguration.java | 30 + .../framework/web/core/FlowableWebFilter.java | 36 + .../bpm/framework/web/package-info.java | 4 + .../com/zt/plat/module/bpm/package-info.java | 12 + .../definition/BpmCategoryService.java | 92 + .../definition/BpmCategoryServiceImpl.java | 130 + .../service/definition/BpmFormService.java | 85 + .../definition/BpmFormServiceImpl.java | 114 + .../service/definition/BpmModelService.java | 134 ++ .../definition/BpmModelServiceImpl.java | 432 ++++ .../BpmProcessDefinitionService.java | 181 ++ .../BpmProcessDefinitionServiceImpl.java | 248 ++ .../BpmProcessExpressionService.java | 54 + .../BpmProcessExpressionServiceImpl.java | 70 + .../definition/BpmProcessListenerService.java | 54 + .../BpmProcessListenerServiceImpl.java | 102 + .../definition/BpmUserGroupService.java | 82 + .../definition/BpmUserGroupServiceImpl.java | 107 + .../definition/dto/BpmFormFieldRespDTO.java | 25 + .../dto/BpmModelMetaInfoRespDTO.java | 46 + .../dto/BpmProcessDefinitionCreateReqDTO.java | 81 + .../service/message/BpmMessageService.java | 46 + .../message/BpmMessageServiceImpl.java | 79 + ...eSendWhenProcessInstanceApproveReqDTO.java | 26 + ...geSendWhenProcessInstanceRejectReqDTO.java | 32 + .../BpmMessageSendWhenTaskCreatedReqDTO.java | 45 + .../BpmMessageSendWhenTaskTimeoutReqDTO.java | 41 + .../bpm/service/oa/BpmOALeaveService.java | 52 + .../bpm/service/oa/BpmOALeaveServiceImpl.java | 89 + .../oa/listener/BpmOALeaveStatusListener.java | 33 + .../task/BpmProcessInstanceCopyService.java | 60 + .../BpmProcessInstanceCopyServiceImpl.java | 96 + .../task/BpmProcessInstanceService.java | 191 ++ .../task/BpmProcessInstanceServiceImpl.java | 964 ++++++++ .../bpm/service/task/BpmTaskService.java | 316 +++ .../bpm/service/task/BpmTaskServiceImpl.java | 1535 ++++++++++++ .../listener/BpmCallActivityListener.java | 96 + .../task/listener/BpmUserTaskListener.java | 59 + .../bpm/service/task/trigger/BpmTrigger.java | 30 + .../trigger/form/BpmFormDeleteTrigger.java | 73 + .../trigger/form/BpmFormUpdateTrigger.java | 66 + .../http/BpmAbstractHttpRequestTrigger.java | 14 + .../trigger/http/BpmHttpCallbackTrigger.java | 51 + .../http/BpmSyncHttpRequestTrigger.java | 46 + .../liquibase/database/core/DmDatabase.java | 546 +++++ .../liquibase/datatype/core/BooleanType.java | 149 ++ .../impl/AbstractEngineConfiguration.java | 2094 +++++++++++++++++ .../main/resources/META-INF/package-info.md | 1 + .../services/liquibase.database.Database | 20 + .../src/main/resources/application-dev.yaml | 94 + .../src/main/resources/application-local.yaml | 111 + .../src/main/resources/application.yaml | 150 ++ .../src/main/resources/logback-spring.xml | 76 + .../BpmTaskCandidateInvokerTest.java | 274 +++ .../BpmTaskAssignLeaderExpressionTest.java | 107 + ...kCandidateDeptLeaderMultiStrategyTest.java | 45 + ...pmTaskCandidateDeptLeaderStrategyTest.java | 44 + ...pmTaskCandidateDeptMemberStrategyTest.java | 47 + ...eStartUserDeptLeaderMultiStrategyTest.java | 85 + ...didateStartUserDeptLeaderStrategyTest.java | 85 + ...kCandidateStartUserSelectStrategyTest.java | 68 + ...mTaskCandidateAssignEmptyStrategyTest.java | 88 + ...pmTaskCandidateExpressionStrategyTest.java | 61 + .../BpmTaskCandidateGroupStrategyTest.java | 44 + .../BpmTaskCandidatePostStrategyTest.java | 48 + .../BpmTaskCandidateRoleStrategyTest.java | 44 + ...BpmTaskCandidateStartUserStrategyTest.java | 56 + .../BpmTaskCandidateUserStrategyTest.java | 31 + .../category/BpmCategoryServiceImplTest.java | 136 ++ .../definition/BpmFormServiceTest.java | 144 ++ .../definition/BpmUserGroupServiceTest.java | 130 + .../test/resources/application-unit-test.yaml | 45 + .../src/test/resources/logback.xml | 4 + .../src/test/resources/sql/clean.sql | 3 + .../src/test/resources/sql/create_tables.sql | 43 + 292 files changed, 26715 insertions(+) create mode 100644 .gitignore create mode 100644 deployment.yaml create mode 100644 pom.xml create mode 100644 zt-module-bpm/pom.xml create mode 100644 zt-module-bpm/zt-module-bpm-api/pom.xml create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmCategoryApi.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmFormApi.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmUserGroupApi.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategoryPageReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategoryRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategorySaveReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormPageReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormSaveReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmUserGroupRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/event/BpmProcessInstanceStatusEvent.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/event/BpmProcessInstanceStatusEventListener.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApi.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmTaskApi.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmApprovalDetailReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmApprovalDetailRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessDefinitionRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceBpmnModelViewRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceCancelReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstancePageReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmSimpleModelNodeDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskApproveReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskPageReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRejectReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/UserSimpleDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/ApiConstants.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/DictTypeConstants.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/ErrorCodeConstants.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmAutoApproveTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmBoundaryEventTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessStartUserEmptyTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessStartUserTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmDelayTimerTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmFieldPermissionEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmHttpRequestParamTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmModelFormTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmModelTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmProcessListenerTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmProcessListenerValueTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmSimpleModeConditionTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmTriggerTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskRejectHandlerTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/message/BpmMessageEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmCommentTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmReasonEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmTaskSignTypeEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmTaskStatusEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-server/Dockerfile create mode 100644 zt-module-bpm/zt-module-bpm-server/pom.xml create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmCategoryApiImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmFormApiImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmUserGroupApiImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApiImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmTaskApiImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmCategoryController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmFormController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmModelController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessExpressionController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessListenerController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmUserGroupController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategoryPageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategoryRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategorySaveReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionPageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionSaveReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormFieldVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormSaveReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSaveReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerPageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerSaveReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/BpmOALeaveController.http create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/BpmOALeaveController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.http create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmTaskController.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskSignCreateReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskSignDeleteReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskTransferReqVO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/app/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/definition/BpmModelConvert.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/definition/BpmProcessDefinitionConvert.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/message/BpmMessageConvert.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceConvert.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceDTOConvert.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmTaskConvert.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmCategoryDO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmFormDO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/category/BpmCategoryMapper.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmFormMapper.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessExpressionMapper.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessListenerMapper.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/redis/BpmProcessIdRedisDAO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/redis/RedisKeyConstants.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmActivityBehaviorFactory.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormDeptLeaderStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/event/BpmProcessInstanceEventPublisher.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTriggerTaskDelegate.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoDelegateClassExecutionListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoDelegateExpressionExecutionListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoSpringExpressionExecutionListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoDelegateClassTaskListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoDelegateExpressionTaskListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoSpringExpressionTaskListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/BpmnModelUtils.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/FlowableUtils.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/SimpleModelUtils.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/config/RpcConfiguration.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/security/config/SecurityConfiguration.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/security/core/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/config/BpmWebConfiguration.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/core/FlowableWebFilter.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/package-info.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmCategoryService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmCategoryServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmFormService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmFormServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmModelService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmModelServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessDefinitionService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessExpressionService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessExpressionServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessListenerService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessListenerServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmFormFieldRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmProcessDefinitionCreateReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/BpmMessageService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/BpmMessageServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceApproveReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceRejectReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenTaskCreatedReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/BpmOALeaveService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/BpmOALeaveServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/listener/BpmOALeaveStatusListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskService.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskServiceImpl.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/listener/BpmCallActivityListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/listener/BpmUserTaskListener.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/BpmTrigger.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/form/BpmFormDeleteTrigger.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/form/BpmFormUpdateTrigger.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmAbstractHttpRequestTrigger.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/database/core/DmDatabase.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/resources/META-INF/package-info.md create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/resources/META-INF/services/liquibase.database.Database create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/resources/application-dev.yaml create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/resources/application-local.yaml create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml create mode 100644 zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategyTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/category/BpmCategoryServiceImplTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/definition/BpmFormServiceTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupServiceTest.java create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/resources/application-unit-test.yaml create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/resources/logback.xml create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/resources/sql/clean.sql create mode 100644 zt-module-bpm/zt-module-bpm-server/src/test/resources/sql/create_tables.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e55eb64 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ + +# 查看更多 .gitignore 配置 -> https://help.github.com/articles/ignoring-files/ + +target/ +!.mvn/wrapper/maven-wrapper.jar + +.flattened-pom.xml + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +*.class +target/* + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ + + + +### admin-web ### + +# dependencies +**/node_modules + +# roadhog-api-doc ignore +/src/utils/request-temp.js +_roadhog-api-doc + +# production +/dist +/.vscode + +# misc +.DS_Store +npm-debug.log* +yarn-error.log + +/coverage +.idea +yarn.lock +package-lock.json +*bak +.vscode + +# visual studio code +.history +*.log + +functions/mock +.temp/** + +# umi +.umi +.umi-production + +# screenshot +screenshot +.firebase +sessionStore diff --git a/deployment.yaml b/deployment.yaml new file mode 100644 index 0000000..f8f3186 --- /dev/null +++ b/deployment.yaml @@ -0,0 +1,362 @@ +# zt-gateway +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-gateway + labels: + app: zt-gateway +spec: + replicas: 1 + selector: + matchLabels: + app: zt-gateway + template: + metadata: + labels: + app: zt-gateway + spec: + containers: + - name: zt-gateway + image: 172.16.46.66:10043/zt/zt-gateway:VERSION_PLACEHOLDER + imagePullPolicy: Always + env: + - name: TZ + value: Asia/Shanghai + ports: + - containerPort: 48080 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-gateway +spec: + type: NodePort + selector: + app: zt-gateway + ports: + - protocol: TCP + port: 48080 + targetPort: 48080 + nodePort: 30081 +--- +# zt-module-infra +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-infra + labels: + app: zt-module-infra + annotations: + version: "VERSION_PLACEHOLDER" + description: DESC_PLACEHOLDER + rollout.kubernetes.io/change-cause: "DESC_PLACEHOLDER:VERSION_PLACEHOLDER" +spec: + replicas: 1 + selector: + matchLabels: + app: zt-module-infra + template: + metadata: + labels: + app: zt-module-infra + spec: + containers: + - name: zt-module-infra + image: 172.16.46.66:10043/zt/zt-module-infra:VERSION_PLACEHOLDER + imagePullPolicy: Always + env: + - name: TZ + value: Asia/Shanghai + readinessProbe: + httpGet: + path: /actuator/health + port: 48082 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /actuator/health + port: 48082 + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 5 + terminationGracePeriodSeconds: 30 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-infra +spec: + type: ClusterIP + selector: + app: zt-module-infra + ports: + - protocol: TCP + port: 48082 + targetPort: 48082 +--- +# zt-module-system +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-system + labels: + app: zt-module-system + annotations: + version: "VERSION_PLACEHOLDER" + description: DESC_PLACEHOLDER + rollout.kubernetes.io/change-cause: "DESC_PLACEHOLDER:VERSION_PLACEHOLDER" +spec: + replicas: 1 + selector: + matchLabels: + app: zt-module-system + template: + metadata: + labels: + app: zt-module-system + spec: + containers: + - name: zt-module-system + image: 172.16.46.66:10043/zt/zt-module-system:VERSION_PLACEHOLDER + imagePullPolicy: Always + env: + - name: TZ + value: Asia/Shanghai + readinessProbe: + httpGet: + path: /actuator/health + port: 48081 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /actuator/health + port: 48081 + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 5 + terminationGracePeriodSeconds: 30 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-system +spec: + type: ClusterIP + selector: + app: zt-module-system + ports: + - protocol: TCP + port: 48081 + targetPort: 48081 +--- +# zt-module-bpm +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-bpm + labels: + app: zt-module-bpm + annotations: + version: "VERSION_PLACEHOLDER" + description: DESC_PLACEHOLDER + rollout.kubernetes.io/change-cause: "DESC_PLACEHOLDER:VERSION_PLACEHOLDER" +spec: + replicas: 1 + selector: + matchLabels: + app: zt-module-bpm + template: + metadata: + labels: + app: zt-module-bpm + spec: + containers: + - name: zt-module-bpm + image: 172.16.46.66:10043/zt/zt-module-bpm:VERSION_PLACEHOLDER + imagePullPolicy: Always + env: + - name: TZ + value: Asia/Shanghai + readinessProbe: + httpGet: + path: /actuator/health + port: 48083 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /actuator/health + port: 48083 + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 5 + terminationGracePeriodSeconds: 30 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-bpm +spec: + type: ClusterIP + selector: + app: zt-module-bpm + ports: + - protocol: TCP + port: 48083 + targetPort: 48083 +--- +# zt-module-report +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-report + labels: + app: zt-module-report + annotations: + version: "VERSION_PLACEHOLDER" + description: DESC_PLACEHOLDER + rollout.kubernetes.io/change-cause: "DESC_PLACEHOLDER:VERSION_PLACEHOLDER" +spec: + replicas: 1 + selector: + matchLabels: + app: zt-module-report + template: + metadata: + labels: + app: zt-module-report + spec: + containers: + - name: zt-module-report + image: 172.16.46.66:10043/zt/zt-module-report:VERSION_PLACEHOLDER + imagePullPolicy: Always + env: + - name: TZ + value: Asia/Shanghai + readinessProbe: + httpGet: + path: /actuator/health + port: 48084 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /actuator/health + port: 48084 + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 5 + terminationGracePeriodSeconds: 30 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-report +spec: + type: ClusterIP + selector: + app: zt-module-report + ports: + - protocol: TCP + port: 48084 + targetPort: 48084 +--- +# zt-module-template +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-template + labels: + app: zt-module-template + annotations: + version: "VERSION_PLACEHOLDER" + description: DESC_PLACEHOLDER + rollout.kubernetes.io/change-cause: "DESC_PLACEHOLDER:VERSION_PLACEHOLDER" +spec: + replicas: 1 + selector: + matchLabels: + app: zt-module-template + template: + metadata: + labels: + app: zt-module-template + spec: + containers: + - name: zt-module-template + image: 172.16.46.66:10043/zt/zt-module-template:VERSION_PLACEHOLDER + imagePullPolicy: Always + env: + - name: TZ + value: Asia/Shanghai + readinessProbe: + httpGet: + path: /actuator/health + port: 48100 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /actuator/health + port: 48100 + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 5 + terminationGracePeriodSeconds: 30 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: ns-f16a3067ca7b434aad127d15eac82503 + name: zt-module-template +spec: + type: ClusterIP + selector: + app: zt-module-template + ports: + - protocol: TCP + port: 48100 + targetPort: 48100 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5dc7e5c --- /dev/null +++ b/pom.xml @@ -0,0 +1,258 @@ + + + 4.0.0 + com.zt.plat + dsc-bpm + ${revision} + pom + + zt-module-bpm + + + ${project.artifactId} + 芋道项目基础脚手架 + https://github.com/YunaiV/ruoyi-vue-pro + + + 3.0.41 + + 17 + ${java.version} + ${java.version} + 3.2.2 + 3.14.0 + 1.6.0 + + 1.18.36 + 3.4.5 + 1.6.3 + UTF-8 + + + + + + com.zt.plat + zt-dependencies + ${revision} + pom + import + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + org.springframework.boot + spring-boot-configuration-processor + ${spring.boot.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + false + + -parameters + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + oss + true + + + + + flatten + + flatten + process-resources + + + + clean + + flatten.clean + clean + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + package + + jar + + + + + + + + + src/main/resources + + **/*.yml + **/*.yaml + + true + + + + src/main/resources + + **/*.yml + **/*.yaml + + false + + + + + + + + huaweicloud + huawei + https://mirrors.huaweicloud.com/repository/maven/ + + + aliyunmaven + aliyun + https://maven.aliyun.com/repository/public + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + ZT + 中铜 ZStack 私服 + http://172.16.46.63:30708/repository/test/ + + false + + + + + + + ZT + 中铜 ZStack 私服 + http://172.16.46.63:30708/repository/test/ + + + + + + + + + + + env-dev + + dev + + 172.16.46.63:30848 + dev + DEFAULT_GROUP + + + 1.0.0 + + + + env-prod + + prod + + 172.16.46.63:30848 + prod + DEFAULT_GROUP + + + 1.0.0 + + + + env-local + + local + + 172.16.46.63:30848 + local + DEFAULT_GROUP + + + 1.0.0 + + + + chenbowen + + chenbowen + + + + + diff --git a/zt-module-bpm/pom.xml b/zt-module-bpm/pom.xml new file mode 100644 index 0000000..10db3bb --- /dev/null +++ b/zt-module-bpm/pom.xml @@ -0,0 +1,27 @@ + + + + com.zt.plat + dsc-bpm + ${revision} + + 4.0.0 + + zt-module-bpm-api + zt-module-bpm-server + + zt-module-bpm + pom + + ${project.artifactId} + + bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能。 + 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等 + bpm 解释:https://baike.baidu.com/item/BPM/1933 + + 工作流基于 Flowable 6 实现,分成流程定义、流程表单、流程实例、流程任务等功能模块。 + + + diff --git a/zt-module-bpm/zt-module-bpm-api/pom.xml b/zt-module-bpm/zt-module-bpm-api/pom.xml new file mode 100644 index 0000000..2aa75a5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/pom.xml @@ -0,0 +1,47 @@ + + + + com.zt.plat + zt-module-bpm + ${revision} + + 4.0.0 + zt-module-bpm-api + jar + + ${project.artifactId} + + bpm 模块 API,暴露给其它模块调用 + + + + + com.zt.plat + zt-common + + + + + org.springdoc + springdoc-openapi-starter-webmvc-api + provided + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmCategoryApi.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmCategoryApi.java new file mode 100644 index 0000000..19f885c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmCategoryApi.java @@ -0,0 +1,55 @@ +package com.zt.plat.module.bpm.api.definition; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.api.definition.dto.BpmCategoryPageReqDTO; +import com.zt.plat.module.bpm.api.definition.dto.BpmCategoryRespDTO; +import com.zt.plat.module.bpm.api.definition.dto.BpmCategorySaveReqDTO; +import com.zt.plat.module.bpm.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - BPM 流程分类") +public interface BpmCategoryApi { + + String PREFIX = ApiConstants.PREFIX + "/category"; + + @PostMapping(PREFIX + "/create") + @Operation(summary = "创建流程分类") + CommonResult createCategory(@Valid @RequestBody BpmCategorySaveReqDTO createReqDTO); + + @PutMapping(PREFIX + "/update") + @Operation(summary = "更新流程分类") + CommonResult updateCategory(@Valid @RequestBody BpmCategorySaveReqDTO updateReqDTO); + + @PutMapping(PREFIX + "/update-sort-batch") + @Operation(summary = "批量更新流程分类的排序") + @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3") + CommonResult updateCategorySortBatch(@RequestParam("ids") List ids); + + @DeleteMapping(PREFIX + "/delete") + @Operation(summary = "删除流程分类") + @Parameter(name = "id", description = "编号", required = true) + CommonResult deleteCategory(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/get") + @Operation(summary = "获得流程分类") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + CommonResult getCategory(@RequestParam("id") Long id); + + @PostMapping(PREFIX + "/page") + @Operation(summary = "获得流程分类分页") + CommonResult> getCategoryPage(@Valid @RequestBody BpmCategoryPageReqDTO pageReqDTO); + + @GetMapping(PREFIX + "/simple-list") + @Operation(summary = "获取流程分类的精简信息列表", description = "只包含被开启的分类,主要用于前端的下拉选项") + CommonResult> getCategorySimpleList(); + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmFormApi.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmFormApi.java new file mode 100644 index 0000000..b21f94a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmFormApi.java @@ -0,0 +1,50 @@ +package com.zt.plat.module.bpm.api.definition; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.api.definition.dto.BpmFormPageReqDTO; +import com.zt.plat.module.bpm.api.definition.dto.BpmFormRespDTO; +import com.zt.plat.module.bpm.api.definition.dto.BpmFormSaveReqDTO; +import com.zt.plat.module.bpm.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - 动态表单") +public interface BpmFormApi { + + String PREFIX = ApiConstants.PREFIX + "/form"; + + @PostMapping(PREFIX + "/create") + @Operation(summary = "创建动态表单") + CommonResult createForm(@Valid @RequestBody BpmFormSaveReqDTO createReqDTO); + + @PutMapping(PREFIX + "/update") + @Operation(summary = "更新动态表单") + CommonResult updateForm(@Valid @RequestBody BpmFormSaveReqDTO updateReqDTO); + + @DeleteMapping(PREFIX + "/delete") + @Operation(summary = "删除动态表单") + @Parameter(name = "id", description = "编号", required = true) + CommonResult deleteForm(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/get") + @Operation(summary = "获得动态表单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + CommonResult getForm(@RequestParam("id") Long id); + + @PostMapping(PREFIX + "/page") + @Operation(summary = "获得动态表单分页") + CommonResult> getFormPage(@Valid @RequestBody BpmFormPageReqDTO pageReqDTO); + + @GetMapping(PREFIX + "/simple-list") + @Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框") + CommonResult> getFormSimpleList(); + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmUserGroupApi.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmUserGroupApi.java new file mode 100644 index 0000000..dbf25d9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/BpmUserGroupApi.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.bpm.api.definition; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.bpm.api.definition.dto.BpmUserGroupRespDTO; +import com.zt.plat.module.bpm.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - 用户组") +public interface BpmUserGroupApi { + + String PREFIX = ApiConstants.PREFIX + "/user-group"; + + @GetMapping(PREFIX + "/get") + @Operation(summary = "获得用户组") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + CommonResult getUserGroup(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/simple-list") + @Operation(summary = "获取用户组精简信息列表", description = "只包含被开启的用户组,主要用于前端的下拉选项") + CommonResult> getUserGroupSimpleList(); + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategoryPageReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategoryPageReqDTO.java new file mode 100644 index 0000000..df35f7a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategoryPageReqDTO.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.api.definition.dto; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageParam; +import com.zt.plat.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "RPC 服务 - BPM 流程分类分页 Request DTO") +@Data +public class BpmCategoryPageReqDTO extends PageParam { + + @Schema(description = "分类名", example = "王五") + private String name; + + @Schema(description = "分类标志", example = "OA") + private String code; + + @Schema(description = "分类状态", example = "1") + @InEnum(CommonStatusEnum.class) + private Integer status; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategoryRespDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategoryRespDTO.java new file mode 100644 index 0000000..8831550 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategoryRespDTO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.api.definition.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "RPC 服务 - BPM 流程分类 Response DTO") +@Data +public class BpmCategoryRespDTO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") + private Long id; + + @Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + private String name; + + @Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA") + private String code; + + @Schema(description = "分类描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜") + private String description; + + @Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer sort; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategorySaveReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategorySaveReqDTO.java new file mode 100644 index 0000000..39c84e5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmCategorySaveReqDTO.java @@ -0,0 +1,37 @@ +package com.zt.plat.module.bpm.api.definition.dto; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "RPC 服务 - BPM 流程分类新增/修改 Request DTO") +@Data +public class BpmCategorySaveReqDTO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") + private Long id; + + @Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @NotEmpty(message = "分类名不能为空") + private String name; + + @Schema(description = "分类描述", example = "你猜") + private String description; + + @Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA") + @NotEmpty(message = "分类标志不能为空") + private String code; + + @Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "分类状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + + @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "分类排序不能为空") + private Integer sort; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormPageReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormPageReqDTO.java new file mode 100644 index 0000000..4ed3bc3 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormPageReqDTO.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.bpm.api.definition.dto; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "动态表单分页 Request DTO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class BpmFormPageReqDTO extends PageParam { + + @Schema(description = "表单名称", example = "芋道") + private String name; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormRespDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormRespDTO.java new file mode 100644 index 0000000..a8cb537 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormRespDTO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.api.definition.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "RPC 服务 - 动态表单 Response DTO") +@Data +public class BpmFormRespDTO { + + @Schema(description = "表单编号", example = "1024") + private Long id; + + @Schema(description = "表单名", example = "芋艿") + private String name; + + @Schema(description = "表单状态", example = "1") + private Integer status; + + @Schema(description = "表单的配置") + private String conf; + + @Schema(description = "表单项的数组") + private String fields; + + @Schema(description = "备注", example = "我是备注") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormSaveReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormSaveReqDTO.java new file mode 100644 index 0000000..787a8cd --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmFormSaveReqDTO.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.api.definition.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "RPC 服务 - 动态表单新增/修改 Request DTO") +@Data +public class BpmFormSaveReqDTO { + + @Schema(description = "表单编号", example = "1024") + private Long id; + + @Schema(description = "表单名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @NotEmpty(message = "表单名不能为空") + private String name; + + @Schema(description = "表单状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "表单的配置", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "表单的配置不能为空") + private String conf; + + @Schema(description = "表单项的数组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "表单项的数组不能为空") + private String fields; + + @Schema(description = "备注", example = "我是备注") + private String remark; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmUserGroupRespDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmUserGroupRespDTO.java new file mode 100644 index 0000000..854efa6 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/definition/dto/BpmUserGroupRespDTO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.api.definition.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Set; + +@Schema(description = "RPC 服务 - 用户组 Response DTO") +@Data +public class BpmUserGroupRespDTO { + + @Schema(description = "编号", example = "1024") + private Long id; + + @Schema(description = "组名", example = "芋艿") + private String name; + + @Schema(description = "描述", example = "芋艿") + private String description; + + @Schema(description = "成员用户编号数组", example = "1,2,3") + private Set memberUserIds; + + @Schema(description = "状态", example = "1") + private Integer status; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/event/BpmProcessInstanceStatusEvent.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/event/BpmProcessInstanceStatusEvent.java new file mode 100644 index 0000000..4a4316c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/event/BpmProcessInstanceStatusEvent.java @@ -0,0 +1,41 @@ +package com.zt.plat.module.bpm.api.event; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.context.ApplicationEvent; + +/** + * 流程实例的状态(结果)发生变化的 Event + * + * @author ZT + */ +@SuppressWarnings("ALL") +@Data +public class BpmProcessInstanceStatusEvent extends ApplicationEvent { + + /** + * 流程实例的编号 + */ + @NotNull(message = "流程实例的编号不能为空") + private String id; + /** + * 流程实例的 key + */ + @NotNull(message = "流程实例的 key 不能为空") + private String processDefinitionKey; + /** + * 流程实例的结果 + */ + @NotNull(message = "流程实例的状态不能为空") + private Integer status; + /** + * 流程实例对应的业务标识 + * 例如说,请假 + */ + private String businessKey; + + public BpmProcessInstanceStatusEvent(Object source) { + super(source); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/event/BpmProcessInstanceStatusEventListener.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/event/BpmProcessInstanceStatusEventListener.java new file mode 100644 index 0000000..553096a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/event/BpmProcessInstanceStatusEventListener.java @@ -0,0 +1,34 @@ +package com.zt.plat.module.bpm.api.event; + +import org.springframework.context.ApplicationListener; + +import java.util.List; + +/** + * {@link BpmProcessInstanceStatusEvent} 的监听器 + * + * @author ZT + */ +public abstract class BpmProcessInstanceStatusEventListener + implements ApplicationListener { + + @Override + public final void onApplicationEvent(BpmProcessInstanceStatusEvent event) { + if (getProcessDefinitionKey().contains(event.getProcessDefinitionKey())){ + onEvent(event); + } + } + + /** + * @return 返回监听的流程定义 Key + */ + protected abstract List getProcessDefinitionKey(); + + /** + * 处理事件 + * + * @param event 事件 + */ + protected abstract void onEvent(BpmProcessInstanceStatusEvent event); + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/package-info.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/package-info.java new file mode 100644 index 0000000..10d2e79 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/package-info.java @@ -0,0 +1,4 @@ +/** + * bpm API 包,定义暴露给其它模块的 API + */ +package com.zt.plat.module.bpm.api; diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApi.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApi.java new file mode 100644 index 0000000..89839e8 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApi.java @@ -0,0 +1,67 @@ +package com.zt.plat.module.bpm.api.task; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.bpm.api.task.dto.*; +import com.zt.plat.module.bpm.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +@Tag(name = "RPC 服务 - 流程实例") +public interface BpmProcessInstanceApi { + + String PREFIX = ApiConstants.PREFIX + "/process-instance"; + + @PostMapping(PREFIX + "/create") + @Operation(summary = "创建流程实例(提供给内部),返回实例编号") + @Parameter(name = "userId", description = "用户编号", required = true, example = "1") + CommonResult createProcessInstance(@RequestParam("userId") Long userId, + @Valid @RequestBody BpmProcessInstanceCreateReqDTO reqDTO); + + @GetMapping(PREFIX + "/get") + @Operation(summary = "获得指定流程实例", description = "在【流程详细】界面中,进行调用") + @Parameter(name = "id", description = "流程实例的编号", required = true) + CommonResult getProcessInstance(@RequestParam("id") String id); + + @DeleteMapping(PREFIX + "/cancel-by-start-user") + @Operation(summary = "用户取消流程实例", description = "取消发起的流程") + CommonResult cancelProcessInstanceByStartUser( + @RequestParam("userId") Long userId, + @Valid @RequestBody BpmProcessInstanceCancelReqDTO cancelReqDTO); + + @DeleteMapping(PREFIX + "/cancel-by-admin") + @Operation(summary = "管理员取消流程实例", description = "管理员撤回流程") + CommonResult cancelProcessInstanceByAdmin( + @RequestParam("userId") Long userId, + @Valid @RequestBody BpmProcessInstanceCancelReqDTO cancelReqDTO); + + @PostMapping(PREFIX + "/get-approval-detail") + @Operation(summary = "获得审批详情") + CommonResult getApprovalDetail(@RequestParam("userId") Long userId, + @Valid BpmApprovalDetailReqDTO reqDTO); + + @PostMapping(PREFIX + "/get-next-approval-nodes") + @Operation(summary = "获取下一个执行的流程节点") + CommonResult> getNextApprovalNodes(@RequestParam("userId") Long userId, + @Valid BpmApprovalDetailReqDTO reqDTO); + + @PostMapping(PREFIX + "/get-bpmn-model-view") + @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用") + @Parameter(name = "id", description = "流程实例的编号", required = true) + CommonResult getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id); + + @PutMapping(PREFIX + "/approveTask") + @Operation(summary = "通过任务") + CommonResult approveTask(@Valid @RequestBody BpmTaskApproveReqDTO reqVO); + + @PutMapping(PREFIX + "/rejectTask") + @Operation(summary = "不通过任务") + CommonResult rejectTask(@Valid @RequestBody BpmTaskRejectReqDTO reqVO); + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmTaskApi.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmTaskApi.java new file mode 100644 index 0000000..b55ae5c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmTaskApi.java @@ -0,0 +1,53 @@ +package com.zt.plat.module.bpm.api.task; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskPageReqDTO; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskRespDTO; +import com.zt.plat.module.bpm.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - 流程任务实例") +public interface BpmTaskApi { + + String PREFIX = ApiConstants.PREFIX + "/task"; + + @PostMapping(PREFIX + "/todo-page") + @Operation(summary = "获取 Todo 待办任务分页") + CommonResult> getTaskTodoPage(@Valid @RequestBody BpmTaskPageReqDTO pageReqDTO); + + @PostMapping(PREFIX + "/done-page") + @Operation(summary = "获取 Done 已办任务分页") + CommonResult> getTaskDonePage(@Valid @RequestBody BpmTaskPageReqDTO pageReqDTO); + + @PostMapping(PREFIX + "/manager-page") + @Operation(summary = "获取全部任务的分页", description = "用于【流程任务】菜单") + CommonResult> getTaskManagerPage(@Valid @RequestBody BpmTaskPageReqDTO pageReqDTO); + + @GetMapping(PREFIX + "/list-by-process-instance-id") + @Operation(summary = "获得指定流程实例的任务列表", description = "包括完成的、未完成的") + @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true) + CommonResult> getTaskListByProcessInstanceId( + @RequestParam("processInstanceId") String processInstanceId); + + @GetMapping(PREFIX + "/list-by-return") + @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮") + @Parameter(name = "id", description = "当前任务ID", required = true) + CommonResult> getTaskListByReturn(@RequestParam("id") String id); + + @GetMapping(PREFIX + "/list-by-parent-task-id") + @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 + @Parameter(name = "parentTaskId", description = "父级任务编号", required = true) + CommonResult> getTaskListByParentTaskId(@RequestParam("parentTaskId") String parentTaskId); + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmApprovalDetailReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmApprovalDetailReqDTO.java new file mode 100644 index 0000000..4794386 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmApprovalDetailReqDTO.java @@ -0,0 +1,40 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import lombok.Data; + +import java.util.Map; + +@Schema(description = "RPC 服务 - 审批详情 Request DTO") +@Data +public class BpmApprovalDetailReqDTO { + + @Schema(description = "流程定义的编号", example = "1024") + private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID + + @Schema(description = "流程变量") + private Map processVariables; // 使用场景:同 processDefinitionId,用于流程预测 + + @Schema(description = "流程变量") + private String processVariablesStr; // 解决 GET 无法传递对象的问题,最终转换成 processVariables 变量 + + @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); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmApprovalDetailRespDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmApprovalDetailRespDTO.java new file mode 100644 index 0000000..3183561 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmApprovalDetailRespDTO.java @@ -0,0 +1,110 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import com.zt.plat.module.bpm.api.task.dto.BpmTaskRespDTO; +import com.fasterxml.jackson.annotation.JsonIgnore; +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 = "RPC 服务 - 审批详情 Response DTO") +@Data +public class BpmApprovalDetailRespDTO { + + @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 + + @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List activityNodes; + + @Schema(description = "表单字段权限") + private Map formFieldsPermission; + + @Schema(description = "待办任务") + private BpmTaskRespDTO todoTask; + + /** + * 所属流程定义信息 + */ + private BpmProcessDefinitionRespDTO processDefinition; + + /** + * 所属流程实例信息 + */ + private BpmProcessInstanceRespDTO 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 tasks; + + @Schema(description = "候选人策略", example = "35") + private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选 + + @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers + private List candidateUserIds; + + @Schema(description = "候选人用户列表") + private List candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表 + + @Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf") + private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 processInstanceId 字段) + + } + + @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 UserSimpleDTO ownerUser; + + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser + private Long assignee; + + @Schema(description = "任务分配人", example = "2048") + private UserSimpleDTO assigneeUser; + + @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; // 参见 BpmTaskStatusEnum 枚举 + + @Schema(description = "审批意见", example = "同意") + private String reason; + + @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") + private String signPicUrl; + + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessDefinitionRespDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessDefinitionRespDTO.java new file mode 100644 index 0000000..81aec42 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessDefinitionRespDTO.java @@ -0,0 +1,55 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "RPC 服务 - 流程定义 Response DTO") +@Data +public class BpmProcessDefinitionRespDTO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_order") + private String key; + + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "请假流程") + private String name; + + @Schema(description = "流程描述", example = "请假流程描述") + private String description; + + @Schema(description = "流程分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "oa") + private String category; + + @Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer formType; + + @Schema(description = "表单编号", example = "1024") + private Long formId; + + @Schema(description = "表单的配置", requiredMode = Schema.RequiredMode.REQUIRED) + private String formConf; + + @Schema(description = "表单项的数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List formFields; + + @Schema(description = "表单项的 JSON 数组字符串", requiredMode = Schema.RequiredMode.REQUIRED) + private String formFieldsStr; + + @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) + private String bpmnXml; + + @Schema(description = "版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer version; + + @Schema(description = "是否挂起", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Integer suspensionState; + + @Schema(description = "部署时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime deploymentTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceBpmnModelViewRespDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceBpmnModelViewRespDTO.java new file mode 100644 index 0000000..9b1f2c3 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceBpmnModelViewRespDTO.java @@ -0,0 +1,41 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; +import java.util.Set; + +@Schema(description = "RPC 服务 - 流程示例的 BPMN 视图 Response DTO") +@Data +public class BpmProcessInstanceBpmnModelViewRespDTO { + + // ========== 基本信息 ========== + + @Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED) + private BpmProcessInstanceRespDTO processInstance; + + @Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List tasks; + + @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) + private String bpmnXml; + + @Schema(description = "SIMPLE 模型") + private BpmSimpleModelNodeDTO simpleModel; + + // ========== 进度信息 ========== + + @Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set unfinishedTaskActivityIds; // 只包括 UserTask + + @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow + + @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set finishedSequenceFlowActivityIds; // 只包括 SequenceFlow + + @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set rejectedTaskActivityIds; // 只包括 UserTask + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceCancelReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceCancelReqDTO.java new file mode 100644 index 0000000..15316d0 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceCancelReqDTO.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "RPC 服务 - 流程实例的取消 Request DTO") +@Data +public class BpmProcessInstanceCancelReqDTO { + + @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "流程实例的编号不能为空") + private String id; + + @Schema(description = "取消原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "不请假了!") + @NotEmpty(message = "取消原因不能为空") + private String reason; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java new file mode 100644 index 0000000..efecfda --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; + +import java.util.List; +import java.util.Map; + +@Schema(description = "RPC 服务 - 流程实例的创建 Request DTO") +@Data +public class BpmProcessInstanceCreateReqDTO { + + @Schema(description = "流程定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "leave") + @NotEmpty(message = "流程定义的标识不能为空") + private String processDefinitionKey; + + @Schema(description = "变量实例", requiredMode = Schema.RequiredMode.REQUIRED) + private Map variables; + + @Schema(description = "业务的唯一标识", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "业务的唯一标识不能为空") + private String businessKey; // 例如说,请假申请的编号。通过它,可以查询到对应的实例 + + /** + * 发起人自选审批人 Map + * + * key:taskKey 任务编码 + * value:审批人的数组 + * 例如:{ taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批 + */ + @Schema(description = "发起人自选审批人 Map") + private Map> startUserSelectAssignees; + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstancePageReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstancePageReqDTO.java new file mode 100644 index 0000000..ce6c38c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstancePageReqDTO.java @@ -0,0 +1,51 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "RPC 服务 - 流程实例分页 Request DTO") +@Data +public class BpmProcessInstancePageReqDTO extends PageParam { + + @Schema(description = "流程实例的编号", example = "1024") + private String id; + + @Schema(description = "流程实例的名字", example = "芋艿") + private String name; + + @Schema(description = "流程定义的编号", example = "2048") + private String processDefinitionId; + + @Schema(description = "流程定义的标识", example = "2048") + private String processDefinitionKey; // 精准匹配 + + @Schema(description = "流程分类", example = "1") + private String category; + + @Schema(description = "流程实例的状态", example = "1") + private Integer status; + + @Schema(description = "流程实例的结果", example = "1") + private Integer result; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + @Schema(description = "结束时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] endTime; + + @Schema(description = "发起用户编号", example = "1024") + private Long startUserId; // 注意,只有在【流程实例】菜单,才使用该参数 + + @Schema(description = "动态表单字段查询 JSON Str", example = "{}") + private String formFieldsParams; // SpringMVC 在 get 请求下,无法方便的定义 Map 类型的参数,所以通过 String 接收后,逻辑里面转换 + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceRespDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceRespDTO.java new file mode 100644 index 0000000..48c779c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmProcessInstanceRespDTO.java @@ -0,0 +1,92 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import com.zt.plat.framework.common.core.KeyValue; +import com.fasterxml.jackson.annotation.JsonIgnore; +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 = "RPC 服务 - 流程实例 Response DTO") +@Data +public class BpmProcessInstanceRespDTO { + + @Schema(description = "流程实例的编号", example = "1024") + private String id; + + @Schema(description = "流程实例的名字", example = "芋艿") + private String name; + + @Schema(description = "流程摘要") + private List> summary; // 只有流程表单,才有摘要! + + @Schema(description = "流程定义的编号", example = "2048") + private String processDefinitionId; + + @Schema(description = "流程分类", example = "1") + private String category; + + @Schema(description = "流程分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "请假") + private String categoryName; + + @Schema(description = "流程实例的状态", example = "1") + private Integer status; + + @Schema(description = "流程实例的结果", example = "1") + private Integer result; + + @Schema(description = "发起时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime startTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "结束时间") + private LocalDateTime endTime; + + @Schema(description = "持续时间", example = "1000") + private Long durationInMillis; + + @Schema(description = "提交的表单值", example = "{\"name\": \"芋艿\"}") + private Map formVariables; + + @Schema(description = "业务的唯一标识", example = "1") + private String businessKey; + + /** + * 发起流程的用户 + */ + private UserSimpleDTO startUser; + + /** + * 流程定义 + */ + private BpmProcessDefinitionRespDTO processDefinition; + + /** + * 当前审批中的任务 + */ + private List tasks; // 仅在流程实例分页才返回 + + @Schema(description = "流程任务") + @Data + public static class Task { + + @Schema(description = "流程任务的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser + private Long assignee; + + @Schema(description = "任务分配人", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + private UserSimpleDTO assigneeUser; + + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmSimpleModelNodeDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmSimpleModelNodeDTO.java new file mode 100644 index 0000000..a01c60d --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmSimpleModelNodeDTO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "RPC 服务 - 简单模型节点 DTO") +@Data +public class BpmSimpleModelNodeDTO { + + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartNode") + private String id; + + @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "开始节点") + private String name; + + @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; + + @Schema(description = "节点属性") + private Map attributes; + + @Schema(description = "子节点列表") + private List childNode; + + @Schema(description = "条件节点列表") + private List conditionNodes; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskApproveReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskApproveReqDTO.java new file mode 100644 index 0000000..705f930 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskApproveReqDTO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "RPC 服务 - 通过流程任务的 Request DTO") +@Data +public class BpmTaskApproveReqDTO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "审批意见", example = "不错不错!") + private String reason; + + @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") + private String signPicUrl; + + @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) + private Map variables; + + @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}") + private Map> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况 + + // 新增任务变量实例,业务表单 + @Schema(description = "任务变量实例,业务表单", example = "{'formField1': 'value1', 'formField2': 'value2'}") + private Map taskVariables; +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskPageReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskPageReqDTO.java new file mode 100644 index 0000000..fae7055 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskPageReqDTO.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "RPC 服务 - 流程任务分页 Request DTO") +@Data +public class BpmTaskPageReqDTO extends PageParam { + + @Schema(description = "流程任务名", example = "芋艿") + private String name; + + @Schema(description = "流程定义的编号", example = "2048") + private String processDefinitionId; + + @Schema(description = "流程分类", example = "1") + private String category; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRejectReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRejectReqDTO.java new file mode 100644 index 0000000..94f73bf --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRejectReqDTO.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "RPC 服务 - 不通过流程任务的 Request DTO") +@Data +public class BpmTaskRejectReqDTO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "审批意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "不错不错!") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRespDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRespDTO.java new file mode 100644 index 0000000..412c098 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRespDTO.java @@ -0,0 +1,133 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import com.zt.plat.framework.common.core.KeyValue; +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 = "RPC 服务 - 流程任务 Response DTO") +@Data +public class BpmTaskRespDTO { + + @Schema(description = "任务编号", example = "1024") + private String id; + + @Schema(description = "任务名字", example = "芋艿") + private String name; + + @Schema(description = "接收人的用户编号", example = "1") + private Long assigneeUserId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "结束时间") + private LocalDateTime endTime; + + @Schema(description = "持续时间", example = "1000") + private Long durationInMillis; + + @Schema(description = "流程实例的编号", example = "1024") + private String processInstanceId; + + @Schema(description = "流程定义的编号", example = "2048") + private String processDefinitionId; + + @Schema(description = "任务状态", example = "1") + private Integer status; + + @Schema(description = "审批建议", example = "不错不错!") + private String reason; + + @Schema(description = "任务定义的标识", example = "Activity_one") + private String taskDefinitionKey; + + @Schema(description = "表单编号", example = "1024") + private Long formId; + + @Schema(description = "表单路径", example = "/form/leave") + private String formPath; + + @Schema(description = "表单名字", example = "请假表单") + private String formName; + + @Schema(description = "表单的配置", example = "[]") + private String formConf; + + @Schema(description = "表单项的数组", example = "[]") + private List formFields; + + @Schema(description = "提交的表单值", example = "{\"name\": \"芋艿\"}") + private Map formVariables; + + @Schema(description = "任务负责人编号", example = "2048") + private Long owner; + + @Schema(description = "负责人的用户信息") + private UserSimpleDTO ownerUser; + + @Schema(description = "任务分配人编号", example = "2048") + private Long assignee; + + @Schema(description = "审核的用户信息") + private UserSimpleDTO assigneeUser; + + @Schema(description = "父任务编号", example = "1024") + private String parentTaskId; + + @Schema(description = "子任务列表(由加签生成)") + private List children; + + @Schema(description = "所属流程实例") + private ProcessInstanceDTO processInstance; + + @Schema(description = "操作按钮设置值") + private Map buttonsSetting; + + @Schema(description = "是否需要签名", example = "false") + private Boolean signEnable; + + @Schema(description = "是否填写审批意见", example = "false") + private Boolean reasonRequire; + + @Schema(description = "节点类型", example = "10") + private Integer nodeType; + + @Data + @Schema(description = "流程实例信息") + public static class ProcessInstanceDTO { + + @Schema(description = "流程实例编号", example = "1024") + private String id; + + @Schema(description = "流程实例名称", example = "芋道") + private String name; + + @Schema(description = "提交时间") + private LocalDateTime createTime; + + @Schema(description = "流程定义的编号", example = "2048") + private String processDefinitionId; + + @Schema(description = "流程摘要", example = "[]") + private List> summary; + + @Schema(description = "发起人的用户信息") + private UserSimpleDTO startUser; + } + + @Data + @Schema(description = "操作按钮设置") + public static class OperationButtonSettingDTO { + + @Schema(description = "显示名称", example = "审批") + private String displayName; + + @Schema(description = "是否启用", example = "true") + private Boolean enable; + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/UserSimpleDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/UserSimpleDTO.java new file mode 100644 index 0000000..ddb00d6 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/UserSimpleDTO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "RPC 服务 - 用户精简信息 DTO") +@Data +public class UserSimpleDTO { + + @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; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/ApiConstants.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/ApiConstants.java new file mode 100644 index 0000000..3c4bac9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/ApiConstants.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.bpm.enums; + +import com.zt.plat.framework.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author ZT + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "bpm-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/bpm"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/DictTypeConstants.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/DictTypeConstants.java new file mode 100644 index 0000000..13dcf72 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/DictTypeConstants.java @@ -0,0 +1,10 @@ +package com.zt.plat.module.bpm.enums; + +/** + * BPM 字典类型的枚举类 + * + * @author ZT + */ +public interface DictTypeConstants { + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/ErrorCodeConstants.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..b9db1ed --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/ErrorCodeConstants.java @@ -0,0 +1,87 @@ +package com.zt.plat.module.bpm.enums; + +import com.zt.plat.framework.common.exception.ErrorCode; + +/** + * Bpm 错误码枚举类 + *

+ * bpm 系统,使用 1-009-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 通用流程处理 模块 1-009-000-000 ========== + + // ========== OA 流程模块 1-009-001-000 ========== + ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1_009_001_001, "请假申请不存在"); + + // ========== 流程模型 1-009-002-000 ========== + ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1_009_002_000, "已经存在流程标识为【{}】的流程"); + ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_009_002_001, "流程模型不存在"); + ErrorCode MODEL_KEY_VALID = new ErrorCode(1_009_002_002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!"); + ErrorCode MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG = new ErrorCode(1_009_002_003, "部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置"); + ErrorCode MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG = new ErrorCode(1_009_002_004, "部署流程失败," + + "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置"); + 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, "操作流程失败,原因:你不是该流程({})的管理员"); + ErrorCode MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR = new ErrorCode(1_009_002_008, "部署流程失败,原因:首个任务({})的审批人不能是【审批人自选】"); + + // ========== 流程定义 1-009-003-000 ========== + ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); + ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1_009_003_001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图"); + ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_009_003_002, "流程定义不存在"); + ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1_009_003_003, "流程定义处于挂起状态"); + + // ========== 流程实例 1-009-004-000 ========== + 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_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); + ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消"); + ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败"); + ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置"); + ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消"); + + // ========== 流程任务 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_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, "任务加签:选择的用户不存在"); + ErrorCode TASK_SIGN_CREATE_TYPE_ERROR = new ErrorCode(1_009_005_010, "任务加签:当前任务已经{},不能{}"); + ErrorCode TASK_SIGN_CREATE_USER_REPEAT = new ErrorCode(1_009_005_011, "任务加签失败,加签人与现有审批人[{}]重复"); + ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务"); + ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人"); + ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在"); + ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!"); + ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!"); + ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!"); + + // ========== 动态表单模块 1-009-010-000 ========== + ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在"); + ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1_009_010_001, "表单项({}) 和 ({}) 使用了相同的字段名({})"); + + // ========== 用户组模块 1-009-011-000 ========== + ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1_009_011_000, "用户分组不存在"); + ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1_009_011_001, "名字为【{}】的用户分组已被禁用"); + + // ========== 用户组模块 1-009-012-000 ========== + ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_009_012_000, "流程分类不存在"); + ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(1_009_012_001, "流程分类名字【{}】重复"); + ErrorCode CATEGORY_CODE_DUPLICATE = new ErrorCode(1_009_012_002, "流程分类编码【{}】重复"); + + // ========== BPM 流程监听器 1-009-013-000 ========== + ErrorCode PROCESS_LISTENER_NOT_EXISTS = new ErrorCode(1_009_013_000, "流程监听器不存在"); + ErrorCode PROCESS_LISTENER_CLASS_NOT_FOUND = new ErrorCode(1_009_013_001, "流程监听器类({})不存在"); + ErrorCode PROCESS_LISTENER_CLASS_IMPLEMENTS_ERROR = new ErrorCode(1_009_013_002, "流程监听器类({})没有实现接口({})"); + ErrorCode PROCESS_LISTENER_EXPRESSION_INVALID = new ErrorCode(1_009_013_003, "流程监听器表达式({})不合法"); + + // ========== BPM 流程表达式 1-009-014-000 ========== + ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在"); + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmAutoApproveTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmAutoApproveTypeEnum.java new file mode 100644 index 0000000..2611d38 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmAutoApproveTypeEnum.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 自动去重的类型的枚举 + * + * @author Lesan + */ +@Getter +@AllArgsConstructor +public enum BpmAutoApproveTypeEnum implements ArrayValuable { + + NONE(0, "不自动通过"), + APPROVE_ALL(1, "仅审批一次,后续重复的审批节点均自动通过"), + APPROVE_SEQUENT(2, "仅针对连续审批的节点自动通过"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmAutoApproveTypeEnum::getType).toArray(Integer[]::new); + + private final Integer type; + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmBoundaryEventTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmBoundaryEventTypeEnum.java new file mode 100644 index 0000000..d168f66 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmBoundaryEventTypeEnum.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 边界事件 (boundary event) 自定义类型枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmBoundaryEventTypeEnum { + + USER_TASK_TIMEOUT(1, "用户任务超时"), + DELAY_TIMER_TIMEOUT(2, "延迟器超时"), + CHILD_PROCESS_TIMEOUT(3, "子流程超时"); + + private final Integer type; + private final String name; + + public static BpmBoundaryEventTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java new file mode 100644 index 0000000..68574cb --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java @@ -0,0 +1,37 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 子流程多实例来源类型枚举 + * + * @author Lesan + */ +@Getter +@AllArgsConstructor +public enum BpmChildProcessMultiInstanceSourceTypeEnum implements ArrayValuable { + + FIXED_QUANTITY(1, "固定数量"), + NUMBER_FORM(2, "数字表单"), + MULTIPLE_FORM(3, "多选表单"); + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessMultiInstanceSourceTypeEnum::getType).toArray(Integer[]::new); + + public static BpmChildProcessMultiInstanceSourceTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessStartUserEmptyTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessStartUserEmptyTypeEnum.java new file mode 100644 index 0000000..5b13462 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessStartUserEmptyTypeEnum.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 当子流程发起人为空时类型枚举 + * + * @author Lesan + */ +@Getter +@AllArgsConstructor +public enum BpmChildProcessStartUserEmptyTypeEnum implements ArrayValuable { + + MAIN_PROCESS_START_USER(1, "同主流程发起人"), + CHILD_PROCESS_ADMIN(2, "子流程管理员"), + MAIN_PROCESS_ADMIN(3, "主流程管理员"); + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserEmptyTypeEnum::getType).toArray(Integer[]::new); + + public static BpmChildProcessStartUserEmptyTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessStartUserTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessStartUserTypeEnum.java new file mode 100644 index 0000000..8470a72 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmChildProcessStartUserTypeEnum.java @@ -0,0 +1,35 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 子流程发起人类型枚举 + * + * @author Lesan + */ +@Getter +@AllArgsConstructor +public enum BpmChildProcessStartUserTypeEnum implements ArrayValuable { + + MAIN_PROCESS_START_USER(1, "同主流程发起人"), + FROM_FORM(2, "表单"); + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserTypeEnum::getType).toArray(Integer[]::new); + + public static BpmChildProcessStartUserTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmDelayTimerTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmDelayTimerTypeEnum.java new file mode 100644 index 0000000..aabaef0 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmDelayTimerTypeEnum.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 延迟器类型枚举 + * + * @author Lesan + */ +@Getter +@AllArgsConstructor +public enum BpmDelayTimerTypeEnum implements ArrayValuable { + + FIXED_TIME_DURATION(1, "固定时长"), + FIXED_DATE_TIME(2, "固定日期"); + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmDelayTimerTypeEnum::getType).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmFieldPermissionEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmFieldPermissionEnum.java new file mode 100644 index 0000000..8571f90 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmFieldPermissionEnum.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 表单权限的枚举 + * + * @author jason + */ +@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()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmHttpRequestParamTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmHttpRequestParamTypeEnum.java new file mode 100644 index 0000000..dd1fc50 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmHttpRequestParamTypeEnum.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM HTTP 请求参数设置类型。用于 Simple 设计器任务监听器和触发器配置。 + * + * @author Lesan + */ +@Getter +@AllArgsConstructor +public enum BpmHttpRequestParamTypeEnum implements ArrayValuable { + + FIXED_VALUE(1, "固定值"), + FROM_FORM(2, "表单"); + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmHttpRequestParamTypeEnum::getType).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmModelFormTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmModelFormTypeEnum.java new file mode 100644 index 0000000..f28b1ed --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmModelFormTypeEnum.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 模型的表单类型的枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum BpmModelFormTypeEnum implements ArrayValuable { + + NORMAL(10, "流程表单"), // 对应 BpmFormDO + CUSTOM(20, "业务表单") // 业务自己定义的表单,自己进行数据的存储 + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmModelFormTypeEnum::getType).toArray(Integer[]::new); + + private final Integer type; + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmModelTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmModelTypeEnum.java new file mode 100644 index 0000000..259b365 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmModelTypeEnum.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 模型的类型的枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum BpmModelTypeEnum implements ArrayValuable { + + BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/ + SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉、飞书工作流的设计器 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmModelTypeEnum::getType).toArray(Integer[]::new); + + private final Integer type; + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmProcessListenerTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmProcessListenerTypeEnum.java new file mode 100644 index 0000000..b1ecfe4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmProcessListenerTypeEnum.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.bpm.enums.definition; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 流程监听器的类型 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum BpmProcessListenerTypeEnum { + + EXECUTION("execution", "执行监听器"), + TASK("task", "任务执行器"); + + private final String type; + private final String name; + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmProcessListenerValueTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmProcessListenerValueTypeEnum.java new file mode 100644 index 0000000..7ff961b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmProcessListenerValueTypeEnum.java @@ -0,0 +1,22 @@ +package com.zt.plat.module.bpm.enums.definition; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 流程监听器的值类型 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum BpmProcessListenerValueTypeEnum { + + CLASS("class", "Java 类"), + DELEGATE_EXPRESSION("delegateExpression", "代理表达式"), + EXPRESSION("expression", "表达式"); + + private final String type; + private final String name; + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmSimpleModeConditionTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmSimpleModeConditionTypeEnum.java new file mode 100644 index 0000000..b8c8ee2 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmSimpleModeConditionTypeEnum.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 仿钉钉的流程器设计器条件节点的条件类型 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmSimpleModeConditionTypeEnum implements ArrayValuable { + + EXPRESSION(1, "条件表达式"), + RULE(2, "条件规则"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmSimpleModeConditionTypeEnum::getType).toArray(Integer[]::new); + + private final Integer type; + + private final String name; + + public static BpmSimpleModeConditionTypeEnum valueOf(Integer type) { + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java new file mode 100644 index 0000000..6590a4c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java @@ -0,0 +1,70 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 仿钉钉的流程器设计器的模型节点类型 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable { + + // 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"), + TRANSACTOR_NODE(13, "办理人", "userTask"), + + DELAY_TIMER_NODE(14, "延迟器", "receiveTask"), + TRIGGER_NODE(15, "触发器", "serviceTask"), + + CHILD_PROCESS(20, "子流程", "callActivity"), + + // 50 ~ 条件分支 + CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 + CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), + PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"), + INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"), + ROUTER_BRANCH_NODE(54, "路由分支", "exclusiveGateway") + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmSimpleModelNodeTypeEnum::getType).toArray(Integer[]::new); + + 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) + || Objects.equals(ROUTER_BRANCH_NODE.getType(), type); + } + + public static BpmSimpleModelNodeTypeEnum valueOf(Integer type) { + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmTriggerTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmTriggerTypeEnum.java new file mode 100644 index 0000000..72c3c39 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmTriggerTypeEnum.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM Simple 触发器类型枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmTriggerTypeEnum implements ArrayValuable { + + HTTP_REQUEST(1, "发起 HTTP 请求"), // BPM => 业务,流程继续执行,无需等待业务 + HTTP_CALLBACK(2, "接收 HTTP 回调"), // BPM => 业务 => BPM,流程卡主,等待业务回调 + + FORM_UPDATE(10, "更新流程表单数据"), + FORM_DELETE(11, "删除流程表单数据"), + ; + + /** + * 触发器执行动作类型 + */ + private final Integer type; + + /** + * 触发器执行动作描述 + */ + private final String desc; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmTriggerTypeEnum::getType).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static BpmTriggerTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java new file mode 100644 index 0000000..065035d --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java @@ -0,0 +1,47 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 多人审批方式的枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskApproveMethodEnum implements ArrayValuable { + + 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 Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskApproveMethodEnum::getMethod).toArray(Integer[]::new); + + public static BpmUserTaskApproveMethodEnum valueOf(Integer method) { + return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java new file mode 100644 index 0000000..df40e18 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 用户任务的审批类型枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskApproveTypeEnum implements ArrayValuable { + + USER(1), // 人工审批 + AUTO_APPROVE(2), // 自动通过 + AUTO_REJECT(3); // 自动拒绝 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskApproveTypeEnum::getType).toArray(Integer[]::new); + + private final Integer type; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java new file mode 100644 index 0000000..237ef8e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * BPM 用户任务的审批人为空时,处理类型枚举 + * + * @author ZT + */ +@RequiredArgsConstructor +@Getter +public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements ArrayValuable { + + APPROVE(1), // 自动通过 + REJECT(2), // 自动拒绝 + ASSIGN_USER(3), // 指定人员审批 + ASSIGN_ADMIN(4), // 转交给流程管理员 + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray(Integer[]::new); + + private final Integer type; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java new file mode 100644 index 0000000..ffed389 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * BPM 用户任务的审批人与发起人相同时,处理类型枚举 + * + * @author ZT + */ +@RequiredArgsConstructor +@Getter +public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements ArrayValuable { + + START_USER_AUDIT(1), // 由发起人对自己审批 + SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 + TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray(Integer[]::new); + + private final Integer type; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskRejectHandlerTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskRejectHandlerTypeEnum.java new file mode 100644 index 0000000..f251971 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskRejectHandlerTypeEnum.java @@ -0,0 +1,35 @@ +package com.zt.plat.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 用户任务拒绝处理类型枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskRejectHandlerTypeEnum implements ArrayValuable { + + FINISH_PROCESS_INSTANCE(1, "终止流程"), + RETURN_USER_TASK(2, "驳回到指定任务节点"); + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskRejectHandlerTypeEnum::getType).toArray(Integer[]::new); + + public static BpmUserTaskRejectHandlerTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java new file mode 100644 index 0000000..8f9460e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.enums.definition; + +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 用户任务超时处理类型枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskTimeoutHandlerTypeEnum implements ArrayValuable { + + REMINDER(1,"自动提醒"), + APPROVE(2, "自动同意"), + REJECT(3, "自动拒绝"); + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/message/BpmMessageEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/message/BpmMessageEnum.java new file mode 100644 index 0000000..d8db9ae --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/message/BpmMessageEnum.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.bpm.enums.message; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Bpm 消息的枚举 + * + * @author ZT + */ +@AllArgsConstructor +@Getter +public enum BpmMessageEnum { + + PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 + PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 + TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人 + TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人 + + /** + * 短信模板的标识 + * + * 关联 SmsTemplateDO 的 code 属性 + */ + private final String smsTemplateCode; + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmCommentTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmCommentTypeEnum.java new file mode 100644 index 0000000..51a5ffa --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmCommentTypeEnum.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.bpm.enums.task; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程任务的 Comment 评论类型枚举 + * + * @author kehaiyou + */ +@Getter +@AllArgsConstructor +public enum BpmCommentTypeEnum { + + APPROVE("1", "审批通过", "审批通过,原因是:{}"), + REJECT("2", "不通过", "审批不通过:原因是:{}"), + CANCEL("3", "已取消", "系统自动取消,原因是:{}"), + RETURN("4", "退回", "任务被退回,原因是:{}"), + DELEGATE_START("5", "委派发起", "[{}]将任务委派给[{}],委派理由为:{}"), + DELEGATE_END("6", "委派完成", "[{}]完成委派任务,任务重新回到[{}]手中,审批建议为:{}"), + TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"), + ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"), + SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"), + ; + + /** + * 操作类型 + * + * 由于 BPM Comment 类型为 String,所以这里就不使用 Integer + */ + private final String type; + /** + * 操作名字 + */ + private final String name; + /** + * 操作描述 + */ + private final String comment; + + public String formatComment(Object... params) { + return StrUtil.format(comment, params); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java new file mode 100644 index 0000000..0e61d22 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java @@ -0,0 +1,80 @@ +package com.zt.plat.module.bpm.enums.task; + +import com.zt.plat.framework.common.core.ArrayValuable; +import com.zt.plat.framework.common.util.object.ObjectUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; + +/** + * 流程实例 ProcessInstance 的状态 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum BpmProcessInstanceStatusEnum implements ArrayValuable { + + NOT_START(-1, "未开始"), + RUNNING(1, "审批中"), + APPROVE(2, "审批通过"), + REJECT(3, "审批不通过"), + CANCEL(4, "已取消"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmProcessInstanceStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态 + */ + private final Integer status; + /** + * 描述 + */ + private final String desc; + + @Override + public Integer[] array() { + 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()); + } + + /** + * 通过流程的状态返回对应的枚举 + * @param status 流程状态 + * @return + */ + public static BpmProcessInstanceStatusEnum getEnumByStatus(Integer status){ + for (BpmProcessInstanceStatusEnum e : BpmProcessInstanceStatusEnum.values()) { + if (e.getStatus().equals(status)) { + return e; + } + } + return NOT_START; + } + + /** + * 通过枚举描述返回对应的枚举 + * @param desc 描述 + * @return + */ + public static BpmProcessInstanceStatusEnum getEnumByDesc(String desc){ + if (StringUtils.isEmpty(desc)) return NOT_START; + for (BpmProcessInstanceStatusEnum e : BpmProcessInstanceStatusEnum.values()) { + if (desc.equals(e.getDesc())) { + return e; + } + } + return NOT_START; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmReasonEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmReasonEnum.java new file mode 100644 index 0000000..8bf768f --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmReasonEnum.java @@ -0,0 +1,52 @@ +package com.zt.plat.module.bpm.enums.task; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程实例/任务的的处理原因枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum BpmReasonEnum { + + // ========== 流程实例的独有原因 ========== + + REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法 + CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程 + CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程 + CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS("子流程自动取消,原因:主流程已取消"), + + // ========== 流程任务的独有原因 ========== + + CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 + TIMEOUT_APPROVE("审批超时,系统自动通过"), + TIMEOUT_REJECT("审批超时,系统自动不通过"), + ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), + ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), + ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE("发起人节点首次自动通过"), // 目前仅“子流程”使用 + 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("非人工审核,自动不通过"), + CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"), + ; + + private final String reason; + + /** + * 格式化理由 + * + * @param args 参数 + * @return 理由 + */ + public String format(Object... args) { + return StrUtil.format(reason, args); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmTaskSignTypeEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmTaskSignTypeEnum.java new file mode 100644 index 0000000..40aa2b1 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmTaskSignTypeEnum.java @@ -0,0 +1,47 @@ +package com.zt.plat.module.bpm.enums.task; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程任务的加签类型枚举 + * + * @author kehaiyou + */ +@Getter +@AllArgsConstructor +public enum BpmTaskSignTypeEnum { + + /** + * 向前加签,需要前置任务审批完成,才回到原审批人 + */ + BEFORE("before", "向前加签"), + /** + * 向后加签,需要后置任务全部审批完,才会通过原审批人节点 + */ + AFTER("after", "向后加签"); + + /** + * 类型 + */ + private final String type; + /** + * 名字 + */ + private final String name; + + public static String nameOfType(String type) { + for (BpmTaskSignTypeEnum value : values()) { + if (value.type.equals(type)) { + return value.name; + } + } + return null; + } + + public static BpmTaskSignTypeEnum of(String type) { + return ArrayUtil.firstMatch(value -> value.getType().equals(type), values()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmTaskStatusEnum.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmTaskStatusEnum.java new file mode 100644 index 0000000..42ac7df --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -0,0 +1,100 @@ +package com.zt.plat.module.bpm.enums.task; + +import cn.hutool.core.util.ObjUtil; +import com.zt.plat.framework.common.util.object.ObjectUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +/** + * 流程任务 Task 的状态枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmTaskStatusEnum { + + NOT_START(-1, "未开始"), + RUNNING(1, "审批中"), + APPROVE(2, "审批通过"), + REJECT(3, "审批不通过"), + CANCEL(4, "已取消"), + + RETURN(5, "已退回"), + + /** + * 使用场景: + * 1. 任务被向后【加签】时,它在审批通过后,会变成 APPROVING 这个状态,然后等到【加签】出来的任务都被审批后,才会变成 APPROVE 审批通过 + */ + APPROVING(7, "审批通过中"), + /** + * 使用场景: + * 1. 任务被向前【加签】时,它会变成 WAIT 状态,需要等待【加签】出来的任务被审批后,它才能继续变为 RUNNING 继续审批 + * 2. 任务被向后【加签】时,【加签】出来的任务处于 WAIT 状态,它们需要等待该任务被审批后,它们才能继续变为 RUNNING 继续审批 + */ + WAIT(0, "待审批"); + + /** + * 状态 + *

+ * 如果新增时,注意 {@link #isEndStatus(Integer)} 是否需要变更 + */ + private final Integer status; + /** + * 名字 + */ + private final String name; + + public static boolean isRejectStatus(Integer status) { + return REJECT.getStatus().equals(status); + } + + /** + * 判断该状态是否已经处于 End 最终状态 + *

+ * 主要用于一些状态更新的逻辑,如果已经是最终状态,就不再进行更新 + * + * @param status 状态 + * @return 是否 + */ + public static boolean isEndStatus(Integer status) { + return ObjectUtils.equalsAny(status, + APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus(), + RETURN.getStatus(), APPROVING.getStatus()); + } + + public static boolean isCancelStatus(Integer status) { + return ObjUtil.equal(status, CANCEL.getStatus()); + } + + + /** + * 通过流程的状态返回对应的枚举 + * @param status 流程状态 + * @return + */ + public static BpmTaskStatusEnum getEnumByStatus(Integer status){ + for (BpmTaskStatusEnum e : BpmTaskStatusEnum.values()) { + if (e.getStatus().equals(status)) { + return e; + } + } + return NOT_START; + } + + /** + * 通过枚举描述返回对应的枚举 + * @param name 描述 + * @return + */ + public static BpmTaskStatusEnum getEnumByName(String name){ + if (StringUtils.isEmpty(name)) return NOT_START; + for (BpmTaskStatusEnum e : BpmTaskStatusEnum.values()) { + if (name.equals(e.getName())) { + return e; + } + } + return NOT_START; + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/Dockerfile b/zt-module-bpm/zt-module-bpm-server/Dockerfile new file mode 100644 index 0000000..230aa5c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/Dockerfile @@ -0,0 +1,19 @@ +## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性 + +FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre + +## 创建目录,并使用它作为工作目录 +RUN mkdir -p /zt-module-bpm-server +WORKDIR /zt-module-bpm-server +## 将后端项目的 Jar 文件,复制到镜像中 +COPY ./target/zt-module-bpm-server.jar app.jar + +## 设置 TZ 时区 +## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" + +## 暴露后端项目的 48080 端口 +EXPOSE 48083 + +## 启动后端项目 +CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar diff --git a/zt-module-bpm/zt-module-bpm-server/pom.xml b/zt-module-bpm/zt-module-bpm-server/pom.xml new file mode 100644 index 0000000..06a5ac4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/pom.xml @@ -0,0 +1,142 @@ + + + + com.zt.plat + zt-module-bpm + ${revision} + + 4.0.0 + zt-module-bpm-server + + ${project.artifactId} + + bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能,基于 Flowable 6 版本实现。 + 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等 + + + + + com.zt.plat + zt-spring-boot-starter-env + + + + + com.zt.plat + zt-module-bpm-api + ${revision} + + + com.zt.plat + zt-module-system-api + ${revision} + + + + + com.zt.plat + zt-spring-boot-starter-biz-data-permission + + + com.zt.plat + zt-spring-boot-starter-biz-tenant + + + + + com.zt.plat + zt-spring-boot-starter-security + + + + + com.zt.plat + zt-spring-boot-starter-mybatis + + + + com.zt.plat + zt-spring-boot-starter-redis + + + + + com.zt.plat + zt-spring-boot-starter-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + + + + + + + com.zt.plat + zt-spring-boot-starter-test + + + + + com.zt.plat + zt-spring-boot-starter-monitor + + + + + com.zt.plat + zt-spring-boot-starter-excel + + + + + org.flowable + flowable-spring-boot-starter-process + + + org.flowable + flowable-spring-boot-starter-actuator + + + com.zt.plat + zt-spring-boot-starter-biz-business + ${revision} + compile + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java new file mode 100644 index 0000000..1c86d9e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java @@ -0,0 +1,781 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package com.alibaba.druid.pool; + +import com.alibaba.cloud.commons.lang.StringUtils; +import com.alibaba.druid.VERSION; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; +import com.alibaba.druid.util.JdbcUtils; +import com.alibaba.druid.util.MySqlUtils; +import java.net.SocketTimeoutException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +public class DruidPooledStatement extends PoolableWrapper implements Statement { + private static final Log LOG = LogFactory.getLog(DruidPooledStatement.class); + private final Statement stmt; + protected DruidPooledConnection conn; + protected List resultSetTrace; + protected boolean closed; + protected int fetchRowPeak = -1; + protected int exceptionCount; + + public DruidPooledStatement(DruidPooledConnection conn, Statement stmt) { + super(stmt); + this.conn = conn; + this.stmt = stmt; + } + + protected void addResultSetTrace(ResultSet resultSet) { + if (this.resultSetTrace == null) { + this.resultSetTrace = new ArrayList(1); + } else if (this.resultSetTrace.size() > 0) { + int lastIndex = this.resultSetTrace.size() - 1; + ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(lastIndex); + + try { + if (lastResultSet.isClosed()) { + this.resultSetTrace.set(lastIndex, resultSet); + return; + } + } catch (SQLException var5) { + } + } + + this.resultSetTrace.add(resultSet); + } + + protected void recordFetchRowCount(int fetchRowCount) { + if (this.fetchRowPeak < fetchRowCount) { + this.fetchRowPeak = fetchRowCount; + } + + } + + public int getFetchRowPeak() { + return this.fetchRowPeak; + } + + protected SQLException checkException(Throwable error) throws SQLException { + String sql = null; + if (this instanceof DruidPooledPreparedStatement) { + sql = ((DruidPooledPreparedStatement)this).getSql(); + } + + this.handleSocketTimeout(error); + ++this.exceptionCount; + return this.conn.handleException(error, sql); + } + + protected SQLException checkException(Throwable error, String sql) throws SQLException { + this.handleSocketTimeout(error); + ++this.exceptionCount; + return this.conn.handleException(error, sql); + } + + protected void handleSocketTimeout(Throwable error) throws SQLException { + if (this.conn != null && this.conn.transactionInfo == null && this.conn.holder != null) { + DruidDataSource dataSource = null; + DruidConnectionHolder holder = this.conn.holder; + if (holder.dataSource instanceof DruidDataSource) { + dataSource = (DruidDataSource)holder.dataSource; + } + + if (dataSource != null) { + if (dataSource.killWhenSocketReadTimeout) { + SQLException sqlException = null; + if (error instanceof SQLException) { + sqlException = (SQLException)error; + } + + if (sqlException != null) { + Throwable cause = error.getCause(); + boolean socketReadTimeout = cause instanceof SocketTimeoutException && "Read timed out".equals(cause.getMessage()); + if (socketReadTimeout) { + if (JdbcUtils.isMysqlDbType(dataSource.dbTypeName)) { + String killQuery = MySqlUtils.buildKillQuerySql(this.conn.getConnection(), (SQLException)error); + if (killQuery != null) { + DruidPooledConnection killQueryConn = null; + Statement killQueryStmt = null; + + try { + killQueryConn = dataSource.getConnection(1000L); + if (killQueryConn != null) { + killQueryStmt = killQueryConn.createStatement(); + killQueryStmt.execute(killQuery); + if (LOG.isDebugEnabled()) { + LOG.debug(killQuery + " success."); + } + + return; + } + } catch (Exception ex) { + LOG.warn(killQuery + " error.", ex); + return; + } finally { + JdbcUtils.close(killQueryStmt); + JdbcUtils.close(killQueryConn); + } + + } + } + } + } + } + } + } + } + + public DruidPooledConnection getPoolableConnection() { + return this.conn; + } + + public Statement getStatement() { + return this.stmt; + } + + protected void checkOpen() throws SQLException { + if (this.closed) { + Throwable disableError = null; + if (this.conn != null) { + disableError = this.conn.getDisableError(); + } + + if (disableError != null) { + throw new SQLException("statement is closed", disableError); + } else { + throw new SQLException("statement is closed"); + } + } + } + + protected void clearResultSet() { + if (this.resultSetTrace != null) { + for(ResultSet rs : this.resultSetTrace) { + try { + if (!rs.isClosed()) { + rs.close(); + } + } catch (SQLException ex) { + LOG.error("clearResultSet error", ex); + } + } + + this.resultSetTrace.clear(); + } + } + + public void incrementExecuteCount() { + DruidPooledConnection conn = this.getPoolableConnection(); + if (conn != null) { + DruidConnectionHolder holder = conn.getConnectionHolder(); + if (holder != null) { + DruidAbstractDataSource dataSource = holder.getDataSource(); + if (dataSource != null) { + dataSource.incrementExecuteCount(); + } + } + } + } + + public void incrementExecuteBatchCount() { + DruidPooledConnection conn = this.getPoolableConnection(); + if (conn != null) { + DruidConnectionHolder holder = conn.getConnectionHolder(); + if (holder != null) { + if (holder.getDataSource() != null) { + DruidAbstractDataSource dataSource = holder.getDataSource(); + if (dataSource != null) { + dataSource.incrementExecuteBatchCount(); + } + } + } + } + } + + public void incrementExecuteUpdateCount() { + DruidPooledConnection conn = this.getPoolableConnection(); + if (conn != null) { + DruidConnectionHolder holder = conn.getConnectionHolder(); + if (holder != null) { + DruidAbstractDataSource dataSource = holder.getDataSource(); + if (dataSource != null) { + dataSource.incrementExecuteUpdateCount(); + } + } + } + } + + public void incrementExecuteQueryCount() { + DruidPooledConnection conn = this.conn; + if (conn != null) { + DruidConnectionHolder holder = conn.holder; + if (holder != null) { + DruidAbstractDataSource dataSource = holder.dataSource; + if (dataSource != null) { + ++dataSource.executeQueryCount; + } + } + } + } + + protected void transactionRecord(String sql) throws SQLException { + this.conn.transactionRecord(sql); + } + + public final ResultSet executeQuery(String sql) throws SQLException { + this.checkOpen(); + this.incrementExecuteQueryCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + ResultSet var3; + try { + ResultSet rs = this.stmt.executeQuery(sql); + if (rs != null) { + DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs); + this.addResultSetTrace(poolableResultSet); + DruidPooledResultSet var4 = poolableResultSet; + return var4; + } + + var3 = rs; + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final int executeUpdate(String sql) throws SQLException { + this.checkOpen(); + this.incrementExecuteUpdateCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + int var2; + try { + var2 = this.stmt.executeUpdate(sql); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var2; + } + + protected final void errorCheck(Throwable t) { + String errorClassName = t.getClass().getName(); + if (errorClassName.endsWith(".CommunicationsException") && this.conn.holder != null && this.conn.holder.dataSource.testWhileIdle) { + DruidConnectionHolder holder = this.conn.holder; + DruidAbstractDataSource dataSource = holder.dataSource; + long currentTimeMillis = System.currentTimeMillis(); + long lastActiveTimeMillis = holder.lastActiveTimeMillis; + if (lastActiveTimeMillis < holder.lastKeepTimeMillis) { + lastActiveTimeMillis = holder.lastKeepTimeMillis; + } + + long idleMillis = currentTimeMillis - lastActiveTimeMillis; + long lastValidIdleMillis = currentTimeMillis - holder.lastActiveTimeMillis; + String errorMsg = "CommunicationsException, druid version " + VERSION.getVersionNumber() + ", jdbcUrl : " + dataSource.jdbcUrl + ", testWhileIdle " + dataSource.testWhileIdle + ", idle millis " + idleMillis + ", minIdle " + dataSource.minIdle + ", poolingCount " + dataSource.getPoolingCount() + ", timeBetweenEvictionRunsMillis " + dataSource.timeBetweenEvictionRunsMillis + ", lastValidIdleMillis " + lastValidIdleMillis + ", driver " + dataSource.driver.getClass().getName(); + if (dataSource.exceptionSorter != null) { + errorMsg = errorMsg + ", exceptionSorter " + dataSource.exceptionSorter.getClass().getName(); + } + + LOG.error(errorMsg); + } + + } + + public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.checkOpen(); + this.incrementExecuteUpdateCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + int var3; + try { + var3 = this.stmt.executeUpdate(sql, autoGeneratedKeys); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.checkOpen(); + this.incrementExecuteUpdateCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + int var3; + try { + var3 = this.stmt.executeUpdate(sql, columnIndexes); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final int executeUpdate(String sql, String[] columnNames) throws SQLException { + this.checkOpen(); + this.incrementExecuteUpdateCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + int var3; + try { + var3 = this.stmt.executeUpdate(sql, columnNames); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + this.checkOpen(); + this.incrementExecuteCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + boolean var3; + try { + var3 = this.stmt.execute(sql, autoGeneratedKeys); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final boolean execute(String sql, int[] columnIndexes) throws SQLException { + this.checkOpen(); + this.incrementExecuteCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + boolean var3; + try { + var3 = this.stmt.execute(sql, columnIndexes); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final boolean execute(String sql, String[] columnNames) throws SQLException { + this.checkOpen(); + this.incrementExecuteCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + boolean var3; + try { + var3 = this.stmt.execute(sql, columnNames); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + + public int getMaxFieldSize() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getMaxFieldSize(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void close() throws SQLException { + if (!this.closed) { + this.clearResultSet(); + if (this.stmt != null) { + this.stmt.close(); + } + + this.closed = true; + DruidConnectionHolder connHolder = this.conn.getConnectionHolder(); + if (connHolder != null) { + connHolder.removeTrace(this); + } + + } + } + + public void setMaxFieldSize(int max) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setMaxFieldSize(max); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getMaxRows() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getMaxRows(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void setMaxRows(int max) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setMaxRows(max); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void setEscapeProcessing(boolean enable) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setEscapeProcessing(enable); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getQueryTimeout() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getQueryTimeout(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void setQueryTimeout(int seconds) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setQueryTimeout(seconds); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void cancel() throws SQLException { + this.checkOpen(); + + try { + this.stmt.cancel(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final SQLWarning getWarnings() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getWarnings(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void clearWarnings() throws SQLException { + this.checkOpen(); + + try { + this.stmt.clearWarnings(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void setCursorName(String name) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setCursorName(name); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + @Override + public final boolean execute(String sql) throws SQLException { + checkOpen(); + + incrementExecuteCount(); + transactionRecord(sql); + + try { + if (StringUtils.isNotEmpty(sql)){ + sql = sql.replace("TRUE", "1"); + sql = sql.replace("FALSE", "0"); + } + return stmt.execute(sql); + } catch (Throwable t) { + errorCheck(t); + throw checkException(t, sql); + } + } + + public final ResultSet getResultSet() throws SQLException { + this.checkOpen(); + + try { + ResultSet rs = this.stmt.getResultSet(); + if (rs == null) { + return null; + } else { + DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs); + this.addResultSetTrace(poolableResultSet); + return poolableResultSet; + } + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getUpdateCount() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getUpdateCount(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final boolean getMoreResults() throws SQLException { + this.checkOpen(); + + try { + boolean moreResults = this.stmt.getMoreResults(); + if (this.resultSetTrace != null && this.resultSetTrace.size() > 0) { + ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(this.resultSetTrace.size() - 1); + if (lastResultSet instanceof DruidPooledResultSet) { + DruidPooledResultSet pooledResultSet = (DruidPooledResultSet)lastResultSet; + pooledResultSet.closed = true; + } + } + + return moreResults; + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void setFetchDirection(int direction) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setFetchDirection(direction); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getFetchDirection() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getFetchDirection(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void setFetchSize(int rows) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setFetchSize(rows); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getFetchSize() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getFetchSize(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getResultSetConcurrency() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getResultSetConcurrency(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getResultSetType() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getResultSetType(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void addBatch(String sql) throws SQLException { + this.checkOpen(); + this.transactionRecord(sql); + + try { + this.stmt.addBatch(sql); + } catch (Throwable t) { + throw this.checkException(t, sql); + } + } + + public final void clearBatch() throws SQLException { + if (!this.closed) { + try { + this.stmt.clearBatch(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + } + + public int[] executeBatch() throws SQLException { + this.checkOpen(); + this.incrementExecuteBatchCount(); + this.conn.beforeExecute(); + + int[] var1; + try { + var1 = this.stmt.executeBatch(); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t); + } finally { + this.conn.afterExecute(); + } + + return var1; + } + + public final Connection getConnection() throws SQLException { + this.checkOpen(); + return this.conn; + } + + public final boolean getMoreResults(int current) throws SQLException { + this.checkOpen(); + + try { + boolean results = this.stmt.getMoreResults(current); + if (this.resultSetTrace != null && this.resultSetTrace.size() > 0) { + ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(this.resultSetTrace.size() - 1); + if (lastResultSet instanceof DruidPooledResultSet) { + DruidPooledResultSet pooledResultSet = (DruidPooledResultSet)lastResultSet; + pooledResultSet.closed = true; + } + } + + return results; + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final ResultSet getGeneratedKeys() throws SQLException { + this.checkOpen(); + + try { + ResultSet rs = this.stmt.getGeneratedKeys(); + DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs); + this.addResultSetTrace(poolableResultSet); + return poolableResultSet; + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getResultSetHoldability() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getResultSetHoldability(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final boolean isClosed() throws SQLException { + return this.closed; + } + + public final void setPoolable(boolean poolable) throws SQLException { + if (!poolable) { + throw new SQLException("not support"); + } + } + + public final boolean isPoolable() throws SQLException { + return false; + } + + public String toString() { + return this.stmt.toString(); + } + + public void closeOnCompletion() throws SQLException { + this.stmt.closeOnCompletion(); + } + + public boolean isCloseOnCompletion() throws SQLException { + return this.stmt.isCloseOnCompletion(); + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java new file mode 100644 index 0000000..f9ce039 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.bpm; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + * + * 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + * + * @author ZT + */ +@SpringBootApplication +public class BpmServerApplication { + + public static void main(String[] args) { + // 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + + SpringApplication.run(BpmServerApplication.class, args); + + // 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 http://172.16.46.63:30888/quick-start/ 文章 + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmCategoryApiImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmCategoryApiImpl.java new file mode 100644 index 0000000..7f161dd --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmCategoryApiImpl.java @@ -0,0 +1,79 @@ +package com.zt.plat.module.bpm.api.definition; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.api.definition.dto.BpmCategoryPageReqDTO; +import com.zt.plat.module.bpm.api.definition.dto.BpmCategoryRespDTO; +import com.zt.plat.module.bpm.api.definition.dto.BpmCategorySaveReqDTO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.service.definition.BpmCategoryService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * BPM 流程分类 Api 实现类 + * + * @author ZT + */ +@RestController +@Validated +public class BpmCategoryApiImpl implements BpmCategoryApi { + + @Resource + private BpmCategoryService categoryService; + + @Override + public CommonResult createCategory(@Valid BpmCategorySaveReqDTO createReqDTO) { + BpmCategorySaveReqVO createReqVO = BeanUtils.toBean(createReqDTO, BpmCategorySaveReqVO.class); + return success(categoryService.createCategory(createReqVO)); + } + + @Override + public CommonResult updateCategory(@Valid BpmCategorySaveReqDTO updateReqDTO) { + BpmCategorySaveReqVO updateReqVO = BeanUtils.toBean(updateReqDTO, BpmCategorySaveReqVO.class); + categoryService.updateCategory(updateReqVO); + return success(true); + } + + @Override + public CommonResult updateCategorySortBatch(List ids) { + categoryService.updateCategorySortBatch(ids); + return success(true); + } + + @Override + public CommonResult deleteCategory(Long id) { + categoryService.deleteCategory(id); + return success(true); + } + + @Override + public CommonResult getCategory(Long id) { + BpmCategoryDO category = categoryService.getCategory(id); + return success(BeanUtils.toBean(category, BpmCategoryRespDTO.class)); + } + + @Override + public CommonResult> getCategoryPage(@Valid BpmCategoryPageReqDTO pageReqDTO) { + BpmCategoryPageReqVO pageReqVO = BeanUtils.toBean(pageReqDTO, BpmCategoryPageReqVO.class); + PageResult pageResult = categoryService.getCategoryPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, BpmCategoryRespDTO.class)); + } + + @Override + public CommonResult> getCategorySimpleList() { + List list = categoryService.getCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(BeanUtils.toBean(list, BpmCategoryRespDTO.class)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmFormApiImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmFormApiImpl.java new file mode 100644 index 0000000..faceb1e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmFormApiImpl.java @@ -0,0 +1,76 @@ +package com.zt.plat.module.bpm.api.definition; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.api.definition.dto.BpmFormPageReqDTO; +import com.zt.plat.module.bpm.api.definition.dto.BpmFormRespDTO; +import com.zt.plat.module.bpm.api.definition.dto.BpmFormSaveReqDTO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.service.definition.BpmFormService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * 动态表单 Api 实现类 + * + * @author ZT + */ +@RestController +@Validated +public class BpmFormApiImpl implements BpmFormApi { + + @Resource + private BpmFormService formService; + + @Override + public CommonResult createForm(@Valid BpmFormSaveReqDTO createReqDTO) { + BpmFormSaveReqVO createReqVO = BeanUtils.toBean(createReqDTO, BpmFormSaveReqVO.class); + return success(formService.createForm(createReqVO)); + } + + @Override + public CommonResult updateForm(@Valid BpmFormSaveReqDTO updateReqDTO) { + BpmFormSaveReqVO updateReqVO = BeanUtils.toBean(updateReqDTO, BpmFormSaveReqVO.class); + formService.updateForm(updateReqVO); + return success(true); + } + + @Override + public CommonResult deleteForm(Long id) { + formService.deleteForm(id); + return success(true); + } + + @Override + public CommonResult getForm(Long id) { + BpmFormDO form = formService.getForm(id); + return success(BeanUtils.toBean(form, BpmFormRespDTO.class)); + } + + @Override + public CommonResult> getFormPage(BpmFormPageReqDTO pageReqDTO) { + BpmFormPageReqVO pageReqVO = BeanUtils.toBean(pageReqDTO, BpmFormPageReqVO.class); + PageResult pageResult = formService.getFormPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, BpmFormRespDTO.class)); + } + + @Override + public CommonResult> getFormSimpleList() { + List list = formService.getFormList(); + // 只返回 id、name 字段 + List dtoList = list.stream() + .map(formDO -> new BpmFormRespDTO().setId(formDO.getId()).setName(formDO.getName())) + .collect(java.util.stream.Collectors.toList()); + return success(dtoList); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmUserGroupApiImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmUserGroupApiImpl.java new file mode 100644 index 0000000..502b9d6 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/definition/BpmUserGroupApiImpl.java @@ -0,0 +1,41 @@ +package com.zt.plat.module.bpm.api.definition; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.api.definition.dto.BpmUserGroupRespDTO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import com.zt.plat.module.bpm.service.definition.BpmUserGroupService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * 用户组 Api 实现类 + * + * @author ZT + */ +@RestController +@Validated +public class BpmUserGroupApiImpl implements BpmUserGroupApi { + + @Resource + private BpmUserGroupService userGroupService; + + @Override + public CommonResult getUserGroup(Long id) { + BpmUserGroupDO userGroup = userGroupService.getUserGroup(id); + return success(BeanUtils.toBean(userGroup, BpmUserGroupRespDTO.class)); + } + + @Override + public CommonResult> getUserGroupSimpleList() { + List list = userGroupService.getUserGroupListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(BeanUtils.toBean(list, BpmUserGroupRespDTO.class)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/package-info.java new file mode 100644 index 0000000..7c3b4c4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/package-info.java @@ -0,0 +1,4 @@ +/** + * bpm API 实现类,定义暴露给其它模块的 API + */ +package com.zt.plat.module.bpm.api; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApiImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApiImpl.java new file mode 100644 index 0000000..b3d1e79 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApiImpl.java @@ -0,0 +1,150 @@ +package com.zt.plat.module.bpm.api.task; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.api.task.dto.*; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.*; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; +import com.zt.plat.module.bpm.convert.task.BpmProcessInstanceConvert; +import com.zt.plat.module.bpm.convert.task.BpmProcessInstanceDTOConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.bpm.service.task.BpmTaskService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.repository.ProcessDefinition; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * Flowable 流程实例 Api 实现类 + * + * @author ZT + * @author jason + */ +@RestController +@Valid +public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi { + + @Resource + private BpmProcessInstanceService processInstanceService; + + @Resource + private BpmTaskService taskService; + + @Resource + private BpmProcessDefinitionService processDefinitionService; + + @Resource + private AdminUserApi adminUserApi; + + @Resource + private DeptApi deptApi; + + @Override + public CommonResult createProcessInstance(Long userId, @Valid @RequestBody BpmProcessInstanceCreateReqDTO reqDTO) { + return success(processInstanceService.createProcessInstance(userId, reqDTO)); + } + + + @Override + public CommonResult getProcessInstance(String id) { + HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(id); + if (processInstance == null) { + return success(null); + } + + // 拼接返回 + ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( + processInstance.getProcessDefinitionId()); + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( + processInstance.getProcessDefinitionId()); + AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData(); + DeptRespDTO dept = null; + if (startUser != null) { + Long deptId = DeptUtil.getDeptId(startUser); + if (deptId > 0) { + dept = deptApi.getDept(deptId).getCheckedData(); + } + } + BpmProcessInstanceRespVO vo = BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance, + processDefinition, processDefinitionInfo, startUser, dept); + return success(BpmProcessInstanceDTOConvert.INSTANCE.convert(vo)); + } + + @Override + public CommonResult cancelProcessInstanceByStartUser( + Long userId, @Valid @RequestBody BpmProcessInstanceCancelReqDTO cancelReqDTO) { + BpmProcessInstanceCancelReqVO cancelReqVO = BpmProcessInstanceDTOConvert.INSTANCE.convertVO(cancelReqDTO); + processInstanceService.cancelProcessInstanceByStartUser(userId, cancelReqVO); + return success(true); + } + + @Override + public CommonResult cancelProcessInstanceByAdmin( + Long userId, @Valid @RequestBody BpmProcessInstanceCancelReqDTO cancelReqDTO) { + BpmProcessInstanceCancelReqVO cancelReqVO = BpmProcessInstanceDTOConvert.INSTANCE.convertVO(cancelReqDTO); + processInstanceService.cancelProcessInstanceByAdmin(userId, cancelReqVO); + return success(true); + } + + @Override + @SuppressWarnings("unchecked") + public CommonResult getApprovalDetail(Long userId, + @Valid @RequestBody BpmApprovalDetailReqDTO reqDTO) { + BpmApprovalDetailReqVO reqVO = BpmProcessInstanceDTOConvert.INSTANCE.convertVO(reqDTO); + if (StrUtil.isNotEmpty(reqDTO.getProcessVariablesStr())) { + reqVO.setProcessVariables(JsonUtils.parseObject(reqDTO.getProcessVariablesStr(), Map.class)); + } + BpmApprovalDetailRespVO respVO = processInstanceService.getApprovalDetail(userId, reqVO); + return success(BpmProcessInstanceDTOConvert.INSTANCE.convert(respVO)); + } + + @Override + @SuppressWarnings("unchecked") + public CommonResult> getNextApprovalNodes(Long userId, + @Valid @RequestBody BpmApprovalDetailReqDTO reqDTO) { + BpmApprovalDetailReqVO reqVO = BpmProcessInstanceDTOConvert.INSTANCE.convertVO(reqDTO); + if (StrUtil.isNotEmpty(reqDTO.getProcessVariablesStr())) { + reqVO.setProcessVariables(JsonUtils.parseObject(reqDTO.getProcessVariablesStr(), Map.class)); + } + List nodes = processInstanceService.getNextApprovalNodes(userId, reqVO); + return success(BpmProcessInstanceDTOConvert.INSTANCE.convertActivityNodes(nodes)); + } + + @Override + public CommonResult getProcessInstanceBpmnModelView(String id) { + BpmProcessInstanceBpmnModelViewRespVO respVO = processInstanceService.getProcessInstanceBpmnModelView(id); + return success(BpmProcessInstanceDTOConvert.INSTANCE.convert(respVO)); + } + + @Override + public CommonResult approveTask(BpmTaskApproveReqDTO reqVO) { + taskService.approveTask(getLoginUserId(), BeanUtils.toBean(reqVO, BpmTaskApproveReqVO.class)); + return success(true); + } + + @Override + public CommonResult rejectTask(BpmTaskRejectReqDTO reqVO) { + taskService.rejectTask(getLoginUserId(), BeanUtils.toBean(reqVO, BpmTaskRejectReqVO.class)); + return success(true); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmTaskApiImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmTaskApiImpl.java new file mode 100644 index 0000000..4d70c6a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmTaskApiImpl.java @@ -0,0 +1,203 @@ +package com.zt.plat.module.bpm.api.task; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskPageReqDTO; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskRespDTO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskPageReqVO; +import com.zt.plat.module.bpm.convert.task.BpmTaskConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.service.definition.BpmFormService; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.bpm.service.task.BpmTaskService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import java.util.*; +import java.util.stream.Stream; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertSet; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + +/** + * BPM 任务 API 实现类 + * + * @author ZT + */ +@RestController +@Validated +public class BpmTaskApiImpl implements BpmTaskApi { + + @Resource + private BpmTaskService taskService; + @Resource + private BpmProcessInstanceService processInstanceService; + @Resource + private BpmFormService formService; + @Resource + private BpmProcessDefinitionService processDefinitionService; + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + @Override + public CommonResult> getTaskTodoPage(@Valid BpmTaskPageReqDTO pageReqDTO) { + // 转换请求参数 + BpmTaskPageReqVO pageReqVO = BeanUtils.toBean(pageReqDTO, BpmTaskPageReqVO.class); + + // 调用 Service 层方法 + PageResult pageResult = taskService.getTaskTodoPage(SecurityFrameworkUtils.getLoginUserId(), pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new ArrayList<>()); + } + + // 拼接数据 - 参考 Controller 逻辑 + Map processInstanceMap = processInstanceService.getProcessInstanceMap(convertSet(pageResult.getList(), Task::getProcessInstanceId)); + Map userMap = adminUserApi.getUserMap(convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(convertSet(pageResult.getList(), Task::getProcessDefinitionId)); + + // 使用转换器构建完整的 VO 结果,然后转换为 DTO + var voPageResult = BpmTaskConvert.INSTANCE.buildTodoTaskPage(pageResult, processInstanceMap, userMap, processDefinitionInfoMap); + List result = BpmTaskConvert.INSTANCE.buildTaskRespDTOList(voPageResult.getList()); + + return success(result); + } + + @Override + public CommonResult> getTaskDonePage(@Valid BpmTaskPageReqDTO pageReqDTO) { + // 转换请求参数 + BpmTaskPageReqVO pageReqVO = BeanUtils.toBean(pageReqDTO, BpmTaskPageReqVO.class); + + // 调用 Service 层方法 + PageResult pageResult = taskService.getTaskDonePage(SecurityFrameworkUtils.getLoginUserId(), pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new ArrayList<>()); + } + + // 拼接数据 - 参考 Controller 逻辑 + Map processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(convertSet(pageResult.getList(), HistoricTaskInstance::getProcessInstanceId)); + Map userMap = adminUserApi.getUserMap(convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(convertSet(pageResult.getList(), HistoricTaskInstance::getProcessDefinitionId)); + + // 使用转换器构建完整的 VO 结果,然后转换为 DTO + var voPageResult = BpmTaskConvert.INSTANCE.buildTaskPage(pageResult, processInstanceMap, userMap, null, processDefinitionInfoMap); + List result = BpmTaskConvert.INSTANCE.buildTaskRespDTOList(voPageResult.getList()); + + return success(result); + } + + @Override + public CommonResult> getTaskManagerPage(@Valid BpmTaskPageReqDTO pageReqDTO) { + // 转换请求参数 + BpmTaskPageReqVO pageReqVO = BeanUtils.toBean(pageReqDTO, BpmTaskPageReqVO.class); + + // 调用 Service 层方法 + PageResult pageResult = taskService.getTaskPage(SecurityFrameworkUtils.getLoginUserId(), pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new ArrayList<>()); + } + + // 拼接数据 - 参考 Controller 逻辑 + Map processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(convertSet(pageResult.getList(), HistoricTaskInstance::getProcessInstanceId)); + // 获得 User 和 Dept Map + Set userIds = convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())); + userIds.addAll(convertSet(pageResult.getList(), task -> NumberUtils.parseLong(task.getAssignee()))); + Map userMap = adminUserApi.getUserMap(userIds); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(convertSet(pageResult.getList(), HistoricTaskInstance::getProcessDefinitionId)); + + // 使用转换器构建完整的 VO 结果,然后转换为 DTO + var voPageResult = BpmTaskConvert.INSTANCE.buildTaskPage(pageResult, processInstanceMap, userMap, deptMap, processDefinitionInfoMap); + List result = BpmTaskConvert.INSTANCE.buildTaskRespDTOList(voPageResult.getList()); + + return success(result); + } + + @Override + public CommonResult> getTaskListByProcessInstanceId(String processInstanceId) { + // 调用 Service 层方法 + List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true); + if (CollUtil.isEmpty(taskList)) { + return success(Collections.emptyList()); + } + + // 拼接数据 - 参考 Controller 逻辑 + Set userIds = convertSetByFlatMap(taskList, task -> Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); + Map userMap = adminUserApi.getUserMap(userIds); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + // 获得 Form Map + Map formMap = formService.getFormMap(convertSet(taskList, task -> { + String formKey = task.getFormKey(); + if (formKey == null || formKey.isBlank()) { + return 0L; + } + try { + return Long.parseLong(formKey); + } catch (NumberFormatException e) { + // 如果 formKey 不是数字(比如是URL),返回0L + return 0L; + } + })); + + // 使用转换器构建完整的 VO 结果,然后转换为 DTO + var voList = BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, formMap, userMap, deptMap); + List result = BpmTaskConvert.INSTANCE.buildTaskRespDTOList(voList); + + return success(result); + } + + @Override + public CommonResult> getTaskListByReturn(String id) { + // 调用 Service 层方法 + var userTaskList = taskService.getUserTaskListByReturn(id); + + // 转换返回结果 - 只返回 id 和 name(对应 taskDefinitionKey 和 name) + List result = userTaskList.stream().map(userTask -> { + BpmTaskRespDTO dto = new BpmTaskRespDTO(); + dto.setName(userTask.getName()); + dto.setTaskDefinitionKey(userTask.getId()); + return dto; + }).toList(); + + return success(result); + } + + @Override + public CommonResult> getTaskListByParentTaskId(String parentTaskId) { + // 调用 Service 层方法 + List taskList = taskService.getTaskListByParentTaskId(parentTaskId); + if (CollUtil.isEmpty(taskList)) { + return success(Collections.emptyList()); + } + + // 拼接数据 - 参考 Controller 逻辑 + Map userMap = adminUserApi.getUserMap(convertSetByFlatMap(taskList, user -> Stream.of(NumberUtils.parseLong(user.getAssignee()), NumberUtils.parseLong(user.getOwner())))); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + + // 使用转换器构建完整的 VO 结果,然后转换为 DTO + var voList = BpmTaskConvert.INSTANCE.buildTaskListByParentTaskId(taskList, userMap, deptMap); + List result = BpmTaskConvert.INSTANCE.buildTaskRespDTOList(voList); + + return success(result); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java new file mode 100644 index 0000000..b0c466b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java @@ -0,0 +1,15 @@ +package com.zt.plat.module.bpm.controller.admin.base.dept; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "部门精简信息 VO") +@Data +public class DeptSimpleBaseVO { + + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术部") + private String name; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/package-info.java new file mode 100644 index 0000000..141c17f --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/package-info.java @@ -0,0 +1,4 @@ +/** + * 基础包,放一些通用的 VO 类 + */ +package com.zt.plat.module.bpm.controller.admin.base; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java new file mode 100644 index 0000000..6fcbb96 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java @@ -0,0 +1,22 @@ +package com.zt.plat.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; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmCategoryController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmCategoryController.java new file mode 100644 index 0000000..b318b4b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmCategoryController.java @@ -0,0 +1,95 @@ +package com.zt.plat.module.bpm.controller.admin.definition; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategoryRespVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.service.definition.BpmCategoryService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Comparator; +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; + +@Tag(name = "管理后台 - BPM 流程分类") +@RestController +@RequestMapping("/bpm/category") +@Validated +public class BpmCategoryController { + + @Resource + private BpmCategoryService categoryService; + + @PostMapping("/create") + @Operation(summary = "创建流程分类") + @PreAuthorize("@ss.hasPermission('bpm:category:create')") + public CommonResult createCategory(@Valid @RequestBody BpmCategorySaveReqVO createReqVO) { + return success(categoryService.createCategory(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新流程分类") + @PreAuthorize("@ss.hasPermission('bpm:category:update')") + public CommonResult updateCategory(@Valid @RequestBody BpmCategorySaveReqVO updateReqVO) { + categoryService.updateCategory(updateReqVO); + 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 updateCategorySortBatch(@RequestParam("ids") List ids) { + categoryService.updateCategorySortBatch(ids); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除流程分类") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:category:delete')") + public CommonResult deleteCategory(@RequestParam("id") Long id) { + categoryService.deleteCategory(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得流程分类") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:category:query')") + public CommonResult getCategory(@RequestParam("id") Long id) { + BpmCategoryDO category = categoryService.getCategory(id); + return success(BeanUtils.toBean(category, BpmCategoryRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得流程分类分页") + @PreAuthorize("@ss.hasPermission('bpm:category:query')") + public CommonResult> getCategoryPage(@Valid BpmCategoryPageReqVO pageReqVO) { + PageResult pageResult = categoryService.getCategoryPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, BpmCategoryRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获取流程分类的精简信息列表", description = "只包含被开启的分类,主要用于前端的下拉选项") + public CommonResult> getCategorySimpleList() { + List list = categoryService.getCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus()); + list.sort(Comparator.comparingInt(BpmCategoryDO::getSort)); + return success(convertList(list, category -> new BpmCategoryRespVO().setId(category.getId()) + .setName(category.getName()).setCode(category.getCode()))); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmFormController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmFormController.java new file mode 100644 index 0000000..fa47346 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmFormController.java @@ -0,0 +1,83 @@ +package com.zt.plat.module.bpm.controller.admin.definition; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormRespVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.service.definition.BpmFormService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; + +@Tag(name = "管理后台 - 动态表单") +@RestController +@RequestMapping("/bpm/form") +@Validated +public class BpmFormController { + + @Resource + private BpmFormService formService; + + @PostMapping("/create") + @Operation(summary = "创建动态表单") + @PreAuthorize("@ss.hasPermission('bpm:form:create')") + public CommonResult createForm(@Valid @RequestBody BpmFormSaveReqVO createReqVO) { + return success(formService.createForm(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新动态表单") + @PreAuthorize("@ss.hasPermission('bpm:form:update')") + public CommonResult updateForm(@Valid @RequestBody BpmFormSaveReqVO updateReqVO) { + formService.updateForm(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除动态表单") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:form:delete')") + public CommonResult deleteForm(@RequestParam("id") Long id) { + formService.deleteForm(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得动态表单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:form:query')") + public CommonResult getForm(@RequestParam("id") Long id) { + BpmFormDO form = formService.getForm(id); + return success(BeanUtils.toBean(form, BpmFormRespVO.class)); + } + + @GetMapping({"/list-all-simple", "/simple-list"}) + @Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框") + public CommonResult> getFormSimpleList() { + List list = formService.getFormList(); + return success(convertList(list, formDO -> // 只返回 id、name 字段 + new BpmFormRespVO().setId(formDO.getId()).setName(formDO.getName()))); + } + + @GetMapping("/page") + @Operation(summary = "获得动态表单分页") + @PreAuthorize("@ss.hasPermission('bpm:form:query')") + public CommonResult> getFormPage(@Valid BpmFormPageReqVO pageVO) { + PageResult pageResult = formService.getFormPage(pageVO); + return success(BeanUtils.toBean(pageResult, BpmFormRespVO.class)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmModelController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmModelController.java new file mode 100644 index 0000000..f787066 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmModelController.java @@ -0,0 +1,200 @@ +package com.zt.plat.module.bpm.controller.admin.definition; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.*; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; +import com.zt.plat.module.bpm.convert.definition.BpmModelConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.service.definition.BpmCategoryService; +import com.zt.plat.module.bpm.service.definition.BpmFormService; +import com.zt.plat.module.bpm.service.definition.BpmModelService; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.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; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.*; +import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 流程模型") +@RestController +@RequestMapping("/bpm/model") +@Validated +public class BpmModelController { + + @Resource + private BpmModelService modelService; + @Resource + private BpmFormService formService; + @Resource + private BpmCategoryService categoryService; + @Resource + private BpmProcessDefinitionService processDefinitionService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + @GetMapping("/list") + @Operation(summary = "获得模型分页") + @Parameter(name = "name", description = "模型名称", example = "芋艿") + public CommonResult> getModelList(@RequestParam(value = "name", required = false) String name) { + List list = modelService.getModelList(name); + if (CollUtil.isEmpty(list)) { + return success(Collections.emptyList()); + } + + // 获得 Form 表单 + Set formIds = convertSet(list, model -> { + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); + return metaInfo != null ? metaInfo.getFormId() : null; + }); + Map formMap = formService.getFormMap(formIds); + // 获得 Category Map + Map categoryMap = categoryService.getCategoryMap( + convertSet(list, Model::getCategory)); + // 获得 Deployment Map + Map deploymentMap = processDefinitionService.getDeploymentMap( + convertSet(list, Model::getDeploymentId)); + // 获得 ProcessDefinition Map + List processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds( + deploymentMap.keySet()); + Map processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); + // 获得 User Map、Dept Map + Set userIds = convertSetByFlatMap(list, model -> { + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); + return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); + }); + Map userMap = adminUserApi.getUserMap(userIds); + Set deptIds = convertSetByFlatMap(list, model -> { + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); + return metaInfo != null && metaInfo.getStartDeptIds() != null ? metaInfo.getStartDeptIds().stream() : Stream.empty(); + }); + Map deptMap = deptApi.getDeptMap(deptIds); + return success(BpmModelConvert.INSTANCE.buildModelList(list, + formMap, categoryMap, deploymentMap, processDefinitionMap, userMap, deptMap)); + } + + @GetMapping("/get") + @Operation(summary = "获得模型") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:model:query')") + public CommonResult getModel(@RequestParam("id") String id) { + Model model = modelService.getModel(id); + if (model == null) { + return null; + } + byte[] bpmnBytes = modelService.getModelBpmnXML(id); + BpmSimpleModelNodeVO simpleModel = modelService.getSimpleModel(id); + return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes, simpleModel)); + } + + @PostMapping("/create") + @Operation(summary = "新建模型") + @PreAuthorize("@ss.hasPermission('bpm:model:create')") + public CommonResult createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) { + return success(modelService.createModel(createRetVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改模型") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) { + modelService.updateModel(getLoginUserId(), modelVO); + return success(true); + } + + @PutMapping("/update-sort-batch") + @Operation(summary = "批量修改模型排序") + @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3") + public CommonResult updateModelSortBatch(@RequestParam("ids") List ids) { + modelService.updateModelSortBatch(getLoginUserId(), ids); + return success(true); + } + + @PostMapping("/deploy") + @Operation(summary = "部署模型") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:model:deploy')") + public CommonResult deployModel(@RequestParam("id") String id) { + modelService.deployModel(getLoginUserId(), id); + return success(true); + } + + @PutMapping("/update-state") + @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) { + modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState()); + return success(true); + } + + @Deprecated + @PutMapping("/update-bpmn") + @Operation(summary = "修改模型的 BPMN") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) { + modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml()); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除模型") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:model:delete')") + public CommonResult deleteModel(@RequestParam("id") String id) { + modelService.deleteModel(getLoginUserId(), id); + return success(true); + } + + @DeleteMapping("/clean") + @Operation(summary = "清理模型") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:model:clean')") + public CommonResult cleanModel(@RequestParam("id") String id) { + modelService.cleanModel(getLoginUserId(), id); + return success(true); + } + + // ========== 仿钉钉/飞书的精简模型 ========= + + @GetMapping("/simple/get") + @Operation(summary = "获得仿钉钉流程设计模型") + @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") + public CommonResult getSimpleModel(@RequestParam("id") String modelId){ + return success(modelService.getSimpleModel(modelId)); + } + + @Deprecated + @PostMapping("/simple/update") + @Operation(summary = "保存仿钉钉流程设计模型") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) { + modelService.updateSimpleModel(getLoginUserId(), reqVO); + return success(Boolean.TRUE); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java new file mode 100644 index 0000000..e5ee29a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java @@ -0,0 +1,133 @@ +package com.zt.plat.module.bpm.controller.admin.definition; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.zt.plat.module.bpm.convert.definition.BpmProcessDefinitionConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.service.definition.BpmCategoryService; +import com.zt.plat.module.bpm.service.definition.BpmFormService; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.common.engine.impl.db.SuspensionState; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertSet; +import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 流程定义") +@RestController +@RequestMapping("/bpm/process-definition") +@Validated +public class BpmProcessDefinitionController { + + @Resource + private BpmProcessDefinitionService processDefinitionService; + @Resource + private BpmFormService formService; + @Resource + private BpmCategoryService categoryService; + + @GetMapping("/page") + @Operation(summary = "获得流程定义分页") + @PreAuthorize("@ss.hasPermission('bpm:process-definition:query')") + public CommonResult> getProcessDefinitionPage( + BpmProcessDefinitionPageReqVO pageReqVO) { + PageResult pageResult = processDefinitionService.getProcessDefinitionPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 获得 Category Map + Map categoryMap = categoryService.getCategoryMap( + convertSet(pageResult.getList(), ProcessDefinition::getCategory)); + // 获得 Deployment Map + Map deploymentMap = processDefinitionService.getDeploymentMap( + convertSet(pageResult.getList(), ProcessDefinition::getDeploymentId)); + // 获得 BpmProcessDefinitionInfoDO Map + Map processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap( + convertSet(pageResult.getList(), ProcessDefinition::getId)); + // 获得 Form Map + Map formMap = formService.getFormMap( + convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfoDO::getFormId)); + return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionPage( + pageResult, deploymentMap, processDefinitionMap, formMap, categoryMap)); + } + + @GetMapping ("/list") + @Operation(summary = "获得流程定义列表") + @Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举 + public CommonResult> getProcessDefinitionList( + @RequestParam("suspensionState") Integer suspensionState) { + // 1.1 获得开启的流程定义 + List list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState); + if (CollUtil.isEmpty(list)) { + return success(Collections.emptyList()); + } + // 1.2 移除不可见的流程定义 + Map 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)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得流程定义精简列表", description = "只包含未挂起的流程,主要用于前端的下拉选项") + public CommonResult> getSimpleProcessDefinitionList() { + // 只查询未挂起的流程 + List list = processDefinitionService.getProcessDefinitionListBySuspensionState( + SuspensionState.ACTIVE.getStateCode()); + // 拼接 VO 返回,只返回 id、name、key + return success(convertList(list, definition -> new BpmProcessDefinitionRespVO() + .setId(definition.getId()).setName(definition.getName()).setKey(definition.getKey()))); + } + + @GetMapping ("/get") + @Operation(summary = "获得流程定义") + @Parameter(name = "id", description = "流程编号", required = true, example = "1024") + @Parameter(name = "key", description = "流程定义标识", required = true, example = "1024") + public CommonResult getProcessDefinition( + @RequestParam(value = "id", required = false) String id, + @RequestParam(value = "key", required = false) String key) { + ProcessDefinition processDefinition = id != null ? processDefinitionService.getProcessDefinition(id) + : processDefinitionService.getActiveProcessDefinition(key); + if (processDefinition == null) { + return success(null); + } + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); + BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); + return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( + processDefinition, null, processDefinitionInfo, null, null, bpmnModel)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessExpressionController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessExpressionController.java new file mode 100644 index 0000000..08fcb42 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessExpressionController.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.bpm.controller.admin.definition; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionRespVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessExpressionDO; +import com.zt.plat.module.bpm.service.definition.BpmProcessExpressionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - BPM 流程表达式") +@RestController +@RequestMapping("/bpm/process-expression") +@Validated +public class BpmProcessExpressionController { + + @Resource + private BpmProcessExpressionService processExpressionService; + + @PostMapping("/create") + @Operation(summary = "创建流程表达式") + @PreAuthorize("@ss.hasPermission('bpm:process-expression:create')") + public CommonResult createProcessExpression(@Valid @RequestBody BpmProcessExpressionSaveReqVO createReqVO) { + return success(processExpressionService.createProcessExpression(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新流程表达式") + @PreAuthorize("@ss.hasPermission('bpm:process-expression:update')") + public CommonResult updateProcessExpression(@Valid @RequestBody BpmProcessExpressionSaveReqVO updateReqVO) { + processExpressionService.updateProcessExpression(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除流程表达式") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:process-expression:delete')") + public CommonResult deleteProcessExpression(@RequestParam("id") Long id) { + processExpressionService.deleteProcessExpression(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得流程表达式") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:process-expression:query')") + public CommonResult getProcessExpression(@RequestParam("id") Long id) { + BpmProcessExpressionDO processExpression = processExpressionService.getProcessExpression(id); + return success(BeanUtils.toBean(processExpression, BpmProcessExpressionRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得流程表达式分页") + @PreAuthorize("@ss.hasPermission('bpm:process-expression:query')") + public CommonResult> getProcessExpressionPage( + @Valid BpmProcessExpressionPageReqVO pageReqVO) { + PageResult pageResult = processExpressionService.getProcessExpressionPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, BpmProcessExpressionRespVO.class)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessListenerController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessListenerController.java new file mode 100644 index 0000000..3077a91 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmProcessListenerController.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.bpm.controller.admin.definition; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerRespVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessListenerDO; +import com.zt.plat.module.bpm.service.definition.BpmProcessListenerService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - BPM 流程监听器") +@RestController +@RequestMapping("/bpm/process-listener") +@Validated +public class BpmProcessListenerController { + + @Resource + private BpmProcessListenerService processListenerService; + + @PostMapping("/create") + @Operation(summary = "创建流程监听器") + @PreAuthorize("@ss.hasPermission('bpm:process-listener:create')") + public CommonResult createProcessListener(@Valid @RequestBody BpmProcessListenerSaveReqVO createReqVO) { + return success(processListenerService.createProcessListener(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新流程监听器") + @PreAuthorize("@ss.hasPermission('bpm:process-listener:update')") + public CommonResult updateProcessListener(@Valid @RequestBody BpmProcessListenerSaveReqVO updateReqVO) { + processListenerService.updateProcessListener(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除流程监听器") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:process-listener:delete')") + public CommonResult deleteProcessListener(@RequestParam("id") Long id) { + processListenerService.deleteProcessListener(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得流程监听器") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:process-listener:query')") + public CommonResult getProcessListener(@RequestParam("id") Long id) { + BpmProcessListenerDO processListener = processListenerService.getProcessListener(id); + return success(BeanUtils.toBean(processListener, BpmProcessListenerRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得流程监听器分页") + @PreAuthorize("@ss.hasPermission('bpm:process-listener:query')") + public CommonResult> getProcessListenerPage( + @Valid BpmProcessListenerPageReqVO pageReqVO) { + PageResult pageResult = processListenerService.getProcessListenerPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, BpmProcessListenerRespVO.class)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmUserGroupController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmUserGroupController.java new file mode 100644 index 0000000..226e5da --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/BpmUserGroupController.java @@ -0,0 +1,83 @@ +package com.zt.plat.module.bpm.controller.admin.definition; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import com.zt.plat.module.bpm.service.definition.BpmUserGroupService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; + +@Tag(name = "管理后台 - 用户组") +@RestController +@RequestMapping("/bpm/user-group") +@Validated +public class BpmUserGroupController { + + @Resource + private BpmUserGroupService userGroupService; + + @PostMapping("/create") + @Operation(summary = "创建用户组") + @PreAuthorize("@ss.hasPermission('bpm:user-group:create')") + public CommonResult createUserGroup(@Valid @RequestBody BpmUserGroupSaveReqVO createReqVO) { + return success(userGroupService.createUserGroup(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新用户组") + @PreAuthorize("@ss.hasPermission('bpm:user-group:update')") + public CommonResult updateUserGroup(@Valid @RequestBody BpmUserGroupSaveReqVO updateReqVO) { + userGroupService.updateUserGroup(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除用户组") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:user-group:delete')") + public CommonResult deleteUserGroup(@RequestParam("id") Long id) { + userGroupService.deleteUserGroup(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得用户组") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('bpm:user-group:query')") + public CommonResult getUserGroup(@RequestParam("id") Long id) { + BpmUserGroupDO userGroup = userGroupService.getUserGroup(id); + return success(BeanUtils.toBean(userGroup, BpmUserGroupRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得用户组分页") + @PreAuthorize("@ss.hasPermission('bpm:user-group:query')") + public CommonResult> getUserGroupPage(@Valid BpmUserGroupPageReqVO pageVO) { + PageResult pageResult = userGroupService.getUserGroupPage(pageVO); + return success(BeanUtils.toBean(pageResult, BpmUserGroupRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获取用户组精简信息列表", description = "只包含被开启的用户组,主要用于前端的下拉选项") + public CommonResult> getUserGroupSimpleList() { + List list = userGroupService.getUserGroupListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, group -> new BpmUserGroupRespVO().setId(group.getId()).setName(group.getName()))); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategoryPageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategoryPageReqVO.java new file mode 100644 index 0000000..5de14e2 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategoryPageReqVO.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.category; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageParam; +import com.zt.plat.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - BPM 流程分类分页 Request VO") +@Data +public class BpmCategoryPageReqVO extends PageParam { + + @Schema(description = "分类名", example = "王五") + private String name; + + @Schema(description = "分类标志", example = "OA") + private String code; + + @Schema(description = "分类状态", example = "1") + @InEnum(CommonStatusEnum.class) + private Integer status; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategoryRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategoryRespVO.java new file mode 100644 index 0000000..5f3db13 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategoryRespVO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.category; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - BPM 流程分类 Response VO") +@Data +public class BpmCategoryRespVO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") + private Long id; + + @Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + private String name; + + @Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA") + private String code; + + @Schema(description = "分类描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜") + private String description; + + @Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer sort; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategorySaveReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategorySaveReqVO.java new file mode 100644 index 0000000..0203453 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/category/BpmCategorySaveReqVO.java @@ -0,0 +1,37 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.category; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - BPM 流程分类新增/修改 Request VO") +@Data +public class BpmCategorySaveReqVO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") + private Long id; + + @Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @NotEmpty(message = "分类名不能为空") + private String name; + + @Schema(description = "分类描述", example = "你猜") + private String description; + + @Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA") + @NotEmpty(message = "分类标志不能为空") + private String code; + + @Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "分类状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + + @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "分类排序不能为空") + private Integer sort; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionPageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionPageReqVO.java new file mode 100644 index 0000000..1d3bbed --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionPageReqVO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.expression; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageParam; +import com.zt.plat.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - BPM 流程表达式分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class BpmProcessExpressionPageReqVO extends PageParam { + + @Schema(description = "表达式名字", example = "李四") + private String name; + + @Schema(description = "表达式状态", example = "1") + @InEnum(CommonStatusEnum.class) + private Integer status; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java new file mode 100644 index 0000000..2bb959b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.expression; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - BPM 流程表达式 Response VO") +@Data +public class BpmProcessExpressionRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3870") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "表达式名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("表达式名字") + private String name; + + @Schema(description = "表达式状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "表达式", requiredMode = Schema.RequiredMode.REQUIRED) + private String expression; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionSaveReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionSaveReqVO.java new file mode 100644 index 0000000..a5771d1 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionSaveReqVO.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.expression; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - BPM 流程表达式新增/修改 Request VO") +@Data +public class BpmProcessExpressionSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3870") + private Long id; + + @Schema(description = "表达式名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotEmpty(message = "表达式名字不能为空") + private String name; + + @Schema(description = "表达式状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "表达式状态不能为空") + private Integer status; + + @Schema(description = "表达式", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "表达式不能为空") + private String expression; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormFieldVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormFieldVO.java new file mode 100644 index 0000000..53af985 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormFieldVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.form; + +import lombok.Data; + +/** + * 流程表单字段 VO + */ +@Data +public class BpmFormFieldVO { + + /** + * 字段类型 + */ + private String type; + /** + * 字段标识 + */ + private String field; + /** + * 字段标题 + */ + private String title; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java new file mode 100644 index 0000000..437f67c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java @@ -0,0 +1,14 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.form; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 动态表单分页 Request VO") +@Data +public class BpmFormPageReqVO extends PageParam { + + @Schema(description = "表单名称", example = "芋道") + private String name; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java new file mode 100644 index 0000000..42295df --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java @@ -0,0 +1,39 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 动态表单 Response VO") +@Data +public class BpmFormRespVO { + + @Schema(description = "表单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "表单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + @NotNull(message = "表单名称不能为空") + private String name; + + @Schema(description = "表单的配置-JSON 字符串", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "表单的配置不能为空") + private String conf; + + @Schema(description = "表单项的数组-JSON 字符串的数组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "表单项的数组不能为空") + private List fields; + + @Schema(description = "表单状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "表单状态不能为空") + private Integer status; // 参见 CommonStatusEnum 枚举 + + @Schema(description = "备注", example = "我是备注") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormSaveReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormSaveReqVO.java new file mode 100644 index 0000000..faa420b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/form/BpmFormSaveReqVO.java @@ -0,0 +1,35 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 动态表单创建/更新 Request VO") +@Data +public class BpmFormSaveReqVO { + + @Schema(description = "表单编号", example = "1024") + private Long id; + + @Schema(description = "表单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + @NotNull(message = "表单名称不能为空") + private String name; + + @Schema(description = "表单的配置-JSON 字符串", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "表单的配置不能为空") + private String conf; + + @Schema(description = "表单项的数组-JSON 字符串的数组", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "表单项的数组不能为空") + private List fields; + + @Schema(description = "表单状态-参见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "表单状态不能为空") + private Integer status; + + @Schema(description = "备注", example = "我是备注") + private String remark; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java new file mode 100644 index 0000000..a146e35 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.group; + +import com.zt.plat.framework.common.pojo.PageParam; +import com.zt.plat.framework.common.util.date.DateUtils; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 用户组分页 Request VO") +@Data +public class BpmUserGroupPageReqVO extends PageParam { + + @Schema(description = "编号", example = "1024") + private Long id; + + @Schema(description = "组名", example = "芋道") + private String name; + + @Schema(description = "状态", example = "1") + private Integer status; + + @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "创建时间") + private LocalDateTime[] createTime; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java new file mode 100644 index 0000000..b356351 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.group; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Set; + +@Schema(description = "管理后台 - 用户组 Response VO") +@Data +public class BpmUserGroupRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "组名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") + private String description; + + @Schema(description = "成员编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") + private Set userIds; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSaveReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSaveReqVO.java new file mode 100644 index 0000000..ce2c7d9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSaveReqVO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.group; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - 用户组创建/修改 Request VO") +@Data +public class BpmUserGroupSaveReqVO { + + @Schema(description = "编号", example = "1024") + private Long id; + + @Schema(description = "组名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + @NotNull(message = "组名不能为空") + private String name; + + @Schema(description = "描述", example = "芋道源码") + private String description; + + @Schema(description = "成员编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") + @NotNull(message = "成员编号数组不能为空") + private Set userIds; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerPageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerPageReqVO.java new file mode 100644 index 0000000..13b8a73 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerPageReqVO.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.listener; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageParam; +import com.zt.plat.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - BPM 流程监听器分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class BpmProcessListenerPageReqVO extends PageParam { + + @Schema(description = "监听器名字", example = "赵六") + private String name; + + @Schema(description = "监听器类型", example = "execution") + private String type; + + @Schema(description = "监听事件", example = "start") + private String event; + + @Schema(description = "状态", example = "1") + @InEnum(CommonStatusEnum.class) + private Integer status; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerRespVO.java new file mode 100644 index 0000000..7bfdde8 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerRespVO.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.listener; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - BPM 流程监听器 Response VO") +@Data +public class BpmProcessListenerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13089") + private Long id; + + @Schema(description = "监听器名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + private String name; + + @Schema(description = "监听器类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "execution") + private String type; + + @Schema(description = "监听器状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "监听事件", requiredMode = Schema.RequiredMode.REQUIRED, example = "start") + private String event; + + @Schema(description = "监听器值类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "class") + private String valueType; + + @Schema(description = "监听器值", requiredMode = Schema.RequiredMode.REQUIRED) + private String value; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerSaveReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerSaveReqVO.java new file mode 100644 index 0000000..908caa9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerSaveReqVO.java @@ -0,0 +1,39 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.listener; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - BPM 流程监听器新增/修改 Request VO") +@Data +public class BpmProcessListenerSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13089") + private Long id; + + @Schema(description = "监听器名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @NotEmpty(message = "监听器名字不能为空") + private String name; + + @Schema(description = "监听器类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "execution") + @NotEmpty(message = "监听器类型不能为空") + private String type; + + @Schema(description = "监听器状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "监听器状态不能为空") + private Integer status; + + @Schema(description = "监听事件", requiredMode = Schema.RequiredMode.REQUIRED, example = "start") + @NotEmpty(message = "监听事件不能为空") + private String event; + + @Schema(description = "监听器值类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "class") + @NotEmpty(message = "监听器值类型不能为空") + private String valueType; + + @Schema(description = "监听器值", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "监听器值不能为空") + private String value; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java new file mode 100644 index 0000000..03e0e5f --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@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; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java new file mode 100644 index 0000000..cabc1fd --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -0,0 +1,180 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.model; + +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.framework.common.validation.InEnum; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.enums.definition.BpmAutoApproveTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmModelFormTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmModelTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import java.util.List; + +/** + * BPM 流程 MetaInfo Response DTO + * 主要用于 { Model#setMetaInfo(String)} 的存储 + * + * 最终,它的字段和 + * {@link com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} + * 是一致的 + * + * @author ZT + */ +@Data +public class BpmModelMetaInfoVO { + + @Schema(description = "流程图标", example = "https://www.iocoder.cn/zt.jpg") + @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 startUserIds; + + @Schema(description = "可发起部门编号数组", example = "[2,4,6]") + private List startDeptIds; + + @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]") + @NotEmpty(message = "可管理用户编号数组不能为空") + private List managerUserIds; + + @Schema(description = "排序", example = "1") + private Long sort; // 创建时,后端自动生成 + + @Schema(description = "允许撤销审批中的申请", example = "true") + private Boolean allowCancelRunningProcess; + + @Schema(description = "流程 ID 规则", example = "{}") + private ProcessIdRule processIdRule; + + @Schema(description = "自动去重类型", example = "1") + @InEnum(BpmAutoApproveTypeEnum.class) + private Integer autoApprovalType; + + @Schema(description = "标题设置", example = "{}") + private TitleSetting titleSetting; + + @Schema(description = "摘要设置", example = "{}") + private SummarySetting summarySetting; + + @Schema(description = "流程前置通知设置", example = "{}") + private HttpRequestSetting processBeforeTriggerSetting; + + @Schema(description = "流程后置通知设置", example = "{}") + private HttpRequestSetting processAfterTriggerSetting; + + @Schema(description = "任务前置通知设置", example = "{}") + private HttpRequestSetting taskBeforeTriggerSetting; + + @Schema(description = "任务后置通知设置", example = "{}") + private HttpRequestSetting taskAfterTriggerSetting; + + @Schema(description = "流程 ID 规则") + @Data + @Valid + public static class ProcessIdRule { + + @Schema(description = "是否启用", example = "false") + @NotNull(message = "是否启用不能为空") + private Boolean enable; + + @Schema(description = "前缀", example = "XX") + private String prefix; + + @Schema(description = "中缀", example = "20250120") + private String infix; // 精确到日、精确到时、精确到分、精确到秒 + + @Schema(description = "后缀", example = "YY") + private String postfix; + + @Schema(description = "序列长度", example = "5") + @NotNull(message = "序列长度不能为空") + private Integer length; + + } + + @Schema(description = "标题设置") + @Data + @Valid + public static class TitleSetting { + + @Schema(description = "是否自定义", example = "false") + @NotNull(message = "是否自定义不能为空") + private Boolean enable; + + @Schema(description = "标题", example = "流程标题") + private String title; + + } + + @Schema(description = "摘要设置") + @Data + @Valid + public static class SummarySetting { + + @Schema(description = "是否自定义", example = "false") + @NotNull(message = "是否自定义不能为空") + private Boolean enable; + + @Schema(description = "摘要字段数组", example = "[]") + private List summary; + + } + + @Schema(description = "http 请求通知设置", example = "{}") + @Data + public static class HttpRequestSetting { + + @Schema(description = "请求路径", example = "http://127.0.0.1") + @NotEmpty(message = "请求 URL 不能为空") + @URL(message = "请求 URL 格式不正确") + private String url; + + @Schema(description = "请求头参数设置", example = "[]") + @Valid + private List header; + + @Schema(description = "请求头参数设置", example = "[]") + @Valid + private List body; + + /** + * 请求返回处理设置,用于修改流程表单值 + *

+ * key:表示要修改的流程表单字段名(name) + * value:接口返回的字段名 + */ + @Schema(description = "请求返回处理设置", example = "[]") + private List> response; + + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java new file mode 100644 index 0000000..12d6ac1 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java @@ -0,0 +1,57 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.model; + +import com.zt.plat.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.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 extends BpmModelMetaInfoVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_zt") + private String key; + + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "流程图标", example = "https://www.iocoder.cn/zt.jpg") + private String icon; + + @Schema(description = "流程分类编号", example = "1") + private String category; + @Schema(description = "流程分类名字", example = "请假") + private String categoryName; + + @Schema(description = "表单名字", example = "请假表单") + private String formName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "可发起的用户数组") + private List startUsers; + + @Schema(description = "可发起的部门数组") + private List startDepts; + + @Schema(description = "BPMN XML") + private String bpmnXml; + + @Schema(description = "仿钉钉流程设计模型对象") + private BpmSimpleModelNodeVO simpleModel; + + /** + * 最新部署的流程定义 + */ + private BpmProcessDefinitionRespVO processDefinition; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java new file mode 100644 index 0000000..279519c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java @@ -0,0 +1,34 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.model; + +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@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_zt") + @NotEmpty(message = "流程标识不能为空") + private String key; + + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + @NotEmpty(message = "流程名称不能为空") + private String name; + + @Schema(description = "流程分类", example = "1") + private String category; + + @Schema(description = "BPMN XML") + private String bpmnXml; + + @Schema(description = "仿钉钉流程设计模型对象") + @Valid + private BpmSimpleModelNodeVO simpleModel; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java new file mode 100644 index 0000000..cd7fea1 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 流程模型更新状态 Request VO") +@Data +public class BpmModelUpdateStateReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "编号不能为空") + private String id; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "状态不能为空") + private Integer state; // 参见 Flowable SuspensionState 枚举 + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java new file mode 100644 index 0000000..8dc222c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -0,0 +1,526 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple; + +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.framework.common.validation.InEnum; +import com.zt.plat.module.bpm.enums.definition.*; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.flowable.bpmn.model.IOParameter; +import org.hibernate.validator.constraints.URL; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +@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(BpmSimpleModelNodeTypeEnum.class) + private Integer type; + + @Schema(description = "模型节点名称", example = "领导审批") + private String name; + + @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") + private String showText; + + @Schema(description = "子节点") + private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个 + + @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> fieldsPermission; + + @Schema(description = "操作按钮设置", example = "[]") + private List buttonsSetting; // 用于审批节点 + + @Schema(description = "是否需要签名", example = "false") + private Boolean signEnable; + + @Schema(description = "是否填写审批意见", example = "false") + private Boolean reasonRequire; + + /** + * 审批节点拒绝处理 + */ + private RejectHandler rejectHandler; + + /** + * 审批节点超时处理 + */ + private TimeoutHandler timeoutHandler; + + @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1") + @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class) + private Integer assignStartUserHandlerType; + + /** + * 空处理策略 + */ + private AssignEmptyHandler assignEmptyHandler; + + /** + * 创建任务监听器 + */ + private ListenerHandler taskCreateListener; + /** + * 指派任务监听器 + */ + private ListenerHandler taskAssignListener; + /** + * 完成任务监听器 + */ + private ListenerHandler taskCompleteListener; + + @Schema(description = "延迟器设置", example = "{}") + private DelaySetting delaySetting; + + @Schema(description = "条件节点") + private List conditionNodes; // 补充说明:有且仅有条件、并行、包容分支会使用 + + /** + * 条件节点设置 + */ + private ConditionSetting conditionSetting; // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE + + @Schema(description = "路由分支组", example = "[]") + private List routerGroups; + + @Schema(description = "路由分支默认分支 ID", example = "Flow_xxx", hidden = true) // 由后端生成(不从前端传递),所以 hidden = true + @JsonIgnore + private String routerDefaultFlowId; // 仅用于路由分支节点 BpmSimpleModelNodeType.ROUTER_BRANCH_NODE + + /** + * 触发器节点设置 + */ + private TriggerSetting triggerSetting; + + @Schema(description = "附加节点 Id", example = "UserTask_xxx", hidden = true) // 由后端生成(不从前端传递),所以 hidden = true + @JsonIgnore + private String attachNodeId; // 目前用于触发器节点(HTTP 回调)。需要 UserTask 和 ReceiveTask(附加节点) 来完成 + + /** + * 子流程设置 + */ + private ChildProcessSetting childProcessSetting; + + @Schema(description = "任务监听器") + @Valid + @Data + public static class ListenerHandler { + + @Schema(description = "是否开启任务监听器", example = "false") + @NotNull(message = "是否开启任务监听器不能为空") + private Boolean enable; + + @Schema(description = "请求路径", example = "http://xxxxx") + private String path; + + @Schema(description = "请求头", example = "[]") + private List header; + + @Schema(description = "请求体", example = "[]") + private List body; + + } + + @Schema(description = "HTTP 请求参数设置") + @Data + public static class HttpRequestParam { + + @Schema(description = "值类型", example = "1") + @InEnum(BpmHttpRequestParamTypeEnum.class) + @NotNull(message = "值类型不能为空") + private Integer type; + + @Schema(description = "键", example = "xxx") + @NotEmpty(message = "键不能为空") + private String key; + + @Schema(description = "值", example = "xxx") + @NotEmpty(message = "值不能为空") + private String value; + + } + + @Schema(description = "审批节点拒绝处理策略") + @Data + public static class RejectHandler { + + @Schema(description = "拒绝处理类型", example = "1") + @InEnum(BpmUserTaskRejectHandlerTypeEnum.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 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 + // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE + public static class ConditionSetting { + + @Schema(description = "条件类型", example = "1") + @InEnum(BpmSimpleModeConditionTypeEnum.class) + private Integer conditionType; + + @Schema(description = "条件表达式", example = "${day>3}") + private String conditionExpression; + + @Schema(description = "是否默认条件", example = "true") + private Boolean defaultFlow; + + /** + * 条件组 + */ + private ConditionGroups conditionGroups; + } + + @Schema(description = "条件组") + @Data + @Valid + public static class ConditionGroups { + + @Schema(description = "条件组下的条件关系是否为与关系", example = "true") + @NotNull(message = "条件关系不能为空") + private Boolean and; + + @Schema(description = "条件组下的条件", example = "[]") + @NotEmpty(message = "条件不能为空") + private List 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 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; + } + + @Schema(description = "延迟器") + @Data + @Valid + public static class DelaySetting { + + @Schema(description = "延迟时间类型", example = "1") + @NotNull(message = "延迟时间类型不能为空") + @InEnum(BpmDelayTimerTypeEnum.class) + private Integer delayType; + + @Schema(description = "延迟时间表达式", example = "PT1H,2025-01-01T00:00:00") + @NotEmpty(message = "延迟时间表达式不能为空") + private String delayTime; + } + + @Schema(description = "路由分支") + @Data + @Valid + public static class RouterSetting { + + @Schema(description = "节点 Id", example = "Activity_xxx") // 跳转到该节点 + @NotEmpty(message = "节点 Id 不能为空") + private String nodeId; + + @Schema(description = "条件类型", example = "1") + @InEnum(BpmSimpleModeConditionTypeEnum.class) + @NotNull(message = "条件类型不能为空") + private Integer conditionType; + + @Schema(description = "条件表达式", example = "${day>3}") + private String conditionExpression; + + @Schema(description = "条件组", example = "{}") + private ConditionGroups conditionGroups; + } + + @Schema(description = "触发器节点配置") + @Data + @Valid + public static class TriggerSetting { + + @Schema(description = "触发器类型", example = "1") + @InEnum(BpmTriggerTypeEnum.class) + @NotNull(message = "触发器类型不能为空") + private Integer type; + + /** + * http 请求触发器设置 + */ + @Valid + private HttpRequestTriggerSetting httpRequestSetting; + + /** + * 流程表单触发器设置 + */ + private List formSettings; + + @Schema(description = "http 请求触发器设置", example = "{}") + @Data + public static class HttpRequestTriggerSetting { + + @Schema(description = "请求路径", example = "http://127.0.0.1") + @NotEmpty(message = "请求 URL 不能为空") + @URL(message = "请求 URL 格式不正确") + private String url; + + @Schema(description = "请求头参数设置", example = "[]") + @Valid + private List header; + + @Schema(description = "请求头参数设置", example = "[]") + @Valid + private List body; + + /** + * 请求返回处理设置,用于修改流程表单值 + *

+ * key:表示要修改的流程表单字段名(name) + * value:接口返回的字段名 + */ + @Schema(description = "请求返回处理设置", example = "[]") + private List> response; + + /** + * Http 回调请求,需要指定回调任务 Key,用于回调执行 + */ + @Schema(description = "回调任务 Key", example = "xxx", hidden = true) + private String callbackTaskDefineKey; + + } + + @Schema(description = "流程表单触发器设置", example = "{}") + @Data + public static class FormTriggerSetting { + + @Schema(description = "条件类型", example = "1") + @InEnum(BpmSimpleModeConditionTypeEnum.class) + private Integer conditionType; + + @Schema(description = "条件表达式", example = "${day>3}") + private String conditionExpression; + + @Schema(description = "条件组", example = "{}") + private ConditionGroups conditionGroups; + + @Schema(description = "修改的表单字段", example = "{}") + private Map updateFormFields; + + @Schema(description = "删除表单字段", example = "[]") + private Set deleteFields; + } + } + + @Schema(description = "子流程节点配置") + @Data + @Valid + public static class ChildProcessSetting { + + @Schema(description = "被调用流程", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx") + @NotEmpty(message = "被调用流程不能为空") + private String calledProcessDefinitionKey; + + @Schema(description = "被调用流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx") + @NotEmpty(message = "被调用流程名称不能为空") + private String calledProcessDefinitionName; + + @Schema(description = "是否异步", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否异步不能为空") + private Boolean async; + + @Schema(description = "输入参数(主->子)", example = "[]") + private List inVariables; + + @Schema(description = "输出参数(子->主)", example = "[]") + private List outVariables; + + @Schema(description = "是否自动跳过子流程发起节点", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否自动跳过子流程发起节点不能为空") + private Boolean skipStartUserNode; + + @Schema(description = "子流程发起人配置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + @NotNull(message = "子流程发起人配置不能为空") + private StartUserSetting startUserSetting; + + @Schema(description = "超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + private TimeoutSetting timeoutSetting; + + @Schema(description = "多实例设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + private MultiInstanceSetting multiInstanceSetting; + + @Schema(description = "子流程发起人配置") + @Data + @Valid + public static class StartUserSetting { + + @Schema(description = "子流程发起人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "子流程发起人类型") + @InEnum(BpmChildProcessStartUserTypeEnum.class) + private Integer type; + + @Schema(description = "表单", example = "xxx") + private String formField; + + @Schema(description = "当子流程发起人为空时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "当子流程发起人为空时类型不能为空") + @InEnum(BpmChildProcessStartUserEmptyTypeEnum.class) + private Integer emptyType; + + } + + @Schema(description = "超时设置") + @Data + @Valid + public static class TimeoutSetting { + + @Schema(description = "是否开启超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否开启超时设置不能为空") + private Boolean enable; + + @Schema(description = "时间类型", example = "1") + @InEnum(BpmDelayTimerTypeEnum.class) + private Integer type; + + @Schema(description = "时间表达式", example = "PT1H,2025-01-01T00:00:00") + private String timeExpression; + + } + + @Schema(description = "多实例设置") + @Data + @Valid + public static class MultiInstanceSetting { + + @Schema(description = "是否开启多实例", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否开启多实例不能为空") + private Boolean enable; + + @Schema(description = "是否串行", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否串行不能为空") + private Boolean sequential; + + @Schema(description = "完成比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + @NotNull(message = "完成比例不能为空") + private Integer approveRatio; + + @Schema(description = "多实例来源类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "多实例来源类型不能为空") + @InEnum(BpmChildProcessMultiInstanceSourceTypeEnum.class) + private Integer sourceType; + + @Schema(description = "多实例来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "多实例来源不能为空") + private String source; + + } + + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java new file mode 100644 index 0000000..43981ba --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 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; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java new file mode 100644 index 0000000..8c61e28 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java @@ -0,0 +1,14 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.process; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 流程定义分页 Request VO") +@Data +public class BpmProcessDefinitionPageReqVO extends PageParam { + + @Schema(description = "标识-精准匹配", example = "process1641042089407") + private String key; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java new file mode 100644 index 0000000..5e65c0f --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java @@ -0,0 +1,71 @@ +package com.zt.plat.module.bpm.controller.admin.definition.vo.process; + +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +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 BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer version; + + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "youdao") + private String key; + + @Schema(description = "流程分类", example = "1") + private String category; + @Schema(description = "流程分类名字", example = "请假") + private String categoryName; + + @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer modelType; // 参见 BpmModelTypeEnum 枚举类 + + @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC") + private String modelId; + + @Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED) + private String formConf; + @Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED) + private List formFields; + @Schema(description = "表单名字", example = "请假表单") + private String formName; + + @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer suspensionState; // 参见 SuspensionState 枚举 + + @Schema(description = "部署时间") + private LocalDateTime deploymentTime; // 需要从对应的 Deployment 读取,非必须返回 + + @Schema(description = "BPMN XML") + private String bpmnXml; // 需要从对应的 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 { + + @Schema(description = "任务标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "sudo") + private String id; + + @Schema(description = "任务名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + private String name; + + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/BpmOALeaveController.http b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/BpmOALeaveController.http new file mode 100644 index 0000000..96bbf96 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/BpmOALeaveController.http @@ -0,0 +1,12 @@ +### 请求 /bpm/oa/leave/create 接口 => 成功 +POST {{baseUrl}}/bpm/oa/leave/create +Content-Type: application/json +tenant-id: 1 +Authorization: Bearer {{token}} + +{ + "startTime": "2022-03-01", + "endTime": "2022-03-05", + "type": 1, + "reason": "我要请假啦啦啦!" +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/BpmOALeaveController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/BpmOALeaveController.java new file mode 100644 index 0000000..51d5094 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/BpmOALeaveController.java @@ -0,0 +1,62 @@ +package com.zt.plat.module.bpm.controller.admin.oa; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO; +import com.zt.plat.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO; +import com.zt.plat.module.bpm.controller.admin.oa.vo.BpmOALeaveRespVO; +import com.zt.plat.module.bpm.dal.dataobject.oa.BpmOALeaveDO; +import com.zt.plat.module.bpm.service.oa.BpmOALeaveService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +/** + * OA 请假申请 Controller,用于演示自己存储数据,接入工作流的例子 + * + * @author jason + * @author ZT + */ +@Tag(name = "管理后台 - OA 请假申请") +@RestController +@RequestMapping("/bpm/oa/leave") +@Validated +public class BpmOALeaveController { + + @Resource + private BpmOALeaveService leaveService; + + @PostMapping("/create") + @PreAuthorize("@ss.hasPermission('bpm:oa-leave:create')") + @Operation(summary = "创建请求申请") + public CommonResult createLeave(@Valid @RequestBody BpmOALeaveCreateReqVO createReqVO) { + return success(leaveService.createLeave(getLoginUserId(), createReqVO)); + } + + @GetMapping("/get") + @PreAuthorize("@ss.hasPermission('bpm:oa-leave:query')") + @Operation(summary = "获得请假申请") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public CommonResult getLeave(@RequestParam("id") Long id) { + BpmOALeaveDO leave = leaveService.getLeave(id); + return success(BeanUtils.toBean(leave, BpmOALeaveRespVO.class)); + } + + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('bpm:oa-leave:query')") + @Operation(summary = "获得请假申请分页") + public CommonResult> getLeavePage(@Valid BpmOALeavePageReqVO pageVO) { + PageResult pageResult = leaveService.getLeavePage(getLoginUserId(), pageVO); + return success(BeanUtils.toBean(pageResult, BpmOALeaveRespVO.class)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/package-info.java new file mode 100644 index 0000000..5f65c5b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/package-info.java @@ -0,0 +1,5 @@ +/** + * OA 示例,用于演示外部业务接入 BPM 工作流的示例 + * 一般的接入方式,只需要调用 接口,后续 Admin 用户在管理后台的【待办事务】进行审批 + */ +package com.zt.plat.module.bpm.controller.admin.oa; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java new file mode 100644 index 0000000..4078b99 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java @@ -0,0 +1,43 @@ +package com.zt.plat.module.bpm.controller.admin.oa.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 请假申请创建 Request VO") +@Data +public class BpmOALeaveCreateReqVO { + + @Schema(description = "请假的开始时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "开始时间不能为空") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime startTime; + + @Schema(description = "请假的结束时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "结束时间不能为空") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime endTime; + + @Schema(description = "请假类型-参见 bpm_oa_type 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; + + @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读芋道源码") + private String reason; + + @Schema(description = "发起人自选审批人 Map", example = "{taskKey1: [1, 2]}") + private Map> startUserSelectAssignees; + + @AssertTrue(message = "结束时间,需要在开始时间之后") + public boolean isEndTimeValid() { + return !getEndTime().isBefore(getStartTime()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java new file mode 100644 index 0000000..f4226a4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.bpm.controller.admin.oa.vo; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 请假申请分页 Request VO") +@Data +public class BpmOALeavePageReqVO extends PageParam { + + @Schema(description = "状态", example = "1") + private Integer status; // 参见 BpmProcessInstanceResultEnum 枚举 + + @Schema(description = "请假类型,参见 bpm_oa_type", example = "1") + private Integer type; + + @Schema(description = "原因,模糊匹配", example = "阅读芋道源码") + private String reason; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Schema(description = "申请时间") + private LocalDateTime[] createTime; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java new file mode 100644 index 0000000..02a5555 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.bpm.controller.admin.oa.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 请假申请 Response VO") +@Data +public class BpmOALeaveRespVO { + + @Schema(description = "请假表单主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "请假类型,参见 bpm_oa_type 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; + + @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读芋道源码") + private String reason; + + @Schema(description = "申请时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "请假的开始时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime startTime; + + @Schema(description = "请假的结束时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime endTime; + + @Schema(description = "流程编号") + private String processInstanceId; + + @Schema(description = "审批结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.http b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.http new file mode 100644 index 0000000..c690827 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/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}} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java new file mode 100644 index 0000000..53ea6fa --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -0,0 +1,202 @@ +package com.zt.plat.module.bpm.controller.admin.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.*; +import com.zt.plat.module.bpm.convert.task.BpmProcessInstanceConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.service.definition.BpmCategoryService; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.bpm.service.task.BpmTaskService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.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; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.task.api.Task; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.*; +import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请” +@RestController +@RequestMapping("/bpm/process-instance") +@Validated +public class BpmProcessInstanceController { + + @Resource + private BpmProcessInstanceService processInstanceService; + @Resource + private BpmTaskService taskService; + @Resource + private BpmProcessDefinitionService processDefinitionService; + @Resource + private BpmCategoryService categoryService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + @GetMapping("/my-page") + @Operation(summary = "获得我的实例分页列表", description = "在【我的流程】菜单中,进行调用") + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + public CommonResult> getProcessInstanceMyPage( + @Valid BpmProcessInstancePageReqVO pageReqVO) { + PageResult pageResult = processInstanceService.getProcessInstancePage( + getLoginUserId(), pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 拼接返回 + Map> taskMap = taskService.getTaskMapByProcessInstanceIds( + convertList(pageResult.getList(), HistoricProcessInstance::getId)); + Map processDefinitionMap = processDefinitionService.getProcessDefinitionMap( + convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId)); + Map categoryMap = categoryService.getCategoryMap( + convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory)); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap( + convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId)); + Set userIds = convertSet(pageResult.getList(), processInstance -> NumberUtils.parseLong(processInstance.getStartUserId())); + userIds.addAll(convertSetByFlatMap(taskMap.values(), + tasks -> tasks.stream().map(Task::getAssignee).filter(StrUtil::isNotBlank).map(Long::parseLong))); + Map userMap = adminUserApi.getUserMap(userIds); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult, + processDefinitionMap, categoryMap, taskMap, userMap, deptMap, processDefinitionInfoMap)); + } + + @GetMapping("/manager-page") + @Operation(summary = "获得管理流程实例的分页列表", description = "在【流程实例】菜单中,进行调用") + @PreAuthorize("@ss.hasPermission('bpm:process-instance:manager-query')") + public CommonResult> getProcessInstanceManagerPage( + @Valid BpmProcessInstancePageReqVO pageReqVO) { + PageResult pageResult = processInstanceService.getProcessInstancePage( + null, pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 拼接返回 + Map> taskMap = taskService.getTaskMapByProcessInstanceIds( + convertList(pageResult.getList(), HistoricProcessInstance::getId)); + Map processDefinitionMap = processDefinitionService.getProcessDefinitionMap( + convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId)); + Map categoryMap = categoryService.getCategoryMap( + convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory)); + // 发起人信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), processInstance -> NumberUtils.parseLong(processInstance.getStartUserId()))); + Map deptMap = deptApi.getDeptMap( + convertSet(userMap.values(), DeptUtil::getDeptId)); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap( + convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId)); + return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult, + processDefinitionMap, categoryMap, taskMap, userMap, deptMap, processDefinitionInfoMap)); + } + + @PostMapping("/create") + @Operation(summary = "新建流程实例") + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + public CommonResult createProcessInstance(@Valid @RequestBody BpmProcessInstanceCreateReqVO createReqVO) { + return success(processInstanceService.createProcessInstance(getLoginUserId(), createReqVO)); + } + + @GetMapping("/get") + @Operation(summary = "获得指定流程实例", description = "在【流程详细】界面中,进行调用") + @Parameter(name = "id", description = "流程实例的编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + public CommonResult getProcessInstance(@RequestParam("id") String id) { + HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(id); + if (processInstance == null) { + return success(null); + } + + // 拼接返回 + ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( + processInstance.getProcessDefinitionId()); + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( + processInstance.getProcessDefinitionId()); + AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData(); + DeptRespDTO dept = null; + if (startUser != null) { + Long deptId = DeptUtil.getDeptId(startUser); + if (deptId > 0) { + dept = deptApi.getDept(deptId).getCheckedData(); + } + } + return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance, + processDefinition, processDefinitionInfo, startUser, dept)); + } + + @DeleteMapping("/cancel-by-start-user") + @Operation(summary = "用户取消流程实例", description = "取消发起的流程") + @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')") + public CommonResult cancelProcessInstanceByStartUser( + @Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) { + processInstanceService.cancelProcessInstanceByStartUser(getLoginUserId(), cancelReqVO); + return success(true); + } + + @DeleteMapping("/cancel-by-admin") + @Operation(summary = "管理员取消流程实例", description = "管理员撤回流程") + @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel-by-admin')") + public CommonResult cancelProcessInstanceByManager( + @Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) { + processInstanceService.cancelProcessInstanceByAdmin(getLoginUserId(), cancelReqVO); + return success(true); + } + + @GetMapping("/get-approval-detail") + @Operation(summary = "获得审批详情") + @Parameter(name = "id", description = "流程实例的编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + @SuppressWarnings("unchecked") + public CommonResult getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { + if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) { + reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class)); + } + return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); + } + + @GetMapping("/get-next-approval-nodes") + @Operation(summary = "获取下一个执行的流程节点") + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + @SuppressWarnings("unchecked") + public CommonResult> getNextApprovalNodes(@Valid BpmApprovalDetailReqVO reqVO) { + if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) { + reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class)); + } + return success(processInstanceService.getNextApprovalNodes(getLoginUserId(), reqVO)); + } + + @GetMapping("/get-bpmn-model-view") + @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用") + @Parameter(name = "id", description = "流程实例的编号", required = true) + public CommonResult getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) { + return success(processInstanceService.getProcessInstanceBpmnModelView(id)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java new file mode 100644 index 0000000..bd644ca --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java @@ -0,0 +1,89 @@ +package com.zt.plat.module.bpm.controller.admin.task; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.collection.MapUtils; +import com.zt.plat.framework.common.util.date.DateUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceCopyService; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.flowable.engine.history.HistoricProcessInstance; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.stream.Stream; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.*; +import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 流程实例抄送") +@RestController +@RequestMapping("/bpm/process-instance/copy") +@Validated +public class BpmProcessInstanceCopyController { + + @Resource + private BpmProcessInstanceCopyService processInstanceCopyService; + @Resource + private BpmProcessInstanceService processInstanceService; + @Resource + private BpmProcessDefinitionService processDefinitionService; + + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/page") + @Operation(summary = "获得抄送流程分页列表") + @PreAuthorize("@ss.hasPermission('bpm:process-instance-cc:query')") + public CommonResult> getProcessInstanceCopyPage( + @Valid BpmProcessInstanceCopyPageReqVO pageReqVO) { + PageResult pageResult = processInstanceCopyService.getProcessInstanceCopyPage( + getLoginUserId(), pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + + // 拼接返回 + Map processInstanceMap = processInstanceService.getHistoricProcessInstanceMap( + convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), + copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator())))); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap( + convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessDefinitionId)); + 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.setSummary(FlowableUtils.getSummary( + processDefinitionInfoMap.get(processInstance.getProcessDefinitionId()), + processInstance.getProcessVariables())); + copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime())); + }); + return copyVO; + })); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmTaskController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmTaskController.java new file mode 100644 index 0000000..54db1af --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmTaskController.java @@ -0,0 +1,252 @@ +package com.zt.plat.module.bpm.controller.admin.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.*; +import com.zt.plat.module.bpm.convert.task.BpmTaskConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.service.definition.BpmFormService; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.bpm.service.task.BpmTaskService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.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; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.*; +import static com.zt.plat.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +/** + * @author chenbowen + */ +@Tag(name = "管理后台 - 流程任务实例") +@RestController +@RequestMapping("/bpm/task") +@Validated +public class BpmTaskController { + + @Resource + private BpmTaskService taskService; + @Resource + private BpmProcessInstanceService processInstanceService; + @Resource + private BpmFormService formService; + @Resource + private BpmProcessDefinitionService processDefinitionService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + @GetMapping("todo-page") + @Operation(summary = "获取 Todo 待办任务分页") + @PreAuthorize("@ss.hasPermission('bpm:task:query')") + public CommonResult> getTaskTodoPage(@Valid BpmTaskPageReqVO pageVO) { + PageResult pageResult = taskService.getTaskTodoPage(getLoginUserId(), pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + + // 拼接数据 + Map processInstanceMap = processInstanceService.getProcessInstanceMap( + convertSet(pageResult.getList(), Task::getProcessInstanceId)); + Map userMap = adminUserApi.getUserMap( + convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap( + convertSet(pageResult.getList(), Task::getProcessDefinitionId)); + return success(BpmTaskConvert.INSTANCE.buildTodoTaskPage(pageResult, processInstanceMap, userMap, processDefinitionInfoMap)); + } + + @GetMapping("done-page") + @Operation(summary = "获取 Done 已办任务分页") + @PreAuthorize("@ss.hasPermission('bpm:task:query')") + public CommonResult> getTaskDonePage(@Valid BpmTaskPageReqVO pageVO) { + PageResult pageResult = taskService.getTaskDonePage(getLoginUserId(), pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + + // 拼接数据 + Map processInstanceMap = processInstanceService.getHistoricProcessInstanceMap( + convertSet(pageResult.getList(), HistoricTaskInstance::getProcessInstanceId)); + Map userMap = adminUserApi.getUserMap( + convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()))); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap( + convertSet(pageResult.getList(), HistoricTaskInstance::getProcessDefinitionId)); + return success(BpmTaskConvert.INSTANCE.buildTaskPage(pageResult, processInstanceMap, userMap, null, processDefinitionInfoMap)); + } + + @GetMapping("manager-page") + @Operation(summary = "获取全部任务的分页", description = "用于【流程任务】菜单") + @PreAuthorize("@ss.hasPermission('bpm:task:mananger-query')") + public CommonResult> getTaskManagerPage(@Valid BpmTaskPageReqVO pageVO) { + PageResult pageResult = taskService.getTaskPage(getLoginUserId(), pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + + // 拼接数据 + Map processInstanceMap = processInstanceService.getHistoricProcessInstanceMap( + convertSet(pageResult.getList(), HistoricTaskInstance::getProcessInstanceId)); + // 获得 User 和 Dept Map + Set userIds = convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())); + userIds.addAll(convertSet(pageResult.getList(), task -> NumberUtils.parseLong(task.getAssignee()))); + Map userMap = adminUserApi.getUserMap(userIds); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap( + convertSet(pageResult.getList(), HistoricTaskInstance::getProcessDefinitionId)); + return success(BpmTaskConvert.INSTANCE.buildTaskPage(pageResult, processInstanceMap, userMap, deptMap, processDefinitionInfoMap)); + } + + @GetMapping("/list-by-process-instance-id") + @Operation(summary = "获得指定流程实例的任务列表", description = "包括完成的、未完成的") + @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:task:query')") + public CommonResult> getTaskListByProcessInstanceId( + @RequestParam("processInstanceId") String processInstanceId) { + List taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true); + if (CollUtil.isEmpty(taskList)) { + return success(Collections.emptyList()); + } + + // 拼接数据 + Set userIds = convertSetByFlatMap(taskList, task -> + Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); + Map userMap = adminUserApi.getUserMap(userIds); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + // 获得 Form Map + Map formMap = formService.getFormMap( + convertSet(taskList, task -> { + String formKey = task.getFormKey(); + if (StrUtil.isBlank(formKey)) { + return 0L; + } + try { + return Long.parseLong(formKey); + } catch (NumberFormatException e) { + // 如果 formKey 不是数字(比如是URL),返回0L + return 0L; + } + })); + return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, + formMap, userMap, deptMap)); + } + + @PutMapping("/approve") + @Operation(summary = "通过任务") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult approveTask(@Valid @RequestBody BpmTaskApproveReqVO reqVO) { + taskService.approveTask(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/reject") + @Operation(summary = "不通过任务") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult rejectTask(@Valid @RequestBody BpmTaskRejectReqVO reqVO) { + taskService.rejectTask(getLoginUserId(), reqVO); + return success(true); + } + + @GetMapping("/list-by-return") + @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮") + @Parameter(name = "taskId", description = "当前任务ID", required = true) + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult> getTaskListByReturn(@RequestParam("id") String id) { + List userTaskList = taskService.getUserTaskListByReturn(id); + return success(convertList(userTaskList, userTask -> // 只返回 id 和 name + new BpmTaskRespVO().setName(userTask.getName()).setTaskDefinitionKey(userTask.getId()))); + } + + @PutMapping("/return") + @Operation(summary = "退回任务", description = "用于【流程详情】的【退回】按钮") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) { + taskService.returnTask(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/delegate") + @Operation(summary = "委派任务", description = "用于【流程详情】的【委派】按钮") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult delegateTask(@Valid @RequestBody BpmTaskDelegateReqVO reqVO) { + taskService.delegateTask(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/transfer") + @Operation(summary = "转派任务", description = "用于【流程详情】的【转派】按钮") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult transferTask(@Valid @RequestBody BpmTaskTransferReqVO reqVO) { + taskService.transferTask(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/create-sign") + @Operation(summary = "加签", description = "before 前加签,after 后加签") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult createSignTask(@Valid @RequestBody BpmTaskSignCreateReqVO reqVO) { + taskService.createSignTask(getLoginUserId(), reqVO); + return success(true); + } + + @DeleteMapping("/delete-sign") + @Operation(summary = "减签") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult deleteSignTask(@Valid @RequestBody BpmTaskSignDeleteReqVO reqVO) { + taskService.deleteSignTask(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/copy") + @Operation(summary = "抄送任务") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult 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) + @PreAuthorize("@ss.hasPermission('bpm:task:query')") + public CommonResult> getTaskListByParentTaskId(@RequestParam("parentTaskId") String parentTaskId) { + List taskList = taskService.getTaskListByParentTaskId(parentTaskId); + if (CollUtil.isEmpty(taskList)) { + return success(Collections.emptyList()); + } + // 拼接数据 + Map userMap = adminUserApi.getUserMap(convertSetByFlatMap(taskList, + user -> Stream.of(NumberUtils.parseLong(user.getAssignee()), NumberUtils.parseLong(user.getOwner())))); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + return success(BpmTaskConvert.INSTANCE.buildTaskListByParentTaskId(taskList, userMap, deptMap)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java new file mode 100644 index 0000000..444aeae --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.activity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 流程活动的 Response VO") +@Data +public class BpmActivityRespVO { + + @Schema(description = "流程活动的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String key; + @Schema(description = "流程活动的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent") + private String type; + + @Schema(description = "流程活动的开始时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime startTime; + @Schema(description = "流程活动的结束时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime endTime; + + @Schema(description = "关联的流程任务的编号", example = "2048") + private String taskId; // 关联的流程任务,只有 UserTask 等类型才有 + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java new file mode 100644 index 0000000..b968f9a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.cc; + +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 流程实例抄送的分页 Item Response VO") +@Data +public class BpmProcessInstanceCopyRespVO { + + @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED) + private UserSimpleBaseVO startUser; + + @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233") + private String processInstanceId; + @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试") + private String processInstanceName; + @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime processInstanceStartTime; + + @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 reason; + + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED) + private UserSimpleBaseVO createUser; + + @Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "流程摘要", example = "[]") + private List> summary; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java new file mode 100644 index 0000000..3f37d7a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java @@ -0,0 +1,40 @@ +package com.zt.plat.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 jakarta.validation.constraints.AssertTrue; +import lombok.Data; + +import java.util.Map; + +@Schema(description = "管理后台 - 审批详情 Request VO") +@Data +public class BpmApprovalDetailReqVO { + + @Schema(description = "流程定义的编号", example = "1024") + private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID + + @Schema(description = "流程变量") + private Map processVariables; // 使用场景:同 processDefinitionId,用于流程预测 + + @Schema(description = "流程变量") + private String processVariablesStr; // 解决 GET 无法传递对象的问题,最终转换成 processVariables 变量 + + @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); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java new file mode 100644 index 0000000..a23c868 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java @@ -0,0 +1,112 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.instance; + +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import com.fasterxml.jackson.annotation.JsonIgnore; +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 activityNodes; + + @Schema(description = "表单字段权限") + private Map 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 tasks; + + @Schema(description = "候选人策略", example = "35") + private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选 + + @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers + private List candidateUserIds; + + @Schema(description = "候选人用户列表") + private List candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表 + + @Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf") + private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 processInstanceId 字段) + + } + + @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; + + @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") + private String signPicUrl; + + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java new file mode 100644 index 0000000..bf520a0 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java @@ -0,0 +1,43 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.instance; + +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.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 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 unfinishedTaskActivityIds; // 只包括 UserTask + + @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow + + @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set finishedSequenceFlowActivityIds; // 只包括 SequenceFlow + + @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set rejectedTaskActivityIds; // 只包括 UserTask + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java new file mode 100644 index 0000000..b9d19e2 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - 流程实例的取消 Request VO") +@Data +public class BpmProcessInstanceCancelReqVO { + + @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "流程实例的编号不能为空") + private String id; + + @Schema(description = "取消原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "不请假了!") + @NotEmpty(message = "取消原因不能为空") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java new file mode 100644 index 0000000..b7956e4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.instance; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.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 = "芋道") + private String processInstanceName; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java new file mode 100644 index 0000000..bb46b1f --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.instance; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - 流程实例的创建 Request VO") +@Data +public class BpmProcessInstanceCreateReqVO { + + @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "流程定义编号不能为空") + private String processDefinitionId; + + @Schema(description = "变量实例(动态表单)") + private Map variables; + + @Schema(description = "发起人自选审批人 Map", example = "{taskKey1: [1, 2]}") + private Map> startUserSelectAssignees; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java new file mode 100644 index 0000000..33875b6 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.instance; + +import com.zt.plat.framework.common.pojo.PageParam; +import com.zt.plat.framework.common.validation.InEnum; +import com.zt.plat.module.bpm.enums.task.BpmProcessInstanceStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.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 = "芋道") + private String name; + + @Schema(description = "流程定义的标识", example = "2048") + private String processDefinitionKey; // 精准匹配 + + @Schema(description = "流程实例的状态", example = "1") + @InEnum(BpmProcessInstanceStatusEnum.class) + private Integer status; + + @Schema(description = "流程分类", example = "1") + private String category; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + @Schema(description = "结束时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] endTime; + + @Schema(description = "发起用户编号", example = "1024") + private Long startUserId; // 注意,只有在【流程实例】菜单,才使用该参数 + + @Schema(description = "动态表单字段查询 JSON Str", example = "{}") + private String formFieldsParams; // SpringMVC 在 get 请求下,无法方便的定义 Map 类型的参数,所以通过 String 接收后,逻辑里面转换 + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java new file mode 100644 index 0000000..fb88587 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java @@ -0,0 +1,86 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.instance; + +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.fasterxml.jackson.annotation.JsonIgnore; +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 BpmProcessInstanceRespVO { + + @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "流程摘要") + private List> summary; // 只有流程表单,才有摘要! + + @Schema(description = "流程分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private String category; + @Schema(description = "流程分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "请假") + private String categoryName; + + @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 + + @Schema(description = "发起时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime startTime; + + @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime endTime; + + @Schema(description = "持续时间", example = "1000") + private Long durationInMillis; + + @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) + private Map formVariables; + + @Schema(description = "业务的唯一标识-例如说,请假申请的编号", example = "1") + private String businessKey; + + /** + * 发起流程的用户 + */ + private UserSimpleBaseVO startUser; + + @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private String processDefinitionId; + /** + * 流程定义 + */ + private BpmProcessDefinitionRespVO processDefinition; + + /** + * 当前审批中的任务 + */ + private List tasks; // 仅在流程实例分页才返回 + + @Schema(description = "流程任务") + @Data + public static class Task { + + @Schema(description = "流程任务的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser + private Long assignee; + + @Schema(description = "任务分配人", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + private UserSimpleBaseVO assigneeUser; + + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java new file mode 100644 index 0000000..7d56754 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - 通过流程任务的 Request VO") +@Data +public class BpmTaskApproveReqVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "审批意见", example = "不错不错!") + private String reason; + + @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") + private String signPicUrl; + + @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) + private Map variables; + + @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}") + private Map> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况 + + // 新增任务变量实例,业务表单 + @Schema(description = "任务变量实例,业务表单", example = "{'formField1': 'value1', 'formField2': 'value2'}") + private Map taskVariables; +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java new file mode 100644 index 0000000..1775be0 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +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 copyUserIds; + + @Schema(description = "抄送意见", example = "帮忙看看!") + private String reason; +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java new file mode 100644 index 0000000..21aa42a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 委派流程任务的 Request VO") +@Data +public class BpmTaskDelegateReqVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "被委派人 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "被委派人 ID 不能为空") + private Long delegateUserId; + + @Schema(description = "委派原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "做不了决定,需要你先帮忙瞅瞅") + @NotEmpty(message = "委派原因不能为空") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java new file mode 100644 index 0000000..c7c1721 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import com.zt.plat.framework.common.pojo.PageParam; +import com.zt.plat.framework.common.util.date.DateUtils; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 流程任务的的分页 Request VO") // 待办、已办,都使用该分页 +@Data +public class BpmTaskPageReqVO extends PageParam { + + @Schema(description = "流程任务名", example = "芋道") + private String name; + + @Schema(description = "流程分类", example = "1") + private String category; + + @Schema(description = "流程定义的标识", example = "2048") + private String processDefinitionKey; // 精准匹配 + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java new file mode 100644 index 0000000..8482f48 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - 不通过流程任务的 Request VO") +@Data +public class BpmTaskRejectReqVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "审批意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "不错不错!") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java new file mode 100644 index 0000000..109e26f --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java @@ -0,0 +1,130 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.fasterxml.jackson.annotation.JsonIgnore; +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 BpmTaskRespVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime endTime; + + @Schema(description = "持续时间", example = "1000") + private Long durationInMillis; + + @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer status; // 参见 BpmTaskStatusEnum 枚举 + + @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 UserSimpleBaseVO ownerUser; + + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser + private Long assignee; + /** + * 审核的用户信息 + */ + private UserSimpleBaseVO assigneeUser; + + @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") + private String taskDefinitionKey; + + @Schema(description = "所属流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8888") + private String processInstanceId; + /** + * 所属流程实例 + */ + private ProcessInstance processInstance; + + @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String parentTaskId; + @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask") + private List children; // 由加签生成,包含多层子任务 + + @Schema(description = "表单编号", example = "1024") + private Long formId; + @Schema(description = "表单路由", example = "1024") + private String formPath; + @Schema(description = "表单名字", example = "请假表单") + private String formName; + @Schema(description = "表单的配置,JSON 字符串") + private String formConf; + @Schema(description = "表单项的数组") + private List formFields; + @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) + private Map formVariables; + @Schema(description = "操作按钮设置值") + private Map buttonsSetting; + + @Schema(description = "是否需要签名", example = "false") + private Boolean signEnable; + + @Schema(description = "是否填写审批意见", example = "false") + private Boolean reasonRequire; + + @Schema(description = "节点类型", example = "10") + private Integer nodeType; // 参见 BpmSimpleModelNodeTypeEnum 枚举。 + + @Data + @Schema(description = "流程实例") + public static class ProcessInstance { + + @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String id; + + @Schema(description = "流程实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String name; + + @Schema(description = "提交时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private String processDefinitionId; + + @Schema(description = "流程摘要", example = "[]") + private List> summary; // 只有流程表单,才有摘要! + + /** + * 发起人的用户信息 + */ + private UserSimpleBaseVO startUser; + + } + + @Data + @Schema(description = "操作按钮设置") + public static class OperationButtonSetting { + + @Schema(description = "显示名称", example = "审批") + private String displayName; + + @Schema(description = "是否启用", example = "true") + private Boolean enable; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java new file mode 100644 index 0000000..22982ab --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - 退回流程任务的 Request VO") +@Data +public class BpmTaskReturnReqVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "退回到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "退回到的任务 Key 不能为空") + private String targetTaskDefinitionKey; + + @Schema(description = "退回意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回") + @NotEmpty(message = "退回意见不能为空") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskSignCreateReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskSignCreateReqVO.java new file mode 100644 index 0000000..0acf4bd --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskSignCreateReqVO.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - 加签任务的创建(加签) Request VO") +@Data +public class BpmTaskSignCreateReqVO { + + @Schema(description = "需要加签的任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "加签的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") + @NotEmpty(message = "加签用户不能为空") + private Set userIds; + + @Schema(description = "加签类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "before") + @NotEmpty(message = "加签类型不能为空") + private String type; // 参见 BpmTaskSignTypeEnum 枚举 + + @Schema(description = "加签原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "需要加签") + @NotEmpty(message = "加签原因不能为空") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskSignDeleteReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskSignDeleteReqVO.java new file mode 100644 index 0000000..a848485 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskSignDeleteReqVO.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - 加签任务的删除(减签) Request VO") +@Data +public class BpmTaskSignDeleteReqVO { + + @Schema(description = "被减签的任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "加签原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "需要减签") + @NotEmpty(message = "加签原因不能为空") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskTransferReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskTransferReqVO.java new file mode 100644 index 0000000..e55bb73 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/task/BpmTaskTransferReqVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 流程任务的转办 Request VO") +@Data +public class BpmTaskTransferReqVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "新审批人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "新审批人的用户编号不能为空") + private Long assigneeUserId; + + @Schema(description = "转办原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "做不了决定,需要你先帮忙瞅瞅") + @NotEmpty(message = "转办原因不能为空") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/app/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/app/package-info.java new file mode 100644 index 0000000..0513848 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/app/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.zt.plat.module.bpm.controller.app; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/package-info.java new file mode 100644 index 0000000..52ffdf8 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 zt-ui-admin 前端项目 + * 2. app 包:提供给用户 APP zt-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package com.zt.plat.module.bpm.controller; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/definition/BpmModelConvert.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/definition/BpmModelConvert.java new file mode 100644 index 0000000..262a598 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/definition/BpmModelConvert.java @@ -0,0 +1,132 @@ +package com.zt.plat.module.bpm.convert.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.util.date.DateUtils; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.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; +import org.flowable.engine.repository.ProcessDefinition; +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 static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; + +/** + * 流程模型 Convert + * + * @author yunlongn + */ +@Mapper +public interface BpmModelConvert { + + BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class); + + default List buildModelList(List list, + Map formMap, + Map categoryMap, + Map deploymentMap, + Map processDefinitionMap, + Map userMap, + Map deptMap) { + List 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; + List startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; + List startDepts = metaInfo != null ? convertList(metaInfo.getStartDeptIds(), deptMap::get) : null; + return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers, startDepts); + }); + // 排序 + result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort)); + return result; + } + + default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes, BpmSimpleModelNodeVO simpleModel) { + BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); + BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null, null); + if (ArrayUtil.isNotEmpty(bpmnBytes)) { + modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes)); + } + modelVO.setSimpleModel(simpleModel); + return modelVO; + } + + default BpmModelRespVO buildModel0(Model model, + BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, + Deployment deployment, ProcessDefinition processDefinition, + List startUsers, List startDepts) { + BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) + .setKey(model.getKey()).setCategory(model.getCategory()) + .setCreateTime(DateUtils.of(model.getCreateTime())); + // Form + BeanUtils.copyProperties(metaInfo, modelRespVO); + if (form != null) { + modelRespVO.setFormName(form.getName()); + } + // Category + if (category != null) { + modelRespVO.setCategoryName(category.getName()); + } + // ProcessDefinition + if (processDefinition != null) { + modelRespVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); + modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ? + SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode()); + if (deployment != null) { + modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime())); + } + } + // User、Dept + modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class)) + .setStartDepts(BeanUtils.toBean(startDepts, DeptSimpleBaseVO.class)); + return modelRespVO; + } + + 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 BpmModelMetaInfoVO parseMetaInfo(Model model) { + BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); + if (vo == null) { + return null; + } + if (vo.getManagerUserIds() == null) { + vo.setManagerUserIds(Collections.emptyList()); + } + if (vo.getStartUserIds() == null) { + vo.setStartUserIds(Collections.emptyList()); + } + // 如果为空,兜底处理,使用 createTime 创建时间 + if (vo.getSort() == null) { + vo.setSort(model.getCreateTime().getTime()); + } + return vo; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/definition/BpmProcessDefinitionConvert.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/definition/BpmProcessDefinitionConvert.java new file mode 100644 index 0000000..86b256c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/definition/BpmProcessDefinitionConvert.java @@ -0,0 +1,99 @@ +package com.zt.plat.module.bpm.convert.definition; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.map.MapUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.common.engine.impl.db.SuspensionState; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * Bpm 流程定义的 Convert + * + * @author yunlong.li + */ +@Mapper +public interface BpmProcessDefinitionConvert { + + BpmProcessDefinitionConvert INSTANCE = Mappers.getMapper(BpmProcessDefinitionConvert.class); + + default PageResult buildProcessDefinitionPage(PageResult page, + Map deploymentMap, + Map processDefinitionInfoMap, + Map formMap, + Map categoryMap) { + List list = buildProcessDefinitionList(page.getList(), deploymentMap, processDefinitionInfoMap, formMap, categoryMap); + return new PageResult<>(list, page.getTotal()); + } + + default List buildProcessDefinitionList(List list, + Map deploymentMap, + Map processDefinitionInfoMap, + Map formMap, + Map categoryMap) { + List 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; + if (processDefinitionInfo != null) { + 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); + }); + // 排序 + result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort)); + return result; + } + + default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition, + Deployment deployment, + BpmProcessDefinitionInfoDO processDefinitionInfo, + BpmFormDO form, + BpmCategoryDO category, + BpmnModel bpmnModel) { + BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class); + respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode()); + // Deployment + if (deployment != null) { + respVO.setDeploymentTime(LocalDateTimeUtil.of(deployment.getDeploymentTime())); + } + // BpmProcessDefinitionInfoDO + if (processDefinitionInfo != null) { + copyTo(processDefinitionInfo, respVO); + // Form + if (form != null) { + respVO.setFormName(form.getName()); + } + } + // Category + if (category != null) { + respVO.setCategoryName(category.getName()); + } + // BpmnModel + if (bpmnModel != null) { + respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel)); + } + return respVO; + } + + @Mapping(source = "from.id", target = "to.id", ignore = true) + void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/message/BpmMessageConvert.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/message/BpmMessageConvert.java new file mode 100644 index 0000000..41de133 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/message/BpmMessageConvert.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.bpm.convert.message; + +import com.zt.plat.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.Map; + +@Mapper +public interface BpmMessageConvert { + + BpmMessageConvert INSTANCE = Mappers.getMapper(BpmMessageConvert.class); + + @Mapping(target = "mobile", ignore = true) + @Mapping(source = "userId", target = "userId") + @Mapping(source = "templateCode", target = "templateCode") + @Mapping(source = "templateParams", target = "templateParams") + SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map templateParams); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/package-info.java new file mode 100644 index 0000000..b72f713 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 POJO 类的实体转换 + * + * 目前使用 MapStruct 框架 + */ +package com.zt.plat.module.bpm.convert; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceConvert.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceConvert.java new file mode 100644 index 0000000..791e694 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceConvert.java @@ -0,0 +1,298 @@ +package com.zt.plat.module.bpm.convert.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.collection.MapUtils; +import com.zt.plat.framework.common.util.collection.SetUtils; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.api.event.BpmProcessInstanceStatusEvent; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import com.zt.plat.module.bpm.convert.definition.BpmProcessDefinitionConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.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.zt.plat.framework.common.util.collection.CollectionUtils.convertList; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertSet; + +/** + * 流程实例 Convert + * + * @author ZT + */ +@Mapper +public interface BpmProcessInstanceConvert { + + BpmProcessInstanceConvert INSTANCE = Mappers.getMapper(BpmProcessInstanceConvert.class); + + default PageResult buildProcessInstancePage(PageResult pageResult, + Map processDefinitionMap, + Map categoryMap, + Map> taskMap, + Map userMap, + Map deptMap, + Map processDefinitionInfoMap) { + PageResult vpPageResult = BeanUtils.toBean(pageResult, BpmProcessInstanceRespVO.class); + for (int i = 0; i < pageResult.getList().size(); i++) { + BpmProcessInstanceRespVO respVO = vpPageResult.getList().get(i); + respVO.setStatus(FlowableUtils.getProcessInstanceStatus(pageResult.getList().get(i))); + MapUtils.findAndThen(processDefinitionMap, respVO.getProcessDefinitionId(), + processDefinition -> respVO.setCategory(processDefinition.getCategory()) + .setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class))); + MapUtils.findAndThen(categoryMap, respVO.getCategory(), category -> respVO.setCategoryName(category.getName())); + respVO.setTasks(BeanUtils.toBean(taskMap.get(respVO.getId()), BpmProcessInstanceRespVO.Task.class)); + // user + if (userMap != null) { + AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId())); + if (startUser != null) { + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); + MapUtils.findAndThen(deptMap, DeptUtil.getDeptId(startUser), dept -> respVO.getStartUser().setDeptName(dept.getName())); + } + if (CollUtil.isNotEmpty(respVO.getTasks())) { + respVO.getTasks().forEach(task -> { + AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee()); + if (assigneeUser!= null) { + task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class)); + MapUtils.findAndThen(deptMap, DeptUtil.getDeptId(assigneeUser), dept -> task.getAssigneeUser().setDeptName(dept.getName())); + } + }); + } + } + // 摘要 + respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()), + pageResult.getList().get(i).getProcessVariables())); + // 表单 + respVO.setFormVariables(pageResult.getList().get(i).getProcessVariables()); + } + return vpPageResult; + } + + default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance, + ProcessDefinition processDefinition, + BpmProcessDefinitionInfoDO processDefinitionInfo, + AdminUserRespDTO startUser, + DeptRespDTO dept) { + BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class); + respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)) + .setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); + // definition + respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); + copyTo(processDefinitionInfo, respVO.getProcessDefinition()); + // user + if (startUser != null) { + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); + if (dept != null) { + respVO.getStartUser().setDeptName(dept.getName()); + } + } + return respVO; + } + + @Mapping(source = "from.id", target = "to.id", ignore = true) + void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); + + default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) { + return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) + .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); + } + + default BpmMessageSendWhenProcessInstanceApproveReqDTO buildProcessInstanceApproveMessage(ProcessInstance instance) { + return new BpmMessageSendWhenProcessInstanceApproveReqDTO() + .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())) + .setProcessInstanceId(instance.getId()) + .setProcessInstanceName(instance.getName()); + } + + default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) { + return new BpmMessageSendWhenProcessInstanceRejectReqDTO() + .setProcessInstanceName(instance.getName()) + .setProcessInstanceId(instance.getId()) + .setReason(reason) + .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); + } + + default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, + List taskInstances, + BpmnModel bpmnModel, + BpmSimpleModelNodeVO simpleModel, + Set unfinishedTaskActivityIds, + Set finishedTaskActivityIds, + Set finishedSequenceFlowActivityIds, + Set rejectTaskActivityIds, + Map userMap, + Map 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 userMap, + Map deptMap) { + if (StrUtil.isEmpty(userIdStr)) { + return null; + } + Long userId = NumberUtils.parseLong(userIdStr); + return buildUser(userId, userMap, deptMap); + } + + default UserSimpleBaseVO buildUser(Long userId, + Map userMap, + Map deptMap) { + if (userId == null) { + return null; + } + AdminUserRespDTO user = userMap.get(userId); + if (user == null) { + return null; + } + UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class); + Long deptId = DeptUtil.getDeptId(user); + DeptRespDTO dept = deptId != null ? deptMap.get(deptId) : 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)) + .setSignPicUrl(FlowableUtils.getTaskSignPicUrl(task)); + } + + default Set parseUserIds(HistoricProcessInstance processInstance, + List activityNodes, + BpmTaskRespVO todoTask) { + Set 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 parseUserIds02(HistoricProcessInstance processInstance, + List tasks) { + Set 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 activityNodes, + BpmTaskRespVO todoTask, + Map formFieldsPermission, + Map userMap, + Map deptMap) { + // 1.1 流程实例 + BpmProcessInstanceRespVO processInstanceResp = null; + if (processInstance != null) { + AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); + Long deptId = DeptUtil.getDeptId(startUser); + DeptRespDTO dept = deptMap.get(deptId); + 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); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceDTOConvert.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceDTOConvert.java new file mode 100644 index 0000000..8d0a6ff --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceDTOConvert.java @@ -0,0 +1,67 @@ +package com.zt.plat.module.bpm.convert.task; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.api.task.dto.*; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.*; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 流程实例 DTO 转换器 + * + * @author ZT + */ +@Mapper +public interface BpmProcessInstanceDTOConvert { + + BpmProcessInstanceDTOConvert INSTANCE = Mappers.getMapper(BpmProcessInstanceDTOConvert.class); + + // VO to DTO + BpmProcessInstancePageReqDTO convert(BpmProcessInstancePageReqVO bean); + BpmProcessInstanceCancelReqDTO convert(BpmProcessInstanceCancelReqVO bean); + BpmApprovalDetailReqDTO convert(BpmApprovalDetailReqVO bean); + + // DTO to VO + BpmProcessInstancePageReqVO convertVO(BpmProcessInstancePageReqDTO bean); + BpmProcessInstanceCancelReqVO convertVO(BpmProcessInstanceCancelReqDTO bean); + BpmApprovalDetailReqVO convertVO(BpmApprovalDetailReqDTO bean); + + PageResult convertPage(PageResult pageResult); + BpmProcessInstanceRespDTO convert(BpmProcessInstanceRespVO bean); + BpmApprovalDetailRespDTO convert(BpmApprovalDetailRespVO bean); + BpmProcessInstanceBpmnModelViewRespDTO convert(BpmProcessInstanceBpmnModelViewRespVO bean); + + List convertActivityNodes(List nodes); + + // 内部类转换 + BpmProcessInstanceRespDTO.Task convert(BpmProcessInstanceRespVO.Task task); + BpmApprovalDetailRespDTO.ActivityNode convert(BpmApprovalDetailRespVO.ActivityNode node); + BpmApprovalDetailRespDTO.ActivityNodeTask convert(BpmApprovalDetailRespVO.ActivityNodeTask task); + + // 用户信息转换 + UserSimpleDTO convertUser(UserSimpleBaseVO user); + BpmProcessDefinitionRespDTO convert(BpmProcessDefinitionRespVO definition); + + @Mapping(target = "childNode", source = "childNode", qualifiedByName = "mapChildNode") + BpmSimpleModelNodeDTO convert(BpmSimpleModelNodeVO simpleModel); + + /** + * 将BpmSimpleModelNodeVO的childNode转换为List + */ + @Named("mapChildNode") + default List mapChildNode(BpmSimpleModelNodeVO childNode) { + if (childNode == null) { + return null; + } + return List.of(convert(childNode)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmTaskConvert.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmTaskConvert.java new file mode 100644 index 0000000..ccba6d7 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmTaskConvert.java @@ -0,0 +1,316 @@ +package com.zt.plat.module.bpm.convert.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.framework.common.util.date.DateUtils; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskRespDTO; +import com.zt.plat.module.bpm.api.task.dto.UserSimpleDTO; +import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.enums.task.BpmTaskStatusEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; +import static com.zt.plat.framework.common.util.collection.MapUtils.findAndThen; + +/** + * Bpm 任务 Convert + * + * @author ZT + */ +@Mapper +public interface BpmTaskConvert { + + BpmTaskConvert INSTANCE = Mappers.getMapper(BpmTaskConvert.class); + + default PageResult buildTodoTaskPage(PageResult pageResult, + Map processInstanceMap, + Map userMap, + Map processDefinitionInfoMap) { + return BeanUtils.toBean(pageResult, BpmTaskRespVO.class, taskVO -> { + ProcessInstance processInstance = processInstanceMap.get(taskVO.getProcessInstanceId()); + if (processInstance == null) { + return; + } + taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); + AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); + taskVO.getProcessInstance().setCreateTime(DateUtils.of(processInstance.getStartTime())); + // 摘要 + taskVO.getProcessInstance().setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(processInstance.getProcessDefinitionId()), + processInstance.getProcessVariables())); + }); + } + + default PageResult buildTaskPage(PageResult pageResult, + Map processInstanceMap, + Map userMap, + Map deptMap, + Map processDefinitionInfoMap) { + List taskVOList = CollectionUtils.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, UserSimpleBaseVO.class)); + findAndThen(deptMap, DeptUtil.getDeptId(assignUser), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); + } + // 流程实例 + HistoricProcessInstance processInstance = processInstanceMap.get(taskVO.getProcessInstanceId()); + 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, UserSimpleBaseVO.class)); + // 摘要 + taskVO.getProcessInstance().setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(processInstance.getProcessDefinitionId()), + processInstance.getProcessVariables())); + } + return taskVO; + }); + return new PageResult<>(taskVOList, pageResult.getTotal()); + } + + default List buildTaskListByProcessInstanceId(List taskList, + Map formMap, + Map userMap, + Map deptMap) { + return CollectionUtils.convertList(taskList, task -> { + // 特殊:已取消的任务,不返回 + BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); + Integer taskStatus = FlowableUtils.getTaskStatus(task); + if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) { + return null; + } + taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); + // 表单信息 + BpmFormDO form = null; + try { + Long formId = NumberUtils.parseLong(task.getFormKey()); + form = MapUtil.get(formMap, formId, BpmFormDO.class); + } catch (NumberFormatException e) { + // 如果 formKey 不是数字(比如是URL),设置 formPath + taskVO.setFormPath(task.getFormKey()); + taskVO.setFormVariables(FlowableUtils.getTaskFormVariable(task)); + } + if (form != null) { + taskVO.setFormId(form.getId()).setFormName(form.getName()).setFormConf(form.getConf()) + .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task)); + } + // 用户信息 + buildTaskAssignee(taskVO, task.getAssignee(), userMap, deptMap); + buildTaskOwner(taskVO, task.getOwner(), userMap, deptMap); + return taskVO; + }); + } + + default List buildTaskListByParentTaskId(List taskList, + Map userMap, + Map deptMap) { + 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, UserSimpleBaseVO.class)); + DeptRespDTO dept = deptMap.get(DeptUtil.getDeptId(assignUser)); + if (dept != null) { + taskVO.getAssigneeUser().setDeptName(dept.getName()); + } + } + AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); + if (ownerUser != null) { + taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); + findAndThen(deptMap, DeptUtil.getDeptId(ownerUser), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); + } + })); + } + + default BpmTaskRespVO buildTodoTask(Task todoTask, List childrenTasks, + Map buttonsSetting, + BpmFormDO form) { + BpmTaskRespVO bpmTaskRespVO = 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)))); + if (form != null) { + bpmTaskRespVO.setFormId(form.getId()).setFormName(form.getName()) + .setFormConf(form.getConf()).setFormFields(form.getFields()); + }else{ + // 任务级别的业务表单 + bpmTaskRespVO.setFormPath(todoTask.getFormKey()); + } + return bpmTaskRespVO; + } + + default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, + Task task) { + BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO(); + reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId()) + .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId()) + .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName()) + .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee())); + return reqDTO; + } + + default void buildTaskOwner(BpmTaskRespVO task, String taskOwner, + Map userMap, + Map deptMap) { + AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner)); + if (ownerUser != null) { + task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); + findAndThen(deptMap, DeptUtil.getDeptId(ownerUser), dept -> task.getOwnerUser().setDeptName(dept.getName())); + } + } + + default void buildTaskChildren(BpmTaskRespVO task, Map> childrenTaskMap, + Map userMap, Map deptMap) { + List 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 userMap, + Map deptMap) { + AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee)); + if (assignUser != null) { + task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); + findAndThen(deptMap, DeptUtil.getDeptId(assignUser), dept -> task.getAssigneeUser().setDeptName(dept.getName())); + } + } + + /** + * 将父任务的属性,拷贝到子任务(加签任务) + *

+ * 为什么不使用 mapstruct 映射?因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的。 + * 使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题。 + * + * @param parentTask 父任务 + * @param childTask 加签任务 + */ + default void copyTo(TaskEntityImpl parentTask, TaskEntityImpl childTask) { + childTask.setName(parentTask.getName()); + childTask.setDescription(parentTask.getDescription()); + childTask.setCategory(parentTask.getCategory()); + childTask.setParentTaskId(parentTask.getId()); + childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); + childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); + childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); + childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); + childTask.setPriority(parentTask.getPriority()); + childTask.setCreateTime(new Date()); + childTask.setTenantId(parentTask.getTenantId()); + } + + /** + * 将 BpmTaskRespVO 转换为 BpmTaskRespDTO,保持完整的嵌套结构 + */ + default List buildTaskRespDTOList(List voList) { + return CollectionUtils.convertList(voList, this::buildTaskRespDTO); + } + + /** + * 将 BpmTaskRespVO 转换为 BpmTaskRespDTO,保持完整的嵌套结构 + */ + default BpmTaskRespDTO buildTaskRespDTO(BpmTaskRespVO vo) { + if (vo == null) { + return null; + } + + BpmTaskRespDTO dto = BeanUtils.toBean(vo, BpmTaskRespDTO.class); + + // 转换用户信息 + if (vo.getAssigneeUser() != null) { + dto.setAssigneeUser(convertToUserSimpleDTO(vo.getAssigneeUser())); + } + if (vo.getOwnerUser() != null) { + dto.setOwnerUser(convertToUserSimpleDTO(vo.getOwnerUser())); + } + + // 转换流程实例信息 + if (vo.getProcessInstance() != null) { + BpmTaskRespDTO.ProcessInstanceDTO processInstanceDTO = new BpmTaskRespDTO.ProcessInstanceDTO(); + processInstanceDTO.setId(vo.getProcessInstance().getId()); + processInstanceDTO.setName(vo.getProcessInstance().getName()); + processInstanceDTO.setCreateTime(vo.getProcessInstance().getCreateTime()); + processInstanceDTO.setProcessDefinitionId(vo.getProcessInstance().getProcessDefinitionId()); + processInstanceDTO.setSummary(vo.getProcessInstance().getSummary()); + + if (vo.getProcessInstance().getStartUser() != null) { + processInstanceDTO.setStartUser(convertToUserSimpleDTO(vo.getProcessInstance().getStartUser())); + } + dto.setProcessInstance(processInstanceDTO); + } + + // 转换操作按钮设置 + if (vo.getButtonsSetting() != null) { + Map buttonsSettingDTO = vo.getButtonsSetting().entrySet() + .stream() + .collect(java.util.stream.Collectors.toMap( + Map.Entry::getKey, + entry -> { + BpmTaskRespDTO.OperationButtonSettingDTO settingDTO = new BpmTaskRespDTO.OperationButtonSettingDTO(); + settingDTO.setDisplayName(entry.getValue().getDisplayName()); + settingDTO.setEnable(entry.getValue().getEnable()); + return settingDTO; + } + )); + dto.setButtonsSetting(buttonsSettingDTO); + } + + // 递归转换子任务 + if (vo.getChildren() != null) { + dto.setChildren(buildTaskRespDTOList(vo.getChildren())); + } + + return dto; + } + + /** + * 将 UserSimpleBaseVO 转换为 UserSimpleDTO,确保所有字段都被正确赋值 + */ + default UserSimpleDTO convertToUserSimpleDTO(UserSimpleBaseVO vo) { + if (vo == null) { + return null; + } + + UserSimpleDTO dto = new UserSimpleDTO(); + dto.setId(vo.getId()); + dto.setNickname(vo.getNickname()); + dto.setAvatar(vo.getAvatar()); + dto.setDeptId(vo.getDeptId()); + dto.setDeptName(vo.getDeptName()); // 确保 deptName 被正确赋值 + return dto; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md new file mode 100644 index 0000000..d796f41 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md @@ -0,0 +1 @@ + diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmCategoryDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmCategoryDO.java new file mode 100644 index 0000000..193ffb4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmCategoryDO.java @@ -0,0 +1,54 @@ +package com.zt.plat.module.bpm.dal.dataobject.definition; + +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * BPM 流程分类 DO + * + * @author ZT + */ +@TableName("bpm_category") +@KeySequence("bpm_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmCategoryDO extends BaseDO { + + /** + * 分类编号 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 分类名 + */ + private String name; + /** + * 分类标志 + */ + private String code; + /** + * 分类描述 + */ + private String description; + /** + * 分类状态 + * + * 枚举 {@link com.zt.plat.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + /** + * 分类排序 + */ + private Integer sort; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmFormDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmFormDO.java new file mode 100644 index 0000000..649d191 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmFormDO.java @@ -0,0 +1,57 @@ +package com.zt.plat.module.bpm.dal.dataobject.definition; + +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * BPM 工作流的表单定义 + * 用于工作流的申请表单,需要动态配置的场景 + * + * @author ZT + */ +@TableName(value = "bpm_form", autoResultMap = true) +@KeySequence("bpm_form_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmFormDO extends BaseDO { + + /** + * 编号 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 表单名 + */ + private String name; + /** + * 状态 + */ + private Integer status; + /** + * 表单的配置 + */ + private String conf; + /** + * 表单项的数组 + * + * 目前直接将 https://github.com/JakHuang/form-generator 生成的 JSON 串,直接保存 + * 定义:https://github.com/JakHuang/form-generator/issues/46 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List fields; + /** + * 备注 + */ + private String remark; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java new file mode 100644 index 0000000..0ebef4b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -0,0 +1,219 @@ +package com.zt.plat.module.bpm.dal.dataobject.definition; + +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.zt.plat.framework.mybatis.core.type.LongListTypeHandler; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import com.zt.plat.module.bpm.enums.definition.BpmAutoApproveTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmModelFormTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmModelTypeEnum; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +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} 不支持拓展字段,所以新建该表 + * + * @author ZT + */ +@TableName(value = "bpm_process_definition_info", autoResultMap = true) +@KeySequence("bpm_process_definition_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmProcessDefinitionInfoDO extends BaseDO { + + /** + * 编号 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 流程定义的编号 + * + * 关联 {@link ProcessDefinition#getId()} 属性 + */ + private String processDefinitionId; + /** + * 流程模型的编号 + * + * 关联 {@link Model#getId()} 属性 + */ + private String modelId; + /** + * 流程模型的类型 + * + * 枚举 {@link BpmModelTypeEnum} + */ + private Integer modelType; + + /** + * 流程分类的编码 + * + * 关联 {@link BpmCategoryDO#getCode()} + * + * 为什么要存储?原因是,{@link ProcessDefinition#getCategory()} 无法设置 + */ + private String category; + /** + * 图标 + */ + private String icon; + /** + * 描述 + */ + private String description; + + /** + * 表单类型 + * + * 枚举 {@link BpmModelFormTypeEnum} + */ + private Integer formType; + /** + * 动态表单编号 + * + * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 + * + * 关联 {@link BpmFormDO#getId()} + */ + private Long formId; + /** + * 表单的配置 + * + * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 + * + * 冗余 {@link BpmFormDO#getConf()} + */ + private String formConf; + /** + * 表单项的数组 + * + * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 + * + * 冗余 {@link BpmFormDO#getFields()} + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List 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 startUserIds; + + /** + * 可发起部门编号数组 + * + * 关联 {@link AdminUserRespDTO#getDeptId()} 字段的数组 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List startDeptIds; + + /** + * 可管理用户编号数组 + * + * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 + */ + @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 + private List managerUserIds; + + /** + * 是否允许撤销审批中的申请 + */ + private Boolean allowCancelRunningProcess; + + /** + * 流程 ID 规则 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.ProcessIdRule processIdRule; + + /** + * 自动去重类型 + * + * 枚举 {@link BpmAutoApproveTypeEnum} + */ + private Integer autoApprovalType; + + /** + * 标题设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.TitleSetting titleSetting; + /** + * 摘要设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.SummarySetting summarySetting; + + /** + * 流程前置通知设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.HttpRequestSetting processBeforeTriggerSetting; + /** + * 流程后置通知设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.HttpRequestSetting processAfterTriggerSetting; + + /** + * 任务前置通知设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.HttpRequestSetting taskBeforeTriggerSetting; + + /** + * 任务后置通知设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java new file mode 100644 index 0000000..29afb73 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.bpm.dal.dataobject.definition; + +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * BPM 流程表达式 DO + * + * @author ZT + */ +@TableName("bpm_process_expression") +@KeySequence("bpm_process_expression_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmProcessExpressionDO extends BaseDO { + + /** + * 编号 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 表达式名字 + */ + private String name; + /** + * 表达式状态 + * + * 枚举 {@link TODO common_status 对应的类} + */ + private Integer status; + /** + * 表达式 + */ + private String expression; + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java new file mode 100644 index 0000000..ed36d7b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java @@ -0,0 +1,74 @@ +package com.zt.plat.module.bpm.dal.dataobject.definition; + +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.zt.plat.module.bpm.enums.definition.BpmProcessListenerTypeEnum; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * BPM 流程监听器 DO + * + * 目的:本质上它是流程监听器的模版,用于 BPMN 在设计时,直接选择这些模版 + * + * @author ZT + */ +@TableName(value = "bpm_process_listener") +@KeySequence("bpm_process_listener_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmProcessListenerDO extends BaseDO { + + /** + * 主键 ID,自增 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 监听器名字 + */ + private String name; + /** + * 状态 + * + * 枚举 {@link com.zt.plat.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + /** + * 监听类型 + * + * 枚举 {@link BpmProcessListenerTypeEnum} + * + * 1. execution:ExecutionListener 执行监听器 + * 2. task:TaskListener 任务监听器 + */ + private String type; + /** + * 监听事件 + * + * execution 时:start、end + * task 时:create 创建、assignment 指派、complete 完成、delete 删除、update 更新、timeout 超时 + */ + private String event; + + /** + * 值类型 + * + * 1. class:Java 类,ExecutionListener 需要 {@link org.flowable.engine.delegate.JavaDelegate},TaskListener 需要 {@link org.flowable.engine.delegate.TaskListener} + * 2. delegateExpression:委托表达式,在 class 的基础上,需要注册到 Spring 容器里,后续表达式通过 Spring Bean 名称即可 + * 3. expression:表达式,一个普通类的普通方法,将这个普通类注册到 Spring 容器中,然后表达式中还可以执行这个类中的方法 + */ + private String valueType; + /** + * 值 + */ + private String value; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java new file mode 100644 index 0000000..bbe42d2 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java @@ -0,0 +1,52 @@ +package com.zt.plat.module.bpm.dal.dataobject.definition; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Set; + +/** + * BPM 用户组 + * + * @author ZT + */ +@TableName(value = "bpm_user_group", autoResultMap = true) +@KeySequence("bpm_user_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmUserGroupDO extends BaseDO { + + /** + * 编号,自增 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 组名 + */ + private String name; + /** + * 描述 + */ + private String description; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 成员用户编号数组 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Set userIds; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java new file mode 100644 index 0000000..76dabd2 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java @@ -0,0 +1,78 @@ +package com.zt.plat.module.bpm.dal.dataobject.oa; + +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.zt.plat.module.bpm.enums.task.BpmTaskStatusEnum; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * OA 请假申请 DO + * + * {@link #day} 请假天数,目前先简单做。一般是分成请假上午和下午,可以是 1 整天,可以是 0.5 半天 + * + * @author jason + * @author ZT + */ +@TableName("bpm_oa_leave") +@KeySequence("bpm_oa_leave_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmOALeaveDO extends BaseDO { + + /** + * 请假表单主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 申请人的用户编号 + * + * 关联 AdminUserDO 的 id 属性 + */ + private Long userId; + /** + * 请假类型 + */ + private String type; + /** + * 原因 + */ + private String reason; + /** + * 开始时间 + */ + private LocalDateTime startTime; + /** + * 结束时间 + */ + private LocalDateTime endTime; + /** + * 请假天数 + */ + private Long day; + /** + * 审批结果 + * + * 枚举 {@link BpmTaskStatusEnum} + * 考虑到简单,所以直接复用了 BpmProcessInstanceStatusEnum 枚举,也可以自己定义一个枚举哈 + */ + private Integer status; + + /** + * 对应的流程编号 + * + * 关联 ProcessInstance 的 id 属性 + */ + private String processInstanceId; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java new file mode 100644 index 0000000..ef06a3e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java @@ -0,0 +1,98 @@ +package com.zt.plat.module.bpm.dal.dataobject.task; + +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.task.api.history.HistoricTaskInstance; + +/** + * 流程抄送 DO + * + * @author kyle + * @since 2024-01-22 + */ +@TableName(value = "bpm_process_instance_copy", autoResultMap = true) +@KeySequence("bpm_process_instance_copy_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BpmProcessInstanceCopyDO extends BaseDO { + + /** + * 编号 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 发起人 Id + * + * 冗余 ProcessInstance 的 startUserId 字段 + */ + private Long startUserId; + /** + * 流程名 + * + * 冗余 ProcessInstance 的 name 字段 + */ + private String processInstanceName; + /** + * 流程实例的编号 + * + * 关联 ProcessInstance 的 id 属性 + */ + private String processInstanceId; + /** + * 流程实例的流程定义编号 + * + * 关联 ProcessInstance 的 processDefinitionId 属性 + */ + private String processDefinitionId; + /** + * 流程分类 + * + * 冗余 ProcessInstance 的 category 字段 + */ + private String category; + /** + * 流程活动的编号 + *

+ * + * 冗余 {@link FlowNode#getId()},对应 BPMN XML 节点编号 + * 原因:用于查询抄送节点的表单字段权限。因为仿钉钉/飞书的抄送节点 (ServiceTask),没有 taskId,只有 activityId + */ + private String activityId; + /** + * 流程活动的名字 + * + * 冗余 {@link FlowNode#getName()} + */ + private String activityName; + /** + * 流程活动的编号 + * + * 关联 {@link HistoricTaskInstance#getId()} + */ + private String taskId; + + /** + * 用户编号(被抄送的用户编号) + * + * 关联 system_users 的 id 属性 + */ + private Long userId; + + /** + * 抄送意见 + */ + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/category/BpmCategoryMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/category/BpmCategoryMapper.java new file mode 100644 index 0000000..5c36ae4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/category/BpmCategoryMapper.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.bpm.dal.mysql.category; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * BPM 流程分类 Mapper + * + * @author ZT + */ +@Mapper +public interface BpmCategoryMapper extends BaseMapperX { + + default PageResult selectPage(BpmCategoryPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(BpmCategoryDO::getName, reqVO.getName()) + .likeIfPresent(BpmCategoryDO::getCode, reqVO.getCode()) + .eqIfPresent(BpmCategoryDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(BpmCategoryDO::getCreateTime, reqVO.getCreateTime()) + .orderByAsc(BpmCategoryDO::getSort)); + } + + default BpmCategoryDO selectByName(String name) { + return selectOne(BpmCategoryDO::getName, name); + } + + default BpmCategoryDO selectByCode(String code) { + return selectOne(BpmCategoryDO::getCode, code); + } + + default List selectListByCode(Collection codes) { + return selectList(BpmCategoryDO::getCode, codes); + } + + default List selectListByStatus(Integer status) { + return selectList(BpmCategoryDO::getStatus, status); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmFormMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmFormMapper.java new file mode 100644 index 0000000..db73b65 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmFormMapper.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.bpm.dal.mysql.definition; + + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.QueryWrapperX; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 动态表单 Mapper + * + * @author 风里雾里 + */ +@Mapper +public interface BpmFormMapper extends BaseMapperX { + + default PageResult selectPage(BpmFormPageReqVO reqVO) { + return selectPage(reqVO, new QueryWrapperX() + .likeIfPresent("name", reqVO.getName()) + .orderByDesc("id")); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java new file mode 100644 index 0000000..5d5be48 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.bpm.dal.mysql.definition; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface BpmProcessDefinitionInfoMapper extends BaseMapperX { + + default List selectListByProcessDefinitionIds(Collection processDefinitionIds) { + return selectList(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionIds); + } + + default BpmProcessDefinitionInfoDO selectByProcessDefinitionId(String processDefinitionId) { + return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId); + } + + default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) { + update(updateObj, + new LambdaQueryWrapperX().eq(BpmProcessDefinitionInfoDO::getModelId, modelId)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessExpressionMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessExpressionMapper.java new file mode 100644 index 0000000..4551fd3 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessExpressionMapper.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.bpm.dal.mysql.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessExpressionDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * BPM 流程表达式 Mapper + * + * @author ZT + */ +@Mapper +public interface BpmProcessExpressionMapper extends BaseMapperX { + + default PageResult selectPage(BpmProcessExpressionPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(BpmProcessExpressionDO::getName, reqVO.getName()) + .eqIfPresent(BpmProcessExpressionDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(BpmProcessExpressionDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(BpmProcessExpressionDO::getId)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessListenerMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessListenerMapper.java new file mode 100644 index 0000000..6f6c89e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmProcessListenerMapper.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.bpm.dal.mysql.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessListenerDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * BPM 流程监听器 Mapper + * + * @author ZT + */ +@Mapper +public interface BpmProcessListenerMapper extends BaseMapperX { + + default PageResult selectPage(BpmProcessListenerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(BpmProcessListenerDO::getName, reqVO.getName()) + .eqIfPresent(BpmProcessListenerDO::getType, reqVO.getType()) + .eqIfPresent(BpmProcessListenerDO::getEvent, reqVO.getEvent()) + .eqIfPresent(BpmProcessListenerDO::getStatus, reqVO.getStatus()) + .orderByDesc(BpmProcessListenerDO::getId)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java new file mode 100644 index 0000000..4c52531 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.dal.mysql.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 用户组 Mapper + * + * @author ZT + */ +@Mapper +public interface BpmUserGroupMapper extends BaseMapperX { + + default PageResult selectPage(BpmUserGroupPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(BpmUserGroupDO::getName, reqVO.getName()) + .eqIfPresent(BpmUserGroupDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(BpmUserGroupDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(BpmUserGroupDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(BpmUserGroupDO::getStatus, status); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java new file mode 100644 index 0000000..7e0f439 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.bpm.dal.mysql.oa; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.oa.BpmOALeaveDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 请假申请 Mapper + * + * @author jason + * @author ZT + */ +@Mapper +public interface BpmOALeaveMapper extends BaseMapperX { + + default PageResult selectPage(Long userId, BpmOALeavePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(BpmOALeaveDO::getUserId, userId) + .eqIfPresent(BpmOALeaveDO::getStatus, reqVO.getStatus()) + .eqIfPresent(BpmOALeaveDO::getType, reqVO.getType()) + .likeIfPresent(BpmOALeaveDO::getReason, reqVO.getReason()) + .betweenIfPresent(BpmOALeaveDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(BpmOALeaveDO::getId)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java new file mode 100644 index 0000000..61a73c3 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.bpm.dal.mysql.task; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface BpmProcessInstanceCopyMapper extends BaseMapperX { + + default PageResult selectPage(Long loginUserId, BpmProcessInstanceCopyPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(BpmProcessInstanceCopyDO::getUserId, loginUserId) + .likeIfPresent(BpmProcessInstanceCopyDO::getProcessInstanceName, reqVO.getProcessInstanceName()) + .betweenIfPresent(BpmProcessInstanceCopyDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(BpmProcessInstanceCopyDO::getId)); + } + + default void deleteByProcessInstanceId(String processInstanceId) { + delete(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/redis/BpmProcessIdRedisDAO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/redis/BpmProcessIdRedisDAO.java new file mode 100644 index 0000000..b6b0f69 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/redis/BpmProcessIdRedisDAO.java @@ -0,0 +1,61 @@ +package com.zt.plat.module.bpm.dal.redis; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.time.LocalDateTime; + +import static cn.hutool.core.date.DatePattern.*; + +/** + * BPM 流程 Id 编码的 Redis DAO + * + * @author Lesan + */ +@Repository +public class BpmProcessIdRedisDAO { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 生成序号,使用定义的 processIdRule 规则生成 + * + * @param processIdRule 规则 + * @return 序号 + */ + public String generate(BpmModelMetaInfoVO.ProcessIdRule processIdRule) { + // 生成日期前缀 + String infix = ""; + switch (processIdRule.getInfix()) { + case "DAY": + infix = DateUtil.format(LocalDateTime.now(), PURE_DATE_PATTERN); + break; + case "HOUR": + infix = DateUtil.format(LocalDateTime.now(), PURE_DATE_PATTERN + "HH"); + break; + case "MINUTE": + infix = DateUtil.format(LocalDateTime.now(), PURE_DATE_PATTERN + "HHmm"); + break; + case "SECOND": + infix = DateUtil.format(LocalDateTime.now(), PURE_DATETIME_PATTERN); + break; + } + + // 生成序号 + String noPrefix = processIdRule.getPrefix() + infix + processIdRule.getPostfix(); + String key = RedisKeyConstants.BPM_PROCESS_ID + noPrefix; + Long no = stringRedisTemplate.opsForValue().increment(key); + if (StrUtil.isNotEmpty(infix)) { + // 特殊:没有前缀,则不能过期,不能每次都是从 0 开始 + stringRedisTemplate.expire(key, Duration.ofDays(1L)); + } + return noPrefix + String.format("%0" + processIdRule.getLength() + "d", no); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/redis/RedisKeyConstants.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/redis/RedisKeyConstants.java new file mode 100644 index 0000000..9146bc9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/redis/RedisKeyConstants.java @@ -0,0 +1,15 @@ +package com.zt.plat.module.bpm.dal.redis; + +/** + * BPM Redis Key 枚举类 + * + * @author ZT + */ +public interface RedisKeyConstants { + + /** + * 流程 ID 的缓存 + */ + String BPM_PROCESS_ID = "bpm:process_id:"; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java new file mode 100644 index 0000000..f0d5b49 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java @@ -0,0 +1,95 @@ +package com.zt.plat.module.bpm.framework.flowable.config; + +import cn.hutool.core.collection.ListUtil; +import com.zt.plat.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; +import com.zt.plat.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; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncListenableTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.List; + +/** + * BPM 模块的 Flowable 配置类 + * + * @author jason + */ +@Configuration(proxyBeanMethods = false) +public class BpmFlowableConfiguration { + + /** + * 参考 {@link org.flowable.spring.boot.FlowableJobConfiguration} 类,创建对应的 AsyncListenableTaskExecutor Bean + * + * 如果不创建,会导致项目启动时,Flowable 报错的问题 + */ + @Bean(name = "applicationTaskExecutor") + @ConditionalOnMissingBean(name = "applicationTaskExecutor") + public AsyncListenableTaskExecutor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); + executor.setMaxPoolSize(8); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("flowable-task-Executor-"); + executor.setAwaitTerminationSeconds(30); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAllowCoreThreadTimeOut(true); + executor.initialize(); + return executor; + } + + /** + * BPM 模块的 ProcessEngineConfigurationConfigurer 实现类: + * + * 1. 设置各种监听器 + * 2. 设置自定义的 ActivityBehaviorFactory 实现 + */ + @Bean + public EngineConfigurationConfigurer bpmProcessEngineConfigurationConfigurer( + ObjectProvider listeners, + ObjectProvider customFlowableFunctionDelegates, + BpmActivityBehaviorFactory bpmActivityBehaviorFactory) { + return configuration -> { + // 注册监听器,例如说 BpmActivityEventListener + configuration.setEventListeners(ListUtil.toList(listeners.iterator())); + // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义 + configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory); + // 设置自定义的函数 + configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator())); + }; + } + + // =========== 审批人相关的 Bean ========== + + @Bean + public BpmActivityBehaviorFactory bpmActivityBehaviorFactory(BpmTaskCandidateInvoker bpmTaskCandidateInvoker) { + BpmActivityBehaviorFactory bpmActivityBehaviorFactory = new BpmActivityBehaviorFactory(); + bpmActivityBehaviorFactory.setTaskCandidateInvoker(bpmTaskCandidateInvoker); + return bpmActivityBehaviorFactory; + } + + @Bean + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") // adminUserApi 可以注入成功 + public BpmTaskCandidateInvoker bpmTaskCandidateInvoker(List strategyList, + AdminUserApi adminUserApi) { + return new BpmTaskCandidateInvoker(strategyList, adminUserApi); + } + + // =========== 自己拓展的 Bean ========== + + @Bean + public BpmProcessInstanceEventPublisher processInstanceEventPublisher(ApplicationEventPublisher publisher) { + return new BpmProcessInstanceEventPublisher(publisher); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmActivityBehaviorFactory.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmActivityBehaviorFactory.java new file mode 100644 index 0000000..ec9cd8f --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmActivityBehaviorFactory.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.bpm.framework.flowable.core.behavior; + +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import lombok.Setter; +import org.flowable.bpmn.model.Activity; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior; +import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory; + +/** + * 自定义的 ActivityBehaviorFactory 实现类,目的如下: + * 1. 自定义 {@link #createUserTaskActivityBehavior(UserTask)}:实现自定义的流程任务的 assignee 负责人的分配 + * + * @author ZT + */ +@Setter +public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory { + + private BpmTaskCandidateInvoker taskCandidateInvoker; + + @Override + public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) { + return new BpmUserTaskActivityBehavior(userTask) + .setTaskCandidateInvoker(taskCandidateInvoker); + } + + @Override + public ParallelMultiInstanceBehavior createParallelMultiInstanceBehavior(Activity activity, + AbstractBpmnActivityBehavior behavior) { + return new BpmParallelMultiInstanceBehavior(activity, behavior) + .setTaskCandidateInvoker(taskCandidateInvoker); + } + + @Override + public SequentialMultiInstanceBehavior createSequentialMultiInstanceBehavior(Activity activity, + AbstractBpmnActivityBehavior behavior) { + return new BpmSequentialMultiInstanceBehavior(activity, behavior) + .setTaskCandidateInvoker(taskCandidateInvoker); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java new file mode 100644 index 0000000..2df2ce7 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java @@ -0,0 +1,91 @@ +package com.zt.plat.module.bpm.framework.flowable.core.behavior; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.util.collection.SetUtils; +import com.zt.plat.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import lombok.Setter; +import org.flowable.bpmn.model.Activity; +import org.flowable.bpmn.model.CallActivity; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; + +import java.util.List; +import java.util.Set; + +/** + * 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配 + * 第一步,基于分配规则,计算出分配任务的【多个】候选人们。 + * 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它 + * + * @author kemengkai + * @since 2022-04-21 16:57 + */ +@Setter +public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior { + + private BpmTaskCandidateInvoker taskCandidateInvoker; + + public BpmParallelMultiInstanceBehavior(Activity activity, + AbstractBpmnActivityBehavior innerActivityBehavior) { + super(activity, innerActivityBehavior); + } + + /** + * 重写该方法,主要实现两个功能: + * 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的 + * 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人 + * + * 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量 + * + * @param execution 执行任务 + * @return 数量 + */ + @Override + protected int resolveNrOfInstances(DelegateExecution execution) { + // 情况一:UserTask 节点 + if (execution.getCurrentFlowElement() instanceof UserTask) { + // 第一步,设置 collectionVariable 和 CollectionVariable + // 从 execution.getVariable() 读取所有任务处理人的 key + super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的 + super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); + // 从 execution.getVariable() 读取当前所有任务处理的人的 key + super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); + + // 第二步,获取任务的所有处理人 + @SuppressWarnings("unchecked") + Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class); + if (assigneeUserIds == null) { + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); + if (CollUtil.isEmpty(assigneeUserIds)) { + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 + assigneeUserIds = SetUtils.asSet((Long) null); + } + execution.setVariableLocal(super.collectionVariable, assigneeUserIds); + } + return assigneeUserIds.size(); + } + + // 情况二:CallActivity 节点 + if (execution.getCurrentFlowElement() instanceof CallActivity) { + FlowElement flowElement = execution.getCurrentFlowElement(); + Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement); + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) { + return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class); + } + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) { + return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size(); + } + } + + return super.resolveNrOfInstances(execution); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java new file mode 100644 index 0000000..2f7f76d --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java @@ -0,0 +1,95 @@ +package com.zt.plat.module.bpm.framework.flowable.core.behavior; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.util.collection.SetUtils; +import com.zt.plat.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import lombok.Setter; +import org.flowable.bpmn.model.Activity; +import org.flowable.bpmn.model.CallActivity; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; + +import java.util.List; +import java.util.Set; + +/** + * 自定义的【串行】的【多个】流程任务的 assignee 负责人的分配 + * + * 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样 + * + * @author ZT + */ +@Setter +public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior { + + private BpmTaskCandidateInvoker taskCandidateInvoker; + + public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) { + super(activity, innerActivityBehavior); + } + + /** + * 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances(DelegateExecution)} 类似 + * + * 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序! + */ + @Override + protected int resolveNrOfInstances(DelegateExecution execution) { + // 情况一:UserTask 节点 + if (execution.getCurrentFlowElement() instanceof UserTask) { + // 第一步,设置 collectionVariable 和 CollectionVariable + // 从 execution.getVariable() 读取所有任务处理人的 key + super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的 + super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); + // 从 execution.getVariable() 读取当前所有任务处理的人的 key + super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); + + // 第二步,获取任务的所有处理人 + // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人 + @SuppressWarnings("unchecked") + Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class); + if (assigneeUserIds == null) { + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); + if (CollUtil.isEmpty(assigneeUserIds)) { + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 + assigneeUserIds = SetUtils.asSet((Long) null); + } + execution.setVariableLocal(super.collectionVariable, assigneeUserIds); + } + return assigneeUserIds.size(); + } + + // 情况二:CallActivity 节点 + if (execution.getCurrentFlowElement() instanceof CallActivity) { + FlowElement flowElement = execution.getCurrentFlowElement(); + Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement); + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) { + return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class); + } + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) { + return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size(); + } + } + + return super.resolveNrOfInstances(execution); + } + + @Override + protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) { + // 参见 https://gitee.com/zhijiantianya/zt-cloud/issues/IC239F + super.collectionExpression = null; + super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); + super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); + super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java new file mode 100644 index 0000000..8d29c54 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java @@ -0,0 +1,86 @@ +package com.zt.plat.module.bpm.framework.flowable.core.behavior; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.RandomUtil; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.UserTask; +import org.flowable.common.engine.impl.el.ExpressionManager; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.TaskHelper; +import org.flowable.engine.interceptor.CreateUserTaskBeforeContext; +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; + +/** + * 自定义的【单个】流程任务的 assignee 负责人的分配 + * 第一步,基于分配规则,计算出分配任务的【单个】候选人。如果找不到,则直接报业务异常,不继续执行后续的流程; + * 第二步,随机选择一个候选人,则选择作为 assignee 负责人。 + * + * @author ZT + */ +@Slf4j +public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior { + + @Setter + private BpmTaskCandidateInvoker taskCandidateInvoker; + + public BpmUserTaskActivityBehavior(UserTask userTask) { + super(userTask); + } + + @Override + @Transactional(rollbackFor = Exception.class) + protected void handleAssignments(TaskService taskService, String assignee, String owner, + List candidateUsers, List candidateGroups, TaskEntity task, ExpressionManager expressionManager, + DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { + // 第一步,获得任务的候选用户 + Long assigneeUserId = calculateTaskCandidateUsers(execution); + // 第二步,设置作为负责人 + if (assigneeUserId != null) { + TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); + } + } + + private Long calculateTaskCandidateUsers(DelegateExecution execution) { + // 情况一,如果是多实例的任务,例如说会签、或签等情况,则从 Variable 中获取。 + // 顺序审批可见 BpmSequentialMultiInstanceBehavior,并发审批可见 BpmSequentialMultiInstanceBehavior + if (super.multiInstanceActivityBehavior != null) { + return execution.getVariable(super.multiInstanceActivityBehavior.getCollectionElementVariable(), Long.class); + } + + // 情况二,如果非多实例的任务,则计算任务处理人 + // 第一步,先计算可处理该任务的处理人们 + Set candidateUserIds = taskCandidateInvoker.calculateUsersByTask(execution); + if (CollUtil.isEmpty(candidateUserIds)) { + return null; + } + // 第二步,后随机选择一个任务的处理人 + // 疑问:为什么一定要选择一个任务处理人? + // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。 + // 如果希望一个任务可以同时被多个人处理,可以考虑使用 BpmParallelMultiInstanceBehavior 实现的会签 or 或签。 + int index = RandomUtil.randomInt(candidateUserIds.size()); + return CollUtil.get(candidateUserIds, index); + } + + @Override + protected void handleCategory(CreateUserTaskBeforeContext beforeContext, ExpressionManager expressionManager, + TaskEntity task, DelegateExecution execution) { + ProcessDefinitionEntity processDefinitionEntity = CommandContextUtil.getProcessDefinitionEntityManager().findById(execution.getProcessDefinitionId()); + if (processDefinitionEntity == null) { + log.warn("[handleCategory][任务编号({}) 找不到流程定义({})]", task.getId(), execution.getProcessDefinitionId()); + return; + } + task.setCategory(processDefinitionEntity.getCategory()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java new file mode 100644 index 0000000..54310ef --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -0,0 +1,207 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate; + +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.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.util.object.ObjectUtils; +import com.zt.plat.framework.datapermission.core.annotation.DataPermission; +import com.zt.plat.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.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.CallActivity; +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.*; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG; + +/** + * {@link BpmTaskCandidateStrategy} 的调用者,用于调用对应的策略,实现任务的候选人的计算 + * + * @author ZT + */ +@Slf4j +public class BpmTaskCandidateInvoker { + + private final Map strategyMap = new HashMap<>(); + + private final AdminUserApi adminUserApi; + + public BpmTaskCandidateInvoker(List strategyList, + AdminUserApi adminUserApi) { + strategyList.forEach(strategy -> { + BpmTaskCandidateStrategy oldStrategy = strategyMap.put(strategy.getStrategy(), strategy); + Assert.isNull(oldStrategy, "策略(%s) 重复", strategy.getStrategy()); + }); + this.adminUserApi = adminUserApi; + } + + /** + * 校验流程模型的任务分配规则全部都配置了 + * 目的:如果有规则未配置,会导致流程任务找不到负责人,进而流程无法进行下去! + * + * @param bpmnBytes BPMN XML + */ + public void validateBpmnConfig(byte[] bpmnBytes) { + BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes); + assert bpmnModel != null; + List userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); + // 遍历所有的 UserTask,校验审批人配置 + userTaskList.forEach(userTask -> { + // 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) { + throw exception(MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG, userTask.getName()); + } + BpmTaskCandidateStrategy candidateStrategy = getCandidateStrategy(strategy); + if (candidateStrategy.isParamRequired() && StrUtil.isBlank(param)) { + throw exception(MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG, userTask.getName()); + } + // 2. 具体策略校验 + getCandidateStrategy(strategy).validateParam(param); + }); + } + + /** + * 计算任务的候选人 + * + * @param execution 执行任务 + * @return 用户编号集合 + */ + @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 + public Set calculateUsersByTask(DelegateExecution execution) { + // 注意:解决极端情况下,Flowable 异步调用,导致租户 id 丢失的情况 + // 例如说,SIMPLE 延迟器在 trigger 的时候!!! + return FlowableUtils.execute(execution.getTenantId(), () -> { + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 + 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 计算任务的候选人 + Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); + String param = BpmnModelUtils.parseCandidateParam(flowElement); + Set userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param); + // 1.2 移除被禁用的用户 + removeDisableUsers(userIds); + + // 2. 候选人为空时,根据“审批人为空”的配置补充 + if (CollUtil.isEmpty(userIds)) { + 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 calculateUsersByActivity(BpmnModel bpmnModel, String activityId, + Long startUserId, String processDefinitionId, Map processVariables) { + // 如果是 CallActivity 子流程,不进行计算候选人 + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); + if (flowElement instanceof CallActivity) { + return new HashSet<>(); + } + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 + 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 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; + } + + @VisibleForTesting + void removeDisableUsers(Set assigneeUserIds) { + if (CollUtil.isEmpty(assigneeUserIds)) { + return; + } + Map userMap = adminUserApi.getUserMap(assigneeUserIds); + assigneeUserIds.removeIf(id -> { + AdminUserRespDTO user = userMap.get(id); + return user == null || CommonStatusEnum.isDisable(user.getStatus()); + }); + } + + /** + * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 + * + * 注意:如果只有一个候选人,则不处理,避免无法审批 + * + * @param assigneeUserIds 当前分配的候选人 + * @param flowElement 当前节点 + * @param startUserId 发起人 + */ + @VisibleForTesting + void removeStartUserIfSkip(Set 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); + BpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum); + Assert.notNull(strategyObj, "策略(%s) 不存在", strategy); + return strategyObj; + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java new file mode 100644 index 0000000..2d916be --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java @@ -0,0 +1,85 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate; + +import com.zt.plat.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 任务的候选人的策略接口 + *

+ * 例如说:分配审批人 + * + * @author ZT + */ +public interface BpmTaskCandidateStrategy { + + /** + * 对应策略 + * + * @return 策略 + */ + BpmTaskCandidateStrategyEnum getStrategy(); + + /** + * 校验参数 + * + * @param param 参数 + */ + void validateParam(String param); + + /** + * 是否一定要输入参数 + * + * @return 是否 + */ + default boolean isParamRequired() { + return true; + } + + /** + * 基于候选人参数,获得任务的候选用户们 + * + * 注意:实现 calculateUsers 系列方法时,有两种选择: + * 1. 只重写 calculateUsers 默认方法 + * 2. 都重写 calculateUsersByTask 和 calculateUsersByActivity 两个方法 + * + * @param param 执行任务 + * @return 用户编号集合 + */ + default Set calculateUsers(String param) { + throw new UnsupportedOperationException("该分配方法未实现,请检查!"); + } + + /** + * 基于【执行任务】,获得任务的候选用户们 + * + * @param execution 执行任务 + * @return 用户编号集合 + */ + default Set calculateUsersByTask(DelegateExecution execution, String param) { + return calculateUsers(param); + } + + /** + * 基于【流程活动】,获得任务的候选用户们 + *

+ * 目的:用于获取未执行节点的候选用户们 + * + * @param bpmnModel 流程图 + * @param activityId 活动 ID (对应 Bpmn XML id) + * @param param 节点的参数 + * @param startUserId 流程发起人编号 + * @param processDefinitionId 流程定义编号 + * @param processVariables 流程变量 + * @return 用户编号集合 + */ + @SuppressWarnings("unused") + default Set calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map processVariables) { + return calculateUsers(param); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java new file mode 100644 index 0000000..fd34457 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java @@ -0,0 +1,79 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.expression; + +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; +import static java.util.Collections.emptySet; + +/** + * 分配给发起人的 Leader 审批的 Expression 流程表达式 + * 目前 Leader 的定义是,发起人所在部门的 Leader + * + * @author ZT + */ +@Component +public class BpmTaskAssignLeaderExpression { + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + @Resource + private BpmProcessInstanceService processInstanceService; + + /** + * 计算审批的候选人 + * + * @param execution 流程执行实体 + * @param level 指定级别 + * @return 指定级别的领导 + */ + public Set calculateUsers(DelegateExecution execution, int level) { + Assert.isTrue(level > 0, "level 必须大于 0"); + // 获得发起人 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); + // 获得对应 leve 的部门 + DeptRespDTO dept = null; + for (int i = 0; i < level; i++) { + // 获得 level 对应的部门 + if (dept == null) { + dept = getStartUserDept(startUserId); + if (dept == null) { // 找不到发起人的部门,所以无法使用该规则 + return emptySet(); + } + } else { + DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()).getCheckedData(); + if (parentDept == null) { // 找不到父级部门,所以只好结束寻找。原因是:例如说,级别比较高的人,所在部门层级比较少 + break; + } + dept = parentDept; + } + } + return dept.getLeaderUserId() != null ? asSet(dept.getLeaderUserId()) : emptySet(); + } + + private DeptRespDTO getStartUserDept(Long startUserId) { + AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData(); + Long deptId = DeptUtil.getDeptId(startUser); + if (deptId == 0L) { // 找不到部门,所以无法使用该规则 + return null; + } + return deptApi.getDept(deptId).getCheckedData(); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java new file mode 100644 index 0000000..bee41e7 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.expression; + +import com.zt.plat.framework.common.util.collection.SetUtils; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import jakarta.annotation.Resource; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.stereotype.Component; + +import java.util.Set; + +/** + * 分配给发起人审批的 Expression 流程表达式 + * + * @author ZT + */ +@Component +public class BpmTaskAssignStartUserExpression { + + @Resource + private BpmProcessInstanceService processInstanceService; + + /** + * 计算审批的候选人 + * + * @param execution 流程执行实体 + * @return 发起人 + */ + public Set calculateUsers(ExecutionEntityImpl execution) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); + return SetUtils.asSet(startUserId); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java new file mode 100644 index 0000000..9c0a760 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java @@ -0,0 +1,95 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类 + * + * @author jason + */ +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 getMultiLevelDeptLeaderIds(List deptIds, Integer level) { + Assert.isTrue(level > 0, "level 必须大于 0"); + if (CollUtil.isEmpty(deptIds)) { + return new HashSet<>(); + } + Set 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 (CollUtil.isEmpty(startUser.getDeptIds())) { // 找不到部门 + return null; + } + return deptApi.getDept(DeptUtil.getDeptId(startUser)).getCheckedData(); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java new file mode 100644 index 0000000..bfd429c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java @@ -0,0 +1,78 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.google.common.collect.Sets; +import jakarta.annotation.Resource; +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 java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +/** + * 审批人自选 {@link BpmTaskCandidateUserStrategy} 实现类 + * 审批人在审批时选择下一个节点的审批人 + * + * @author smallNorthLee + */ +@Component +public class BpmTaskCandidateApproveUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT; + } + + @Override + public void validateParam(String param) {} + + @Override + public boolean isParamRequired() { + return false; + } + + @Override + public LinkedHashSet calculateUsersByTask(DelegateExecution execution, String param) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); + Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance); + Assert.notNull(approveUserSelectAssignees, "流程实例({}) 的下一个执行节点审批人不能为空", + execution.getProcessInstanceId()); + if (approveUserSelectAssignees == null) { + return Sets.newLinkedHashSet(); + } + // 获得审批人 + List assignees = approveUserSelectAssignees.get(execution.getCurrentActivityId()); + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet(); + } + + @Override + public LinkedHashSet calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map processVariables) { + if (processVariables == null) { + return Sets.newLinkedHashSet(); + } + // 流程预测时会使用,允许审批人为空,如果为空前端会弹出提示选择下一个节点审批人,避免流程无法进行,审批时会真正校验节点是否配置审批人 + Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processVariables); + if (approveUserSelectAssignees == null) { + return Sets.newLinkedHashSet(); + } + // 获得审批人 + List assignees = approveUserSelectAssignees.get(activityId); + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet(); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java new file mode 100644 index 0000000..f851e66 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.lang.Assert; +import com.zt.plat.framework.common.util.string.StrUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; + +/** + * 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author jason + */ +@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 deptIds = StrUtils.splitToLong(params[0], ","); + int level = Integer.parseInt(params[1]); + // 校验部门存在 + deptApi.validateDeptList(deptIds).checkError(); + Assert.isTrue(level > 0, "部门层级必须大于 0"); + } + + @Override + public Set calculateUsers(String param) { + String[] params = param.split("\\|"); + List deptIds = StrUtils.splitToLong(params[0], ","); + int level = Integer.parseInt(params[1]); + return super.getMultiLevelDeptLeaderIds(deptIds, level); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java new file mode 100644 index 0000000..ad1c93b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import com.zt.plat.framework.common.util.string.StrUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertSet; + +/** + * 部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author kyle + */ +@Component +public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { + + @Resource + private DeptApi deptApi; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.DEPT_LEADER; + } + + @Override + public void validateParam(String param) { + Set deptIds = StrUtils.splitToLongSet(param); + deptApi.validateDeptList(deptIds).checkError(); + } + + @Override + public Set calculateUsers(String param) { + Set deptIds = StrUtils.splitToLongSet(param); + List depts = deptApi.getDeptList(deptIds).getCheckedData(); + return convertSet(depts, DeptRespDTO::getLeaderUserId); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java new file mode 100644 index 0000000..aa2c3f0 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import com.zt.plat.framework.common.util.string.StrUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertSet; + +/** + * 部门的成员 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author kyle + */ +@Component +public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy { + + @Resource + private DeptApi deptApi; + @Resource + private AdminUserApi adminUserApi; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.DEPT_MEMBER; + } + + @Override + public void validateParam(String param) { + Set deptIds = StrUtils.splitToLongSet(param); + deptApi.validateDeptList(deptIds).checkError(); + } + + @Override + public Set calculateUsers(String param) { + Set deptIds = StrUtils.splitToLongSet(param); + List users = adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(); + return convertSet(users, AdminUserRespDTO::getId); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java new file mode 100644 index 0000000..04e4963 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java @@ -0,0 +1,70 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.lang.Assert; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import jakarta.annotation.Resource; +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 java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static cn.hutool.core.collection.ListUtil.toList; + +/** + * 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author jason + */ +@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 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 calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map processVariables) { + int level = Integer.parseInt(param); // 参数是部门的层级 + DeptRespDTO dept = super.getStartUserDept(startUserId); + if (dept == null) { + return new HashSet<>(); + } + return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java new file mode 100644 index 0000000..9670fa9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java @@ -0,0 +1,71 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.lang.Assert; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import jakarta.annotation.Resource; +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 java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; + +/** + * 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author jason + */ +@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 calculateUsersByTask(DelegateExecution execution, String param) { + // 获得流程发起人 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); + // 获取发起人的部门负责人 + return getStartUserDeptLeader(startUserId, param); + } + + @Override + public Set calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map processVariables) { + // 获取发起人的部门负责人 + return getStartUserDeptLeader(startUserId, param); + } + + private Set 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<>(); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java new file mode 100644 index 0000000..189e5db --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.google.common.collect.Sets; +import jakarta.annotation.Resource; +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 java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +/** + * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类 + * + * @author ZT + */ +@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 calculateUsersByTask(DelegateExecution execution, String param) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); + Map> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); + Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", + execution.getProcessInstanceId()); + // 获得审批人 + List assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet(); + } + + @Override + public LinkedHashSet calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map processVariables) { + if (processVariables == null) { + return Sets.newLinkedHashSet(); + } + Map> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables); + if (startUserSelectAssignees == null) { + return Sets.newLinkedHashSet(); + } + // 获得审批人 + List assignees = startUserSelectAssignees.get(activityId); + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet(); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormDeptLeaderStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormDeptLeaderStrategy.java new file mode 100644 index 0000000..119b59d --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormDeptLeaderStrategy.java @@ -0,0 +1,56 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.form; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Assert; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept.AbstractBpmTaskCandidateDeptLeaderStrategy; +import com.zt.plat.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 jason + */ +@Component +public class BpmTaskCandidateFormDeptLeaderStrategy 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 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 calculateUsersByActivity(BpmnModel bpmnModel, String activityId, + String param, Long startUserId, String processDefinitionId, + Map 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); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java new file mode 100644 index 0000000..435717b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java @@ -0,0 +1,47 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.form; + +import cn.hutool.core.lang.Assert; +import com.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; +import com.zt.plat.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 jason + */ +@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 calculateUsersByTask(DelegateExecution execution, String param) { + Object result = execution.getVariable(param); + return CollectionUtils.toLinkedHashSet(Long.class, result); + } + + @Override + public Set calculateUsersByActivity(BpmnModel bpmnModel, String activityId, + String param, Long startUserId, String processDefinitionId, + Map processVariables) { + Object result = processVariables == null ? null : processVariables.get(param); + return CollectionUtils.toLinkedHashSet(Long.class, result); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java new file mode 100644 index 0000000..8c360bc --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.other; + +import cn.hutool.core.lang.Assert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import jakarta.annotation.Resource; +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 java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * 审批人为空 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author kyle + */ +@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 calculateUsersByTask(DelegateExecution execution, String param) { + return getCandidateUsers(execution.getProcessDefinitionId(), execution.getCurrentFlowElement()); + } + + @Override + public Set calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map processVariables) { + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); + return getCandidateUsers(processDefinitionId, flowElement); + } + + private Set 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<>(); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java new file mode 100644 index 0000000..8350a8a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java @@ -0,0 +1,58 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.other; + +import com.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.google.common.collect.Sets; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * 流程表达式 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author ZT + */ +@Component +@Slf4j +public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy { + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.EXPRESSION; + } + + @Override + public void validateParam(String param) { + // do nothing 因为它基本做不了校验 + } + + @Override + public Set calculateUsersByTask(DelegateExecution execution, String param) { + Object result = FlowableUtils.getExpressionValue(execution, param); + return CollectionUtils.toLinkedHashSet(Long.class, result); + } + + @Override + public Set calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map processVariables) { + Map variables = processVariables == null ? new HashMap<>() : processVariables; + try { + Object result = FlowableUtils.getExpressionValue(variables, param); + return CollectionUtils.toLinkedHashSet(Long.class, result); + } catch (FlowableException ex) { + // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常, + log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex); + // 不能预测候选人,返回空列表, 避免流程无法进行 + return Sets.newHashSet(); + } + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java new file mode 100644 index 0000000..5bc1232 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.common.util.string.StrUtils; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.service.definition.BpmUserGroupService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + +/** + * 用户组 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author kyle + */ +@Component +public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { + + @Resource + private BpmUserGroupService userGroupService; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.USER_GROUP; + } + + @Override + public void validateParam(String param) { + Set groupIds = StrUtils.splitToLongSet(param); + userGroupService.validUserGroups(groupIds); + } + + @Override + public Set calculateUsers(String param) { + Set groupIds = StrUtils.splitToLongSet(param); + List groups = userGroupService.getUserGroupList(groupIds); + return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java new file mode 100644 index 0000000..74edf8a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.common.util.string.StrUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.system.api.dept.PostApi; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertSet; + +/** + * 岗位 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author kyle + */ +@Component +public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { + + @Resource + private PostApi postApi; + @Resource + private AdminUserApi adminUserApi; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.POST; + } + + @Override + public void validateParam(String param) { + Set postIds = StrUtils.splitToLongSet(param); + postApi.validPostList(postIds); + } + + @Override + public Set calculateUsers(String param) { + Set postIds = StrUtils.splitToLongSet(param); + List users = adminUserApi.getUserListByPostIds(postIds).getCheckedData(); + return convertSet(users, AdminUserRespDTO::getId); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java new file mode 100644 index 0000000..f8223d9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java @@ -0,0 +1,43 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.common.util.string.StrUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.system.api.permission.PermissionApi; +import com.zt.plat.module.system.api.permission.RoleApi; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.Set; + +/** + * 角色 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author kyle + */ +@Component +public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { + + @Resource + private RoleApi roleApi; + @Resource + private PermissionApi permissionApi; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.ROLE; + } + + @Override + public void validateParam(String param) { + Set roleIds = StrUtils.splitToLongSet(param); + roleApi.validRoleList(roleIds); + } + + @Override + public Set calculateUsers(String param) { + Set roleIds = StrUtils.splitToLongSet(param); + return permissionApi.getUserRoleIdListByRoleIds(roleIds).getCheckedData(); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java new file mode 100644 index 0000000..ed527fa --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java @@ -0,0 +1,57 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.common.util.collection.SetUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import jakarta.annotation.Resource; +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 java.util.Map; +import java.util.Set; + +/** + * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类 + *

+ * 适合场景:用于需要发起人信息复核等场景 + * + * @author jason + */ +@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 calculateUsersByTask(DelegateExecution execution, String param) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); + } + + @Override + public Set calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map processVariables) { + return SetUtils.asSet(startUserId); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java new file mode 100644 index 0000000..f6a64ee --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java @@ -0,0 +1,39 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import cn.hutool.core.text.StrPool; +import com.zt.plat.framework.common.util.string.StrUtils; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashSet; + +/** + * 用户 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author kyle + */ +@Component +public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { + + @Resource + private AdminUserApi adminUserApi; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.USER; + } + + @Override + public void validateParam(String param) { + adminUserApi.validateUserList(StrUtils.splitToLongSet(param)).checkError(); + } + + @Override + public LinkedHashSet calculateUsers(String param) { + return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java new file mode 100644 index 0000000..c5db881 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.framework.flowable.core.el; + +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction; +import org.springframework.stereotype.Component; + +/** + * 根据流程变量 variable 的类型,转换参数的值 + * + * 目前用于 ConditionNodeConvert 的 buildConditionExpression 方法中 + * + * @author jason + */ +@Component +public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVariableExpressionFunction { + + public VariableConvertByTypeExpressionFunction() { + super("convertByType"); + } + + public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) { + Object variable = variableContainer.getVariable(variableName); + if (variable != null && parmaValue != null) { + // 如果值不是字符串类型,流程变量的类型是字符串,把值转成字符串 + if (!(parmaValue instanceof String) && variable instanceof String ) { + return parmaValue.toString(); + } + } + return parmaValue; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java new file mode 100644 index 0000000..9a8b399 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java @@ -0,0 +1,59 @@ +package com.zt.plat.module.bpm.framework.flowable.core.enums; + +import cn.hutool.core.util.ArrayUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 任务的候选人策略枚举 + * + * 例如说:分配给指定人审批 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum BpmTaskCandidateStrategyEnum implements ArrayValuable { + + ROLE(10, "角色"), + DEPT_MEMBER(20, "部门的成员"), // 包括负责人 + DEPT_LEADER(21, "部门的负责人"), + MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), + POST(22, "岗位"), + USER(30, "用户"), + APPROVE_USER_SELECT(34, "审批人自身"), // 当前审批人,可在审批时,选择下一个节点的审批人 + 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 Integer[] ARRAYS = Arrays.stream(values()).map(BpmTaskCandidateStrategyEnum::getStrategy).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer strategy; + /** + * 描述 + */ + private final String description; + + public static BpmTaskCandidateStrategyEnum valueOf(Integer strategy) { + return ArrayUtil.firstMatch(o -> o.getStrategy().equals(strategy), values()); + } + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java new file mode 100644 index 0000000..de18134 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -0,0 +1,146 @@ +package com.zt.plat.module.bpm.framework.flowable.core.enums; + +import com.zt.plat.module.bpm.enums.definition.BpmModelTypeEnum; + +/** + * BPMN XML 常量信息 + * + * @author ZT + */ +public interface BpmnModelConstants { + + String BPMN_FILE_SUFFIX = ".bpmn"; + + /** + * BPMN 中的命名空间 + */ + String NAMESPACE = "http://flowable.org/bpmn"; + + /** + * BPMN UserTask 的扩展属性,用于标记候选人策略 + */ + String USER_TASK_CANDIDATE_STRATEGY = "candidateStrategy"; + /** + * BPMN UserTask 的扩展属性,用于标记候选人参数 + */ + 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 Child Process 的扩展属性,用于标记多实例来源类型 + */ + String CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = "childProcessMultiInstanceSourceType"; + + /** + * 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 ExtensionElement 的扩展属性,用于标记触发器的类型 + */ + String TRIGGER_TYPE = "triggerType"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记触发器参数 + */ + String TRIGGER_PARAM = "triggerParam"; + + /** + * BPMN Start Event Node Id + */ + String START_EVENT_NODE_ID = "StartEvent"; + + /** + * 发起人节点 ID + */ + String START_USER_NODE_ID = "StartUserNode"; + + /** + * 是否需要签名 + */ + String SIGN_ENABLE = "signEnable"; + + /** + * 审批意见是否必填 + */ + String REASON_REQUIRE = "reasonRequire"; + + /** + * 节点类型 + * + * 目前只有 {@link BpmModelTypeEnum#SIMPLE} 的 UserTask 节点会设置该属性,用于区分是审批节点、还是办理节点 + */ + String NODE_TYPE = "nodeType"; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java new file mode 100644 index 0000000..0e6a697 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java @@ -0,0 +1,99 @@ +package com.zt.plat.module.bpm.framework.flowable.core.enums; + +import org.flowable.engine.runtime.ProcessInstance; + +/** + * BPM Variable 通用常量 + * + * @author ZT + */ +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() + * @see BpmTaskCandidateStrategyEnum#START_USER_SELECT + */ + public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; + /** + * 流程实例的变量 - 审批人选择的审批人 Map + * + * @see ProcessInstance#getProcessVariables() + * @see BpmTaskCandidateStrategyEnum#APPROVE_USER_SELECT + */ + public static final String PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES = "PROCESS_APPROVE_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 ProcessInstance#getProcessVariables() + * @see Flowable/Activiti之SkipExpression 完成自动审批 + */ + public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; + + /** + * 流程实例的变量 - 用于判断流程是否需要跳过发起人节点 + * + * @see ProcessInstance#getProcessVariables() + */ + public static final String PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE = "PROCESS_SKIP_START_USER_NODE"; + + /** + * 流程实例的变量 - 流程开始时间 + * + * 【非存储变量】用于部分需要 format 的场景,例如说:流程实例的自定义标题 + */ + public static final String PROCESS_START_TIME = "PROCESS_START_TIME"; + /** + * 流程实例的变量 - 流程定义名称 + */ + public static final String PROCESS_DEFINITION_NAME = "PROCESS_DEFINITION_NAME"; + + /** + * 任务的变量 - 状态 + * + * @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"; + /** + * 任务变量 - 签名图片 URL + */ + public static final String TASK_SIGN_PIC_URL = "TASK_SIGN_PIC_URL"; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/event/BpmProcessInstanceEventPublisher.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/event/BpmProcessInstanceEventPublisher.java new file mode 100644 index 0000000..5ec3654 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/event/BpmProcessInstanceEventPublisher.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.bpm.framework.flowable.core.event; + +import com.zt.plat.module.bpm.api.event.BpmProcessInstanceStatusEvent; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.validation.annotation.Validated; + +/** + * {@link BpmProcessInstanceStatusEvent} 的生产者 + * + * @author ZT + */ +@AllArgsConstructor +@Validated +public class BpmProcessInstanceEventPublisher { + + private final ApplicationEventPublisher publisher; + + public void sendProcessInstanceResultEvent(@Valid BpmProcessInstanceStatusEvent event) { + publisher.publishEvent(event); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java new file mode 100644 index 0000000..a821f05 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java @@ -0,0 +1,47 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceCopyService; +import jakarta.annotation.Resource; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Component; + +import java.util.Set; + +import static com.zt.plat.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate.BEAN_NAME; + +/** + * 处理抄送用户的 {@link JavaDelegate} 的实现类 + *

+ * 目前只有仿钉钉/飞书模式的【抄送节点】使用 + * + * @author jason + */ +@Component(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 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); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java new file mode 100644 index 0000000..7c4c0e9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java @@ -0,0 +1,54 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener; + +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.google.common.collect.ImmutableSet; +import jakarta.annotation.Resource; +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.FlowableCancelledEvent; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.Set; + +/** + * 监听 {@link ProcessInstance} 的状态变更,更新其对应的 status 状态 + * + * @author jason + */ +@Component +public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener { + + public static final Set PROCESS_INSTANCE_EVENTS = ImmutableSet.builder() + .add(FlowableEngineEventType.PROCESS_CREATED) + .add(FlowableEngineEventType.PROCESS_COMPLETED) + .add(FlowableEngineEventType.PROCESS_CANCELLED) + .build(); + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessInstanceService processInstanceService; + + public BpmProcessInstanceEventListener(){ + super(PROCESS_INSTANCE_EVENTS); + } + + @Override + protected void processCreated(FlowableEngineEntityEvent event) { + processInstanceService.processProcessInstanceCreated((ProcessInstance)event.getEntity()); + } + + @Override + protected void processCompleted(FlowableEngineEntityEvent event) { + processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity()); + } + + @Override // 特殊情况:当跳转到 EndEvent 流程实例未结束, 会执行 deleteProcessInstance 方法 + protected void processCancelled(FlowableCancelledEvent event) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(event.getProcessInstanceId()); + processInstanceService.processProcessInstanceCompleted(processInstance); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java new file mode 100644 index 0000000..eda5244 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -0,0 +1,125 @@ +package com.zt.plat.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.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.module.bpm.enums.definition.BpmBoundaryEventTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.service.definition.BpmModelService; +import com.zt.plat.module.bpm.service.task.BpmTaskService; +import com.google.common.collect.ImmutableSet; +import jakarta.annotation.Resource; +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; + +import java.util.List; +import java.util.Set; + +/** + * 监听 {@link Task} 的开始与完成 + * + * @author jason + */ +@Component +@Slf4j +public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmModelService modelService; + @Resource + @Lazy // 解决循环依赖 + private BpmTaskService taskService; + + public static final Set TASK_EVENTS = ImmutableSet.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() { + super(TASK_EVENTS); + } + + @Override + protected void taskCreated(FlowableEngineEntityEvent event) { + taskService.processTaskCreated((Task) event.getEntity()); + } + + @Override + protected void taskAssigned(FlowableEngineEntityEvent event) { + taskService.processTaskAssigned((Task) event.getEntity()); + } + + @Override + protected void taskCompleted(FlowableEngineEntityEvent event) { + taskService.processTaskCompleted((Task) event.getEntity()); + } + + @Override + protected void activityCancelled(FlowableActivityCancelledEvent event) { + List activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId()); + if (CollUtil.isEmpty(activityList)) { + log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); + return; + } + // 遍历处理 + activityList.forEach(activity -> { + if (StrUtil.isEmpty(activity.getTaskId())) { + return; + } + 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); + BpmBoundaryEventTypeEnum bpmTimerBoundaryEventType = BpmBoundaryEventTypeEnum.typeOf(NumberUtils.parseInt(boundaryEventType)); + + // 2. 处理超时 + if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.USER_TASK_TIMEOUT)) { + // 2.1 用户任务超时处理 + String timeoutHandlerType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, + BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_TYPE); + String taskKey = boundaryEvent.getAttachedToRefId(); + taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutHandlerType)); + // 2.2 延迟器超时处理 + } else if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT)) { + String taskKey = boundaryEvent.getAttachedToRefId(); + taskService.triggerTask(event.getProcessInstanceId(), taskKey); + // 2.3 子流程超时处理 + } else if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.CHILD_PROCESS_TIMEOUT)) { + String taskKey = boundaryEvent.getAttachedToRefId(); + taskService.processChildProcessTimeout(event.getProcessInstanceId(), taskKey); + } + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTriggerTaskDelegate.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTriggerTaskDelegate.java new file mode 100644 index 0000000..f88ce20 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTriggerTaskDelegate.java @@ -0,0 +1,55 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener; + +import com.zt.plat.module.bpm.enums.definition.BpmTriggerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.service.task.trigger.BpmTrigger; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Component; + +import java.util.EnumMap; +import java.util.List; + +import static com.zt.plat.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate.BEAN_NAME; + + +/** + * 处理触发器任务 {@link JavaDelegate} 的实现类 + *

+ * 目前只有 Simple 设计器【触发器节点】使用 + * + * @author jason + */ +@Component(BEAN_NAME) +@Slf4j +public class BpmTriggerTaskDelegate implements JavaDelegate { + + public static final String BEAN_NAME = "bpmTriggerTaskDelegate"; + + @Resource + private List triggers; + + private final EnumMap triggerMap = new EnumMap<>(BpmTriggerTypeEnum.class); + + @PostConstruct + private void init() { + triggers.forEach(trigger -> triggerMap.put(trigger.getType(), trigger)); + } + + @Override + public void execute(DelegateExecution execution) { + FlowElement flowElement = execution.getCurrentFlowElement(); + BpmTriggerTypeEnum bpmTriggerType = BpmnModelUtils.parserTriggerType(flowElement); + BpmTrigger bpmTrigger = triggerMap.get(bpmTriggerType); + if (bpmTrigger == null) { + log.error("[execute][FlowElement({}), {} 找不到匹配的触发器]", execution.getCurrentActivityId(), flowElement); + return; + } + bpmTrigger.execute(execution.getProcessInstanceId(), BpmnModelUtils.parserTriggerParam(flowElement)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoDelegateClassExecutionListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoDelegateClassExecutionListener.java new file mode 100644 index 0000000..84763fe --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoDelegateClassExecutionListener.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener.demo.exection; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; + +/** + * 类型为 class 的 ExecutionListener 监听器示例 + * + * @author ZT + */ +@Slf4j +public class DemoDelegateClassExecutionListener implements JavaDelegate { + + @Override + public void execute(DelegateExecution execution) { + log.info("[execute][execution({}) 被调用!变量有:{}]", execution.getId(), + execution.getCurrentFlowableListener().getFieldExtensions()); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoDelegateExpressionExecutionListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoDelegateExpressionExecutionListener.java new file mode 100644 index 0000000..64dba65 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoDelegateExpressionExecutionListener.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener.demo.exection; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Component; + +/** + * 类型为 delegateExpression 的 ExecutionListener 监听器示例 + * + * 和 {@link DemoDelegateClassExecutionListener} 的差异是,需要注册到 Spring 中 + */ +@Component +@Slf4j +public class DemoDelegateExpressionExecutionListener implements JavaDelegate { + + @Override + public void execute(DelegateExecution execution) { + log.info("[execute][execution({}) 被调用!变量有:{}]", execution.getId(), + execution.getCurrentFlowableListener().getFieldExtensions()); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoSpringExpressionExecutionListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoSpringExpressionExecutionListener.java new file mode 100644 index 0000000..c6e5b99 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/exection/DemoSpringExpressionExecutionListener.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener.demo.exection; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +/** + * 类型为 expression 的 ExecutionListener 监听器示例 + * + * 和 {@link DemoDelegateClassExecutionListener} 的差异是,需要注册到 Spring 中,但不用实现 {@link org.flowable.engine.delegate.JavaDelegate} 接口 + */ +@Component +@Slf4j +public class DemoSpringExpressionExecutionListener { + + public void execute(DelegateExecution execution) { + log.info("[execute][execution({}) 被调用!变量有:{}]", execution.getId(), + execution.getCurrentFlowableListener().getFieldExtensions()); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoDelegateClassTaskListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoDelegateClassTaskListener.java new file mode 100644 index 0000000..6b4e94e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoDelegateClassTaskListener.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener.demo.task; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; + +/** + * 类型为 class 的 TaskListener 监听器示例 + * + * @author ZT + */ +@Slf4j +public class DemoDelegateClassTaskListener implements TaskListener { + + @Override + public void notify(DelegateTask delegateTask) { + log.info("[execute][task({}) 被调用]", delegateTask.getId()); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoDelegateExpressionTaskListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoDelegateExpressionTaskListener.java new file mode 100644 index 0000000..41fa05b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoDelegateExpressionTaskListener.java @@ -0,0 +1,22 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener.demo.task; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.stereotype.Component; + +/** + * 类型为 delegateExpression 的 TaskListener 监听器示例 + * + * @author ZT + */ +@Component +@Slf4j +public class DemoDelegateExpressionTaskListener implements TaskListener { + + @Override + public void notify(DelegateTask delegateTask) { + log.info("[execute][task({}) 被调用]", delegateTask.getId()); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoSpringExpressionTaskListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoSpringExpressionTaskListener.java new file mode 100644 index 0000000..b9a4ccf --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/demo/task/DemoSpringExpressionTaskListener.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.bpm.framework.flowable.core.listener.demo.task; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.stereotype.Component; + +/** + * 类型为 expression 的 TaskListener 监听器示例 + * + * @author ZT + */ +@Slf4j +@Component +public class DemoSpringExpressionTaskListener { + + public void notify(DelegateTask delegateTask) { + log.info("[execute][task({}) 被调用]", delegateTask.getId()); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java new file mode 100644 index 0000000..de5b2a4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java @@ -0,0 +1,158 @@ +package com.zt.plat.module.bpm.framework.flowable.core.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.common.util.spring.SpringUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR; + +/** + * 工作流发起 HTTP 请求工具类 + * + * @author ZT + */ +@Slf4j +public class BpmHttpRequestUtils { + + public static void executeBpmHttpRequest(ProcessInstance processInstance, + String url, + List headerParams, + List bodyParams, + Boolean handleResponse, + List> response) { + RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class); + BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class); + + // 1.1 设置请求头 + MultiValueMap headers = buildHttpHeaders(processInstance, headerParams); + // 1.2 设置请求体 + MultiValueMap body = buildHttpBody(processInstance, bodyParams); + + // 2. 发起请求 + ResponseEntity responseEntity = sendHttpRequest(url, headers, body, restTemplate); + + // 3. 处理返回 + if (Boolean.FALSE.equals(handleResponse)) { + return; + } + // 3.1 判断是否需要解析返回值 + if (responseEntity == null + || StrUtil.isEmpty(responseEntity.getBody()) + || !responseEntity.getStatusCode().is2xxSuccessful() + || CollUtil.isEmpty(response)) { + return; + } + // 3.2 解析返回值, 返回值必须符合 CommonResult 规范。 + CommonResult> respResult = JsonUtils.parseObjectQuietly(responseEntity.getBody(), + new TypeReference<>() {}); + if (respResult == null || !respResult.isSuccess()) { + return; + } + // 3.3 获取需要更新的流程变量 + Map updateVariables = getNeedUpdatedVariablesFromResponse(respResult.getData(), response); + // 3.4 更新流程变量 + if (CollUtil.isNotEmpty(updateVariables)) { + processInstanceService.updateProcessInstanceVariables(processInstance.getId(), updateVariables); + } + } + + public static ResponseEntity sendHttpRequest(String url, + MultiValueMap headers, + MultiValueMap body, + RestTemplate restTemplate) { + HttpEntity> requestEntity = new HttpEntity<>(body, headers); + ResponseEntity responseEntity; + try { + responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + log.info("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity); + } catch (RestClientException e) { + log.error("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage()); + throw exception(PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR); + } + return responseEntity; + } + + public static MultiValueMap buildHttpHeaders(ProcessInstance processInstance, + List headerSettings) { + Map processVariables = processInstance.getProcessVariables(); + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add(HEADER_TENANT_ID, processInstance.getTenantId()); + addHttpRequestParam(headers, headerSettings, processVariables); + return headers; + } + + public static MultiValueMap buildHttpBody(ProcessInstance processInstance, + List bodySettings) { + Map processVariables = processInstance.getProcessVariables(); + MultiValueMap body = new LinkedMultiValueMap<>(); + addHttpRequestParam(body, bodySettings, processVariables); + body.add("processInstanceId", processInstance.getId()); + return body; + } + + /** + * 从请求返回值获取需要更新的流程变量 + * + * @param result 请求返回结果 + * @param responseSettings 返回设置 + * @return 需要更新的流程变量 + */ + public static Map getNeedUpdatedVariablesFromResponse(Map result, + List> responseSettings) { + Map updateVariables = new HashMap<>(); + if (CollUtil.isEmpty(result)) { + return updateVariables; + } + responseSettings.forEach(responseSetting -> { + if (StrUtil.isNotEmpty(responseSetting.getKey()) && result.containsKey(responseSetting.getValue())) { + updateVariables.put(responseSetting.getKey(), result.get(responseSetting.getValue())); + } + }); + return updateVariables; + } + + /** + * 添加 HTTP 请求参数。请求头或者请求体 + * + * @param params HTTP 请求参数 + * @param paramSettings HTTP 请求参数设置 + * @param processVariables 流程变量 + */ + public static void addHttpRequestParam(MultiValueMap params, + List paramSettings, + Map processVariables) { + if (CollUtil.isEmpty(paramSettings)) { + return; + } + paramSettings.forEach(item -> { + if (item.getType().equals(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())) { + params.add(item.getKey(), item.getValue()); + } else if (item.getType().equals(BpmHttpRequestParamTypeEnum.FROM_FORM.getType())) { + params.add(item.getKey(), processVariables.get(item.getValue()).toString()); + } + }); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/BpmnModelUtils.java new file mode 100644 index 0000000..1be0cd5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -0,0 +1,1025 @@ +package com.zt.plat.module.bpm.framework.flowable.core.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Assert; +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.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.framework.common.util.string.StrUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import com.zt.plat.module.bpm.enums.definition.*; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import com.google.common.collect.Maps; +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 org.flowable.engine.impl.el.FixedValue; + +import java.util.*; + +import static com.zt.plat.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 ZT + */ +@Slf4j +public class BpmnModelUtils { + + // ========== 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 void addExtensionElement(FlowElement element, String name, Integer value) { + if (value == null) { + return; + } + addExtensionElement(element, name, String.valueOf(value)); + } + + public static void addExtensionElementJson(FlowElement element, String name, Object value) { + if (value == null) { + return; + } + addExtensionElement(element, name, JsonUtils.toJsonString(value)); + } + + public static void addExtensionElement(FlowElement element, String name, Map 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); + 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)); + } + + /** + * 解析子流程多实例来源类型 + * + * @see BpmChildProcessMultiInstanceSourceTypeEnum + * @param element 任务节点 + * @return 多实例来源类型 + */ + public static Integer parseMultiInstanceSourceType(FlowElement element) { + return NumberUtils.parseInt(parseExtensionElement(element, BpmnModelConstants.CHILD_PROCESS_MULTI_INSTANCE_SOURCE_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 BpmUserTaskRejectHandlerTypeEnum parseRejectHandlerType(FlowElement userTask) { + Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); + return BpmUserTaskRejectHandlerTypeEnum.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 parseAssignEmptyHandlerUserIds(FlowElement userTask) { + return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ","); + } + + /** + * 给节点添加表单字段权限元素 + * + * @param fieldsPermissions 表单字段权限 + * @param flowElement 节点 + */ + public static void addFormFieldsPermission(List> 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 parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { + if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) { + return null; + } + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); + if (flowElement == null) { + return null; + } + List extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT); + if (CollUtil.isEmpty(extensionElements)) { + return null; + } + Map fieldsPermission = MapUtil.newHashMap(); + extensionElements.forEach(element -> { + String field = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE); + String permission = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE); + if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) { + fieldsPermission.put(field, permission); + } + }); + return fieldsPermission; + } + + /** + * 给节点添加操作按钮设置元素 + */ + public static void addButtonsSetting(List buttonsSetting, UserTask userTask) { + if (CollUtil.isNotEmpty(buttonsSetting)) { + List> list = CollectionUtils.convertList(buttonsSetting, item -> { + Map 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 parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) { + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); + if (flowElement == null) { + return null; + } + List extensionElements = flowElement.getExtensionElements().get(BUTTON_SETTING_ELEMENT); + if (CollUtil.isEmpty(extensionElements)) { + return null; + } + Map buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size()); + extensionElements.forEach(element -> { + String id = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE); + String displayName = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE); + String enable = element.getAttributeValue(null, 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); + } + + public static void addSignEnable(Boolean signEnable, FlowElement userTask) { + addExtensionElement(userTask, SIGN_ENABLE, + ObjUtil.isNotNull(signEnable) ? signEnable.toString() : Boolean.FALSE.toString()); + } + + public static Boolean parseSignEnable(BpmnModel bpmnModel, String flowElementId) { + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); + if (flowElement == null) { + return false; + } + List extensionElements = flowElement.getExtensionElements().get(SIGN_ENABLE); + if (CollUtil.isEmpty(extensionElements)) { + return false; + } + return Convert.toBool(extensionElements.get(0).getElementText(), false); + } + + public static void addReasonRequire(Boolean reasonRequire, FlowElement userTask) { + addExtensionElement(userTask, REASON_REQUIRE, + ObjUtil.isNotNull(reasonRequire) ? reasonRequire.toString() : Boolean.FALSE.toString()); + } + + public static Boolean parseReasonRequire(BpmnModel bpmnModel, String flowElementId) { + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); + if (flowElement == null) { + return false; + } + List extensionElements = flowElement.getExtensionElements().get(REASON_REQUIRE); + if (CollUtil.isEmpty(extensionElements)) { + return false; + } + return Convert.toBool(extensionElements.get(0).getElementText(), false); + } + + public static void addListenerConfig(FlowableListener flowableListener, BpmSimpleModelNodeVO.ListenerHandler handler) { + FieldExtension fieldExtension = new FieldExtension(); + fieldExtension.setFieldName("listenerConfig"); + fieldExtension.setStringValue(JsonUtils.toJsonString(handler)); + flowableListener.getFieldExtensions().add(fieldExtension); + } + + public static BpmSimpleModelNodeVO.ListenerHandler parseListenerConfig(FixedValue fixedValue) { + String expressionText = fixedValue.getExpressionText(); + Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText); + return JsonUtils.parseObject(expressionText, BpmSimpleModelNodeVO.ListenerHandler.class); + } + + public static BpmTriggerTypeEnum parserTriggerType(FlowElement flowElement) { + Integer triggerType = NumberUtils.parseInt(parseExtensionElement(flowElement, TRIGGER_TYPE)); + return BpmTriggerTypeEnum.typeOf(triggerType); + } + + public static String parserTriggerParam(FlowElement flowElement) { + return parseExtensionElement(flowElement, TRIGGER_PARAM); + } + + /** + * 给节点添加节点类型 + * + * @param nodeType 节点类型 + * @param flowElement 节点 + */ + public static void addNodeType(Integer nodeType, FlowElement flowElement) { + addExtensionElement(flowElement, BpmnModelConstants.NODE_TYPE, nodeType); + } + + /** + * 解析节点类型 + * + * @param flowElement 节点 + * @return 节点类型 + */ + public static Integer parseNodeType(FlowElement flowElement) { + return NumberUtils.parseInt(parseExtensionElement(flowElement, BpmnModelConstants.NODE_TYPE)); + } + + // ========== BPM 简单查找相关的方法 ========== + + /** + * 根据节点,获取入口连线 + * + * @param source 起始节点 + * @return 入口连线列表 + */ + public static List getElementIncomingFlows(FlowElement source) { + if (source instanceof FlowNode) { + return ((FlowNode) source).getIncomingFlows(); + } + return new ArrayList<>(); + } + + /** + * 根据节点,获取出口连线 + * + * @param source 起始节点 + * @return 出口连线列表 + */ + public static List getElementOutgoingFlows(FlowElement source) { + if (source instanceof FlowNode) { + return ((FlowNode) source).getOutgoingFlows(); + } + return new ArrayList<>(); + } + + /** + * 获取流程元素信息 + * + * @param model bpmnModel 对象 + * @param flowElementId 元素 ID + * @return 元素信息 + */ + public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) { + Process process = model.getMainProcess(); + return process.getFlowElement(flowElementId); + } + + /** + * 获得 BPMN 流程中,指定的元素们 + * + * @param model 模型 + * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等 + * @return 元素们 + */ + @SuppressWarnings("unchecked") + public static List getBpmnModelElements(BpmnModel model, Class clazz) { + List result = new ArrayList<>(); + model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> { + if (flowElement.getClass().isAssignableFrom(clazz)) { + result.add((T) flowElement); + } + })); + return result; + } + + public static StartEvent getStartEvent(BpmnModel model) { + Process process = model.getMainProcess(); + // 从 initialFlowElement 找 + FlowElement startElement = process.getInitialFlowElement(); + if (startElement instanceof StartEvent) { + return (StartEvent) startElement; + } + // 从 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) { + if (ArrayUtil.isEmpty(bpmnBytes)) { + return null; + } + BpmnXMLConverter converter = new BpmnXMLConverter(); + // 补充说明:由于在 Flowable 中自定义了属性,所以 validateSchema 传递 false + return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), false, false); + } + + public static String getBpmnXml(BpmnModel model) { + if (model == null) { + return null; + } + BpmnXMLConverter converter = new BpmnXMLConverter(); + return StrUtil.utf8Str(converter.convertToXML(model)); + } + + public static String getBpmnXml(byte[] bpmnBytes) { + if (ArrayUtil.isEmpty(bpmnBytes)) { + return null; + } + return StrUtil.utf8Str(bpmnBytes); + } + + // ========== BPMN 复杂遍历相关的方法 ========== + + /** + * 找到 source 节点之前的所有用户任务节点 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 已找到的用户任务节点 + * @return 用户任务节点 数组 + */ + public static List getPreviousUserTaskList(FlowElement source, Set hasSequenceFlow, List userTaskList) { + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + if (sequenceFlows == null) { + return userTaskList; + } + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 类型为用户节点,则新增父级节点 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); + } + // 类型为子流程,则添加子流程开始节点出口处相连的节点 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + // 获取子流程用户任务节点 + List childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (CollUtil.isNotEmpty(childUserTaskList)) { + userTaskList.addAll(childUserTaskList); + } + } + // 继续迭代 + userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList); + } + return userTaskList; + } + + /** + * 迭代获取子流程用户任务节点 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return 用户任务节点 + */ + public static List findChildProcessUserTaskList(FlowElement source, Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + if (sequenceFlows == null) { + return userTaskList; + } + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (CollUtil.isNotEmpty(childUserTaskList)) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList); + } + return userTaskList; + } + + /** + * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 + * 不存在直接退回到子流程中的情况,但存在从子流程出去到父流程情况 + * + * @param source 起始节点 + * @param target 目标节点 + * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 + * @return 结果 + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set visitedElements) { + visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; + // 不能是开始事件和子流程 + if (source instanceof StartEvent && isInEventSubprocess(source)) { + return false; + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + if (CollUtil.isEmpty(sequenceFlows)) { + return true; + } + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (visitedElements.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + visitedElements.add(sequenceFlow.getId()); + // 这条线路存在目标节点,这条线路完成,进入下个线路 + FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); + if (target.getId().equals(sourceFlowElement.getId())) { + continue; + } + // 如果目标节点为并行网关,则不继续 + if (sourceFlowElement instanceof ParallelGateway) { + return false; + } + // 否则就继续迭代 + if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) { + return false; + } + } + return true; + } + + /** + * 判断当前节点是否属于不同的子流程 + * + * @param flowElement 被判断的节点 + * @return true 表示属于子流程 + */ + private static boolean isInEventSubprocess(FlowElement flowElement) { + FlowElementsContainer flowElementsContainer = flowElement.getParentContainer(); + while (flowElementsContainer != null) { + if (flowElementsContainer instanceof EventSubProcess) { + return true; + } + + if (flowElementsContainer instanceof FlowElement) { + flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer(); + } else { + flowElementsContainer = null; + } + } + return false; + } + + /** + * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 + * + * @param source 起始节点 + * @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return 子级任务节点列表 + */ + public static List iteratorFindChildUserTasks(FlowElement source, List runTaskKeyList, + Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList); + } + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + if (sequenceFlows == null) { + return userTaskList; + } + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (CollUtil.isNotEmpty(childUserTaskList)) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList); + } + return userTaskList; + } + + // ========== BPMN 流程预测相关的方法 ========== + + /** + * 流程预测,返回 StartEvent、UserTask、ServiceTask、EndEvent 节点元素,最终是 List 串行结果 + * + * @param bpmnModel BPMN 图 + * @param variables 变量 + * @return 节点元素数组 + */ + public static List simulateProcess(BpmnModel bpmnModel, Map variables) { + List resultElements = new ArrayList<>(); + Set visitElements = new HashSet<>(); + + // 从 StartEvent 开始遍历 + StartEvent startEvent = getStartEvent(bpmnModel); + simulateNextFlowElements(startEvent, variables, resultElements, visitElements); + + // 将 EndEvent 放在末尾。原因是,DFS 遍历,可能 EndEvent 在 resultElements 中 + List endEvents = CollUtil.removeWithAddIf(resultElements, + flowElement -> flowElement instanceof EndEvent); + resultElements.addAll(endEvents); + return resultElements; + } + + @SuppressWarnings("PatternVariableCanBeUsed") + private static void simulateNextFlowElements(FlowElement currentElement, Map variables, + List resultElements, Set 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 路径 + SequenceFlow matchSequenceFlow = findMatchSequenceFlowByExclusiveGateway((Gateway) currentElement, variables); + // 遍历满足条件的 SequenceFlow 路径 + if (matchSequenceFlow != null) { + simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements); + } + } + // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的 + else if (currentElement instanceof InclusiveGateway) { + // 查找满足条件的 SequenceFlow 路径 + Collection matchSequenceFlows = findMatchSequenceFlowsByInclusiveGateway((Gateway) currentElement, variables); + // 遍历满足条件的 SequenceFlow 路径 + matchSequenceFlows.forEach( + flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements)); + } + // 情况:ParallelGateway 并行,都满足,都走 + else if (currentElement instanceof ParallelGateway) { + Gateway gateway = (Gateway) currentElement; + // 遍历子节点 + gateway.getOutgoingFlows().forEach( + nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); + } + } + + /** + * 根据当前节点,获取下一个节点 + * + * @param currentElement 当前节点 + * @param bpmnModel BPMN模型 + * @param variables 流程变量 + */ + @SuppressWarnings("PatternVariableCanBeUsed") + public static List getNextFlowNodes(FlowElement currentElement, BpmnModel bpmnModel, + Map variables){ + List nextFlowNodes = new ArrayList<>(); // 下一个执行的流程节点集合 + FlowNode currentNode = (FlowNode) currentElement; // 当前执行节点的基本属性 + List outgoingFlows = currentNode.getOutgoingFlows(); // 当前节点的关联节点 + if (CollUtil.isEmpty(outgoingFlows)) { + log.warn("[getNextFlowNodes][当前节点({}) 的 outgoingFlows 为空]", currentNode.getId()); + return nextFlowNodes; + } + + // 遍历每个出口流 + for (SequenceFlow outgoingFlow : outgoingFlows) { + // 获取目标节点的基本属性 + FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef()); + if (targetElement == null) { + continue; + } + // 如果是结束节点,直接返回 + if (targetElement instanceof EndEvent) { + break; + } + // 情况一:处理不同类型的网关 + if (targetElement instanceof Gateway) { + Gateway gateway = (Gateway) targetElement; + if (gateway instanceof ExclusiveGateway) { + handleExclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes); + } else if (gateway instanceof InclusiveGateway) { + handleInclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes); + } else if (gateway instanceof ParallelGateway) { + handleParallelGateway(gateway, bpmnModel, variables, nextFlowNodes); + } + } else { + // 情况二:如果不是网关,直接添加到下一个节点列表 + nextFlowNodes.add((FlowNode) targetElement); + } + } + return nextFlowNodes; + } + + /** + * 处理排它网关 + * + * @param gateway 排他网关 + * @param bpmnModel BPMN模型 + * @param variables 流程变量 + * @param nextFlowNodes 下一个执行的流程节点集合 + */ + private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel, + Map variables, List nextFlowNodes) { + // 查找满足条件的 SequenceFlow 路径 + SequenceFlow matchSequenceFlow = findMatchSequenceFlowByExclusiveGateway(gateway, variables); + // 遍历满足条件的 SequenceFlow 路径 + if (matchSequenceFlow != null) { + FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef()); + if (targetElement instanceof FlowNode) { + nextFlowNodes.add((FlowNode) targetElement); + } + } + } + + /** + * 处理排它网关(Exclusive Gateway),选择符合条件的路径 + * + * @param gateway 排他网关 + * @param variables 流程变量 + * @return 符合条件的路径 + */ + private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map variables) { + 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.equal(gateway.getDefaultFlow(), flow.getId())); + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的 + if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) { + matchSequenceFlow = gateway.getOutgoingFlows().get(0); + } + } + return matchSequenceFlow; + } + + /** + * 处理包容网关 + * + * @param gateway 排他网关 + * @param bpmnModel BPMN模型 + * @param variables 流程变量 + * @param nextFlowNodes 下一个执行的流程节点集合 + */ + private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel, + Map variables, List nextFlowNodes) { + // 查找满足条件的 SequenceFlow 路径集合 + Collection matchSequenceFlows = findMatchSequenceFlowsByInclusiveGateway(gateway, variables); + // 遍历满足条件的 SequenceFlow 路径,获取目标节点 + matchSequenceFlows.forEach(flow -> { + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef()); + if (targetElement instanceof FlowNode) { + nextFlowNodes.add((FlowNode) targetElement); + } + }); + } + + /** + * 处理排它网关(Inclusive Gateway),选择符合条件的路径 + * + * @param gateway 排他网关 + * @param variables 流程变量 + * @return 符合条件的路径 + */ + private static Collection findMatchSequenceFlowsByInclusiveGateway(Gateway gateway, Map variables) { + // 查找满足条件的 SequenceFlow 路径 + Collection 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.equal(gateway.getDefaultFlow(), flow.getId())); + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的 + if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) { + matchSequenceFlows = gateway.getOutgoingFlows(); + } + } + return matchSequenceFlows; + } + + + /** + * 处理并行网关 + * + * @param gateway 排他网关 + * @param bpmnModel BPMN模型 + * @param variables 流程变量 + * @param nextFlowNodes 下一个执行的流程节点集合 + */ + private static void handleParallelGateway(Gateway gateway, BpmnModel bpmnModel, + Map variables, List nextFlowNodes) { + // 并行网关,遍历所有出口路径,获取目标节点 + gateway.getOutgoingFlows().forEach(flow -> { + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef()); + if (targetElement instanceof FlowNode) { + nextFlowNodes.add((FlowNode) targetElement); + } + }); + } + + /** + * 计算条件表达式是否为 true 满足条件 + * + * @param variables 流程实例 + * @param expression 条件表达式 + * @return 是否满足条件 + */ + public static boolean evalConditionExpress(Map variables, String expression) { + if (expression == null) { + return Boolean.FALSE; + } + // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables + if (variables == null) { + variables = new HashMap<>(); + } + + // 执行计算 + try { + Object result = FlowableUtils.getExpressionValue(variables, expression); + return Boolean.TRUE.equals(result); + } catch (FlowableException ex) { + // 为什么使用 info 日志?原因是,expression 如果从 variables 取不到值,会报错。实际这种情况下,可以忽略 + log.info("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", expression, 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(); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/FlowableUtils.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/FlowableUtils.java new file mode 100644 index 0000000..7d54ce5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/FlowableUtils.java @@ -0,0 +1,362 @@ +package com.zt.plat.module.bpm.framework.flowable.core.util; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.tenant.core.context.TenantContextHolder; +import com.zt.plat.framework.tenant.core.util.TenantUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormFieldVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.enums.definition.BpmModelFormTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import lombok.SneakyThrows; +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; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.TaskInfo; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; + +/** + * Flowable 相关的工具方法 + * + * @author ZT + */ +public class FlowableUtils { + + // ========== User 相关的工具方法 ========== + + public static void setAuthenticatedUserId(Long userId) { + Authentication.setAuthenticatedUserId(String.valueOf(userId)); + } + + public static void clearAuthenticatedUserId() { + Authentication.setAuthenticatedUserId(null); + } + + public static V executeAuthenticatedUserId(Long userId, Callable callable) { + setAuthenticatedUserId(userId); + try { + return callable.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + clearAuthenticatedUserId(); + } + } + + 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); + } + } + + @SneakyThrows + public static V execute(String tenantIdStr, Callable callable) { + if (ObjectUtil.isEmpty(tenantIdStr) + || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) { + return callable.call(); + } else { + Long tenantId = Long.valueOf(tenantIdStr); + return TenantUtils.execute(tenantId, callable); + } + } + + // ========== Execution 相关的工具方法 ========== + + /** + * 格式化多实例(并签、或签)的 collectionVariable 变量(多实例对应的多审批人列表) + * + * @param activityId 活动编号 + * @return collectionVariable 变量 + */ + public static String formatExecutionCollectionVariable(String activityId) { + return activityId + "_assignees"; + } + + /** + * 格式化多实例(并签、或签)的 collectionElementVariable 变量(当前实例对应的一个审批人) + * + * @param activityId 活动编号 + * @return collectionElementVariable 变量 + */ + public static String formatExecutionCollectionElementVariable(String activityId) { + return activityId + "_assignee"; + } + + // ========== ProcessInstance 相关的工具方法 ========== + + public static Integer getProcessInstanceStatus(ProcessInstance processInstance) { + return getProcessInstanceStatus(processInstance.getProcessVariables()); + } + + public static Integer getProcessInstanceStatus(HistoricProcessInstance processInstance) { + return getProcessInstanceStatus(processInstance.getProcessVariables()); + } + + /** + * 获得流程实例的状态 + * + * @param processVariables 流程实例的 variables + * @return 状态 + */ + private static Integer getProcessInstanceStatus(Map processVariables) { + 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 getProcessInstanceFormVariable(ProcessInstance processInstance) { + Map processVariables = new HashMap<>(processInstance.getProcessVariables()); + return filterProcessInstanceFormVariable(processVariables); + } + + /** + * 获得流程实例的表单 + * + * @param processInstance 流程实例 + * @return 表单 + */ + public static Map getProcessInstanceFormVariable(HistoricProcessInstance processInstance) { + Map processVariables = new HashMap<>(processInstance.getProcessVariables()); + return filterProcessInstanceFormVariable(processVariables); + } + + /** + * 过滤流程实例的表单 + * + * 为什么要过滤?目前使用 processVariables 存储所有流程实例的拓展字段,需要过滤掉一部分的系统字段,从而实现表单的展示 + * + * @param processVariables 流程实例的 variables + * @return 过滤后的表单 + */ + public static Map filterProcessInstanceFormVariable(Map processVariables) { + processVariables.remove(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); + return processVariables; + } + + /** + * 获得流程实例的发起用户选择的审批人 Map + * + * @param processInstance 流程实例 + * @return 发起用户选择的审批人 Map + */ + public static Map> getStartUserSelectAssignees(ProcessInstance processInstance) { + return processInstance != null ? getStartUserSelectAssignees(processInstance.getProcessVariables()) : null; + } + + /** + * 获得流程实例的发起用户选择的审批人 Map + * + * @param processVariables 流程变量 + * @return 发起用户选择的审批人 Map + */ + @SuppressWarnings("unchecked") + public static Map> getStartUserSelectAssignees(Map processVariables) { + if (processVariables == null) { + return new HashMap<>(); + } + return (Map>) processVariables.get( + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); + } + + /** + * 获得流程实例的审批用户选择的下一个节点的审批人 Map + * + * @param processInstance 流程实例 + * @return 审批用户选择的下一个节点的审批人Map + */ + public static Map> getApproveUserSelectAssignees(ProcessInstance processInstance) { + return processInstance != null ? getApproveUserSelectAssignees(processInstance.getProcessVariables()) : null; + } + + /** + * 获得流程实例的审批用户选择的下一个节点的审批人 Map + * + * @param processVariables 流程变量 + * @return 审批用户选择的下一个节点的审批人Map Map + */ + @SuppressWarnings("unchecked") + public static Map> getApproveUserSelectAssignees(Map processVariables) { + if (processVariables == null) { + return new HashMap<>(); + } + return (Map>) processVariables.get( + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); + } + + /** + * 获得流程实例的摘要 + * + * 仅有 {@link BpmModelFormTypeEnum#getType()} 表单,才有摘要。 + * 原因是,只有它才有表单项的配置,从而可以根据配置,展示摘要。 + * + * @param processDefinitionInfo 流程定义 + * @param processVariables 流程实例的 variables + * @return 摘要 + */ + public static List> getSummary(BpmProcessDefinitionInfoDO processDefinitionInfo, + Map processVariables) { + // 只有流程表单才会显示摘要! + if (ObjectUtil.isNull(processDefinitionInfo) + || !BpmModelFormTypeEnum.NORMAL.getType().equals(processDefinitionInfo.getFormType())) { + return null; + } + + // 解析表单配置 + Map formFieldsMap = new HashMap<>(); + processDefinitionInfo.getFormFields().forEach(formFieldStr -> { + BpmFormFieldVO formField = JsonUtils.parseObject(formFieldStr, BpmFormFieldVO.class); + if (formField != null) { + formFieldsMap.put(formField.getField(), formField); + } + }); + + // 情况一:当自定义了摘要 + if (ObjectUtil.isNotNull(processDefinitionInfo.getSummarySetting()) + && Boolean.TRUE.equals(processDefinitionInfo.getSummarySetting().getEnable())) { + return convertList(processDefinitionInfo.getSummarySetting().getSummary(), item -> { + BpmFormFieldVO formField = formFieldsMap.get(item); + if (formField != null) { + return new KeyValue(formField.getTitle(), + processVariables.getOrDefault(item, "").toString()); + } + return null; + }); + } + + // 情况二:默认摘要展示前三个表单字段 + return formFieldsMap.entrySet().stream() + .limit(3) + .map(entry -> new KeyValue<>(entry.getValue().getTitle(), + MapUtil.getStr(processVariables, entry.getValue().getField(), ""))) + .collect(Collectors.toList()); + } + + // ========== Task 相关的工具方法 ========== + + /** + * 获得任务的状态 + * + * @param task 任务 + * @return 状态 + */ + public static Integer getTaskStatus(TaskInfo task) { + return (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); + } + + /** + * 获得任务的审批原因 + * + * @param task 任务 + * @return 审批原因 + */ + public static String getTaskReason(TaskInfo task) { + return (String) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_REASON); + } + + /** + * 获得任务的签名图片 URL + * + * @param task 任务 + * @return 签名图片 URL + */ + public static String getTaskSignPicUrl(TaskInfo task) { + return (String) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_SIGN_PIC_URL); + } + + /** + * 获得任务的表单 + * + * @param task 任务 + * @return 表单 + */ + public static Map getTaskFormVariable(TaskInfo task) { + Map formVariables = new HashMap<>(task.getTaskLocalVariables()); + filterTaskFormVariable(formVariables); + return formVariables; + } + + /** + * 过滤任务的表单 + * + * 为什么要过滤?目前使用 taskLocalVariables 存储所有任务的拓展字段,需要过滤掉一部分的系统字段,从而实现表单的展示 + * + * @param taskLocalVariables 任务的 taskLocalVariables + * @return 过滤后的表单 + */ + public static Map filterTaskFormVariable(Map taskLocalVariables) { + taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_STATUS); + taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_REASON); + return taskLocalVariables; + } + + // ========== Expression 相关的工具方法 ========== + + private static Object getExpressionValue(VariableContainer variableContainer, String expressionString, + ProcessEngineConfigurationImpl processEngineConfiguration) { + assert processEngineConfiguration != null; + ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager(); + 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 variable, String expressionString) { + VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty()); + return getExpressionValue(variableContainer, expressionString); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/SimpleModelUtils.java new file mode 100644 index 0000000..3f2408d --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -0,0 +1,1007 @@ +package com.zt.plat.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.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; +import com.zt.plat.module.bpm.enums.definition.*; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import com.zt.plat.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; +import com.zt.plat.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate; +import com.zt.plat.module.bpm.service.task.listener.BpmCallActivityListener; +import com.zt.plat.module.bpm.service.task.listener.BpmUserTaskListener; +import org.flowable.bpmn.BpmnAutoLayout; +import org.flowable.bpmn.constants.BpmnXMLConstants; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.*; +import org.flowable.engine.delegate.ExecutionListener; +import org.flowable.engine.delegate.TaskListener; + +import java.util.*; + +import static com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; +import static com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; +import static java.util.Arrays.asList; + +/** + * 仿钉钉/飞书的模型相关的工具方法 + *

+ * 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法 + * 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类 + * + * @author jason + */ +public class SimpleModelUtils { + + private static final Map NODE_CONVERTS = MapUtil.newHashMap(); + + static { + List converts = asList(new StartNodeConvert(), new EndNodeConvert(), + new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(), + new DelayTimerNodeConvert(), new TriggerNodeConvert(), + new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert(), + new ChildProcessConvert()); + converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); + } + + /** + * 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model + *

+ * 整体逻辑如下: + * 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 = 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(BpmSimpleModelNodeTypeEnum.START_NODE.getName()) + .setType(BpmSimpleModelNodeTypeEnum.START_NODE.getType()); + } + + /** + * 遍历节点,构建 FlowNode 元素 + * + * @param node SIMPLE 节点 + * @param process BPMN 流程 + */ + private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { + // 1. 判断是否有效节点 + if (!isValidNode(node)) { + return; + } + BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(node.getType()); + Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType()); + + // 2. 处理当前节点 + NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType); + Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType()); + List flowElements = nodeConvert.convertList(node); + flowElements.forEach(process::addFlowElement); + + // 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点 + if (BpmSimpleModelNodeTypeEnum.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 直接返回 + BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(node.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + if (nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { + return; + } + + // 2.1 情况一:普通节点 + if (!BpmSimpleModelNodeTypeEnum.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; + + // 如果没有附加节点:则直接建立连线 + if (StrUtil.isEmpty(node.getAttachNodeId())) { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId); + process.addFlowElement(sequenceFlow); + } else { + // 如果有附加节点:需要先建立和附加节点的连线,再建立附加节点和目标节点的连线。例如说,触发器节点(HTTP 回调) + List sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), finalTargetNodeId); + sequenceFlows.forEach(process::addFlowElement); + } + + // 因为有子节点,递归调用后续子节点 + if (isChildNodeValid) { + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); + } + } + + /** + * 构建有附加节点的连线 + * + * @param nodeId 当前节点 ID + * @param attachNodeId 附属节点 ID + * @param targetNodeId 目标节点 ID + */ + private static List buildAttachNodeSequenceFlow(String nodeId, String attachNodeId, String targetNodeId) { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(nodeId, attachNodeId, null, null, null); + SequenceFlow attachSequenceFlow = buildBpmnSequenceFlow(attachNodeId, targetNodeId, null, null, null); + return CollUtil.newArrayList(sequenceFlow, attachSequenceFlow); + } + + /** + * 遍历条件节点,构建 SequenceFlow 元素 + * + * @param process Bpmn 流程 + * @param node 当前节点 + * @param targetNodeId 目标节点 ID + */ + private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { + BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(node.getType()); + BpmSimpleModelNodeVO childNode = node.getChildNode(); + List conditionNodes = node.getConditionNodes(); + // TODO @芋艿 路由分支没有conditionNodes 这里注释会影响吗?@jason:一起帮忙瞅瞅! +// Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); + // 分支终点节点 ID + String branchEndNodeId = null; + if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE + || nodeType == BpmSimpleModelNodeTypeEnum.ROUTER_BRANCH_NODE) { // 条件分支或路由分支 + // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID + branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; + } else if (nodeType == BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE + || nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { // 并行分支或包容分支 + // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。 + branchEndNodeId = buildGatewayJoinId(node.getId()); + } + Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空"); + + // 3. 遍历分支节点 + if (nodeType == BpmSimpleModelNodeTypeEnum.ROUTER_BRANCH_NODE) { + // 路由分支遍历 + for (BpmSimpleModelNodeVO.RouterSetting router : node.getRouterGroups()) { + SequenceFlow sequenceFlow = RouteBranchNodeConvert.buildSequenceFlow(node.getId(), router); + process.addFlowElement(sequenceFlow); + } + } else { + // 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点 + for (BpmSimpleModelNodeVO item : conditionNodes) { + Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeTypeEnum.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.1 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线 + if (nodeType == BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE + || nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { + String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId); + process.addFlowElement(sequenceFlow); + // 4.2 如果是路由分支,需要连接后续节点为默认路由 + } else if (nodeType == BpmSimpleModelNodeTypeEnum.ROUTER_BRANCH_NODE) { + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), branchEndNodeId, node.getRouterDefaultFlowId(), + null, null); + 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 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType().equals(node.getType()) + && BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod()); + } + + // ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ========== + + private interface NodeConvert { + + default List convertList(BpmSimpleModelNodeVO node) { + return Collections.singletonList(convert(node)); + } + + default FlowElement convert(BpmSimpleModelNodeVO node) { + throw new UnsupportedOperationException("请实现该方法"); + } + + BpmSimpleModelNodeTypeEnum 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 BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.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 BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.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 BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.START_USER_NODE; + } + + } + + private static class ApproveNodeConvert implements NodeConvert { + + @Override + public List convertList(BpmSimpleModelNodeVO node) { + List 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 BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.APPROVE_NODE; + } + + /** + * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 + * + * @param userTask 审批任务 + * @param timeoutHandler 超时处理器 + * @return BoundaryEvent 超时事件 + */ + private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, + BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) { + // 1. 创建 Timeout Boundary Event + String timeCycle = null; + if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) && + timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { + timeCycle = String.format("R%d/%s", + timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()); + } + BoundaryEvent boundaryEvent = buildTimeoutBoundaryEvent(userTask, BpmBoundaryEventTypeEnum.USER_TASK_TIMEOUT.getType(), + timeoutHandler.getTimeDuration(), timeCycle, null); + + // 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()); + } + // 设置监听器 + addUserTaskListener(node, userTask); + // 添加是否需要签名 + addSignEnable(node.getSignEnable(), userTask); + // 审批意见 + addReasonRequire(node.getReasonRequire(), userTask); + // 节点类型 + addNodeType(node.getType(), userTask); + return userTask; + } + + private void addUserTaskListener(BpmSimpleModelNodeVO node, UserTask userTask) { + List flowableListeners = new ArrayList<>(3); + if (node.getTaskCreateListener() != null + && Boolean.TRUE.equals(node.getTaskCreateListener().getEnable())) { + FlowableListener flowableListener = new FlowableListener(); + flowableListener.setEvent(TaskListener.EVENTNAME_CREATE); + flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION); + addListenerConfig(flowableListener, node.getTaskCreateListener()); + flowableListeners.add(flowableListener); + } + if (node.getTaskAssignListener() != null + && Boolean.TRUE.equals(node.getTaskAssignListener().getEnable())) { + FlowableListener flowableListener = new FlowableListener(); + flowableListener.setEvent(TaskListener.EVENTNAME_ASSIGNMENT); + flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION); + addListenerConfig(flowableListener, node.getTaskAssignListener()); + flowableListeners.add(flowableListener); + } + if (node.getTaskCompleteListener() != null + && Boolean.TRUE.equals(node.getTaskCompleteListener().getEnable())) { + FlowableListener flowableListener = new FlowableListener(); + flowableListener.setEvent(TaskListener.EVENTNAME_COMPLETE); + flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION); + addListenerConfig(flowableListener, node.getTaskCompleteListener()); + flowableListeners.add(flowableListener); + } + if (CollUtil.isNotEmpty(flowableListeners)) { + userTask.setTaskListeners(flowableListeners); + } + } + + private void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { + BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); + Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum); + // 添加审批方式的扩展属性 + addExtensionElement(userTask, 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 TransactorNodeConvert extends ApproveNodeConvert { + + @Override + public BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE; + } + + } + + 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 BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.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.getConditionSetting().getDefaultFlow())); + Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId()); + exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); + return exclusiveGateway; + } + + @Override + public BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE; + } + + } + + private static class ParallelBranchNodeConvert implements NodeConvert { + + @Override + public List 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 BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE; + } + + } + + private static class InclusiveBranchNodeConvert implements NodeConvert { + + @Override + public List convertList(BpmSimpleModelNodeVO node) { + InclusiveGateway inclusiveGateway = new InclusiveGateway(); + inclusiveGateway.setId(node.getId()); + // 设置默认的序列流(条件) + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), + item -> BooleanUtil.isTrue(item.getConditionSetting().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 BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE; + } + + } + + public static class ConditionNodeConvert implements NodeConvert { + + @Override + public List convertList(BpmSimpleModelNodeVO node) { + // 原因是:正常情况下,它不会被调用到 + throw new UnsupportedOperationException("条件节点不支持转换"); + } + + @Override + public BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.CONDITION_NODE; + } + + public static SequenceFlow buildSequenceFlow(String sourceId, String targetId, + BpmSimpleModelNodeVO node) { + String conditionExpression = buildConditionExpression(node.getConditionSetting()); + return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression); + } + } + + /** + * 构造条件表达式 + */ + public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { + // 并行网关不需要设置条件 + if (conditionSetting == null) { + return null; + } + return buildConditionExpression(conditionSetting.getConditionType(), conditionSetting.getConditionExpression(), + conditionSetting.getConditionGroups()); + } + + public static String buildConditionExpression(BpmSimpleModelNodeVO.RouterSetting routerSetting) { + return buildConditionExpression(routerSetting.getConditionType(), routerSetting.getConditionExpression(), + routerSetting.getConditionGroups()); + } + + public static String buildConditionExpression(Integer conditionType, String conditionExpression, ConditionGroups conditionGroups) { + BpmSimpleModeConditionTypeEnum conditionTypeEnum = BpmSimpleModeConditionTypeEnum.valueOf(conditionType); + if (conditionTypeEnum == BpmSimpleModeConditionTypeEnum.EXPRESSION) { + return conditionExpression; + } + if (conditionTypeEnum == BpmSimpleModeConditionTypeEnum.RULE) { + if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) { + return null; + } + List strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> { + if (CollUtil.isEmpty(item.getRules())) { + return ""; + } + // 构造规则表达式 + List 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; + } + + public static class DelayTimerNodeConvert implements NodeConvert { + + @Override + public List convertList(BpmSimpleModelNodeVO node) { + List flowElements = new ArrayList<>(2); + // 1. 构建接收任务,通过接收任务可卡住节点 + ReceiveTask receiveTask = new ReceiveTask(); + receiveTask.setId(node.getId()); + receiveTask.setName(node.getName()); + flowElements.add(receiveTask); + + // 2. 添加接收任务的 Timer Boundary Event + if (node.getDelaySetting() != null) { + BoundaryEvent boundaryEvent = null; + if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) { + boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), + node.getDelaySetting().getDelayTime(), null, null); + } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) { + boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), + null, null, node.getDelaySetting().getDelayTime()); + } else { + throw new UnsupportedOperationException("不支持的延迟类型:" + node.getDelaySetting()); + } + flowElements.add(boundaryEvent); + } + return flowElements; + } + + @Override + public BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.DELAY_TIMER_NODE; + } + } + + public static class TriggerNodeConvert implements NodeConvert { + + @Override + public List convertList(BpmSimpleModelNodeVO node) { + Assert.notNull(node.getTriggerSetting(), "触发器节点设置不能为空"); + List flowElements = new ArrayList<>(2); + // HTTP 回调请求。需要附加一个 ReceiveTask、发起请求后、等待回调执行 + if (BpmTriggerTypeEnum.HTTP_CALLBACK.getType().equals(node.getTriggerSetting().getType())) { + Assert.notNull(node.getTriggerSetting().getHttpRequestSetting(), "触发器 HTTP 回调请求设置不能为空"); + ReceiveTask receiveTask = new ReceiveTask(); + receiveTask.setId("Activity_" + IdUtil.fastUUID()); + receiveTask.setName("HTTP 回调"); + node.setAttachNodeId(receiveTask.getId()); + flowElements.add(receiveTask); + // 重要:设置 callbackTaskDefineKey,用于 HTTP 回调 + node.getTriggerSetting().getHttpRequestSetting().setCallbackTaskDefineKey(receiveTask.getId()); + } + + // 触发器使用 ServiceTask 来实现 + ServiceTask serviceTask = new ServiceTask(); + serviceTask.setId(node.getId()); + serviceTask.setName(node.getName()); + serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + serviceTask.setImplementation("${" + BpmTriggerTaskDelegate.BEAN_NAME + "}"); + addExtensionElement(serviceTask, TRIGGER_TYPE, node.getTriggerSetting().getType()); + if (node.getTriggerSetting().getHttpRequestSetting() != null) { + addExtensionElementJson(serviceTask, TRIGGER_PARAM, node.getTriggerSetting().getHttpRequestSetting()); + } + if (node.getTriggerSetting().getFormSettings() != null) { + addExtensionElementJson(serviceTask, TRIGGER_PARAM, node.getTriggerSetting().getFormSettings()); + } + flowElements.add(serviceTask); + return flowElements; + } + + @Override + public BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.TRIGGER_NODE; + } + } + + public static class RouteBranchNodeConvert implements NodeConvert { + + @Override + public ExclusiveGateway convert(BpmSimpleModelNodeVO node) { + ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); + exclusiveGateway.setId(node.getId()); + + // 设置默认的序列流(条件) + node.setRouterDefaultFlowId("Flow_" + IdUtil.fastUUID()); + exclusiveGateway.setDefaultFlow(node.getRouterDefaultFlowId()); + return exclusiveGateway; + } + + @Override + public BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.ROUTER_BRANCH_NODE; + } + + public static SequenceFlow buildSequenceFlow(String nodeId, BpmSimpleModelNodeVO.RouterSetting router) { + String conditionExpression = SimpleModelUtils.buildConditionExpression(router); + return buildBpmnSequenceFlow(nodeId, router.getNodeId(), null, null, conditionExpression); + } + + } + + private static class ChildProcessConvert implements NodeConvert { + + @Override + public List convertList(BpmSimpleModelNodeVO node) { + List flowElements = new ArrayList<>(2); + BpmSimpleModelNodeVO.ChildProcessSetting childProcessSetting = node.getChildProcessSetting(); + List inVariables = childProcessSetting.getInVariables() == null ? + new ArrayList<>() : new ArrayList<>(childProcessSetting.getInVariables()); + CallActivity callActivity = new CallActivity(); + callActivity.setId(node.getId()); + callActivity.setName(node.getName()); + callActivity.setCalledElementType("key"); + // 1. 是否异步 + if (node.getChildProcessSetting().getAsync()) { + callActivity.setAsynchronous(true); + } + + // 2. 调用的子流程 + callActivity.setCalledElement(childProcessSetting.getCalledProcessDefinitionKey()); + callActivity.setProcessInstanceName(childProcessSetting.getCalledProcessDefinitionName()); + + // 3. 是否自动跳过子流程发起节点 + IOParameter ioParameter = new IOParameter(); + ioParameter.setSourceExpression(childProcessSetting.getSkipStartUserNode().toString()); + ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE); + inVariables.add(ioParameter); + + // 4. 【默认需要传递的一些变量】流程状态 + ioParameter = new IOParameter(); + ioParameter.setSource(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); + ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); + inVariables.add(ioParameter); + + // 5. 主→子变量传递、子->主变量传递 + callActivity.setInParameters(inVariables); + if (ArrayUtil.isNotEmpty(childProcessSetting.getOutVariables()) && ObjUtil.notEqual(childProcessSetting.getAsync(), Boolean.TRUE)) { + callActivity.setOutParameters(childProcessSetting.getOutVariables()); + } + + // 6. 子流程发起人配置 + List executionListeners = new ArrayList<>(); + FlowableListener flowableListener = new FlowableListener(); + flowableListener.setEvent(ExecutionListener.EVENTNAME_START); + flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + flowableListener.setImplementation(BpmCallActivityListener.DELEGATE_EXPRESSION); + FieldExtension fieldExtension = new FieldExtension(); + fieldExtension.setFieldName("listenerConfig"); + fieldExtension.setStringValue(JsonUtils.toJsonString(childProcessSetting.getStartUserSetting())); + flowableListener.getFieldExtensions().add(fieldExtension); + executionListeners.add(flowableListener); + callActivity.setExecutionListeners(executionListeners); + + // 7. 超时设置 + if (childProcessSetting.getTimeoutSetting() != null && Boolean.TRUE.equals(childProcessSetting.getTimeoutSetting().getEnable())) { + BoundaryEvent boundaryEvent = null; + if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) { + boundaryEvent = buildTimeoutBoundaryEvent(callActivity, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), + childProcessSetting.getTimeoutSetting().getTimeExpression(), null, null); + } else if (childProcessSetting.getTimeoutSetting().getType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) { + boundaryEvent = buildTimeoutBoundaryEvent(callActivity, BpmBoundaryEventTypeEnum.CHILD_PROCESS_TIMEOUT.getType(), + null, null, childProcessSetting.getTimeoutSetting().getTimeExpression()); + } + flowElements.add(boundaryEvent); + } + + // 8. 多实例 + if (childProcessSetting.getMultiInstanceSetting() != null && Boolean.TRUE.equals(childProcessSetting.getMultiInstanceSetting().getEnable())) { + MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); + multiInstanceCharacteristics.setSequential(childProcessSetting.getMultiInstanceSetting().getSequential()); + if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY.getType())) { + multiInstanceCharacteristics.setLoopCardinality(childProcessSetting.getMultiInstanceSetting().getSource()); + } + if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType()) || + childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) { + multiInstanceCharacteristics.setInputDataItem(childProcessSetting.getMultiInstanceSetting().getSource()); + } + multiInstanceCharacteristics.setCompletionCondition(String.format(BpmUserTaskApproveMethodEnum.RATIO.getCompletionCondition(), + String.format("%.2f", childProcessSetting.getMultiInstanceSetting().getApproveRatio() / 100D))); + callActivity.setLoopCharacteristics(multiInstanceCharacteristics); + addExtensionElement(callActivity, CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE, childProcessSetting.getMultiInstanceSetting().getSourceType()); + } + + // 添加节点类型 + addNodeType(node.getType(), callActivity); + flowElements.add(callActivity); + return flowElements; + } + + @Override + public BpmSimpleModelNodeTypeEnum getType() { + return BpmSimpleModelNodeTypeEnum.CHILD_PROCESS; + } + + } + + private static String buildGatewayJoinId(String id) { + return id + "_join"; + } + + private static BoundaryEvent buildTimeoutBoundaryEvent(Activity attachedToRef, Integer type, + String timeDuration, String timeCycle, String timeDate) { + // 1.1 定时器边界事件 + BoundaryEvent boundaryEvent = new BoundaryEvent(); + boundaryEvent.setId("Event-" + IdUtil.fastUUID()); + boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 + boundaryEvent.setAttachedToRef(attachedToRef); + // 1.2 定义超时时间表达式 + TimerEventDefinition eventDefinition = new TimerEventDefinition(); + if (ObjUtil.isNotNull(timeDuration)) { + eventDefinition.setTimeDuration(timeDuration); + } + if (ObjUtil.isNotNull(timeDuration)) { + eventDefinition.setTimeCycle(timeCycle); + } + if (ObjUtil.isNotNull(timeDate)) { + eventDefinition.setTimeDate(timeDate); + } + boundaryEvent.addEventDefinition(eventDefinition); + + // 2. 添加定时器边界事件类型 + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, type); + return boundaryEvent; + } + + // ========== SIMPLE 流程预测相关的方法 ========== + + public static List simulateProcess(BpmSimpleModelNodeVO rootNode, Map variables) { + List resultNodes = new ArrayList<>(); + + // 从头开始遍历 + simulateNextNode(rootNode, variables, resultNodes); + return resultNodes; + } + + private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map variables, + List resultNodes) { + // 如果不合法(包括为空),则直接结束 + if (!isValidNode(currentNode)) { + return; + } + BpmSimpleModelNodeTypeEnum nodeType = BpmSimpleModelNodeTypeEnum.valueOf(currentNode.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + + // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE/TRANSACTOR_NODE + if (nodeType == BpmSimpleModelNodeTypeEnum.START_NODE + || nodeType == BpmSimpleModelNodeTypeEnum.START_USER_NODE + || nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE + || nodeType == BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE + || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE + || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS + || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { + // 添加元素 + resultNodes.add(currentNode); + } + + // 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的 + if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) { + // 查找满足条件的 BpmSimpleModelNodeVO 节点 + BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), + conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) + && evalConditionExpress(variables, conditionNode.getConditionSetting())); + if (matchConditionNode == null) { + matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), + conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); + } + Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode); + // 遍历满足条件的 BpmSimpleModelNodeVO 节点 + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes); + } + + // 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的 + if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { + // 查找满足条件的 BpmSimpleModelNodeVO 节点 + Collection matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), + conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) + && evalConditionExpress(variables, conditionNode.getConditionSetting())); + if (CollUtil.isEmpty(matchConditionNodes)) { + matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), + conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); + } + Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode); + // 遍历满足条件的 BpmSimpleModelNodeVO 节点 + matchConditionNodes.forEach(matchConditionNode -> + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); + } + + // 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走 + if (nodeType == BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE) { + // 遍历所有 BpmSimpleModelNodeVO 节点 + currentNode.getConditionNodes().forEach(matchConditionNode -> + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); + } + + // 遍历子节点 + simulateNextNode(currentNode.getChildNode(), variables, resultNodes); + } + + public static boolean evalConditionExpress(Map variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { + return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/package-info.java new file mode 100644 index 0000000..73f8350 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 bpm 模块的 framework 封装 + * + * @author ZT + */ +package com.zt.plat.module.bpm.framework; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/config/RpcConfiguration.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 0000000..0be19c0 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,17 @@ +package com.zt.plat.module.bpm.framework.rpc.config; + +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.PostApi; +import com.zt.plat.module.system.api.dict.DictDataApi; +import com.zt.plat.module.system.api.permission.PermissionApi; +import com.zt.plat.module.system.api.permission.RoleApi; +import com.zt.plat.module.system.api.sms.SmsSendApi; +import com.zt.plat.module.system.api.user.AdminUserApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(value = "bpmRpcConfiguration", proxyBeanMethods = false) +@EnableFeignClients(clients = {RoleApi.class, DeptApi.class, PostApi.class, AdminUserApi.class, SmsSendApi.class, DictDataApi.class, + PermissionApi.class}) +public class RpcConfiguration { +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/package-info.java new file mode 100644 index 0000000..4a733c4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.zt.plat.module.bpm.framework.rpc; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/security/config/SecurityConfiguration.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..eb6e099 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,40 @@ +package com.zt.plat.module.bpm.framework.security.config; + +import com.zt.plat.framework.security.config.AuthorizeRequestsCustomizer; +import com.zt.plat.module.bpm.enums.ApiConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Bpm 模块的 Security 配置 + */ +@Configuration(proxyBeanMethods = false, value = "bpmSecurityConfiguration") +public class SecurityConfiguration { + + @Bean("bpmAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // TODO 芋艿:这个每个项目都需要重复配置,得捉摸有没通用的方案 + // Swagger 接口文档 + registry.requestMatchers("/v3/api-docs/**").permitAll() + .requestMatchers("/webjars/**").permitAll() + .requestMatchers("/swagger-ui").permitAll() + .requestMatchers("/swagger-ui/**").permitAll(); + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/security/core/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/security/core/package-info.java new file mode 100644 index 0000000..a637358 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.zt.plat.module.bpm.framework.security.core; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/config/BpmWebConfiguration.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/config/BpmWebConfiguration.java new file mode 100644 index 0000000..fed2cc5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/config/BpmWebConfiguration.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.bpm.framework.web.config; + +import com.zt.plat.framework.common.enums.WebFilterOrderEnum; +import com.zt.plat.framework.swagger.config.ZtSwaggerAutoConfiguration; +import com.zt.plat.module.bpm.framework.web.core.FlowableWebFilter; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * bpm 模块的 web 组件的 Configuration + * + * @author ZT + */ +@Configuration(proxyBeanMethods = false) +public class BpmWebConfiguration { + + /** + * 配置 Flowable Web 过滤器 + */ + @Bean + public FilterRegistrationBean flowableWebFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new FlowableWebFilter()); + registrationBean.setOrder(WebFilterOrderEnum.FLOWABLE_FILTER); + return registrationBean; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/core/FlowableWebFilter.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/core/FlowableWebFilter.java new file mode 100644 index 0000000..4699de5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/core/FlowableWebFilter.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.bpm.framework.web.core; + +import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * Flowable Web 过滤器,将 userId 设置到 {@link org.flowable.common.engine.impl.identity.Authentication} 中 + * + * @author jason + */ +public class FlowableWebFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + try { + // 设置工作流的用户 + Long userId = SecurityFrameworkUtils.getLoginUserId(); + if (userId != null) { + FlowableUtils.setAuthenticatedUserId(userId); + } + // 过滤 + chain.doFilter(request, response); + } finally { + // 清理 + FlowableUtils.clearAuthenticatedUserId(); + } + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/package-info.java new file mode 100644 index 0000000..62601dc --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/web/package-info.java @@ -0,0 +1,4 @@ +/** + * bpm 模块的 web 配置 + */ +package com.zt.plat.module.bpm.framework.web; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/package-info.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/package-info.java new file mode 100644 index 0000000..4039eb8 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/package-info.java @@ -0,0 +1,12 @@ +/** + * bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能,基于 Flowable 6 版本实现。 + * 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等 + * + * bpm 解释:https://baike.baidu.com/item/BPM/1933 + * + * 1. Controller URL:以 /bpm/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 bpm_ 开头,方便在数据库中区分 + * + * 注意,由于 Bpm 模块下,容易和其它模块重名,所以类名都加载 Bpm 的前缀~ + */ +package com.zt.plat.module.bpm; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmCategoryService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmCategoryService.java new file mode 100644 index 0000000..6e24f44 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmCategoryService.java @@ -0,0 +1,92 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * BPM 流程分类 Service 接口 + * + * @author ZT + */ +public interface BpmCategoryService { + + /** + * 创建流程分类 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createCategory(@Valid BpmCategorySaveReqVO createReqVO); + + /** + * 更新流程分类 + * + * @param updateReqVO 更新信息 + */ + void updateCategory(@Valid BpmCategorySaveReqVO updateReqVO); + + /** + * 删除流程分类 + * + * @param id 编号 + */ + void deleteCategory(Long id); + + /** + * 获得流程分类 + * + * @param id 编号 + * @return BPM 流程分类 + */ + BpmCategoryDO getCategory(Long id); + + /** + * 获得流程分类分页 + * + * @param pageReqVO 分页查询 + * @return 流程分类分页 + */ + PageResult getCategoryPage(BpmCategoryPageReqVO pageReqVO); + + /** + * 获得流程分类 Map,基于指定编码 + * + * @param codes 编号数组 + * @return 流程分类 Map + */ + default Map getCategoryMap(Collection codes) { + return convertMap(getCategoryListByCode(codes), BpmCategoryDO::getCode); + } + + /** + * 获得流程分类列表,基于指定编码 + * + * @return 流程分类列表 + */ + List getCategoryListByCode(Collection codes); + + /** + * 获得流程分类列表,基于指定状态 + * + * @param status 状态 + * @return 流程分类列表 + */ + List getCategoryListByStatus(Integer status); + + /** + * 批量更新流程分类的排序:每个分类的 sort 值,从 0 开始递增 + * + * @param ids 分类编号列表 + */ + void updateCategorySortBatch(List ids); + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmCategoryServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmCategoryServiceImpl.java new file mode 100644 index 0000000..d111015 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmCategoryServiceImpl.java @@ -0,0 +1,130 @@ +package com.zt.plat.module.bpm.service.definition; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.dal.mysql.category.BpmCategoryMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +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.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.*; + +/** + * BPM 流程分类 Service 实现类 + * + * @author ZT + */ +@Service +@Validated +public class BpmCategoryServiceImpl implements BpmCategoryService { + + @Resource + private BpmCategoryMapper bpmCategoryMapper; + + @Override + public Long createCategory(BpmCategorySaveReqVO createReqVO) { + // 校验唯一 + validateCategoryNameUnique(createReqVO); + validateCategoryCodeUnique(createReqVO); + // 插入 + BpmCategoryDO category = BeanUtils.toBean(createReqVO, BpmCategoryDO.class); + bpmCategoryMapper.insert(category); + return category.getId(); + } + + @Override + public void updateCategory(BpmCategorySaveReqVO updateReqVO) { + // 校验存在 + validateCategoryExists(updateReqVO.getId()); + validateCategoryNameUnique(updateReqVO); + validateCategoryCodeUnique(updateReqVO); + // 更新 + BpmCategoryDO updateObj = BeanUtils.toBean(updateReqVO, BpmCategoryDO.class); + bpmCategoryMapper.updateById(updateObj); + } + + private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) { + BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName()); + if (category == null + || ObjUtil.equal(category.getId(), updateReqVO.getId())) { + return; + } + throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName()); + } + + private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) { + BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode()); + if (category == null + || ObjUtil.equal(category.getId(), updateReqVO.getId())) { + return; + } + throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode()); + } + + @Override + public void deleteCategory(Long id) { + // 校验存在 + validateCategoryExists(id); + // 删除 + bpmCategoryMapper.deleteById(id); + } + + private void validateCategoryExists(Long id) { + if (bpmCategoryMapper.selectById(id) == null) { + throw exception(CATEGORY_NOT_EXISTS); + } + } + + @Override + public BpmCategoryDO getCategory(Long id) { + return bpmCategoryMapper.selectById(id); + } + + @Override + public PageResult getCategoryPage(BpmCategoryPageReqVO pageReqVO) { + return bpmCategoryMapper.selectPage(pageReqVO); + } + + @Override + public List getCategoryListByCode(Collection codes) { + if (CollUtil.isEmpty(codes)) { + return Collections.emptyList(); + } + return bpmCategoryMapper.selectListByCode(codes); + } + + @Override + public List getCategoryListByStatus(Integer status) { + return bpmCategoryMapper.selectListByStatus(status); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateCategorySortBatch(List ids) { + // 校验分类都存在 + List categories = bpmCategoryMapper.selectByIds(ids); + if (categories.size() != ids.size()) { + throw exception(CATEGORY_NOT_EXISTS); + } + + // 批量更新排序 + List updateList = IntStream.range(0, ids.size()) + .mapToObj(index -> new BpmCategoryDO().setId(ids.get(index)).setSort(index)) + .collect(Collectors.toList()); + bpmCategoryMapper.updateBatch(updateList); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmFormService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmFormService.java new file mode 100644 index 0000000..6a5a8c0 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmFormService.java @@ -0,0 +1,85 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + + +/** + * 动态表单 Service 接口 + * + * @author @风里雾里 + */ +public interface BpmFormService { + + /** + * 创建动态表单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createForm(@Valid BpmFormSaveReqVO createReqVO); + + /** + * 更新动态表单 + * + * @param updateReqVO 更新信息 + */ + void updateForm(@Valid BpmFormSaveReqVO updateReqVO); + + /** + * 删除动态表单 + * + * @param id 编号 + */ + void deleteForm(Long id); + + /** + * 获得动态表单 + * + * @param id 编号 + * @return 动态表单 + */ + BpmFormDO getForm(Long id); + + /** + * 获得动态表单列表 + * + * @return 动态表单列表 + */ + List getFormList(); + + /** + * 获得动态表单列表 + * + * @param ids 编号 + * @return 动态表单列表 + */ + List getFormList(Collection ids); + + /** + * 获得动态表单 Map + * + * @param ids 编号 + * @return 动态表单 Map + */ + default Map getFormMap(Collection ids) { + return CollectionUtils.convertMap(this.getFormList(ids), BpmFormDO::getId); + } + + /** + * 获得动态表单分页 + * + * @param pageReqVO 分页查询 + * @return 动态表单分页 + */ + PageResult getFormPage(BpmFormPageReqVO pageReqVO); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmFormServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmFormServiceImpl.java new file mode 100644 index 0000000..73511ad --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmFormServiceImpl.java @@ -0,0 +1,114 @@ +package com.zt.plat.module.bpm.service.definition; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.mysql.definition.BpmFormMapper; +import com.zt.plat.module.bpm.enums.ErrorCodeConstants; +import com.zt.plat.module.bpm.service.definition.dto.BpmFormFieldRespDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.*; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 动态表单 Service 实现类 + * + * @author 风里雾里 + */ +@Service +@Validated +public class BpmFormServiceImpl implements BpmFormService { + + @Resource + private BpmFormMapper formMapper; + + @Override + public Long createForm(BpmFormSaveReqVO createReqVO) { + this.validateFields(createReqVO.getFields()); + // 插入 + BpmFormDO form = BeanUtils.toBean(createReqVO, BpmFormDO.class); + formMapper.insert(form); + // 返回 + return form.getId(); + } + + @Override + public void updateForm(BpmFormSaveReqVO updateReqVO) { + validateFields(updateReqVO.getFields()); + // 校验存在 + validateFormExists(updateReqVO.getId()); + // 更新 + BpmFormDO updateObj = BeanUtils.toBean(updateReqVO, BpmFormDO.class); + formMapper.updateById(updateObj); + } + + @Override + public void deleteForm(Long id) { + // 校验存在 + this.validateFormExists(id); + // 删除 + formMapper.deleteById(id); + } + + private void validateFormExists(Long id) { + if (formMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.FORM_NOT_EXISTS); + } + } + + @Override + public BpmFormDO getForm(Long id) { + return formMapper.selectById(id); + } + + @Override + public List getFormList() { + return formMapper.selectList(); + } + + @Override + public List getFormList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return formMapper.selectBatchIds(ids); + } + + @Override + public PageResult getFormPage(BpmFormPageReqVO pageReqVO) { + return formMapper.selectPage(pageReqVO); + } + + /** + * 校验 Field,避免 field 重复 + * + * @param fields field 数组 + */ + private void validateFields(List fields) { + if (true) { // TODO 芋艿:兼容 Vue3 工作流:因为采用了新的表单设计器,所以暂时不校验 + return; + } + Map fieldMap = new HashMap<>(); // key 是 vModel,value 是 label + for (String field : fields) { + BpmFormFieldRespDTO fieldDTO = JsonUtils.parseObject(field, BpmFormFieldRespDTO.class); + Assert.notNull(fieldDTO); + String oldLabel = fieldMap.put(fieldDTO.getVModel(), fieldDTO.getLabel()); + // 如果不存在,则直接返回 + if (oldLabel == null) { + continue; + } + // 如果存在,则报错 + throw exception(ErrorCodeConstants.FORM_FIELD_REPEAT, oldLabel, fieldDTO.getLabel(), fieldDTO.getVModel()); + } + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmModelService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmModelService.java new file mode 100644 index 0000000..a9f8b1d --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmModelService.java @@ -0,0 +1,134 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; +import jakarta.validation.Valid; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.repository.Model; + +import java.util.List; + +/** + * 流程模型接口 + * + * @author yunlongn + */ +public interface BpmModelService { + + /** + * 获得流程模型列表 + * + * @param name 模型名称 + * @return 流程模型列表 + */ + List getModelList(String name); + + /** + * 创建流程模型 + * + * @param modelVO 创建信息 + * @return 创建的流程模型的编号 + */ + String createModel(@Valid BpmModelSaveReqVO modelVO); + + /** + * 获得流程模块 + * + * @param id 编号 + * @return 流程模型 + */ + Model getModel(String id); + + /** + * 获得流程模型的 BPMN XML + * + * @param id 编号 + * @return BPMN XML + */ + byte[] getModelBpmnXML(String id); + + /** + * 修改流程模型的 BPMN XML + * + * @param id 编号 + * @param bpmnXml BPMN XML + */ + void updateModelBpmnXml(String id, String bpmnXml); + + /** + * 修改流程模型 + * + * @param userId 用户编号 + * @param updateReqVO 更新信息 + */ + void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); + + /** + * 批量更新模型排序 + * + * @param userId 用户编号 + * @param ids 编号列表 + */ + void updateModelSortBatch(Long userId, List ids); + + /** + * 将流程模型,部署成一个流程定义 + * + * @param userId 用户编号 + * @param id 编号 + */ + void deployModel(Long userId, String id); + + /** + * 删除模型 + * + * @param userId 用户编号 + * @param id 编号 + */ + void deleteModel(Long userId, String id); + + /** + * 清理模型,包括流程实例 + * + * @param userId 用户编号 + * @param id 编号 + */ + void cleanModel(Long userId, String id); + + /** + * 修改模型的状态,实际更新的部署的流程定义的状态 + * + * @param userId 用户编号 + * @param id 编号 + * @param state 状态 + */ + void updateModelState(Long userId, String id, Integer state); + + /** + * 获得流程定义编号对应的 BPMN Model + * + * @param processDefinitionId 流程定义编号 + * @return BPMN Model + */ + BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); + + // ========== 仿钉钉/飞书的精简模型 ========= + + /** + * 获取仿钉钉流程设计模型结构 + * + * @param modelId 流程模型编号 + * @return 仿钉钉流程设计模型结构 + */ + BpmSimpleModelNodeVO getSimpleModel(String modelId); + + /** + * 更新仿钉钉流程设计模型 + * + * @param userId 用户编号 + * @param reqVO 请求信息 + */ + void updateSimpleModel(Long userId, @Valid BpmSimpleModelUpdateReqVO reqVO); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmModelServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmModelServiceImpl.java new file mode 100644 index 0000000..f4815c2 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmModelServiceImpl.java @@ -0,0 +1,432 @@ +package com.zt.plat.module.bpm.service.definition; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.common.util.validation.ValidationUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; +import com.zt.plat.module.bpm.convert.definition.BpmModelConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.enums.definition.BpmModelFormTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmModelTypeEnum; +import com.zt.plat.module.bpm.enums.task.BpmReasonEnum; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.SimpleModelUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceCopyService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.*; +import org.flowable.common.engine.impl.db.SuspensionState; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ModelQuery; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.*; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertMap; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.*; +import static com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseCandidateStrategy; + +/** + * 流程模型实现:主要进行 Flowable {@link Model} 的维护 + * + * @author yunlongn + * @author ZT + * @author jason + */ +@Service +@Validated +@Slf4j +public class BpmModelServiceImpl implements BpmModelService { + + @Resource + private RepositoryService repositoryService; + @Resource + private BpmProcessDefinitionService processDefinitionService; + @Resource + private BpmFormService bpmFormService; + + @Resource + private BpmTaskCandidateInvoker taskCandidateInvoker; + + @Resource + private HistoryService historyService; + @Resource + private RuntimeService runtimeService; + @Resource + private TaskService taskService; + @Resource + private BpmProcessInstanceCopyService processInstanceCopyService; + + @Override + public List getModelList(String name) { + ModelQuery modelQuery = repositoryService.createModelQuery(); + if (StrUtil.isNotEmpty(name)) { + modelQuery.modelNameLike("%" + name + "%"); + } + modelQuery.modelTenantId(FlowableUtils.getTenantId()); + return modelQuery.list(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + 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. 创建 Model 对象 + createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序 + Model model = repositoryService.newModel(); + BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); + model.setTenantId(FlowableUtils.getTenantId()); + + // 3. 保存模型 + saveModel(model, createReqVO); + return model.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 + public void updateModel(Long userId, BpmModelSaveReqVO updateReqVO) { + // 1. 校验流程模型存在 + Model model = validateModelManager(updateReqVO.getId(), userId); + + // 2. 填充 Model 信息 + BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO); + + // 3. 保存模型 + saveModel(model, updateReqVO); + } + + /** + * 保存模型的基本信息、流程图 + * + * @param model 模型 + * @param saveReqVO 保存信息 + */ + private void saveModel(Model model, BpmModelSaveReqVO saveReqVO) { + // 1. 保存模型的基础信息 + repositoryService.saveModel(model); + + // 2. 保存流程图 + if (ObjUtil.equals(BpmModelTypeEnum.BPMN.getType(), saveReqVO.getType()) + && StrUtil.isNotEmpty(saveReqVO.getBpmnXml())) { + updateModelBpmnXml(model.getId(), saveReqVO.getBpmnXml()); + } else if (ObjUtil.equals(BpmModelTypeEnum.SIMPLE.getType(), saveReqVO.getType()) + && saveReqVO.getSimpleModel() != null) { + // JSON 转换成 bpmnModel + BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), + saveReqVO.getSimpleModel()); + // 保存 Bpmn XML + updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); + // 保存 JSON 数据 + updateModelSimpleJson(model.getId(), saveReqVO.getSimpleModel()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateModelSortBatch(Long userId, List ids) { + // 1.1 校验流程模型存在 + List 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 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; + } + + /** + * 校验是否有流程模型的管理权限 + * + * @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(Long userId, String id) { + // 1.1 校验流程模型存在 + Model model = validateModelManager(id, userId); + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); + // 1.2 校验流程图 + byte[] bpmnBytes = getModelBpmnXML(model.getId()); + validateBpmnXml(bpmnBytes, metaInfo.getType()); + // 1.3 校验表单已配 + 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, simpleJson, + form); + + // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。 + updateProcessDefinitionSuspended(model.getDeploymentId()); + + // 2.3 更新 model 的 deploymentId,进行关联 + ProcessDefinition definition = processDefinitionService.getProcessDefinition(definitionId); + model.setDeploymentId(definition.getDeploymentId()); + repositoryService.saveModel(model); + } + + private void validateBpmnXml(byte[] bpmnBytes, Integer type) { + BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes); + if (bpmnModel == null) { + throw exception(MODEL_NOT_EXISTS); + } + // 1. 没有 StartEvent + StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel); + if (startEvent == null) { + throw exception(MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS); + } + // 2. 校验 UserTask 的 name 都配置了 + List userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); + userTasks.forEach(userTask -> { + if (StrUtil.isEmpty(userTask.getName())) { + throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS, userTask.getId()); + } + }); + // 3. 校验第一个用户任务节点的规则类型是否为“审批人自选”,BPMN 设计器,校验第一个用户任务节点,SIMPLE 设计器,第一个节点固定为发起人所以校验第二个用户任务节点 + UserTask firUserTask = CollUtil.get(userTasks, BpmModelTypeEnum.BPMN.getType().equals(type) ? 0 : 1); + if (firUserTask == null) { + return; + } + Integer candidateStrategy = parseCandidateStrategy(firUserTask); + if (Objects.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) { + throw exception(MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR, firUserTask.getName()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteModel(Long userId, String id) { + // 校验流程模型存在 + Model model = validateModelManager(id, userId); + + // 执行删除 + repositoryService.deleteModel(id); + // 禁用流程定义 + updateProcessDefinitionSuspended(model.getDeploymentId()); + } + + @Override + public void cleanModel(Long userId, String id) { + // 1. 校验流程模型存在 + Model model = validateModelManager(id, userId); + + // 2. 清理所有流程数据 + // 2.1 先取消所有正在运行的流程 + List processInstances = runtimeService.createProcessInstanceQuery() + .processDefinitionKey(model.getKey()).list(); + processInstances.forEach(processInstance -> { + runtimeService.deleteProcessInstance(processInstance.getId(), + BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); + historyService.deleteHistoricProcessInstance(processInstance.getId()); + processInstanceCopyService.deleteProcessInstanceCopy(processInstance.getId()); + }); + // 2.2 再从历史中删除所有相关的流程数据 + List historicProcessInstances = historyService.createHistoricProcessInstanceQuery() + .processDefinitionKey(model.getKey()).list(); + historicProcessInstances.forEach(historicProcessInstance -> { + historyService.deleteHistoricProcessInstance(historicProcessInstance.getId()); + processInstanceCopyService.deleteProcessInstanceCopy(historicProcessInstance.getId()); + }); + // 2.3 清理所有 Task + List tasks = taskService.createTaskQuery() + .processDefinitionKey(model.getKey()).list(); + tasks.forEach(task -> taskService.deleteTask(task.getId(),BpmReasonEnum.CANCEL_BY_PROCESS_CLEAN.getReason())); + } + + @Override + public void updateModelState(Long userId, String id, Integer state) { + // 1.1 校验流程模型存在 + Model model = validateModelManager(id, userId); + // 1.2 校验流程定义存在 + ProcessDefinition definition = processDefinitionService + .getProcessDefinitionByDeploymentId(model.getDeploymentId()); + if (definition == null) { + throw exception(PROCESS_DEFINITION_NOT_EXISTS); + } + + // 2. 更新状态 + processDefinitionService.updateProcessDefinitionState(definition.getId(), state); + } + + @Override + public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) { + 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(BpmModelMetaInfoVO metaInfo) { + if (metaInfo == null || metaInfo.getFormType() == null) { + throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); + } + // 校验表单存在 + if (Objects.equals(metaInfo.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) { + if (metaInfo.getFormId() == null) { + throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); + } + BpmFormDO form = bpmFormService.getForm(metaInfo.getFormId()); + if (form == null) { + throw exception(FORM_NOT_EXISTS); + } + return form; + } else { + if (StrUtil.isEmpty(metaInfo.getFormCustomCreatePath()) + || StrUtil.isEmpty(metaInfo.getFormCustomViewPath())) { + throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); + } + return null; + } + } + + @Override + public void updateModelBpmnXml(String id, String bpmnXml) { + if (StrUtil.isEmpty(bpmnXml)) { + return; + } + 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 对应的流程定义 + *

+ * 注意:这里一个 deploymentId 只关联一个流程定义 + * + * @param deploymentId 流程发布Id + */ + private void updateProcessDefinitionSuspended(String deploymentId) { + if (StrUtil.isEmpty(deploymentId)) { + return; + } + ProcessDefinition oldDefinition = processDefinitionService.getProcessDefinitionByDeploymentId(deploymentId); + if (oldDefinition == null) { + return; + } + processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), + SuspensionState.SUSPENDED.getStateCode()); + } + + private Model getModelByKey(String key) { + return repositoryService.createModelQuery() + .modelTenantId(FlowableUtils.getTenantId()) + .modelKey(key).singleResult(); + } + + @Override + public Model getModel(String id) { + return repositoryService.getModel(id); + } + + @Override + public byte[] getModelBpmnXML(String id) { + return repositoryService.getModelEditorSource(id); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessDefinitionService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessDefinitionService.java new file mode 100644 index 0000000..f819b48 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessDefinitionService.java @@ -0,0 +1,181 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * 流程定义接口 + * + * @author yunlong.li + * @author ZJQ + * @author ZT + */ +public interface BpmProcessDefinitionService { + + /** + * 获得流程定义分页 + * + * @param pageReqVO 分页入参 + * @return 流程定义 Page + */ + PageResult getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageReqVO); + + /** + * 获得流程定义列表 + * + * @param suspensionState 中断状态 + * @return 流程定义列表 + */ + List getProcessDefinitionListBySuspensionState(Integer suspensionState); + + /** + * 基于流程模型,创建流程定义 + * + * @param model 流程模型 + * @param modelMetaInfo 流程模型元信息 + * @param bpmnBytes BPMN XML 字节数组 + * @param simpleJson SIMPLE Model JSON + * @param form 表单 + * @return 流程编号 + */ + String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, + byte[] bpmnBytes, String simpleJson, BpmFormDO form); + + /** + * 更新流程定义状态 + * + * @param id 流程定义的编号 + * @param state 状态 + */ + void updateProcessDefinitionState(String id, Integer state); + + /** + * 更新模型编号 + * + * @param modelId 流程定义编号 + * @param sort 排序 + */ + void updateProcessDefinitionSortByModelId(String modelId, Long sort); + + /** + * 获得流程定义对应的 BPMN + * + * @param id 流程定义编号 + * @return BPMN + */ + BpmnModel getProcessDefinitionBpmnModel(String id); + + /** + * 获得流程定义的信息 + * + * @param id 流程定义编号 + * @return 流程定义信息 + */ + BpmProcessDefinitionInfoDO getProcessDefinitionInfo(String id); + + /** + * 获得流程定义的信息 List + * + * @param ids 流程定义编号数组 + * @return 流程额定义信息数组 + */ + List getProcessDefinitionInfoList(Collection ids); + + default Map getProcessDefinitionInfoMap(Set ids) { + return convertMap(getProcessDefinitionInfoList(ids), BpmProcessDefinitionInfoDO::getProcessDefinitionId); + } + + /** + * 获得流程定义编号对应的 ProcessDefinition + * + * @param id 流程定义编号 + * @return 流程定义 + */ + ProcessDefinition getProcessDefinition(String id); + + /** + * 获得 ids 对应的 ProcessDefinition 数组 + * + * @param ids 编号的数组 + * @return 流程定义的数组 + */ + List getProcessDefinitionList(Set ids); + + default Map getProcessDefinitionMap(Set ids) { + return convertMap(getProcessDefinitionList(ids), ProcessDefinition::getId); + } + + /** + * 获得 deploymentId 对应的 ProcessDefinition + * + * @param deploymentId 部署编号 + * @return 流程定义 + */ + ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId); + + /** + * 获得 deploymentIds 对应的 ProcessDefinition 数组 + * + * @param deploymentIds 部署编号的数组 + * @return 流程定义的数组 + */ + List getProcessDefinitionListByDeploymentIds(Set deploymentIds); + + /** + * 获得流程定义标识对应的激活的流程定义 + * + * @param key 流程定义的标识 + * @return 流程定义 + */ + ProcessDefinition getActiveProcessDefinition(String key); + + /** + * 判断用户是否可以使用该流程定义,进行流程的发起 + * + * @param processDefinition 流程定义 + * @param userId 用户编号 + * @return 是否可以发起流程 + */ + boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId); + + /** + * 获得 ids 对应的 Deployment Map + * + * @param ids 部署编号的数组 + * @return 流程部署 Map + */ + default Map getDeploymentMap(Set ids) { + return convertMap(getDeploymentList(ids), Deployment::getId); + } + + /** + * 获得 ids 对应的 Deployment 数组 + * + * @param ids 部署编号的数组 + * @return 流程部署的数组 + */ + List getDeploymentList(Set ids); + + /** + * 获得 id 对应的 Deployment + * + * @param id 部署编号 + * @return 流程部署 + */ + Deployment getDeployment(String id); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java new file mode 100644 index 0000000..f2f1d6e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -0,0 +1,248 @@ +package com.zt.plat.module.bpm.service.definition; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.framework.common.util.object.PageUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.common.engine.impl.db.SuspensionState; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.repository.ProcessDefinitionQuery; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.*; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.addIfNotNull; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.*; +import static java.util.Collections.emptyList; + +/** + * 流程定义实现 + * 主要进行 Flowable {@link ProcessDefinition} 和 {@link Deployment} 的维护 + * + * @author yunlongn + * @author ZJQ + * @author ZT + */ +@Service +@Validated +@Slf4j +public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService { + + @Resource + private RepositoryService repositoryService; + + @Resource + private BpmProcessDefinitionInfoMapper processDefinitionMapper; + + @Resource + private AdminUserApi adminUserApi; + + @Override + public ProcessDefinition getProcessDefinition(String id) { + return repositoryService.getProcessDefinition(id); + } + + @Override + public List getProcessDefinitionList(Set ids) { + return repositoryService.createProcessDefinitionQuery().processDefinitionIds(ids).list(); + } + + @Override + public ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId) { + if (StrUtil.isEmpty(deploymentId)) { + return null; + } + return repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult(); + } + + @Override + public List getProcessDefinitionListByDeploymentIds(Set deploymentIds) { + if (CollUtil.isEmpty(deploymentIds)) { + return emptyList(); + } + return repositoryService.createProcessDefinitionQuery().deploymentIds(deploymentIds).list(); + } + + @Override + public ProcessDefinition getActiveProcessDefinition(String key) { + 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.isNotEmpty(processDefinition.getStartUserIds())) { + return processDefinition.getStartUserIds().contains(userId); + } + + // 校验用户是否在允许发起的部门列表中 + if (CollUtil.isNotEmpty(processDefinition.getStartDeptIds())) { + AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + return user != null + && DeptUtil.getDeptId(user) > 0L + && processDefinition.getStartDeptIds().contains(DeptUtil.getDeptId(user)); + } + + // 都为空,则所有人都可以发起 + return true; + } + + @Override + public List getDeploymentList(Set ids) { + if (CollUtil.isEmpty(ids)) { + return emptyList(); + } + List list = new ArrayList<>(ids.size()); + for (String id : ids) { + addIfNotNull(list, getDeployment(id)); + } + return list; + } + + @Override + public Deployment getDeployment(String id) { + if (StrUtil.isEmpty(id)) { + return null; + } + return repositoryService.createDeploymentQuery().deploymentId(id).singleResult(); + } + + @Override + 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()) + .addBytes(model.getKey() + BpmnModelConstants.BPMN_FILE_SUFFIX, bpmnBytes) + .tenantId(FlowableUtils.getTenantId()) + .disableSchemaValidation() // 禁用 XML Schema 验证,因为有自定义的属性 + .deploy(); + + // 设置 ProcessDefinition 的 category 分类 + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery() + .deploymentId(deploy.getId()).singleResult(); + repositoryService.setProcessDefinitionCategory(definition.getId(), model.getCategory()); + // 注意 1,ProcessDefinition 的 key 和 name 是通过 BPMN 中的 的 id 和 name 决定 + // 注意 2,目前该项目的设计上,需要保证 Model、Deployment、ProcessDefinition 使用相同的 key,保证关联性。 + // 否则,会导致 ProcessDefinition 的分页无法查询到。 + if (!Objects.equals(definition.getKey(), model.getKey())) { + throw exception(PROCESS_DEFINITION_KEY_NOT_MATCH, model.getKey(), definition.getKey()); + } + if (!Objects.equals(definition.getName(), model.getName())) { + throw exception(PROCESS_DEFINITION_NAME_NOT_MATCH, model.getName(), definition.getName()); + } + + // 插入拓展表 + BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) + .setModelId(model.getId()).setCategory(model.getCategory()).setProcessDefinitionId(definition.getId()) + .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson); + if (form != null) { + definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); + } + processDefinitionMapper.insert(definitionDO); + return definition.getId(); + } + + @Override + public void updateProcessDefinitionState(String id, Integer state) { + ProcessDefinition processDefinition = repositoryService.getProcessDefinition(id); + if (processDefinition == null) { + throw exception(PROCESS_DEFINITION_NOT_EXISTS); + } + + // 激活 + if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) { + if (processDefinition.isSuspended()) { + repositoryService.activateProcessDefinitionById(id, false, null); + } + return; + } + // 挂起 + if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) { + // suspendProcessInstances = false,进行中的任务,不进行挂起。 + // 原因:只要新的流程不允许发起即可,老流程继续可以执行。 + if (!processDefinition.isSuspended()) { + repositoryService.suspendProcessDefinitionById(id, false, null); + } + return; + } + log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state); + } + + @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); + } + + @Override + public BpmProcessDefinitionInfoDO getProcessDefinitionInfo(String id) { + return processDefinitionMapper.selectByProcessDefinitionId(id); + } + + @Override + public List getProcessDefinitionInfoList(Collection ids) { + return processDefinitionMapper.selectListByProcessDefinitionIds(ids); + } + + @Override + public PageResult getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) { + ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); + query.processDefinitionTenantId(FlowableUtils.getTenantId()); + if (StrUtil.isNotBlank(pageVO.getKey())) { + query.processDefinitionKey(pageVO.getKey()); + } + // 执行查询 + long count = query.count(); + if (count == 0) { + return PageResult.empty(count); + } + List list = query.orderByProcessDefinitionVersion().desc() + .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); + return new PageResult<>(list, count); + } + + @Override + public List getProcessDefinitionListBySuspensionState(Integer suspensionState) { + // 拼接查询条件 + ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); + if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), suspensionState)) { + query.suspended(); + } else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), suspensionState)) { + query.active(); + } + // 执行查询 + query.processDefinitionTenantId(FlowableUtils.getTenantId()); + return query.list(); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessExpressionService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessExpressionService.java new file mode 100644 index 0000000..448300c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessExpressionService.java @@ -0,0 +1,54 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessExpressionDO; +import jakarta.validation.Valid; + +/** + * BPM 流程表达式 Service 接口 + * + * @author ZT + */ +public interface BpmProcessExpressionService { + + /** + * 创建流程表达式 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProcessExpression(@Valid BpmProcessExpressionSaveReqVO createReqVO); + + /** + * 更新流程表达式 + * + * @param updateReqVO 更新信息 + */ + void updateProcessExpression(@Valid BpmProcessExpressionSaveReqVO updateReqVO); + + /** + * 删除流程表达式 + * + * @param id 编号 + */ + void deleteProcessExpression(Long id); + + /** + * 获得流程表达式 + * + * @param id 编号 + * @return 流程表达式 + */ + BpmProcessExpressionDO getProcessExpression(Long id); + + /** + * 获得流程表达式分页 + * + * @param pageReqVO 分页查询 + * @return 流程表达式分页 + */ + PageResult getProcessExpressionPage(BpmProcessExpressionPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessExpressionServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessExpressionServiceImpl.java new file mode 100644 index 0000000..e367aff --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessExpressionServiceImpl.java @@ -0,0 +1,70 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessExpressionDO; +import com.zt.plat.module.bpm.dal.mysql.definition.BpmProcessExpressionMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.PROCESS_EXPRESSION_NOT_EXISTS; + +/** + * BPM 流程表达式 Service 实现类 + * + * @author ZT + */ +@Service +@Validated +public class BpmProcessExpressionServiceImpl implements BpmProcessExpressionService { + + @Resource + private BpmProcessExpressionMapper processExpressionMapper; + + @Override + public Long createProcessExpression(BpmProcessExpressionSaveReqVO createReqVO) { + // 插入 + BpmProcessExpressionDO processExpression = BeanUtils.toBean(createReqVO, BpmProcessExpressionDO.class); + processExpressionMapper.insert(processExpression); + // 返回 + return processExpression.getId(); + } + + @Override + public void updateProcessExpression(BpmProcessExpressionSaveReqVO updateReqVO) { + // 校验存在 + validateProcessExpressionExists(updateReqVO.getId()); + // 更新 + BpmProcessExpressionDO updateObj = BeanUtils.toBean(updateReqVO, BpmProcessExpressionDO.class); + processExpressionMapper.updateById(updateObj); + } + + @Override + public void deleteProcessExpression(Long id) { + // 校验存在 + validateProcessExpressionExists(id); + // 删除 + processExpressionMapper.deleteById(id); + } + + private void validateProcessExpressionExists(Long id) { + if (processExpressionMapper.selectById(id) == null) { + throw exception(PROCESS_EXPRESSION_NOT_EXISTS); + } + } + + @Override + public BpmProcessExpressionDO getProcessExpression(Long id) { + return processExpressionMapper.selectById(id); + } + + @Override + public PageResult getProcessExpressionPage(BpmProcessExpressionPageReqVO pageReqVO) { + return processExpressionMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessListenerService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessListenerService.java new file mode 100644 index 0000000..9f111a7 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessListenerService.java @@ -0,0 +1,54 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessListenerDO; +import jakarta.validation.Valid; + +/** + * BPM 流程监听器 Service 接口 + * + * @author ZT + */ +public interface BpmProcessListenerService { + + /** + * 创建流程监听器 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProcessListener(@Valid BpmProcessListenerSaveReqVO createReqVO); + + /** + * 更新流程监听器 + * + * @param updateReqVO 更新信息 + */ + void updateProcessListener(@Valid BpmProcessListenerSaveReqVO updateReqVO); + + /** + * 删除流程监听器 + * + * @param id 编号 + */ + void deleteProcessListener(Long id); + + /** + * 获得流程监听器 + * + * @param id 编号 + * @return 流程监听器 + */ + BpmProcessListenerDO getProcessListener(Long id); + + /** + * 获得流程监听器分页 + * + * @param pageReqVO 分页查询 + * @return 流程监听器分页 + */ + PageResult getProcessListenerPage(BpmProcessListenerPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessListenerServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessListenerServiceImpl.java new file mode 100644 index 0000000..916949b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmProcessListenerServiceImpl.java @@ -0,0 +1,102 @@ +package com.zt.plat.module.bpm.service.definition; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessListenerDO; +import com.zt.plat.module.bpm.dal.mysql.definition.BpmProcessListenerMapper; +import com.zt.plat.module.bpm.enums.definition.BpmProcessListenerTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmProcessListenerValueTypeEnum; +import jakarta.annotation.Resource; +import org.flowable.engine.delegate.JavaDelegate; +import org.flowable.engine.delegate.TaskListener; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.*; + +/** + * BPM 流程监听器 Service 实现类 + * + * @author ZT + */ +@Service +@Validated +public class BpmProcessListenerServiceImpl implements BpmProcessListenerService { + + @Resource + private BpmProcessListenerMapper processListenerMapper; + + @Override + public Long createProcessListener(BpmProcessListenerSaveReqVO createReqVO) { + // 校验 + validateCreateProcessListenerValue(createReqVO); + // 插入 + BpmProcessListenerDO processListener = BeanUtils.toBean(createReqVO, BpmProcessListenerDO.class); + processListenerMapper.insert(processListener); + return processListener.getId(); + } + + @Override + public void updateProcessListener(BpmProcessListenerSaveReqVO updateReqVO) { + // 校验存在 + validateProcessListenerExists(updateReqVO.getId()); + validateCreateProcessListenerValue(updateReqVO); + // 更新 + BpmProcessListenerDO updateObj = BeanUtils.toBean(updateReqVO, BpmProcessListenerDO.class); + processListenerMapper.updateById(updateObj); + } + + private void validateCreateProcessListenerValue(BpmProcessListenerSaveReqVO createReqVO) { + // class 类型 + if (createReqVO.getValueType().equals(BpmProcessListenerValueTypeEnum.CLASS.getType())) { + try { + Class clazz = Class.forName(createReqVO.getValue()); + if (createReqVO.getType().equals(BpmProcessListenerTypeEnum.EXECUTION.getType()) + && !JavaDelegate.class.isAssignableFrom(clazz)) { + throw exception(PROCESS_LISTENER_CLASS_IMPLEMENTS_ERROR, createReqVO.getValue(), + JavaDelegate.class.getName()); + } else if (createReqVO.getType().equals(BpmProcessListenerTypeEnum.TASK.getType()) + && !TaskListener.class.isAssignableFrom(clazz)) { + throw exception(PROCESS_LISTENER_CLASS_IMPLEMENTS_ERROR, createReqVO.getValue(), + TaskListener.class.getName()); + } + } catch (ClassNotFoundException e) { + throw exception(PROCESS_LISTENER_CLASS_NOT_FOUND, createReqVO.getValue()); + } + return; + } + // 表达式 + if (!StrUtil.startWith(createReqVO.getValue(), "${") || !StrUtil.endWith(createReqVO.getValue(), "}")) { + throw exception(PROCESS_LISTENER_EXPRESSION_INVALID, createReqVO.getValue()); + } + } + + @Override + public void deleteProcessListener(Long id) { + // 校验存在 + validateProcessListenerExists(id); + // 删除 + processListenerMapper.deleteById(id); + } + + private void validateProcessListenerExists(Long id) { + if (processListenerMapper.selectById(id) == null) { + throw exception(PROCESS_LISTENER_NOT_EXISTS); + } + } + + @Override + public BpmProcessListenerDO getProcessListener(Long id) { + return processListenerMapper.selectById(id); + } + + @Override + public PageResult getProcessListenerPage(BpmProcessListenerPageReqVO pageReqVO) { + return processListenerMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupService.java new file mode 100644 index 0000000..40bfea3 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupService.java @@ -0,0 +1,82 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 用户组 Service 接口 + * + * @author ZT + */ +public interface BpmUserGroupService { + + /** + * 创建用户组 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createUserGroup(@Valid BpmUserGroupSaveReqVO createReqVO); + + /** + * 更新用户组 + * + * @param updateReqVO 更新信息 + */ + void updateUserGroup(@Valid BpmUserGroupSaveReqVO updateReqVO); + + /** + * 删除用户组 + * + * @param id 编号 + */ + void deleteUserGroup(Long id); + + /** + * 获得用户组 + * + * @param id 编号 + * @return 用户组 + */ + BpmUserGroupDO getUserGroup(Long id); + + /** + * 获得用户组列表 + * + * @param ids 编号 + * @return 用户组列表 + */ + List getUserGroupList(Collection ids); + + /** + * 获得指定状态的用户组列表 + * + * @param status 状态 + * @return 用户组列表 + */ + List getUserGroupListByStatus(Integer status); + + /** + * 获得用户组分页 + * + * @param pageReqVO 分页查询 + * @return 用户组分页 + */ + PageResult getUserGroupPage(BpmUserGroupPageReqVO pageReqVO); + + /** + * 校验用户组们是否有效。如下情况,视为无效: + * 1. 用户组编号不存在 + * 2. 用户组被禁用 + * + * @param ids 用户组编号数组 + */ + void validUserGroups(Collection ids); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupServiceImpl.java new file mode 100644 index 0000000..f0e6ddb --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupServiceImpl.java @@ -0,0 +1,107 @@ +package com.zt.plat.module.bpm.service.definition; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupSaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import com.zt.plat.module.bpm.dal.mysql.definition.BpmUserGroupMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertMap; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.USER_GROUP_IS_DISABLE; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS; + +/** + * 用户组 Service 实现类 + * + * @author ZT + */ +@Service +@Validated +public class BpmUserGroupServiceImpl implements BpmUserGroupService { + + @Resource + private BpmUserGroupMapper userGroupMapper; + + @Override + public Long createUserGroup(BpmUserGroupSaveReqVO createReqVO) { + BpmUserGroupDO userGroup = BeanUtils.toBean(createReqVO, BpmUserGroupDO.class); + userGroupMapper.insert(userGroup); + return userGroup.getId(); + } + + @Override + public void updateUserGroup(BpmUserGroupSaveReqVO updateReqVO) { + // 校验存在 + validateUserGroupExists(updateReqVO.getId()); + // 更新 + BpmUserGroupDO updateObj = BeanUtils.toBean(updateReqVO, BpmUserGroupDO.class); + userGroupMapper.updateById(updateObj); + } + + @Override + public void deleteUserGroup(Long id) { + // 校验存在 + this.validateUserGroupExists(id); + // 删除 + userGroupMapper.deleteById(id); + } + + private void validateUserGroupExists(Long id) { + if (userGroupMapper.selectById(id) == null) { + throw exception(USER_GROUP_NOT_EXISTS); + } + } + + @Override + public BpmUserGroupDO getUserGroup(Long id) { + return userGroupMapper.selectById(id); + } + + @Override + public List getUserGroupList(Collection ids) { + return userGroupMapper.selectBatchIds(ids); + } + + + @Override + public List getUserGroupListByStatus(Integer status) { + return userGroupMapper.selectListByStatus(status); + } + + @Override + public PageResult getUserGroupPage(BpmUserGroupPageReqVO pageReqVO) { + return userGroupMapper.selectPage(pageReqVO); + } + + @Override + public void validUserGroups(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + // 获得用户组信息 + List userGroups = userGroupMapper.selectBatchIds(ids); + Map userGroupMap = convertMap(userGroups, BpmUserGroupDO::getId); + // 校验 + ids.forEach(id -> { + BpmUserGroupDO userGroup = userGroupMap.get(id); + if (userGroup == null) { + throw exception(USER_GROUP_NOT_EXISTS); + } + if (!CommonStatusEnum.ENABLE.getStatus().equals(userGroup.getStatus())) { + throw exception(USER_GROUP_IS_DISABLE, userGroup.getName()); + } + }); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmFormFieldRespDTO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmFormFieldRespDTO.java new file mode 100644 index 0000000..f99ab3a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmFormFieldRespDTO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.bpm.service.definition.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * Bpm 表单的 Field 表单项 Response DTO + * 字段的定义,可见 https://github.com/JakHuang/form-generator/issues/46 文档 + * + * @author ZT + */ +@Data +public class BpmFormFieldRespDTO { + + /** + * 表单标题 + */ + private String label; + /** + * 表单字段的属性名,可自定义 + */ + @JsonProperty(value = "vModel") + private String vModel; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java new file mode 100644 index 0000000..69767d9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.bpm.service.definition.dto; + +import com.zt.plat.module.bpm.enums.definition.BpmModelFormTypeEnum; +import lombok.Data; + +/** + * BPM 流程 MetaInfo Response DTO + * 主要用于 { Model#setMetaInfo(String)} 的存储 + * + * 最终,它的字段和 {@link com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的 + * + * @author ZT + */ +@Data +public class BpmModelMetaInfoRespDTO { + + /** + * 流程图标 + */ + private String icon; + /** + * 流程描述 + */ + private String description; + + /** + * 表单类型 + */ + private Integer formType; + /** + * 表单编号 + * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 + */ + private Long formId; + /** + * 自定义表单的提交路径,使用 Vue 的路由地址 + * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 + */ + private String formCustomCreatePath; + /** + * 自定义表单的查看路径,使用 Vue 的路由地址 + * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 + */ + private String formCustomViewPath; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmProcessDefinitionCreateReqDTO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmProcessDefinitionCreateReqDTO.java new file mode 100644 index 0000000..2132a21 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/definition/dto/BpmProcessDefinitionCreateReqDTO.java @@ -0,0 +1,81 @@ +package com.zt.plat.module.bpm.service.definition.dto; + +import com.zt.plat.module.bpm.enums.definition.BpmModelFormTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 流程定义创建 Request DTO + */ +@Data +public class BpmProcessDefinitionCreateReqDTO { + + // ========== 模型相关 ========== + + /** + * 流程模型的编号 + */ + @NotEmpty(message = "流程模型编号不能为空") + private String modelId; + /** + * 流程标识 + */ + @NotEmpty(message = "流程标识不能为空") + private String key; + /** + * 流程名称 + */ + @NotEmpty(message = "流程名称不能为空") + private String name; + /** + * 流程描述 + */ + private String description; + /** + * 流程分类 + */ + @NotEmpty(message = "流程分类不能为空") + private String category; + /** + * BPMN XML + */ + @NotEmpty(message = "BPMN XML 不能为空") + private byte[] bpmnBytes; + + // ========== 表单相关 ========== + + /** + * 表单类型 + */ + @NotNull(message = "表单类型不能为空") + private Integer formType; + /** + * 动态表单编号 + * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 + */ + private Long formId; + /** + * 表单的配置 + * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 + */ + private String formConf; + /** + * 表单项的数组 + * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 + */ + private List formFields; + /** + * 自定义表单的提交路径,使用 Vue 的路由地址 + * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 + */ + private String formCustomCreatePath; + /** + * 自定义表单的查看路径,使用 Vue 的路由地址 + * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 + */ + private String formCustomViewPath; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/BpmMessageService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/BpmMessageService.java new file mode 100644 index 0000000..1698829 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/BpmMessageService.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.bpm.service.message; + +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; +import jakarta.validation.Valid; + +/** + * BPM 消息 Service 接口 + * + * TODO 芋艿:未来支持消息的可配置;不同的流程,在什么场景下,需要发送什么消息,消息的内容是什么; + * + * @author ZT + */ +public interface BpmMessageService { + + /** + * 发送流程实例被通过的消息 + * + * @param reqDTO 发送信息 + */ + void sendMessageWhenProcessInstanceApprove(@Valid BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO); + + /** + * 发送流程实例被不通过的消息 + * + * @param reqDTO 发送信息 + */ + void sendMessageWhenProcessInstanceReject(@Valid BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO); + + /** + * 发送任务被分配的消息 + * + * @param reqDTO 发送信息 + */ + void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); + + /** + * 发送任务审批超时的消息 + * + * @param reqDTO 发送信息 + */ + void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/BpmMessageServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/BpmMessageServiceImpl.java new file mode 100644 index 0000000..9650cb7 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/BpmMessageServiceImpl.java @@ -0,0 +1,79 @@ +package com.zt.plat.module.bpm.service.message; + +import com.zt.plat.framework.web.config.WebProperties; +import com.zt.plat.module.bpm.convert.message.BpmMessageConvert; +import com.zt.plat.module.bpm.enums.message.BpmMessageEnum; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; +import com.zt.plat.module.system.api.sms.SmsSendApi; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.HashMap; +import java.util.Map; + +/** + * BPM 消息 Service 实现类 + * + * @author ZT + */ +@Service +@Validated +@Slf4j +public class BpmMessageServiceImpl implements BpmMessageService { + + @Resource + private SmsSendApi smsSendApi; + + @Resource + private WebProperties webProperties; + + @Override + public void sendMessageWhenProcessInstanceApprove(BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO) { + Map templateParams = new HashMap<>(); + templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); + templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(), + BpmMessageEnum.PROCESS_INSTANCE_APPROVE.getSmsTemplateCode(), templateParams)).checkError(); + } + + @Override + public void sendMessageWhenProcessInstanceReject(BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO) { + Map templateParams = new HashMap<>(); + templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); + templateParams.put("reason", reqDTO.getReason()); + templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(), + BpmMessageEnum.PROCESS_INSTANCE_REJECT.getSmsTemplateCode(), templateParams)).checkError(); + } + + @Override + public void sendMessageWhenTaskAssigned(BpmMessageSendWhenTaskCreatedReqDTO reqDTO) { + Map templateParams = new HashMap<>(); + templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); + templateParams.put("taskName", reqDTO.getTaskName()); + templateParams.put("startUserNickname", reqDTO.getStartUserNickname()); + templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(), + BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)).checkError(); + } + + @Override + public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) { + Map 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; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceApproveReqDTO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceApproveReqDTO.java new file mode 100644 index 0000000..d06cbf4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceApproveReqDTO.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.bpm.service.message.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * BPM 发送流程实例被通过 Request DTO + */ +@Data +public class BpmMessageSendWhenProcessInstanceApproveReqDTO { + + /** + * 流程实例的编号 + */ + @NotEmpty(message = "流程实例的编号不能为空") + private String processInstanceId; + /** + * 流程实例的名字 + */ + @NotEmpty(message = "流程实例的名字不能为空") + private String processInstanceName; + @NotNull(message = "发起人的用户编号") + private Long startUserId; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceRejectReqDTO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceRejectReqDTO.java new file mode 100644 index 0000000..cf4bd29 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceRejectReqDTO.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.bpm.service.message.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * BPM 发送流程实例被不通过 Request DTO + */ +@Data +public class BpmMessageSendWhenProcessInstanceRejectReqDTO { + + /** + * 流程实例的编号 + */ + @NotEmpty(message = "流程实例的编号不能为空") + private String processInstanceId; + /** + * 流程实例的名字 + */ + @NotEmpty(message = "流程实例的名字不能为空") + private String processInstanceName; + @NotNull(message = "发起人的用户编号") + private Long startUserId; + + /** + * 不通过理由 + */ + @NotEmpty(message = "不通过理由不能为空") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenTaskCreatedReqDTO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenTaskCreatedReqDTO.java new file mode 100644 index 0000000..03adf51 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenTaskCreatedReqDTO.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.bpm.service.message.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * BPM 发送任务被分配 Request DTO + */ +@Data +public class BpmMessageSendWhenTaskCreatedReqDTO { + + /** + * 流程实例的编号 + */ + @NotEmpty(message = "流程实例的编号不能为空") + private String processInstanceId; + /** + * 流程实例的名字 + */ + @NotEmpty(message = "流程实例的名字不能为空") + private String processInstanceName; + @NotNull(message = "发起人的用户编号") + private Long startUserId; + @NotEmpty(message = "发起人的昵称") + private String startUserNickname; + + /** + * 流程任务的编号 + */ + @NotEmpty(message = "流程任务的编号不能为空") + private String taskId; + /** + * 流程任务的名字 + */ + @NotEmpty(message = "流程任务的名字不能为空") + private String taskName; + + /** + * 审批人的用户编号 + */ + @NotNull(message = "审批人的用户编号不能为空") + private Long assigneeUserId; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java new file mode 100644 index 0000000..f9a9067 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java @@ -0,0 +1,41 @@ +package com.zt.plat.module.bpm.service.message.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 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; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/BpmOALeaveService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/BpmOALeaveService.java new file mode 100644 index 0000000..bf19ddb --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/BpmOALeaveService.java @@ -0,0 +1,52 @@ +package com.zt.plat.module.bpm.service.oa; + + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO; +import com.zt.plat.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.oa.BpmOALeaveDO; +import jakarta.validation.Valid; + +/** + * 请假申请 Service 接口 + * + * @author jason + * @author ZT + */ +public interface BpmOALeaveService { + + /** + * 创建请假申请 + * + * @param userId 用户编号 + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createLeave(Long userId, @Valid BpmOALeaveCreateReqVO createReqVO); + + /** + * 更新请假申请的状态 + * + * @param id 编号 + * @param status 结果 + */ + void updateLeaveStatus(Long id, Integer status); + + /** + * 获得请假申请 + * + * @param id 编号 + * @return 请假申请 + */ + BpmOALeaveDO getLeave(Long id); + + /** + * 获得请假申请分页 + * + * @param userId 用户编号 + * @param pageReqVO 分页查询 + * @return 请假申请分页 + */ + PageResult getLeavePage(Long userId, BpmOALeavePageReqVO pageReqVO); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/BpmOALeaveServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/BpmOALeaveServiceImpl.java new file mode 100644 index 0000000..fec6e27 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/BpmOALeaveServiceImpl.java @@ -0,0 +1,89 @@ +package com.zt.plat.module.bpm.service.oa; + +import cn.hutool.core.date.LocalDateTimeUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.bpm.api.task.BpmProcessInstanceApi; +import com.zt.plat.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; +import com.zt.plat.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO; +import com.zt.plat.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.oa.BpmOALeaveDO; +import com.zt.plat.module.bpm.dal.mysql.oa.BpmOALeaveMapper; +import com.zt.plat.module.bpm.enums.task.BpmTaskStatusEnum; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.HashMap; +import java.util.Map; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.OA_LEAVE_NOT_EXISTS; + +/** + * OA 请假申请 Service 实现类 + * + * @author jason + * @author ZT + */ +@Service +@Validated +public class BpmOALeaveServiceImpl implements BpmOALeaveService { + + /** + * OA 请假对应的流程定义 KEY + */ + public static final String PROCESS_KEY = "oa_leave"; + + @Resource + private BpmOALeaveMapper leaveMapper; + + @Resource + private BpmProcessInstanceApi processInstanceApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createLeave(Long userId, BpmOALeaveCreateReqVO createReqVO) { + // 插入 OA 请假单 + long day = LocalDateTimeUtil.between(createReqVO.getStartTime(), createReqVO.getEndTime()).toDays(); + BpmOALeaveDO leave = BeanUtils.toBean(createReqVO, BpmOALeaveDO.class) + .setUserId(userId).setDay(day).setStatus(BpmTaskStatusEnum.RUNNING.getStatus()); + leaveMapper.insert(leave); + + // 发起 BPM 流程 + Map processInstanceVariables = new HashMap<>(); + processInstanceVariables.put("day", day); + String processInstanceId = processInstanceApi.createProcessInstance(userId, + new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY) + .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(leave.getId())) + .setStartUserSelectAssignees(createReqVO.getStartUserSelectAssignees())).getCheckedData(); + + // 将工作流的编号,更新到 OA 请假单中 + leaveMapper.updateById(new BpmOALeaveDO().setId(leave.getId()).setProcessInstanceId(processInstanceId)); + return leave.getId(); + } + + @Override + public void updateLeaveStatus(Long id, Integer status) { + validateLeaveExists(id); + leaveMapper.updateById(new BpmOALeaveDO().setId(id).setStatus(status)); + } + + private void validateLeaveExists(Long id) { + if (leaveMapper.selectById(id) == null) { + throw exception(OA_LEAVE_NOT_EXISTS); + } + } + + @Override + public BpmOALeaveDO getLeave(Long id) { + return leaveMapper.selectById(id); + } + + @Override + public PageResult getLeavePage(Long userId, BpmOALeavePageReqVO pageReqVO) { + return leaveMapper.selectPage(userId, pageReqVO); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/listener/BpmOALeaveStatusListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/listener/BpmOALeaveStatusListener.java new file mode 100644 index 0000000..122a6ff --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/oa/listener/BpmOALeaveStatusListener.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.service.oa.listener; + +import com.zt.plat.module.bpm.api.event.BpmProcessInstanceStatusEvent; +import com.zt.plat.module.bpm.api.event.BpmProcessInstanceStatusEventListener; +import com.zt.plat.module.bpm.service.oa.BpmOALeaveService; +import com.zt.plat.module.bpm.service.oa.BpmOALeaveServiceImpl; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * OA 请假单的结果的监听器实现类 + * + * @author ZT + */ +@Component +public class BpmOALeaveStatusListener extends BpmProcessInstanceStatusEventListener { + + @Resource + private BpmOALeaveService leaveService; + + @Override + protected List getProcessDefinitionKey() { + return List.of(BpmOALeaveServiceImpl.PROCESS_KEY); + } + + @Override + protected void onEvent(BpmProcessInstanceStatusEvent event) { + leaveService.updateLeaveStatus(Long.parseLong(event.getBusinessKey()), event.getStatus()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java new file mode 100644 index 0000000..e5f06b6 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -0,0 +1,60 @@ +package com.zt.plat.module.bpm.service.task; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; +import jakarta.validation.constraints.NotEmpty; +import org.flowable.bpmn.model.FlowNode; + +import java.util.Collection; + +/** + * 流程抄送 Service 接口 + * + * 现在是在审批的时候进行流程抄送 + */ +public interface BpmProcessInstanceCopyService { + + /** + * 【管理员】流程实例的抄送 + * + * @param userIds 抄送的用户编号 + * @param reason 抄送意见 + * @param taskId 流程任务编号 + */ + void createProcessInstanceCopy(Collection 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 userIds, String reason, + @NotEmpty(message = "流程实例编号不能为空") String processInstanceId, + @NotEmpty(message = "流程活动编号不能为空") String activityId, + @NotEmpty(message = "流程活动名字不能为空") String activityName, + String taskId); + + /** + * 获得抄送的流程的分页 + * + * @param userId 当前登录用户 + * @param pageReqVO 分页请求 + * @return 抄送的分页结果 + */ + PageResult getProcessInstanceCopyPage(Long userId, + BpmProcessInstanceCopyPageReqVO pageReqVO); + + /** + * 删除抄送流程 + * + * @param processInstanceId 流程实例 ID + */ + void deleteProcessInstanceCopy(String processInstanceId); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java new file mode 100644 index 0000000..dd842b9 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -0,0 +1,96 @@ +package com.zt.plat.module.bpm.service.task; + +import cn.hutool.core.util.ObjectUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; +import com.zt.plat.module.bpm.dal.mysql.task.BpmProcessInstanceCopyMapper; +import com.zt.plat.module.bpm.enums.ErrorCodeConstants; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; + +/** + * 流程抄送 Service 实现类 + * + * @author kyle + */ +@Service +@Validated +@Slf4j +public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopyService { + + @Resource + private BpmProcessInstanceCopyMapper processInstanceCopyMapper; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmTaskService taskService; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessInstanceService processInstanceService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessDefinitionService processDefinitionService; + + @Override + public void createProcessInstanceCopy(Collection userIds, String reason, String taskId) { + Task task = taskService.getTask(taskId); + if (ObjectUtil.isNull(task)) { + throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); + } + // 执行抄送 + createProcessInstanceCopy(userIds, reason, + task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getId(), task.getName()); + } + + @Override + public void createProcessInstanceCopy(Collection 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.2 校验流程定义存在 + ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( + processInstance.getProcessDefinitionId()); + if (processDefinition == null) { + throw exception(ErrorCodeConstants.PROCESS_DEFINITION_NOT_EXISTS); + } + + // 2. 创建抄送流程 + List copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() + .setUserId(userId).setReason(reason).setStartUserId(Long.valueOf(processInstance.getStartUserId())) + .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) + .setCategory(processDefinition.getCategory()).setTaskId(taskId) + .setActivityId(activityId).setActivityName(activityName) + .setProcessDefinitionId(processInstance.getProcessDefinitionId())); + processInstanceCopyMapper.insertBatch(copyList); + } + + @Override + public PageResult getProcessInstanceCopyPage(Long userId, + BpmProcessInstanceCopyPageReqVO pageReqVO) { + return processInstanceCopyMapper.selectPage(userId, pageReqVO); + } + + @Override + public void deleteProcessInstanceCopy(String processInstanceId) { + processInstanceCopyMapper.deleteByProcessInstanceId(processInstanceId); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceService.java new file mode 100644 index 0000000..98af5ee --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceService.java @@ -0,0 +1,191 @@ +package com.zt.plat.module.bpm.service.task; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.*; +import jakarta.validation.Valid; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * 流程实例 Service 接口 + * + * @author ZT + */ +public interface BpmProcessInstanceService { + + // ========== Query 查询相关方法 ========== + + /** + * 获得流程实例 + * + * @param id 流程实例的编号 + * @return 流程实例 + */ + ProcessInstance getProcessInstance(String id); + + /** + * 获得流程实例列表 + * + * @param ids 流程实例的编号集合 + * @return 流程实例列表 + */ + List getProcessInstances(Set ids); + + /** + * 获得流程实例 Map + * + * @param ids 流程实例的编号集合 + * @return 流程实例列表 Map + */ + default Map getProcessInstanceMap(Set ids) { + return convertMap(getProcessInstances(ids), ProcessInstance::getProcessInstanceId); + } + + /** + * 获得历史的流程实例 + * + * @param id 流程实例的编号 + * @return 历史的流程实例 + */ + HistoricProcessInstance getHistoricProcessInstance(String id); + + /** + * 获得历史的流程实例列表 + * + * @param ids 流程实例的编号集合 + * @return 历史的流程实例列表 + */ + List getHistoricProcessInstances(Set ids); + + /** + * 获得历史的流程实例 Map + * + * @param ids 流程实例的编号集合 + * @return 历史的流程实例列表 Map + */ + default Map getHistoricProcessInstanceMap(Set ids) { + return convertMap(getHistoricProcessInstances(ids), HistoricProcessInstance::getId); + } + + /** + * 获得流程实例的分页 + * + * @param userId 用户编号 + * @param pageReqVO 分页请求 + * @return 流程实例的分页 + */ + PageResult getProcessInstancePage(Long userId, + @Valid BpmProcessInstancePageReqVO pageReqVO); + + /** + * 获取审批详情。 + *

+ * 可以是准备发起的流程、进行中的流程、已经结束的流程 + * + * @param loginUserId 登录人的用户编号 + * @param reqVO 请求信息 + * @return 流程实例的进度 + */ + BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); + + /** + * 获取下一个执行节点信息 + * + * @param loginUserId 登录人的用户编号 + * @param reqVO 请求信息 + * @return 下一个执行节点信息 + */ + List getNextApprovalNodes(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); + + /** + * 获取流程实例的 BPMN 模型视图 + * + * @param id 流程实例的编号 + * @return BPMN 模型视图 + */ + BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id); + + // ========== Update 写入相关方法 ========== + + /** + * 创建流程实例(提供给前端) + * + * @param userId 用户编号 + * @param createReqVO 创建信息 + * @return 实例的编号 + */ + String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO); + + /** + * 创建流程实例(提供给内部) + * + * @param userId 用户编号 + * @param createReqDTO 创建信息 + * @return 实例的编号 + */ + String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO); + + /** + * 发起人取消流程实例 + * + * @param userId 用户编号 + * @param cancelReqVO 取消信息 + */ + void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO); + + /** + * 管理员取消流程实例 + * + * @param userId 用户编号 + * @param cancelReqVO 取消信息 + */ + void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO); + + /** + * 更新 ProcessInstance 为不通过 + * + * @param processInstance 流程实例 + * @param reason 理由。例如说,审批不通过时,需要传递该值 + */ + void updateProcessInstanceReject(ProcessInstance processInstance, String reason); + + /** + * 更新 ProcessInstance 的变量 + * + * @param id 流程编号 + * @param variables 流程变量 + */ + void updateProcessInstanceVariables(String id, Map variables); + + /** + * 删除 ProcessInstance 的变量 + * + * @param id 流程编号 + * @param variableNames 流程变量名 + */ + void removeProcessInstanceVariables(String id, Collection variableNames); + + // ========== Event 事件相关方法 ========== + + /** + * 处理 ProcessInstance 完成事件,例如说:审批通过、不通过、取消 + * + * @param instance 流程任务 + */ + void processProcessInstanceCompleted(ProcessInstance instance); + + /** + * 处理 ProcessInstance 开始事件,例如说:流程前置通知 + * + * @param instance 流程任务 + */ + void processProcessInstanceCreated(ProcessInstance instance); +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceServiceImpl.java new file mode 100644 index 0000000..fc01ac7 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -0,0 +1,964 @@ +package com.zt.plat.module.bpm.service.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.business.core.util.DeptUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.date.DateUtils; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.common.util.object.ObjectUtils; +import com.zt.plat.framework.common.util.object.PageUtils; +import com.zt.plat.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.*; +import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import com.zt.plat.module.bpm.convert.task.BpmProcessInstanceConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.dal.redis.BpmProcessIdRedisDAO; +import com.zt.plat.module.bpm.enums.ErrorCodeConstants; +import com.zt.plat.module.bpm.enums.definition.BpmModelTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum; +import com.zt.plat.module.bpm.enums.task.BpmProcessInstanceStatusEnum; +import com.zt.plat.module.bpm.enums.task.BpmReasonEnum; +import com.zt.plat.module.bpm.enums.task.BpmTaskStatusEnum; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import com.zt.plat.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.SimpleModelUtils; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.bpm.service.message.BpmMessageService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +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.engine.runtime.ProcessInstanceBuilder; +import org.flowable.task.api.Task; +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 java.util.*; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.*; +import static com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.*; +import static com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; +import static com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseNodeType; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.flowable.bpmn.constants.BpmnXMLConstants.*; + +/** + * 流程实例 Service 实现类 + *

+ * ProcessDefinition & ProcessInstance & Execution & Task 的关系: + * 1. + *

+ * HistoricProcessInstance & ProcessInstance 的关系: + * 1. + *

+ * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 + * + * @author ZT + */ +@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; + + @Resource + private BpmProcessIdRedisDAO processIdRedisDAO; + + // ========== Query 查询相关方法 ========== + + @Override + public ProcessInstance getProcessInstance(String id) { + return runtimeService.createProcessInstanceQuery() + .includeProcessVariables() + .processInstanceId(id) + .singleResult(); + } + + @Override + public List getProcessInstances(Set ids) { + return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).includeProcessVariables().list(); + } + + @Override + public HistoricProcessInstance getHistoricProcessInstance(String id) { + return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables() + .singleResult(); + } + + @Override + public List getHistoricProcessInstances(Set ids) { + return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).includeProcessVariables() + .list(); + } + + private Map 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 processVariables = new HashMap<>(); // 流程变量 + // 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); + // 合并 DB 和前端传递的流量变量,以前端的为主 + if (CollUtil.isNotEmpty(historicProcessInstance.getProcessVariables())) { + processVariables.putAll(historicProcessInstance.getProcessVariables()); + } + } + if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) { + processVariables.putAll(reqVO.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 endActivityNodes = null; // 已结束的审批信息 + List runActivityNodes = null; // 进行中的审批信息 + List activities = null; // 流程实例列表 + if (reqVO.getProcessInstanceId() != null) { + activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId()); + List 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 计算当前登录用户的待办任务 + BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId()); + // 3.2 预测未运行节点的审批信息 + List simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, + processDefinitionInfo, + processVariables, activities); + // 3.3 如果是发起动作,activityId 为开始节点,不校验审批人自选节点 + if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) { + simulateActivityNodes.removeIf(node -> + BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy())); + } + + // 4. 拼接最终数据 + return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, + processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask); + } + + @Override + public List getNextApprovalNodes(Long loginUserId, BpmApprovalDetailReqVO reqVO) { + // 1.1 校验任务存在,且是当前用户的 + Task task = taskService.validateTask(loginUserId, reqVO.getTaskId()); + // 1.2 校验流程实例存在 + ProcessInstance instance = getProcessInstance(task.getProcessInstanceId()); + if (instance == null) { + throw exception(PROCESS_INSTANCE_NOT_EXISTS); + } + HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(task.getProcessInstanceId()); + if (historicProcessInstance == null) { + throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); + } + // 1.3 校验BpmnModel + BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(task.getProcessDefinitionId()); + if (bpmnModel == null) { + return null; + } + + // 2. 设置流程变量 + Map processVariables = new HashMap<>(); + // 2.1 获取历史中流程变量 + if (CollUtil.isNotEmpty(historicProcessInstance.getProcessVariables())) { + processVariables.putAll(historicProcessInstance.getProcessVariables()); + } + // 2.2 合并前端传递的流程变量,以前端为准 + if (CollUtil.isNotEmpty(reqVO.getProcessVariables())) { + processVariables.putAll(reqVO.getProcessVariables()); + } + + // 3. 获取下一个将要执行的节点集合 + FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); + List nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables); + List nextActivityNodes = convertList(nextFlowNodes, node -> new ActivityNode().setId(node.getId()) + .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) + .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) + .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) + .setCandidateUserIds(getTaskCandidateUserList(bpmnModel, node.getId(), + loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables))); + if (CollUtil.isNotEmpty(nextActivityNodes)) { + return nextActivityNodes; + } + + // 4. 拼接基础信息 + Map userMap = adminUserApi.getUserMap( + convertSetByFlatMap(nextActivityNodes, ActivityNode::getCandidateUserIds, Collection::stream)); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + nextActivityNodes.forEach(node -> node.setCandidateUsers(convertList(node.getCandidateUserIds(), userId -> { + AdminUserRespDTO user = userMap.get(userId); + if (user != null) { + return BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap); + } + return null; + }))); + return nextActivityNodes; + } + + @Override + @SuppressWarnings("unchecked") + public PageResult getProcessInstancePage(Long userId, + BpmProcessInstancePageReqVO pageReqVO) { + // 1. 构建查询条件 + 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])); + } + if (ArrayUtil.isNotEmpty(pageReqVO.getEndTime())) { + processInstanceQuery.finishedAfter(DateUtils.of(pageReqVO.getEndTime()[0])); + processInstanceQuery.finishedBefore(DateUtils.of(pageReqVO.getEndTime()[1])); + } + // 表单字段查询 + Map formFieldsParams = JsonUtils.parseObject(pageReqVO.getFormFieldsParams(), Map.class); + if (CollUtil.isNotEmpty(formFieldsParams)) { + formFieldsParams.forEach((key, value) -> { + if (StrUtil.isEmpty(String.valueOf(value))) { + return; + } + // TODO @lesan:应支持多种类型的查询方式,目前只有字符串全等 + processInstanceQuery.variableValueEquals(key, value); + }); + } + + // 2.1 查询数量 + long processInstanceCount = processInstanceQuery.count(); + if (processInstanceCount == 0) { + return PageResult.empty(processInstanceCount); + } + // 2.2 查询列表 + List processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), + pageReqVO.getPageSize()); + return new PageResult<>(processInstanceList, processInstanceCount); + } + + /** + * 拼接审批详情的最终数据 + *

+ * 主要是,拼接审批人的用户信息、部门信息 + */ + private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO, + BpmnModel bpmnModel, + ProcessDefinition processDefinition, + BpmProcessDefinitionInfoDO processDefinitionInfo, + HistoricProcessInstance processInstance, + Integer processInstanceStatus, + List endApprovalNodeInfos, + List runningApprovalNodeInfos, + List simulateApprovalNodeInfos, + BpmTaskRespVO todoTask) { + // 1. 获取所有需要读取用户信息的 userIds + List approveNodes = newArrayList( + asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos)); + Set userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes, todoTask); + Map userMap = adminUserApi.getUserMap(userIds); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::getDeptId)); + + // 2. 表单权限 + String taskId = reqVO.getTaskId() == null && todoTask != null ? todoTask.getId() : reqVO.getTaskId(); + Map formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), taskId); + + // 3. 拼接数据 + return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, + processDefinitionInfo, processInstance, + processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap); + } + + /** + * 获得【已结束】的活动节点们 + */ + private List getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel, + BpmProcessDefinitionInfoDO processDefinitionInfo, + HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus, + List activities, List tasks) { + // 遍历 tasks 列表,只处理已结束的 UserTask + // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 + List endTasks = filterList(tasks, task -> task.getEndTime() != null); + List 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()) + ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() + : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别 + BpmSimpleModelNodeTypeEnum.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 endActivities = filterList(activities, activity -> activity.getEndTime() != null + && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_CALL_ACTIVITY, 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(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName()) + .setNodeType(BpmSimpleModelNodeTypeEnum.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(BpmSimpleModelNodeTypeEnum.END_NODE.getName()) + .setNodeType(BpmSimpleModelNodeTypeEnum.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); + } + // CallActivity + if (ELEMENT_CALL_ACTIVITY.equals(activity.getActivityType())) { + ActivityNode callActivity = new ActivityNode().setId(activity.getId()) + .setName(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getName()) + .setNodeType(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType()).setStatus(processInstanceStatus) + .setStartTime(DateUtils.of(activity.getStartTime())) + .setEndTime(DateUtils.of(activity.getEndTime())) + .setProcessInstanceId(activity.getProcessInstanceId()); + approvalNodes.add(callActivity); + } + }); + + // 按照时间排序 + approvalNodes.sort(Comparator.comparing(ActivityNode::getStartTime)); + return approvalNodes; + } + + /** + * 获得【进行中】的活动节点们 + */ + private List getRunApproveNodeList(Long startUserId, + BpmnModel bpmnModel, + ProcessDefinition processDefinition, + Map processVariables, + List activities, + List tasks) { + // 构建运行中的任务、子流程,基于 activityId 分组 + List runActivities = filterList(activities, activity -> activity.getEndTime() == null + && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER, ELEMENT_CALL_ACTIVITY))); + Map> runningTaskMap = convertMultiMap(runActivities, + HistoricActivityInstance::getActivityId); + + // 按照 activityId 分组,构建 ApprovalNodeInfo 节点 + Map taskMap = convertMap(tasks, HistoricTaskInstance::getId); + return convertList(runningTaskMap.entrySet(), entry -> { + String activityId = entry.getKey(); + List 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(ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”和"子流程"的识别 + BpmSimpleModelNodeTypeEnum.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()); + // 特殊情况:子流程节点 ChildProcess 仅存在于 activity 中,并且没有自身的 task,需要跳过执行 + // TODO @芋艿:后续看看怎么优化! + if (task == null) { + continue; + } + activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); + // 加签子任务,需要过滤掉已经完成的加签子任务 + List 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 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())); + } + if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(activityNode.getNodeType())) { + activityNode.setProcessInstanceId(firstActivity.getProcessInstanceId()); + } + return activityNode; + }); + } + + /** + * 获得【预测(未来)】的活动节点们 + */ + private List getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, + BpmProcessDefinitionInfoDO processDefinitionInfo, + Map processVariables, + List activities) { + // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance + // 包括了历史的操作,不是只有 startEvent 到当前节点的记录 + Set runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); + // 情况一:BPMN 设计器 + if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { + List 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 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 processVariables, + BpmSimpleModelNodeVO node, Set 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(), + BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType(), + BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType(), + BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE.getType())) { + List candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), + startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); + activityNode.setCandidateUserIds(candidateUserIds); + return activityNode; + } + + // 2. 结束节点 + if (BpmSimpleModelNodeTypeEnum.END_NODE.getType().equals(node.getType())) { + return activityNode; + } + + // 3. 抄送节点 + if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人 + BpmSimpleModelNodeTypeEnum.COPY_NODE.getType().equals(node.getType())) { + List candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), + startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); + activityNode.setCandidateUserIds(candidateUserIds); + return activityNode; + } + + // 4. 子流程节点 + if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(node.getType())) { + return activityNode; + } + return null; + } + + private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, + BpmProcessDefinitionInfoDO processDefinitionInfo, Map processVariables, + FlowElement node, Set 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(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName()) + .setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()); + } + + // 2. 审批节点 + if (node instanceof UserTask) { + List candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), + startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); + return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) + .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) + .setCandidateUserIds(candidateUserIds); + } + + // 3. 结束节点 + if (node instanceof EndEvent) { + return activityNode.setName(BpmSimpleModelNodeTypeEnum.END_NODE.getName()) + .setNodeType(BpmSimpleModelNodeTypeEnum.END_NODE.getType()); + } + return null; + } + + private List getTaskCandidateUserList(BpmnModel bpmnModel, String activityId, + Long startUserId, String processDefinitionId, Map processVariables) { + Set 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 activities = taskService.getActivityListByProcessInstanceId(id); + List tasks = taskService.getTaskListByProcessInstanceId(id, true); + + // 2.1 拼接进度信息 + Set unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, + activityInstance -> activityInstance.getEndTime() == null); + Set finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, + activityInstance -> activityInstance.getEndTime() != null + && ObjectUtil.notEqual(activityInstance.getActivityType(), + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); + Set finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, + activityInstance -> activityInstance.getEndTime() != null + && ObjectUtil.equals(activityInstance.getActivityType(), + BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); + // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉 + finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds); + // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。 + // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点 + Set 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 userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks); + Map userMap = adminUserApi.getUserMap(userIds); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), DeptUtil::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) { + return FlowableUtils.executeAuthenticatedUserId(userId, () -> { + // 获得流程定义 + ProcessDefinition definition = processDefinitionService + .getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); + // 发起流程 + return createProcessInstance0(userId, definition, createReqDTO.getVariables(), + createReqDTO.getBusinessKey(), + createReqDTO.getStartUserSelectAssignees()); + }); + } + + private String createProcessInstance0(Long userId, ProcessDefinition definition, + Map variables, String businessKey, + Map> 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(userId, definition, startUserSelectAssignees, variables); + + // 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()); + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED, true); // 跳过表达式需要添加此变量为 true,不影响没配置 skipExpression 的节点 + if (CollUtil.isNotEmpty(startUserSelectAssignees)) { + // 设置流程变量,发起人自选审批人 + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, + startUserSelectAssignees); + } + + // 3. 创建流程 + ProcessInstanceBuilder processInstanceBuilder = runtimeService.createProcessInstanceBuilder() + .processDefinitionId(definition.getId()) + .businessKey(businessKey) + .variables(variables); + // 3.1 创建流程 ID + BpmModelMetaInfoVO.ProcessIdRule processIdRule = processDefinitionInfo.getProcessIdRule(); + if (processIdRule != null && Boolean.TRUE.equals(processIdRule.getEnable())) { + processInstanceBuilder.predefineProcessInstanceId(processIdRedisDAO.generate(processIdRule)); + } + // 3.2 流程名称 + BpmModelMetaInfoVO.TitleSetting titleSetting = processDefinitionInfo.getTitleSetting(); + if (titleSetting != null && Boolean.TRUE.equals(titleSetting.getEnable())) { + AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + Map cloneVariables = new HashMap<>(variables); + cloneVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, user.getNickname()); + cloneVariables.put(BpmnVariableConstants.PROCESS_START_TIME, DateUtil.now()); + cloneVariables.put(BpmnVariableConstants.PROCESS_DEFINITION_NAME, definition.getName().trim()); + processInstanceBuilder.name(StrUtil.format(titleSetting.getTitle(), cloneVariables)); + } else { + processInstanceBuilder.name(definition.getName().trim()); + } + // 3.3 发起流程实例 + ProcessInstance instance = processInstanceBuilder.start(); + return instance.getId(); + } + + private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition, + Map> startUserSelectAssignees, + Map variables) { + // 1. 获取预测的节点信息 + BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO() + .setProcessDefinitionId(definition.getId()) + .setProcessVariables(variables)); + List activityNodes = detailRespVO.getActivityNodes(); + if (CollUtil.isEmpty(activityNodes)) { + return; + } + + // 2.1 移除掉不是发起人自选审批人节点 + activityNodes.removeIf(task -> + ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy())); + // 2.2 流程发起时要先获取当前流程的预测走向节点,发起时只校验预测的节点发起人自选审批人的审批人和抄送人是否都配置了 + activityNodes.forEach(task -> { + List 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 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); + } + // 1.3 校验允许撤销审批中的申请 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService + .getProcessDefinitionInfo(instance.getProcessDefinitionId()); + Assert.notNull(processDefinitionInfo, "流程定义({})不存在", processDefinitionInfo); + if (processDefinitionInfo.getAllowCancelRunningProcess() != null // 防止未配置 AllowCancelRunningProcess , 默认为可取消 + && Boolean.FALSE.equals(processDefinitionInfo.getAllowCancelRunningProcess())) { + throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW); + } + // 1.4 子流程不允许取消 + if (StrUtil.isNotBlank(instance.getSuperExecutionId())) { + throw exception(PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW); + } + + // 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. 取消所有子流程 + List childProcessInstances = runtimeService.createProcessInstanceQuery() + .superProcessInstanceId(id).list(); + childProcessInstances.forEach(processInstance -> updateProcessInstanceCancel( + processInstance.getProcessInstanceId(), BpmReasonEnum.CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS.getReason())); + + // 3. 结束流程 + taskService.moveTaskToEnd(id, reason); + } + + @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)); + } + + @Override + public void updateProcessInstanceVariables(String id, Map variables) { + runtimeService.setVariables(id, variables); + } + + @Override + public void removeProcessInstanceVariables(String id, Collection variableNames) { + runtimeService.removeVariables(id, variableNames); + } + + // ========== 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)); + + // 4. 流程后置通知 + if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService. + getProcessDefinitionInfo(instance.getProcessDefinitionId()); + if (ObjUtil.isNotNull(processDefinitionInfo) && + ObjUtil.isNotNull(processDefinitionInfo.getProcessAfterTriggerSetting())) { + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessAfterTriggerSetting(); + + BpmHttpRequestUtils.executeBpmHttpRequest(instance, + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); + } + } + }); + } + + @Override + public void processProcessInstanceCreated(ProcessInstance instance) { + // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 + FlowableUtils.execute(instance.getTenantId(), () -> { + // 流程前置通知 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService. + getProcessDefinitionInfo(instance.getProcessDefinitionId()); + if (ObjUtil.isNull(processDefinitionInfo) || + ObjUtil.isNull(processDefinitionInfo.getProcessBeforeTriggerSetting())) { + return; + } + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessBeforeTriggerSetting(); + BpmHttpRequestUtils.executeBpmHttpRequest(instance, + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); + }); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskService.java new file mode 100644 index 0000000..8211bfe --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskService.java @@ -0,0 +1,316 @@ +package com.zt.plat.module.bpm.service.task; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.*; +import com.zt.plat.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; +import jakarta.validation.Valid; +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 java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 流程任务实例 Service 接口 + * + * @author jason + * @author ZT + */ +public interface BpmTaskService { + + // ========== Query 查询相关方法 ========== + + /** + * 获得待办的流程任务分页 + * + * @param userId 用户编号 + * @param pageReqVO 分页请求 + * @return 流程任务分页 + */ + PageResult getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO); + + /** + * 获得用户(待办)的任务: + * 1. 根据 taskId 查询待办任务 + * 2. 如果任务不存在(或者已审核),获取指定流程下,首个需要处理任务 + * + * @param userId 用户编号 + * @param taskId 任务编号 + * @param processInstanceId 流程实例编号 + * @return 待办任务 + */ + BpmTaskRespVO getTodoTask(Long userId, String taskId, String processInstanceId); + + /** + * 获得已办的流程任务分页 + * + * @param userId 用户编号 + * @param pageReqVO 分页请求 + * @return 流程任务分页 + */ + PageResult getTaskDonePage(Long userId, BpmTaskPageReqVO pageReqVO); + + /** + * 获得全部的流程任务分页 + * + * @param userId 用户编号 + * @param pageReqVO 分页请求 + * @return 流程任务分页 + */ + PageResult getTaskPage(Long userId, BpmTaskPageReqVO pageReqVO); + + /** + * 获得流程任务 Map + * + * @param processInstanceIds 流程实例的编号数组 + * @return 流程任务 Map + */ + default Map> getTaskMapByProcessInstanceIds(List processInstanceIds) { + return CollectionUtils.convertMultiMap(getTasksByProcessInstanceIds(processInstanceIds), + Task::getProcessInstanceId); + } + + /** + * 获得流程任务列表 + * + * @param processInstanceIds 流程实例的编号数组 + * @return 流程任务列表 + */ + List getTasksByProcessInstanceIds(List processInstanceIds); + + /** + * 获得指定流程实例的流程任务列表,包括所有状态的 + * + * @param processInstanceId 流程实例的编号 + * @param asc 是否升序 + * @return 流程任务列表 + */ + List getTaskListByProcessInstanceId(String processInstanceId, Boolean asc); + + /** + * 校验任务是否存在,并且是否是分配给自己的任务 + * + * @param userId 用户 id + * @param taskId task id + */ + Task validateTask(Long userId, String taskId); + + /** + * 获取任务 + * + * @param id 任务编号 + * @return 任务 + */ + Task getTask(String id); + + /** + * 获取历史任务 + * + * @param id 任务编号 + * @return 历史任务 + */ + HistoricTaskInstance getHistoricTask(String id); + + /** + * 获取历史任务列表 + * + * @param taskIds 任务编号集合 + * @return 历史任务列表 + */ + List getHistoricTasks(Collection taskIds); + + /** + * 根据条件查询正在进行中的任务 + * + * @param processInstanceId 流程实例编号,不允许为空 + * @param assigned 是否分配了审批人,允许空 + * @param taskDefineKey 任务定义 Key,允许空 + */ + List getRunningTaskListByProcessInstanceId(String processInstanceId, + Boolean assigned, + String taskDefineKey); + + /** + * 获取当前任务的可退回的 UserTask 集合 + * + * @param id 当前的任务 ID + * @return 可以退回的节点列表 + */ + List getUserTaskListByReturn(String id); + + /** + * 获取指定任务的子任务列表(多层) + * + * @param parentTaskId 父任务 ID + * @param tasks 任务列表 + * @return 子任务列表 + */ + List getAllChildrenTaskListByParentTaskId(String parentTaskId, List tasks); + + /** + * 获取指定任务的子任务列表 + * + * @param parentTaskId 父任务ID + * @return 子任务列表 + */ + List getTaskListByParentTaskId(String parentTaskId); + + /** + * 获得指定流程实例的活动实例列表 + * + * @param processInstanceId 流程实例的编号 + * @return 活动实例列表 + */ + List getActivityListByProcessInstanceId(String processInstanceId); + + /** + * 获得执行编号对应的活动实例 + * + * @param executionId 执行编号 + * @return 活动实例 + */ + List getHistoricActivityListByExecutionId(String executionId); + + // ========== Update 写入相关方法 ========== + + /** + * 通过任务 + * + * @param userId 用户编号 + * @param reqVO 通过请求 + */ + void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO); + + /** + * 不通过任务 + * + * @param userId 用户编号 + * @param reqVO 不通过请求 + */ + void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO); + + /** + * 将流程任务分配给指定用户 + * + * @param userId 用户编号 + * @param reqVO 分配请求 + */ + void transferTask(Long userId, BpmTaskTransferReqVO reqVO); + + /** + * 将指定流程实例的、进行中的流程任务,移动到结束节点 + * + * @param processInstanceId 流程编号 + * @param reason 原因 + */ + void moveTaskToEnd(String processInstanceId, String reason); + + /** + * 将任务退回到指定的 targetDefinitionKey 位置 + * + * @param userId 用户编号 + * @param reqVO 退回的任务key和当前所在的任务ID + */ + void returnTask(Long userId, BpmTaskReturnReqVO reqVO); + + /** + * 将指定任务委派给其他人处理,等接收人处理后再回到原审批人手中审批 + * + * @param userId 用户编号 + * @param reqVO 被委派人和被委派的任务编号理由参数 + */ + void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO); + + /** + * 任务加签 + * + * @param userId 被加签的用户和任务 ID,加签类型 + * @param reqVO 当前用户 ID + */ + void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO); + + /** + * 任务减签 + * + * @param userId 当前用户ID + * @param reqVO 被减签的任务 ID,理由 + */ + void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO); + + /** + * 抄送任务 + * + * @param userId 用户编号 + * @param reqVO 通过请求 + */ + void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO); + + // ========== Event 事件相关方法 ========== + + /** + * 处理 Task 创建事件,目前是 + *

+ * 1. 更新它的状态为审批中 + * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 + *

+ * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后 + * + * @param task 任务实体 + */ + void processTaskCreated(Task task); + + /** + * 处理 Task 取消事件,目前是更新它的状态为已取消 + * + * @param taskId 任务的编号 + */ + void processTaskCanceled(String taskId); + + /** + * 处理 Task 设置审批人事件,目前是发送审批消息 + * + * @param task 任务实体 + */ + void processTaskAssigned(Task task); + + /** + * 处理 Task 完成事件,目前是发送任务后置通知 + * + * @param task 任务实体 + */ + void processTaskCompleted(Task task); + + /** + * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 + * + * @param processInstanceId 流程示例编号 + * @param taskDefineKey 任务 Key + * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum} + */ + void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType); + + /** + * 处理 ChildProcess 子流程的审批超时事件 + * + * @param processInstanceId 流程示例编号 + * @param taskDefineKey 任务 Key + */ + void processChildProcessTimeout(String processInstanceId, String taskDefineKey); + + /** + * 触发流程任务 (ReceiveTask) 的执行 + *

+ * 1. Simple 模型 HTTP 回调请求触发器节点的回调,触发流程继续执行 + * 2. Simple 模型延迟器节点,到时触发流程继续执行 + * + * @param processInstanceId 流程示例编号 + * @param taskDefineKey 任务 Key + */ + void triggerTask(String processInstanceId, String taskDefineKey); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskServiceImpl.java new file mode 100644 index 0000000..1c44b8c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskServiceImpl.java @@ -0,0 +1,1535 @@ +package com.zt.plat.module.bpm.service.task; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.*; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.date.DateUtils; +import com.zt.plat.framework.common.util.number.NumberUtils; +import com.zt.plat.framework.common.util.object.ObjectUtils; +import com.zt.plat.framework.common.util.object.PageUtils; +import com.zt.plat.framework.datapermission.core.annotation.DataPermission; +import com.zt.plat.framework.web.core.util.WebFrameworkUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.*; +import com.zt.plat.module.bpm.convert.task.BpmTaskConvert; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.enums.definition.*; +import com.zt.plat.module.bpm.enums.task.BpmCommentTypeEnum; +import com.zt.plat.module.bpm.enums.task.BpmReasonEnum; +import com.zt.plat.module.bpm.enums.task.BpmTaskSignTypeEnum; +import com.zt.plat.module.bpm.enums.task.BpmTaskStatusEnum; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.service.definition.BpmFormService; +import com.zt.plat.module.bpm.service.definition.BpmModelService; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.bpm.service.message.BpmMessageService; +import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.*; +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.ActivityInstance; +import org.flowable.engine.runtime.Execution; +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; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.util.*; +import java.util.stream.Stream; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.*; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.*; +import static com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; +import static com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; +import static com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE; +import static com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; + +/** + * 流程任务实例 Service 实现类 + * + * @author ZT + * @author jason + */ +@Slf4j +@Service +public class BpmTaskServiceImpl implements BpmTaskService { + + @Resource + private TaskService taskService; + @Resource + private HistoryService historyService; + @Resource + private RuntimeService runtimeService; + @Resource + private ManagementService managementService; + + @Resource + private BpmProcessInstanceService processInstanceService; + @Resource + private BpmProcessDefinitionService bpmProcessDefinitionService; + @Resource + private BpmProcessInstanceCopyService processInstanceCopyService; + @Resource + private BpmModelService modelService; + @Resource + private BpmMessageService messageService; + @Resource + private BpmFormService formService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + // ========== Query 查询相关方法 ========== + + @Override + public PageResult getTaskTodoPage(Long userId, BpmTaskPageReqVO pageVO) { + TaskQuery taskQuery = taskService.createTaskQuery() + .taskAssignee(String.valueOf(userId)) // 分配给自己 + .active() + .includeProcessVariables() + .orderByTaskCreateTime().desc(); // 创建时间倒序 + if (StrUtil.isNotBlank(pageVO.getName())) { + taskQuery.taskNameLike("%" + pageVO.getName() + "%"); + } + if (StrUtil.isNotEmpty(pageVO.getCategory())) { + taskQuery.taskCategory(pageVO.getCategory()); + } + if (StrUtil.isNotEmpty(pageVO.getProcessDefinitionKey())) { + taskQuery.processDefinitionKey(pageVO.getProcessDefinitionKey()); + } + if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { + taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); + } + long count = taskQuery.count(); + if (count == 0) { + return PageResult.empty(); + } + List tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); + return new PageResult<>(tasks, count); + } + + @Override + public BpmTaskRespVO getTodoTask(Long userId, String taskId, String processInstanceId) { + // 1.1 获取指定的用户待办任务 + Task todoTask = getMyTodoTask(userId, taskId); + // 1.2 获取不到,则获取该流程实例下,第一个用户的待办任务 + if (todoTask == null) { + todoTask = getMyFirstTodoTask(userId, processInstanceId); + } + if (todoTask == null) { + return null; + } + + // 2. 查询该任务的子任务 + List childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), CollUtil.newArrayList(todoTask)); + + // 3. 转换返回 + BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId()); + Map buttonsSetting = BpmnModelUtils.parseButtonsSetting( + bpmnModel, todoTask.getTaskDefinitionKey()); + Boolean signEnable = parseSignEnable(bpmnModel, todoTask.getTaskDefinitionKey()); + Boolean reasonRequire = parseReasonRequire(bpmnModel, todoTask.getTaskDefinitionKey()); + Integer nodeType = parseNodeType(BpmnModelUtils.getFlowElementById(bpmnModel, todoTask.getTaskDefinitionKey())); + + // 4. 任务表单 + BpmFormDO taskForm = null; + if (StrUtil.isNotBlank(todoTask.getFormKey())) { + try { + Long formId = Long.parseLong(todoTask.getFormKey()); + taskForm = formService.getForm(formId); + } catch (NumberFormatException e) { + // 如果 formKey 不是数字(比如是URL),则不处理表单 + taskForm = null; + } + } + + return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting, taskForm) + .setNodeType(nodeType).setSignEnable(signEnable).setReasonRequire(reasonRequire); + } + + /** + * 获得用户指定 taskId 任务编号的“待办”(未审批、且可审核)的任务 + * + * @param userId 用户编号 + * @param taskId 任务编号 + * @return 任务 + */ + private Task getMyTodoTask(Long userId, String taskId) { + if (StrUtil.isEmpty(taskId)) { + return null; + } + Task task = getTask(taskId); + if (task == null) { + return null; + } + if (!isAssignUserTask(userId, task) && !isAddSignUserTask(userId, task)) { + return null; + } + return task; + } + + /** + * 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务 + * + * @param userId 用户编号 + * @param processInstanceId 流程编号 + * @return 任务 + */ + private Task getMyFirstTodoTask(Long userId, String processInstanceId) { + if (processInstanceId == null) { + return null; + } + // 1. 查询所有任务 + List tasks = taskService.createTaskQuery() + .active() + .processInstanceId(processInstanceId) + .includeTaskLocalVariables() + .includeProcessVariables() + .orderByTaskCreateTime().asc() // 按创建时间升序 + .list(); + + // 2. 查询我的首个任务 + return CollUtil.findOne(tasks, task -> { + return isAssignUserTask(userId, task) // 当前用户为审批人 + || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签) + }); + } + + @Override + public PageResult getTaskDonePage(Long userId, BpmTaskPageReqVO pageVO) { + HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() + .finished() // 已完成 + .taskAssignee(String.valueOf(userId)) // 分配给自己 + .includeTaskLocalVariables() + .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 + if (StrUtil.isNotBlank(pageVO.getName())) { + taskQuery.taskNameLike("%" + pageVO.getName() + "%"); + } + if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { + taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); + } + // 执行查询 + long count = taskQuery.count(); + if (count == 0) { + return PageResult.empty(); + } + List tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); + + // 特殊:强制移除自动完成的“发起人”节点 + // 补充说明:由于 taskQuery 无法方面的过滤,所以暂时通过内存过滤 + tasks.removeIf(task -> task.getTaskDefinitionKey().equals(START_USER_NODE_ID)); + return new PageResult<>(tasks, count); + } + + @Override + public PageResult getTaskPage(Long userId, BpmTaskPageReqVO pageVO) { + HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() + .includeTaskLocalVariables() + .taskTenantId(FlowableUtils.getTenantId()) + .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 + if (StrUtil.isNotBlank(pageVO.getName())) { + taskQuery.taskNameLike("%" + pageVO.getName() + "%"); + } + if (StrUtil.isNotEmpty(pageVO.getCategory())) { + taskQuery.taskCategory(pageVO.getCategory()); + } + if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { + taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); + } + // 执行查询 + long count = taskQuery.count(); + if (count == 0) { + return PageResult.empty(); + } + List tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); + return new PageResult<>(tasks, count); + } + + @Override + public List getTasksByProcessInstanceIds(List processInstanceIds) { + if (CollUtil.isEmpty(processInstanceIds)) { + return Collections.emptyList(); + } + return taskService.createTaskQuery().processInstanceIdIn(processInstanceIds).list(); + } + + @Override + public List getTaskListByProcessInstanceId(String processInstanceId, Boolean asc) { + HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery() + .includeTaskLocalVariables() + .processInstanceId(processInstanceId); + if (Boolean.TRUE.equals(asc)) { + query.orderByHistoricTaskInstanceStartTime().asc(); + } else { + query.orderByHistoricTaskInstanceStartTime().desc(); + } + return query.list(); + } + + @Override + public 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 getHistoricTasks(Collection taskIds) { + return historyService.createHistoricTaskInstanceQuery().taskIds(taskIds).includeTaskLocalVariables().list(); + } + + @Override + public List 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 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 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 List getAllChildrenTaskListByParentTaskId(String parentTaskId, List tasks) { + if (CollUtil.isEmpty(tasks)) { + return Collections.emptyList(); + } + Map> parentTaskMap = convertMultiMap( + filterList(tasks, task -> StrUtil.isNotEmpty(task.getParentTaskId())), TaskInfo::getParentTaskId); + if (CollUtil.isEmpty(parentTaskMap)) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + // 1. 递归获取子级 + Stack 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 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 getAllChildTaskList(Task parentTask) { + List result = new ArrayList<>(); + // 1. 递归获取子级 + Stack 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 childTaskList = getTaskListByParentTaskId(task.getId()); + // 2.2 如果非空,则添加到 stack 进一步递归 + if (CollUtil.isNotEmpty(childTaskList)) { + stack.addAll(childTaskList); + result.addAll(childTaskList); + } + } + return result; + } + + @Override + public List 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 getActivityListByProcessInstanceId(String processInstanceId) { + return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) + .orderByHistoricActivityInstanceStartTime().asc().list(); + } + + @Override + public List 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) + public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) { + // 1.1 校验任务存在 + Task task = validateTask(userId, reqVO.getId()); + // 1.2 校验流程实例存在 + ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (instance == null) { + throw exception(PROCESS_INSTANCE_NOT_EXISTS); + } + // 1.3 校验签名 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + Boolean signEnable = parseSignEnable(bpmnModel, task.getTaskDefinitionKey()); + if (signEnable && StrUtil.isEmpty(reqVO.getSignPicUrl())) { + throw exception(TASK_SIGNATURE_NOT_EXISTS); + } + // 1.4 校验审批意见 + Boolean reasonRequire = parseReasonRequire(bpmnModel, task.getTaskDefinitionKey()); + if (reasonRequire && StrUtil.isEmpty(reqVO.getReason())) { + throw exception(TASK_REASON_REQUIRE); + } + + // 情况一:被委派的任务,不调用 complete 去完成任务 + if (DelegationState.PENDING.equals(task.getDelegationState())) { + approveDelegateTask(reqVO, task); + return; + } + + // 情况二:审批有【后】加签的任务 + if (BpmTaskSignTypeEnum.AFTER.getType().equals(task.getScopeType())) { + approveAfterSignTask(task, reqVO); + return; + } + + // 情况三:审批普通的任务。大多数情况下,都是这样 + // 2.1 更新 task 状态、原因、签字 + updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason()); + if (signEnable) { + taskService.setVariableLocal(task.getId(), BpmnVariableConstants.TASK_SIGN_PIC_URL, reqVO.getSignPicUrl()); + } + // 2.2 添加评论 + taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), + BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); + + // 3. 设置流程变量。如果流程变量前端传空,需要从历史实例中获取,原因:前端表单如果在当前节点无可编辑的字段时 variables 一定会为空 + // 场景一:A 节点发起,B 节点表单无可编辑字段,审批通过时,C 节点需要流程变量获取下一个执行节点,但因为 B 节点无可编辑的字段,variables 为空,流程可能出现问题。 + // 场景二:A 节点发起,B 节点只有某一个字段可编辑(比如 day),但 C 节点需要多个节点。 + // (比如 work + day 变量,在发起时填写,因为 B 节点只有 day 的编辑权限,在审批后,variables 会缺少 work 的值) + Map processVariables = new HashMap<>(); + if (CollUtil.isNotEmpty(instance.getProcessVariables())) { // 获取历史中流程变量 + processVariables.putAll(instance.getProcessVariables()); + } + if (CollUtil.isNotEmpty(reqVO.getVariables())) { // 合并前端传递的流程变量,以前端为准 + processVariables.putAll(reqVO.getVariables()); + } + + // 如果任务变量不为空,设置任务级别的变量实例信息 + if (CollUtil.isNotEmpty(reqVO.getTaskVariables())) { + Map taskVariables = new HashMap<>(); + taskVariables.put("taskVariables", JSONUtil.toJsonStr(reqVO.getTaskVariables())); + taskService.setVariablesLocal(task.getId(), taskVariables); + } + + // 4. 校验并处理 APPROVE_USER_SELECT 当前审批人,选择下一节点审批人的逻辑 + Map variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), processVariables, + bpmnModel, reqVO.getNextAssignees(), instance); + runtimeService.setVariables(task.getProcessInstanceId(), variables); + + // 5. 调用 BPM complete 去完成任务 + taskService.complete(task.getId(), variables, true); + + // 【加签专属】处理加签任务 + handleParentTaskIfSign(task.getParentTaskId()); + } + + /** + * 校验选择的下一个节点的审批人,是否合法 + * + * 1. 是否有漏选:没有选择审批人 + * 2. 是否有多选:非下一个节点 + * + * @param taskDefinitionKey 当前任务节点标识 + * @param variables 流程变量 + * @param bpmnModel 流程模型 + * @param nextAssignees 下一个节点审批人集合(参数) + * @param processInstance 流程实例 + */ + @SuppressWarnings("unchecked") + private Map validateAndSetNextAssignees(String taskDefinitionKey, Map variables, BpmnModel bpmnModel, + Map> nextAssignees, ProcessInstance processInstance) { + // simple 设计器第一个节点默认为发起人节点,不校验是否存在审批人 + if (Objects.equals(taskDefinitionKey, START_USER_NODE_ID)) { + return variables; + } + // 1. 获取下一个将要执行的节点集合 + FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); + List nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables); + + // 2. 校验选择的下一个节点的审批人,是否合法 + for (FlowNode nextFlowNode : nextFlowNodes) { + Integer candidateStrategy = parseCandidateStrategy(nextFlowNode); + // 2.1 情况一:如果节点中的审批人策略为 发起人自选 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) { + // 特殊:如果当前节点已经存在审批人,则不允许覆盖 + Map> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables()); + if (startUserSelectAssignees != null && CollUtil.isNotEmpty(startUserSelectAssignees.get(nextFlowNode.getId()))) { + continue; + } + // 如果节点存在,但未配置审批人 + List assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; + if (CollUtil.isEmpty(assignees)) { + throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); + } + + // 设置 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES + if (startUserSelectAssignees == null) { + startUserSelectAssignees = new HashMap<>(); + } + startUserSelectAssignees.put(nextFlowNode.getId(), assignees); + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); + continue; + } + + // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) { + // 如果节点存在,但未配置审批人 + Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables()); + List assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; + if (CollUtil.isEmpty(assignees)) { + throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); + } + + // 设置 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES + if (approveUserSelectAssignees == null) { + approveUserSelectAssignees = new HashMap<>(); + } + approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); + Map> existingApproveUserSelectAssignees = (Map>) variables.get( + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); + if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) { + approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees); + } + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, approveUserSelectAssignees); + } + } + return variables; + } + + /** + * 审批通过存在“后加签”的任务。 + *

+ * 注意:该任务不能马上完成,需要一个中间状态(APPROVING),并激活剩余所有子任务(PROCESS)为可审批处理 + * 如果马上完成,则会触发下一个任务,甚至如果没有下一个任务则流程实例就直接结束了! + * + * @param task 当前任务 + * @param reqVO 前端请求参数 + */ + private void approveAfterSignTask(Task task, BpmTaskApproveReqVO reqVO) { + // 更新父 task 状态 + 原因 + updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVING.getStatus(), reqVO.getReason()); + + // 2. 激活子任务 + List childrenTaskList = getTaskListByParentTaskId(task.getId()); + for (Task childrenTask : childrenTaskList) { + taskService.resolveTask(childrenTask.getId()); + // 更新子 task 状态 + updateTaskStatus(childrenTask.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); + } + } + + /** + * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务: + *

+ * 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批 + * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批 + * + * @param parentTaskId 父任务编号 + */ + private void handleParentTaskIfSign(String parentTaskId) { + if (StrUtil.isBlank(parentTaskId)) { + return; + } + // 1.1 判断是否还有子任务。如果没有,就不处理 + Long childrenTaskCount = getTaskCountByParentTaskId(parentTaskId); + if (childrenTaskCount > 0) { + return; + } + // 1.2 只处理加签的父任务 + Task parentTask = validateTaskExist(parentTaskId); + String scopeType = parentTask.getScopeType(); + if (BpmTaskSignTypeEnum.of(scopeType) == null) { + return; + } + + // 2. 子任务已处理完成,清空 scopeType 字段,修改 parentTask 信息,方便后续可以继续向前后向后加签 + TaskEntityImpl parentTaskImpl = (TaskEntityImpl) parentTask; + parentTaskImpl.setScopeType(null); + taskService.saveTask(parentTaskImpl); + + // 3.1 情况一:处理向【向前】加签 + if (BpmTaskSignTypeEnum.BEFORE.getType().equals(scopeType)) { + // 3.1.1 owner 重新赋值给父任务的 assignee,这样它就可以被审批 + taskService.resolveTask(parentTaskId); + // 3.1.2 更新流程任务 status + updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus()); + // 3.2 情况二:处理向【向后】加签 + } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) { + // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成 + // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题 + Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); + if (ObjectUtil.notEqual(status, BpmTaskStatusEnum.APPROVING.getStatus())) { + return; + } + // 3.2.2 完成自己(因为它已经没有子任务,所以也可以完成) + updateTaskStatus(parentTaskId, BpmTaskStatusEnum.APPROVE.getStatus()); + taskService.complete(parentTaskId); + } + + // 4. 递归处理父任务 + handleParentTaskIfSign(parentTask.getParentTaskId()); + } + + /** + * 审批被委派的任务 + * + * @param reqVO 前端请求参数,包含当前任务ID,审批意见等 + * @param task 当前被审批的任务 + */ + private void approveDelegateTask(BpmTaskApproveReqVO reqVO, Task task) { + // 1. 添加审批意见 + AdminUserRespDTO currentUser = adminUserApi.getUser(WebFrameworkUtils.getLoginUserId()).getCheckedData(); + AdminUserRespDTO ownerUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())).getCheckedData(); // 发起委托的用户 + Assert.notNull(ownerUser, "委派任务找不到原审批人,需要检查数据"); + taskService.addComment(reqVO.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_END.getType(), + BpmCommentTypeEnum.DELEGATE_END.formatComment(currentUser.getNickname(), ownerUser.getNickname(), reqVO.getReason())); + + // 2.1 调用 resolveTask 完成任务。 + // 底层调用 TaskHelper.changeTaskAssignee(task, task.getOwner()):将 owner 设置为 assignee + taskService.resolveTask(task.getId()); + // 2.2 更新 task 状态 + 原因 + updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus(), reqVO.getReason()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) { + // 1.1 校验任务存在 + Task task = validateTask(userId, reqVO.getId()); + // 1.2 校验流程实例存在 + ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (instance == null) { + throw exception(PROCESS_INSTANCE_NOT_EXISTS); + } + + // 2.1 更新流程任务为不通过 + updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); + // 2.2 添加流程评论 + taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), + BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); + // 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过 + // 疑问:为什么要标记未通过呢? + // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 zt-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. 根据不同的 RejectHandler 处理策略 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + // 3.1 情况一:驳回到指定的任务节点 + BpmUserTaskRejectHandlerTypeEnum userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); + if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerTypeEnum.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(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); // 结束流程 + } + + /** + * 更新流程任务的 status 状态 + * + * @param id 任务编号 + * @param status 状态 + */ + private void updateTaskStatus(String id, Integer status) { + taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_STATUS, status); + } + + /** + * 更新流程任务的 status 状态、reason 理由 + * + * @param id 任务编号 + * @param status 状态 + * @param reason 理由(审批通过、审批不通过的理由) + */ + private void updateTaskStatusAndReason(String id, Integer status, String reason) { + updateTaskStatus(id, status); + taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_REASON, reason); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void returnTask(Long userId, BpmTaskReturnReqVO reqVO) { + // 1.1 当前任务 task + Task task = validateTask(userId, reqVO.getId()); + if (task.isSuspended()) { + throw exception(TASK_IS_PENDING); + } + // 1.2 校验源头和目标节点的关系,并返回目标元素 + FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), + reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId()); + + // 2. 调用 Flowable 框架的退回逻辑 + returnTask(userId, task, targetElement, reqVO); + } + + /** + * 退回流程节点时,校验目标任务节点是否可退回 + * + * @param sourceKey 当前任务节点 Key + * @param targetKey 目标任务节点 key + * @param processDefinitionId 当前流程定义 ID + * @return 目标任务节点元素 + */ + private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { + // 1.1 获取流程模型信息 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); + // 1.3 获取当前任务节点元素 + FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); + // 1.3 获取跳转的节点元素 + FlowElement target = BpmnModelUtils.getFlowElementById(bpmnModel, targetKey); + if (target == null) { + throw exception(TASK_TARGET_NODE_NOT_EXISTS); + } + + // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 + if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { + throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); + } + return target; + } + + /** + * 执行退回逻辑 + * + * @param userId 用户编号 + * @param currentTask 当前退回的任务 + * @param targetElement 需要退回到的目标任务 + * @param reqVO 前端参数封装 + */ + public void returnTask(Long userId, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { + // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤 + // 1.1 获取所有正常进行的任务节点 Key + List taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list(); + List runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey); + // 1.2 通过 targetElement 的出口连线,计算在 runTaskKeyList 有哪些 key 需要被撤回 + // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F + List returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); + List returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); + + List runExecutionIds = new ArrayList<>(); + // 2. 给当前要被退回的 task 数组,设置退回意见 + taskList.forEach(task -> { + // 需要排除掉,不需要设置退回意见的任务 + if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { + return; + } + runExecutionIds.add(task.getExecutionId()); + + // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务 + if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记 + // 2.1.1 添加评论 + taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), BpmCommentTypeEnum.RETURN.getType(), + BpmCommentTypeEnum.RETURN.formatComment(reqVO.getReason())); + // 2.1.2 更新 task 状态 + 原因 + updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason()); + } else { // 情况二:别人的任务,进行 CANCEL 标记 + processTaskCanceled(task.getId()); + } + }); + + // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过 + runtimeService.setVariable(currentTask.getProcessInstanceId(), + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE); + // 4. 执行驳回 + // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因: + // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944 + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(currentTask.getProcessInstanceId()) + .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) + .changeState(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) { + String taskId = reqVO.getId(); + // 1.1 校验任务 + Task task = validateTask(userId, reqVO.getId()); + if (task.getAssignee().equals(reqVO.getDelegateUserId().toString())) { // 校验当前审批人和被委派人不是同一人 + throw exception(TASK_DELEGATE_FAIL_USER_REPEAT); + } + // 1.2 校验目标用户存在 + AdminUserRespDTO delegateUser = adminUserApi.getUser(reqVO.getDelegateUserId()).getCheckedData(); + if (delegateUser == null) { + throw exception(TASK_DELEGATE_FAIL_USER_NOT_EXISTS); + } + + // 2. 添加委托意见 + AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData(); + taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_START.getType(), + BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason())); + + // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) + taskService.setOwner(taskId, task.getAssignee()); + // 3.2 执行委派,将任务委派给 delegateUser + taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); + // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中 + } + + @Override + public void transferTask(Long userId, BpmTaskTransferReqVO reqVO) { + String taskId = reqVO.getId(); + // 1.1 校验任务 + Task task = validateTask(userId, reqVO.getId()); + if (task.getAssignee().equals(reqVO.getAssigneeUserId().toString())) { // 校验当前审批人和被转派人不是同一人 + throw exception(TASK_TRANSFER_FAIL_USER_REPEAT); + } + // 1.2 校验目标用户存在 + AdminUserRespDTO assigneeUser = adminUserApi.getUser(reqVO.getAssigneeUserId()).getCheckedData(); + if (assigneeUser == null) { + throw exception(TASK_TRANSFER_FAIL_USER_NOT_EXISTS); + } + + // 2. 添加委托意见 + AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData(); + taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.TRANSFER.getType(), + BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason())); + + // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) + taskService.setOwner(taskId, task.getAssignee()); + // 3.2 执行转派(审批人),将任务转派给 assigneeUser + // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!! + taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString()); + } + + @Override + public void moveTaskToEnd(String processInstanceId, String reason) { + List 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 activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); + EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); + Assert.notNull(endEvent, "结束节点不能为空"); + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstanceId) + .moveActivityIdsToSingleActivityId(activityIds, endEvent.getId()) + .changeState(); + + // 3. 特殊:如果跳转到 EndEvent 流程还未结束, 执行 deleteProcessInstance 方法 + // TODO 芋艿:目前发现并行分支情况下,会存在这个情况,后续看看有没更好的方案; + List executions = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list(); + if (CollUtil.isNotEmpty(executions)) { + log.warn("[moveTaskToEnd][执行跳转到 EndEvent 后, 流程实例未结束,强制执行 deleteProcessInstance 方法]"); + runtimeService.deleteProcessInstance(processInstanceId, reason); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO) { + // 1. 获取和校验任务 + TaskEntityImpl taskEntity = validateTaskCanCreateSign(userId, reqVO); + List userList = adminUserApi.getUserList(reqVO.getUserIds()).getCheckedData(); + if (CollUtil.isEmpty(userList)) { + throw exception(TASK_SIGN_CREATE_USER_NOT_EXIST); + } + + // 2. 处理当前任务 + // 2.1 开启计数功能,主要用于为了让表 ACT_RU_TASK 中的 SUB_TASK_COUNT_ 字段记录下总共有多少子任务,后续可能有用 + taskEntity.setCountEnabled(true); + // 2.2 向前加签,设置 owner,置空 assign。等子任务都完成后,再调用 resolveTask 重新将 owner 设置为 assign + // 原因是:不能和向前加签的子任务一起审批,需要等前面的子任务都完成才能审批 + if (reqVO.getType().equals(BpmTaskSignTypeEnum.BEFORE.getType())) { + taskEntity.setOwner(taskEntity.getAssignee()); + taskEntity.setAssignee(null); + } + // 2.4 记录加签方式,完成任务时需要用到判断 + taskEntity.setScopeType(reqVO.getType()); + // 2.5 保存当前任务修改后的值 + taskService.saveTask(taskEntity); + // 2.6 更新 task 状态为 WAIT,只有在向前加签的时候 + if (reqVO.getType().equals(BpmTaskSignTypeEnum.BEFORE.getType())) { + updateTaskStatus(taskEntity.getId(), BpmTaskStatusEnum.WAIT.getStatus()); + } + + // 3. 创建加签任务 + createSignTaskList(convertList(reqVO.getUserIds(), String::valueOf), taskEntity); + + // 4. 记录加签的评论到 task 任务 + AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData(); + String comment = StrUtil.format(BpmCommentTypeEnum.ADD_SIGN.getComment(), + currentUser.getNickname(), BpmTaskSignTypeEnum.nameOfType(reqVO.getType()), + String.join(",", convertList(userList, AdminUserRespDTO::getNickname)), reqVO.getReason()); + taskService.addComment(reqVO.getId(), taskEntity.getProcessInstanceId(), BpmCommentTypeEnum.ADD_SIGN.getType(), comment); + } + + /** + * 校验任务是否可以加签,主要校验加签类型是否一致: + *

+ * 1. 如果存在“向前加签”的任务,则不能“向后加签” + * 2. 如果存在“向后加签”的任务,则不能“向前加签” + * + * @param userId 当前用户 ID + * @param reqVO 请求参数,包含任务 ID 和加签类型 + * @return 当前任务 + */ + private TaskEntityImpl validateTaskCanCreateSign(Long userId, BpmTaskSignCreateReqVO reqVO) { + TaskEntityImpl taskEntity = (TaskEntityImpl) validateTask(userId, reqVO.getId()); + // 向前加签和向后加签不能同时存在 + if (taskEntity.getScopeType() != null + && ObjectUtil.notEqual(taskEntity.getScopeType(), reqVO.getType())) { + throw exception(TASK_SIGN_CREATE_TYPE_ERROR, + BpmTaskSignTypeEnum.nameOfType(taskEntity.getScopeType()), BpmTaskSignTypeEnum.nameOfType(reqVO.getType())); + } + + // 同一个 key 的任务,审批人不重复 + List taskList = taskService.createTaskQuery().processInstanceId(taskEntity.getProcessInstanceId()) + .taskDefinitionKey(taskEntity.getTaskDefinitionKey()).list(); + List currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner + Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); + if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) { + List userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())).getCheckedData(); + throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname))); + } + return taskEntity; + } + + /** + * 创建加签子任务 + * + * @param userIds 被加签的用户 ID + * @param taskEntity 被加签的任务 + */ + private void createSignTaskList(List userIds, TaskEntityImpl taskEntity) { + if (CollUtil.isEmpty(userIds)) { + return; + } + // 创建加签人的新任务,全部基于 taskEntity 为父任务来创建 + for (String addSignId : userIds) { + if (StrUtil.isBlank(addSignId)) { + continue; + } + createSignTask(taskEntity, addSignId); + } + } + + /** + * 创建加签子任务 + * + * @param parentTask 父任务 + * @param assignee 子任务的执行人 + */ + private void createSignTask(TaskEntityImpl parentTask, String assignee) { + // 1. 生成子任务 + TaskEntityImpl task = (TaskEntityImpl) taskService.newTask(IdUtil.fastSimpleUUID()); + BpmTaskConvert.INSTANCE.copyTo(parentTask, task); + + // 2.1 向前加签,设置审批人 + if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) { + task.setAssignee(assignee); + // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 + } else { + task.setOwner(assignee); + } + // 2.3 保存子任务 + taskService.saveTask(task); + + // 3. 向后前签,设置子任务的状态为 WAIT,因为需要等父任务审批完 + if (BpmTaskSignTypeEnum.AFTER.getType().equals(parentTask.getScopeType())) { + updateTaskStatus(task.getId(), BpmTaskStatusEnum.WAIT.getStatus()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + @SuppressWarnings("DataFlowIssue") + public void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO) { + // 1.1 校验 task 可以被减签 + Task task = validateTaskCanSignDelete(reqVO.getId()); + // 1.2 校验取消人存在 + AdminUserRespDTO cancelUser = null; + if (StrUtil.isNotBlank(task.getAssignee())) { + cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee())).getCheckedData(); + } + if (cancelUser == null && StrUtil.isNotBlank(task.getOwner())) { + cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())).getCheckedData(); + } + Assert.notNull(cancelUser, "任务中没有所有者和审批人,数据错误"); + + // 2.1 获得子任务列表,包括子任务的子任务 + List childTaskList = getAllChildTaskList(task); + childTaskList.add(task); + // 2.2 更新子任务为已取消 + String cancelReason = StrUtil.format("任务被取消,原因:由于[{}]操作[减签],", cancelUser.getNickname()); + childTaskList.forEach(childTask -> updateTaskStatusAndReason(childTask.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), cancelReason)); + // 2.3 删除任务和所有子任务 + taskService.deleteTasks(convertList(childTaskList, Task::getId)); + + // 3. 记录日志到父任务中。先记录日志是因为,通过 handleParentTask 方法之后,任务可能被完成了,并且不存在了,会报异常,所以先记录 + AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), BpmCommentTypeEnum.SUB_SIGN.getType(), + StrUtil.format(BpmCommentTypeEnum.SUB_SIGN.getComment(), user.getNickname(), cancelUser.getNickname())); + + // 4. 处理当前任务的父任务 + handleParentTaskIfSign(task.getParentTaskId()); + } + + @Override + public void copyTask(Long userId, BpmTaskCopyReqVO reqVO) { + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId()); + } + + /** + * 校验任务是否能被减签 + * + * @param id 任务编号 + * @return 任务信息 + */ + private Task validateTaskCanSignDelete(String id) { + Task task = validateTaskExist(id); + if (task.getParentTaskId() == null) { + throw exception(TASK_SIGN_DELETE_NO_PARENT); + } + Task parentTask = getTask(task.getParentTaskId()); + if (parentTask == null) { + throw exception(TASK_SIGN_DELETE_NO_PARENT); + } + if (BpmTaskSignTypeEnum.of(parentTask.getScopeType()) == null) { + throw exception(TASK_SIGN_DELETE_NO_PARENT); + } + return task; + } + + // ========== Event 事件相关方法 ========== + + @Override + 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()); + + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (processInstance == null) { + log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId()); + return; + } + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService. + getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); + if (processDefinitionInfo == null) { + log.error("[processTaskCreated][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId()); + return; + } + + // 2. 任务前置通知 + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){ + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting(); + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); + } + + // 3. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 + 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() { + + /** + * 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以 + * 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时 + * 参见 issue 反馈 + */ + @Override + public void afterCompletion(int transactionStatus) { + // 回滚情况,直接返回 + if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) { + return; + } + // 特殊情况:第一个 task 【自动通过】时,第二个任务设置审批人时 transactionStatus 会为 STATUS_UNKNOWN,不知道啥原因 + if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_UNKNOWN) + && getTask(task.getId()) == null) { + return; + } + // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { + // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝 + if (!ObjectUtil.isAllEmpty(task.getAssignee(), task.getOwner())) { + return; + } + if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { + getSelf().approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); + } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { + getSelf().rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); + } + // 特殊情况二:【自动审核】审批类型为自动通过、不通过 + } else { + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) { + getSelf().approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason())); + } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + getSelf().rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason())); + } + } + } + + }); + } + + /** + * 重要补充说明:该方法目前主要有两个情况会调用到: + *

+ * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消 + * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 + */ + @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 + @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 + public void processTaskAssigned(Task task) { + // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + /** + * 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以 + * 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时 + * 参见 issue 反馈 + */ + @Override + public void afterCompletion(int transactionStatus) { + // 回滚情况,直接返回 + if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) { + return; + } + // 特殊情况:第一个 task 【自动通过】时,第二个任务设置审批人时 transactionStatus 会为 STATUS_UNKNOWN,不知道啥原因 + if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_UNKNOWN) + && getTask(task.getId()) == null) { + return; + } + 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; + } + + // 自动去重,通过自动审批的方式 TODO @芋艿 驳回的情况得考虑一下;@lesan:驳回后,又自动审批么? + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(task.getProcessDefinitionId()); + if (processDefinitionInfo == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程定义({})]", task.getId(), task.getProcessDefinitionId()); + return; + } + if (processDefinitionInfo.getAutoApprovalType() != null) { + HistoricTaskInstanceQuery sameAssigneeQuery = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .taskAssignee(task.getAssignee()) // 相同审批人 + .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus()) + .finished(); + if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType()) + && sameAssigneeQuery.count() > 0) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName())); + return; + } + if (BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getType().equals(processDefinitionInfo.getAutoApprovalType())) { + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型({})]", task.getId(), task.getProcessDefinitionId()); + return; + } + List sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点 + BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), + SequenceFlow::getSourceRef); + if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getName())); + return; + } + } + } + + // 获取发起人节点 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); + return; + } + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略 + // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识 + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); + Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), + PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); + if (userTaskElement.getId().equals(START_USER_NODE_ID) + && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 + || Boolean.TRUE.equals(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 + && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason())); + return; + } + // 当不为发起人节点时,审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 + if (ObjectUtil.notEqual(userTaskElement.getId(), START_USER_NODE_ID) + && StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { + if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { + 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()); + Long deptId = startUser.getDeptIds().stream().findAny().orElse(null); + DeptRespDTO dept = deptId != null ? deptApi.getDept(deptId).getCheckedData() : null; + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), deptId); + // 找不到部门负责人的情况下,自动审批通过 + // 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; + } + // 如果部门负责人是自己,还是自己审批吧~ + } + } + } + // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 + FlowableUtils.execute(processInstance.getTenantId(), () -> { + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); + messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); + }); + } + + }); + } + + @Override + public void processTaskCompleted(Task task) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (processInstance == null) { + log.error("[processTaskCompleted][taskId({}) 没有找到流程实例]", task.getId()); + return; + } + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService. + getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); + if (processDefinitionInfo == null) { + log.error("[processTaskCompleted][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId()); + return; + } + + // 任务后置通知 + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){ + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting(); + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); + } + } + + @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 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())); + } + })); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void processChildProcessTimeout(String processInstanceId, String taskDefineKey) { + List activityInstances = runtimeService.createActivityInstanceQuery() + .processInstanceId(processInstanceId) + .activityId(taskDefineKey).list(); + activityInstances.forEach(activityInstance -> FlowableUtils.execute(activityInstance.getTenantId(), + () -> moveTaskToEnd(activityInstance.getCalledProcessInstanceId(), BpmReasonEnum.TIMEOUT_APPROVE.getReason()))); + } + + @Override + public void triggerTask(String processInstanceId, String taskDefineKey) { + Execution execution = runtimeService.createExecutionQuery() + .processInstanceId(processInstanceId) + .activityId(taskDefineKey) + .singleResult(); + if (execution == null) { + log.error("[triggerTask][processInstanceId({}) activityId({}) 没有找到执行活动]", processInstanceId, taskDefineKey); + return; + } + + // 若存在直接触发接收任务,执行后续节点 + FlowableUtils.execute(execution.getTenantId(), + () -> runtimeService.trigger(execution.getId())); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private BpmTaskServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/listener/BpmCallActivityListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/listener/BpmCallActivityListener.java new file mode 100644 index 0000000..e458af4 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/listener/BpmCallActivityListener.java @@ -0,0 +1,96 @@ +package com.zt.plat.module.bpm.service.task.listener; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.enums.definition.BpmChildProcessStartUserEmptyTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmChildProcessStartUserTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import jakarta.annotation.Resource; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.flowable.engine.impl.el.FixedValue; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * BPM 子流程监听器:设置流程的发起人 + * + * @author Lesan + */ +@Component +@Slf4j +public class BpmCallActivityListener implements ExecutionListener { + + public static final String DELEGATE_EXPRESSION = "${bpmCallActivityListener}"; + + @Setter + private FixedValue listenerConfig; + + @Resource + private BpmProcessDefinitionService processDefinitionService; + + @Resource + private BpmProcessInstanceService processInstanceService; + + @Override + public void notify(DelegateExecution execution) { + String expressionText = listenerConfig.getExpressionText(); + Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText); + BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting startUserSetting = JsonUtils.parseObject( + expressionText, BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting.class); + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getRootProcessInstanceId()); + + // 1. 当发起人来源为主流程发起人时,并兜底 startUserSetting 为空时 + if (startUserSetting == null + || startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER.getType())) { + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId())); + return; + } + + // 2. 当发起人来源为表单时 + if (startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.FROM_FORM.getType())) { + String formFieldValue = MapUtil.getStr(processInstance.getProcessVariables(), startUserSetting.getFormField()); + // 2.1 当表单值为空时 + if (StrUtil.isEmpty(formFieldValue)) { + // 2.1.1 来自主流程发起人 + if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER.getType())) { + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId())); + return; + } + // 2.1.2 来自子流程管理员 + if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN.getType())) { + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId()); + List managerUserIds = processDefinition.getManagerUserIds(); + FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0)); + return; + } + // 2.1.3 来自主流程管理员 + if (startUserSetting.getEmptyType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN.getType())) { + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); + List managerUserIds = processDefinition.getManagerUserIds(); + FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0)); + return; + } + } + // 2.2 使用表单值,并兜底字符串转 Long 失败时使用主流程发起人 + try { + FlowableUtils.setAuthenticatedUserId(Long.parseLong(formFieldValue)); + } catch (Exception e) { + log.error("[notify][监听器:{},子流程监听器设置流程的发起人字符串转 Long 失败,字符串:{}]", + DELEGATE_EXPRESSION, formFieldValue); + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId())); + } + } + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/listener/BpmUserTaskListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/listener/BpmUserTaskListener.java new file mode 100644 index 0000000..2102560 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/listener/BpmUserTaskListener.java @@ -0,0 +1,59 @@ +package com.zt.plat.module.bpm.service.task.listener; + +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import jakarta.annotation.Resource; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.impl.el.FixedValue; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import static com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseListenerConfig; + +// TODO @芋艿:可能会想换个包地址 +/** + * BPM 用户任务通用监听器 + * + * @author Lesan + */ +@Component +@Slf4j +@Scope("prototype") +public class BpmUserTaskListener implements TaskListener { + + public static final String DELEGATE_EXPRESSION = "${bpmUserTaskListener}"; + + @Resource + private BpmProcessInstanceService processInstanceService; + + @Setter + private FixedValue listenerConfig; + + @Override + public void notify(DelegateTask delegateTask) { + // 1. 获取所需基础信息 + ProcessInstance processInstance = processInstanceService.getProcessInstance(delegateTask.getProcessInstanceId()); + BpmSimpleModelNodeVO.ListenerHandler listenerHandler = parseListenerConfig(listenerConfig); + + // 2. 发起请求 + // TODO @芋艿:哪些默认参数,后续再调研下;感觉可以搞个 task 字段,把整个 delegateTask 放进去; + listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("processInstanceId") + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getProcessInstanceId())); + listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("assignee") + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getAssignee())); + listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskDefinitionKey") + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getTaskDefinitionKey())); + listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskId") + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getId())); + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, + listenerHandler.getPath(), listenerHandler.getHeader(), listenerHandler.getBody(), false, null); + + // 3. 是否需要后续操作?TODO 芋艿:待定! + } +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/BpmTrigger.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/BpmTrigger.java new file mode 100644 index 0000000..02fb2d5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/BpmTrigger.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.bpm.service.task.trigger; + +import com.zt.plat.module.bpm.enums.definition.BpmTriggerTypeEnum; + +// TODO @芋艿:可能会想换个包地址 +/** + * BPM 触发器接口 + *

+ * 处理不同的动作 + * + * @author jason + */ +public interface BpmTrigger { + + /** + * 对应触发器类型 + * + * @return 触发器类型 + */ + BpmTriggerTypeEnum getType(); + + /** + * 触发器执行 + * + * @param processInstanceId 流程实例编号 + * @param param 触发器参数 + */ + void execute(String processInstanceId, String param); + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/form/BpmFormDeleteTrigger.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/form/BpmFormDeleteTrigger.java new file mode 100644 index 0000000..46ed559 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/form/BpmFormDeleteTrigger.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.bpm.service.task.trigger.form; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.enums.definition.BpmTriggerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.SimpleModelUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.bpm.service.task.trigger.BpmTrigger; +import com.fasterxml.jackson.core.type.TypeReference; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * BPM 删除流程表单数据触发器 + * + * @author jason + */ +@Component +@Slf4j +public class BpmFormDeleteTrigger implements BpmTrigger { + + @Resource + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTriggerTypeEnum getType() { + return BpmTriggerTypeEnum.FORM_DELETE; + } + + @Override + public void execute(String processInstanceId, String param) { + // 1. 解析删除流程表单数据配置 + List settings = JsonUtils.parseObject(param, new TypeReference<>() {}); + if (CollUtil.isEmpty(settings)) { + log.error("[execute][流程({}) 删除流程表单数据触发器配置为空]", processInstanceId); + return; + } + + // 2. 获取流程变量 + Map processVariables = processInstanceService.getProcessInstance(processInstanceId).getProcessVariables(); + + // 3.1 获取需要删除的表单字段 + Set deleteFields = new HashSet<>(); + settings.forEach(setting -> { + if (CollUtil.isEmpty(setting.getDeleteFields())) { + return; + } + // 配置了条件,判断条件是否满足 + boolean isFieldDeletedNeeded = true; + if (setting.getConditionType() != null) { + String conditionExpression = SimpleModelUtils.buildConditionExpression( + setting.getConditionType(), setting.getConditionExpression(), setting.getConditionGroups()); + isFieldDeletedNeeded = BpmnModelUtils.evalConditionExpress(processVariables, conditionExpression); + } + if (isFieldDeletedNeeded) { + deleteFields.addAll(setting.getDeleteFields()); + } + }); + + // 3.2 删除流程变量 + if (CollUtil.isNotEmpty(deleteFields)) { + processInstanceService.removeProcessInstanceVariables(processInstanceId, deleteFields); + } + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/form/BpmFormUpdateTrigger.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/form/BpmFormUpdateTrigger.java new file mode 100644 index 0000000..c8427b2 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/form/BpmFormUpdateTrigger.java @@ -0,0 +1,66 @@ +package com.zt.plat.module.bpm.service.task.trigger.form; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TriggerSetting.FormTriggerSetting; +import com.zt.plat.module.bpm.enums.definition.BpmTriggerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.SimpleModelUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.bpm.service.task.trigger.BpmTrigger; +import com.fasterxml.jackson.core.type.TypeReference; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * BPM 更新流程表单触发器 + * + * @author jason + */ +@Component +@Slf4j +public class BpmFormUpdateTrigger implements BpmTrigger { + + @Resource + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTriggerTypeEnum getType() { + return BpmTriggerTypeEnum.FORM_UPDATE; + } + + @Override + public void execute(String processInstanceId, String param) { + // 1. 解析更新流程表单配置 + List settings = JsonUtils.parseObject(param, new TypeReference<>() {}); + if (CollUtil.isEmpty(settings)) { + log.error("[execute][流程({}) 更新流程表单触发器配置为空]", processInstanceId); + return; + } + + // 2. 获取流程变量 + Map processVariables = processInstanceService.getProcessInstance(processInstanceId).getProcessVariables(); + + // 3. 更新流程变量 + for (FormTriggerSetting setting : settings) { + if (CollUtil.isEmpty(setting.getUpdateFormFields())) { + continue; + } + // 配置了条件,判断条件是否满足 + boolean isFormUpdateNeeded = true; + if (setting.getConditionType() != null) { + String conditionExpression = SimpleModelUtils.buildConditionExpression( + setting.getConditionType(), setting.getConditionExpression(), setting.getConditionGroups()); + isFormUpdateNeeded = BpmnModelUtils.evalConditionExpress(processVariables, conditionExpression); + } + // 更新流程表单 + if (isFormUpdateNeeded) { + processInstanceService.updateProcessInstanceVariables(processInstanceId, setting.getUpdateFormFields()); + } + } + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmAbstractHttpRequestTrigger.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmAbstractHttpRequestTrigger.java new file mode 100644 index 0000000..676b260 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmAbstractHttpRequestTrigger.java @@ -0,0 +1,14 @@ +package com.zt.plat.module.bpm.service.task.trigger.http; + +import com.zt.plat.module.bpm.service.task.trigger.BpmTrigger; +import lombok.extern.slf4j.Slf4j; + +/** + * BPM 发送 HTTP 请求触发器抽象类 + * + * @author jason + */ +@Slf4j +public abstract class BpmAbstractHttpRequestTrigger implements BpmTrigger { + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java new file mode 100644 index 0000000..3fe0606 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java @@ -0,0 +1,51 @@ +package com.zt.plat.module.bpm.service.task.trigger.http; + +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.zt.plat.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum; +import com.zt.plat.module.bpm.enums.definition.BpmTriggerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +/** + * BPM HTTP 回调触发器 + * + * @author jason + */ +@Component +@Slf4j +public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger { + + @Resource + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTriggerTypeEnum getType() { + return BpmTriggerTypeEnum.HTTP_CALLBACK; + } + + @Override + public void execute(String processInstanceId, String param) { + // 1. 解析 http 请求配置 + BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting setting = JsonUtils.parseObject(param, + BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting.class); + if (setting == null) { + log.error("[execute][流程({}) HTTP 回调触发器配置为空]", processInstanceId); + return; + } + + // 2. 发起请求 + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); + setting.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam() + .setKey("taskDefineKey") // 重要:回调请求 taskDefineKey 需要传给被调用方,用于回调执行 + .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(setting.getCallbackTaskDefineKey())); + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, + setting.getUrl(), setting.getHeader(), setting.getBody(), false, null); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java new file mode 100644 index 0000000..617bf7a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.bpm.service.task.trigger.http; + +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting; +import com.zt.plat.module.bpm.enums.definition.BpmTriggerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +/** + * BPM 发送同步 HTTP 请求触发器 + * + * @author jason + */ +@Component +@Slf4j +public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger { + + @Resource + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTriggerTypeEnum getType() { + return BpmTriggerTypeEnum.HTTP_REQUEST; + } + + @Override + public void execute(String processInstanceId, String param) { + // 1. 解析 http 请求配置 + HttpRequestTriggerSetting setting = JsonUtils.parseObject(param, HttpRequestTriggerSetting.class); + if (setting == null) { + log.error("[execute][流程({}) HTTP 触发器请求配置为空]", processInstanceId); + return; + } + + // 2. 发起请求 + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/database/core/DmDatabase.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/database/core/DmDatabase.java new file mode 100644 index 0000000..f7558a1 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/database/core/DmDatabase.java @@ -0,0 +1,546 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package liquibase.database.core; + +import java.lang.reflect.Method; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import liquibase.CatalogAndSchema; +import liquibase.GlobalConfiguration; +import liquibase.Scope; +import liquibase.database.AbstractJdbcDatabase; +import liquibase.database.DatabaseConnection; +import liquibase.database.OfflineConnection; +import liquibase.database.jvm.JdbcConnection; +import liquibase.exception.DatabaseException; +import liquibase.exception.UnexpectedLiquibaseException; +import liquibase.exception.ValidationErrors; +import liquibase.executor.ExecutorService; +import liquibase.statement.DatabaseFunction; +import liquibase.statement.SequenceCurrentValueFunction; +import liquibase.statement.SequenceNextValueFunction; +import liquibase.statement.UniqueConstraint; +import liquibase.statement.core.RawCallStatement; +import liquibase.statement.core.RawParameterizedSqlStatement; +import liquibase.structure.DatabaseObject; +import liquibase.structure.core.Catalog; +import liquibase.structure.core.Column; +import liquibase.structure.core.Index; +import liquibase.structure.core.PrimaryKey; +import liquibase.structure.core.Schema; +import liquibase.util.JdbcUtil; +import liquibase.util.StringUtil; +import org.apache.commons.lang3.StringUtils; + +public class DmDatabase extends AbstractJdbcDatabase { + private static final String PROXY_USER_REGEX = ".*(?:thin|oci)\\:(.+)/@.*"; + public static final Pattern PROXY_USER_PATTERN = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*"); + private static final String VERSION_REGEX = "(\\d+)\\.(\\d+)\\..*"; + private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\..*"); + public static final String PRODUCT_NAME = "DM DBMS"; + private static final ResourceBundle coreBundle = ResourceBundle.getBundle("liquibase/i18n/liquibase-core"); + protected final int SHORT_IDENTIFIERS_LENGTH = 30; + protected final int LONG_IDENTIFIERS_LEGNTH = 128; + public static final int ORACLE_12C_MAJOR_VERSION = 12; + public static final int ORACLE_23C_MAJOR_VERSION = 23; + private final Set reservedWords = new HashSet(); + private Set userDefinedTypes; + private Map savedSessionNlsSettings; + private Boolean canAccessDbaRecycleBin; + private Integer databaseMajorVersion; + private Integer databaseMinorVersion; + + public DmDatabase() { + super.unquotedObjectsAreUppercased = true; + super.setCurrentDateTimeFunction("SYSTIMESTAMP"); + this.dateFunctions.add(new DatabaseFunction("SYSDATE")); + this.dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP")); + this.dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP")); + super.sequenceNextValueFunction = "%s.nextval"; + super.sequenceCurrentValueFunction = "%s.currval"; + } + + public int getPriority() { + return 1; + } + + private void tryProxySession(String url, Connection con) { + Matcher m = PROXY_USER_PATTERN.matcher(url); + if (m.matches()) { + Properties props = new Properties(); + props.put("PROXY_USER_NAME", m.group(1)); + + try { + Method method = con.getClass().getMethod("openProxySession", Integer.TYPE, Properties.class); + method.setAccessible(true); + method.invoke(con, 1, props); + } catch (Exception e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage()); + return; + } + + try { + Method method = con.getClass().getMethod("isProxySession"); + method.setAccessible(true); + boolean b = (Boolean)method.invoke(con); + if (!b) { + Scope.getCurrentScope().getLog(this.getClass()).info("Proxy session not established on OracleDatabase: "); + } + } catch (Exception e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage()); + } + } + + } + + public void setConnection(DatabaseConnection conn) { + this.reservedWords.addAll(Arrays.asList("GROUP", "USER", "SESSION", "PASSWORD", "RESOURCE", "START", "SIZE", "UID", "DESC", "ORDER")); + Connection sqlConn = null; + if (!(conn instanceof OfflineConnection)) { + try { + if (conn instanceof JdbcConnection) { + sqlConn = ((JdbcConnection)conn).getWrappedConnection(); + } + } catch (Exception e) { + throw new UnexpectedLiquibaseException(e); + } + + if (sqlConn != null) { + this.tryProxySession(conn.getURL(), sqlConn); + + try { + this.reservedWords.addAll(Arrays.asList(sqlConn.getMetaData().getSQLKeywords().toUpperCase().split(",\\s*"))); + } catch (SQLException e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Could get sql keywords on OracleDatabase: " + e.getMessage()); + } + + try { + Method method = sqlConn.getClass().getMethod("setRemarksReporting", Boolean.TYPE); + method.setAccessible(true); + method.invoke(sqlConn, true); + } catch (Exception e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Could not set remarks reporting on OracleDatabase: " + e.getMessage()); + } + + CallableStatement statement = null; + + try { + statement = sqlConn.prepareCall("{call DBMS_UTILITY.DB_VERSION(?,?)}"); + statement.registerOutParameter(1, 12); + statement.registerOutParameter(2, 12); + statement.execute(); + String compatibleVersion = statement.getString(2); + if (compatibleVersion != null) { + Matcher majorVersionMatcher = VERSION_PATTERN.matcher(compatibleVersion); + if (majorVersionMatcher.matches()) { + this.databaseMajorVersion = Integer.valueOf(majorVersionMatcher.group(1)); + this.databaseMinorVersion = Integer.valueOf(majorVersionMatcher.group(2)); + } + } + } catch (SQLException e) { + String message = "Cannot read from DBMS_UTILITY.DB_VERSION: " + e.getMessage(); + Scope.getCurrentScope().getLog(this.getClass()).info("Could not set check compatibility mode on OracleDatabase, assuming not running in any sort of compatibility mode: " + message); + } finally { + JdbcUtil.closeStatement(statement); + } + + if (GlobalConfiguration.DDL_LOCK_TIMEOUT.getCurrentValue() != null) { + int timeoutValue = (Integer)GlobalConfiguration.DDL_LOCK_TIMEOUT.getCurrentValue(); + Scope.getCurrentScope().getLog(this.getClass()).fine("Setting DDL_LOCK_TIMEOUT value to " + timeoutValue); + String sql = "ALTER SESSION SET DDL_LOCK_TIMEOUT=" + timeoutValue; + PreparedStatement ddlLockTimeoutStatement = null; + + try { + ddlLockTimeoutStatement = sqlConn.prepareStatement(sql); + ddlLockTimeoutStatement.execute(); + } catch (SQLException sqle) { + Scope.getCurrentScope().getUI().sendErrorMessage("Unable to set the DDL_LOCK_TIMEOUT_VALUE: " + sqle.getMessage(), sqle); + Scope.getCurrentScope().getLog(this.getClass()).warning("Unable to set the DDL_LOCK_TIMEOUT_VALUE: " + sqle.getMessage(), sqle); + } finally { + JdbcUtil.closeStatement(ddlLockTimeoutStatement); + } + } + } + } + + super.setConnection(conn); + } + + public String getShortName() { + return "dm"; + } + + protected String getDefaultDatabaseProductName() { + return PRODUCT_NAME; + } + + public int getDatabaseMajorVersion() throws DatabaseException { + return this.databaseMajorVersion == null ? super.getDatabaseMajorVersion() : this.databaseMajorVersion; + } + + public int getDatabaseMinorVersion() throws DatabaseException { + return this.databaseMinorVersion == null ? super.getDatabaseMinorVersion() : this.databaseMinorVersion; + } + + public Integer getDefaultPort() { + return 5236; + } + + public String getJdbcCatalogName(CatalogAndSchema schema) { + return null; + } + + public String getJdbcSchemaName(CatalogAndSchema schema) { + return this.correctObjectName(schema.getCatalogName() == null ? schema.getSchemaName() : schema.getCatalogName(), Schema.class); + } + + protected String getAutoIncrementClause(String generationType, Boolean defaultOnNull) { + if (StringUtil.isEmpty(generationType)) { + return super.getAutoIncrementClause(); + } else { + String autoIncrementClause = "GENERATED %s AS IDENTITY"; + String generationStrategy = generationType; + if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) { + generationStrategy = generationType + " ON NULL"; + } + + return String.format(autoIncrementClause, generationStrategy); + } + } + + public String generatePrimaryKeyName(String tableName) { + return tableName.length() > 27 ? "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27) : "PK_" + tableName.toUpperCase(Locale.US); + } + + public boolean supportsInitiallyDeferrableColumns() { + return true; + } + + public boolean isReservedWord(String objectName) { + return this.reservedWords.contains(objectName.toUpperCase()); + } + + public boolean supportsSequences() { + return true; + } + + public boolean supports(Class object) { + return Schema.class.isAssignableFrom(object) ? false : super.supports(object); + } + + public boolean supportsSchemas() { + return false; + } + + protected String getConnectionCatalogName() throws DatabaseException { + if (this.getConnection() instanceof OfflineConnection) { + return this.getConnection().getCatalog(); + } else if (!(this.getConnection() instanceof JdbcConnection)) { + return this.defaultCatalogName; + } else { + try { + return (String)((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class); + } catch (Exception e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Error getting default schema", e); + return null; + } + } + } + + public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException { + return "oracle".equalsIgnoreCase(conn.getDatabaseProductName()); + } + + public String getDefaultDriver(String url) { + return url.startsWith("jdbc:dm") ? "dm.jdbc.driver.DmDriver" : null; + } + + public String getDefaultCatalogName() { + String defaultCatalogName = super.getDefaultCatalogName(); + if (Boolean.TRUE.equals(GlobalConfiguration.PRESERVE_SCHEMA_CASE.getCurrentValue())) { + return defaultCatalogName; + } else { + return defaultCatalogName == null ? null : defaultCatalogName.toUpperCase(Locale.US); + } + } + + public String getDateLiteral(String isoDate) { + String normalLiteral = super.getDateLiteral(isoDate); + if (this.isDateOnly(isoDate)) { + return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')"; + } else if (this.isTimeOnly(isoDate)) { + return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')"; + } else if (this.isTimestamp(isoDate)) { + return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')"; + } else if (this.isDateTime(isoDate)) { + int seppos = normalLiteral.lastIndexOf(46); + if (seppos != -1) { + normalLiteral = normalLiteral.substring(0, seppos) + "'"; + } + + return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')"; + } else { + return "UNSUPPORTED:" + isoDate; + } + } + + public boolean isSystemObject(DatabaseObject example) { + if (example == null) { + return false; + } else if (this.isLiquibaseObject(example)) { + return false; + } else { + if (example instanceof Schema) { + if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) { + return true; + } + + if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) { + return true; + } + } else if (this.isSystemObject(example.getSchema())) { + return true; + } + + if (example instanceof Catalog) { + if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) { + return true; + } + } else if (example.getName() != null) { + if (example.getName().startsWith("BIN$")) { + boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin(); + if (!filteredInOriginalQuery) { + filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName()); + } + + if (!filteredInOriginalQuery) { + return true; + } + + return !(example instanceof PrimaryKey) && !(example instanceof Index) && !(example instanceof UniqueConstraint); + } + + if (example.getName().startsWith("AQ$")) { + return true; + } + + if (example.getName().startsWith("DR$")) { + return true; + } + + if (example.getName().startsWith("SYS_IOT_OVER")) { + return true; + } + + if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) { + return true; + } + + if (example.getName().startsWith("MLOG$_")) { + return true; + } + + if (example.getName().startsWith("RUPD$_")) { + return true; + } + + if (example.getName().startsWith("WM$_")) { + return true; + } + + if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { + return true; + } + + if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { + return true; + } + + if (example.getName().startsWith("ISEQ$$_")) { + return true; + } + + if (example.getName().startsWith("USLOG$")) { + return true; + } + + if (example.getName().startsWith("SYS_FBA")) { + return true; + } + } + + return super.isSystemObject(example); + } + } + + public boolean supportsTablespaces() { + return true; + } + + public boolean supportsAutoIncrement() { + boolean isAutoIncrementSupported = false; + + try { + if (this.getDatabaseMajorVersion() >= 12) { + isAutoIncrementSupported = true; + } + } catch (DatabaseException var3) { + isAutoIncrementSupported = false; + } + + return isAutoIncrementSupported; + } + + public boolean supportsRestrictForeignKeys() { + return false; + } + + public int getDataTypeMaxParameters(String dataTypeName) { + if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) { + return 0; + } else { + return "BINARY_DOUBLE".equals(dataTypeName.toUpperCase()) ? 0 : super.getDataTypeMaxParameters(dataTypeName); + } + } + + public String getSystemTableWhereClause(String tableNameColumn) { + List clauses = new ArrayList(Arrays.asList("BIN$", "AQ$", "DR$", "SYS_IOT_OVER", "MLOG$_", "RUPD$_", "WM$_", "ISEQ$$_", "USLOG$", "SYS_FBA")); + clauses.replaceAll((s) -> tableNameColumn + " NOT LIKE '" + s + "%'"); + return "(" + StringUtil.join(clauses, " AND ") + ")"; + } + + public boolean jdbcCallsCatalogsSchemas() { + return true; + } + + public Set getUserDefinedTypes() { + if (this.userDefinedTypes == null) { + this.userDefinedTypes = new HashSet(); + if (this.getConnection() != null && !(this.getConnection() instanceof OfflineConnection)) { + try { + try { + this.userDefinedTypes.addAll(((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForList(new RawParameterizedSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class)); + } catch (DatabaseException var2) { + this.userDefinedTypes.addAll(((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForList(new RawParameterizedSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class)); + } + } catch (DatabaseException var3) { + } + } + } + + return this.userDefinedTypes; + } + + public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) { + if (databaseFunction != null && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) { + return databaseFunction.toString(); + } else if (!(databaseFunction instanceof SequenceNextValueFunction) && !(databaseFunction instanceof SequenceCurrentValueFunction)) { + return super.generateDatabaseFunctionValue(databaseFunction); + } else { + String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction); + return quotedSeq.replaceFirst("\"([^.\"]+)\\.([^.\"]+)\"", "\"$1\".\"$2\""); + } + } + + public ValidationErrors validate() { + ValidationErrors errors = super.validate(); + DatabaseConnection connection = this.getConnection(); + if (connection != null && !(connection instanceof OfflineConnection)) { + if (!this.canAccessDbaRecycleBin()) { + errors.addWarning(this.getDbaRecycleBinWarning()); + } + + return errors; + } else { + Scope.getCurrentScope().getLog(this.getClass()).info("Cannot validate offline database"); + return errors; + } + } + + public String getDbaRecycleBinWarning() { + return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where constraints are deleted and restored. Since Oracle doesn't properly restore the original table names referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this issue.\n\nThe user you used to connect to the database (" + this.getConnection().getConnectionUserName() + ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. Please run the following SQL to set the appropriate permissions, and try running the command again.\n\n GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + this.getConnection().getConnectionUserName() + ";"; + } + + public boolean canAccessDbaRecycleBin() { + if (this.canAccessDbaRecycleBin == null) { + DatabaseConnection connection = this.getConnection(); + if (connection == null || connection instanceof OfflineConnection) { + return false; + } + + Statement statement = null; + + try { + statement = ((JdbcConnection)connection).createStatement(); + ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1"); + resultSet.close(); + this.canAccessDbaRecycleBin = true; + } catch (Exception var7) { + if (var7 instanceof SQLException && var7.getMessage().startsWith("ORA-00942")) { + this.canAccessDbaRecycleBin = false; + } else { + Scope.getCurrentScope().getLog(this.getClass()).warning("Cannot check dba_recyclebin access", var7); + this.canAccessDbaRecycleBin = false; + } + } finally { + JdbcUtil.close((ResultSet)null, statement); + } + } + + return this.canAccessDbaRecycleBin; + } + + public boolean supportsNotNullConstraintNames() { + return true; + } + + public boolean isValidOracleIdentifier(String identifier, Class type) { + if (identifier != null && identifier.length() >= 1) { + if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$")) { + return false; + } else { + return identifier.length() <= 128; + } + } else { + return false; + } + } + + public int getIdentifierMaximumLength() { + try { + if (this.getDatabaseMajorVersion() < 12) { + return 30; + } else { + return this.getDatabaseMajorVersion() == 12 && this.getDatabaseMinorVersion() <= 1 ? 30 : 128; + } + } catch (DatabaseException ex) { + throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex); + } + } + + public boolean supportsDatabaseChangeLogHistory() { + return true; + } + + public String correctObjectName(String objectName, Class objectType) { + return objectType.equals(Column.class) && StringUtils.startsWithIgnoreCase(objectName, "int") ? "NUMBER(*, 0)" : super.correctObjectName(objectName, objectType); + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java new file mode 100644 index 0000000..6f57410 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java @@ -0,0 +1,149 @@ +package liquibase.datatype.core; + +import liquibase.change.core.LoadDataChange; +import liquibase.database.Database; +import liquibase.database.core.*; +import liquibase.datatype.DataTypeInfo; +import liquibase.datatype.DatabaseDataType; +import liquibase.datatype.LiquibaseDataType; +import liquibase.exception.UnexpectedLiquibaseException; +import liquibase.statement.DatabaseFunction; +import liquibase.util.StringUtil; + +import java.util.Locale; +import java.util.regex.Pattern; + +@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT) +public class BooleanType extends LiquibaseDataType { + + @Override + public DatabaseDataType toDatabaseDataType(Database database) { + String originalDefinition = StringUtil.trimToEmpty(getRawDefinition()); +// if ((database instanceof Firebird3Database)) { +// return new DatabaseDataType("BOOLEAN"); +// } + + if ((database instanceof AbstractDb2Database) || (database instanceof FirebirdDatabase)) { + return new DatabaseDataType("SMALLINT"); + } else if (database instanceof MSSQLDatabase) { + return new DatabaseDataType(database.escapeDataTypeName("bit")); + } else if (database instanceof MySQLDatabase) { + if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) { + return new DatabaseDataType("BIT", getParameters()); + } + return new DatabaseDataType("BIT", 1); + } else if (database instanceof OracleDatabase) { + return new DatabaseDataType("NUMBER", 1); + } else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) { + return new DatabaseDataType("BIT"); + } else if (database instanceof DerbyDatabase) { + if (((DerbyDatabase) database).supportsBooleanDataType()) { + return new DatabaseDataType("BOOLEAN"); + } else { + return new DatabaseDataType("SMALLINT"); + } + } else if (database.getClass().isAssignableFrom(DB2Database.class)) { + if (((DB2Database) database).supportsBooleanDataType()) + return new DatabaseDataType("BOOLEAN"); + else + return new DatabaseDataType("SMALLINT"); + } else if (database instanceof HsqlDatabase) { + return new DatabaseDataType("BOOLEAN"); + } else if (database instanceof PostgresDatabase) { + if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) { + return new DatabaseDataType("BIT", getParameters()); + } + } else if(database instanceof DmDatabase) { + return new DatabaseDataType("bit"); + } + + return super.toDatabaseDataType(database); + } + + @Override + public String objectToSql(Object value, Database database) { + if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) { + return null; + } + + String returnValue; + if (value instanceof String) { + value = ((String) value).replaceAll("'", ""); + if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) { + returnValue = this.getTrueBooleanValue(database); + } else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals( + ((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) { + returnValue = this.getFalseBooleanValue(database); + } else { + throw new UnexpectedLiquibaseException("Unknown boolean value: " + value); + } + } else if (value instanceof Long) { + if (Long.valueOf(1).equals(value)) { + returnValue = this.getTrueBooleanValue(database); + } else { + returnValue = this.getFalseBooleanValue(database); + } + } else if (value instanceof Number) { + if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) { + returnValue = this.getTrueBooleanValue(database); + } else { + returnValue = this.getFalseBooleanValue(database); + } + } else if (value instanceof DatabaseFunction) { + return value.toString(); + } else if (value instanceof Boolean) { + if (((Boolean) value)) { + returnValue = this.getTrueBooleanValue(database); + } else { + returnValue = this.getFalseBooleanValue(database); + } + } else { + throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value"); + } + + return returnValue; + } + + protected boolean isNumericBoolean(Database database) { + if (database instanceof DerbyDatabase) { + return !((DerbyDatabase) database).supportsBooleanDataType(); + } else if (database.getClass().isAssignableFrom(DB2Database.class)) { + return !((DB2Database) database).supportsBooleanDataType(); + } + return (database instanceof Db2zDatabase) || (database instanceof DB2Database) || (database instanceof FirebirdDatabase) || (database instanceof + MSSQLDatabase) || (database instanceof MySQLDatabase) || (database instanceof OracleDatabase) || + (database instanceof SQLiteDatabase) || (database instanceof SybaseASADatabase) || (database instanceof + SybaseDatabase) || (database instanceof DmDatabase); + } + + /** + * The database-specific value to use for "false" "boolean" columns. + */ + public String getFalseBooleanValue(Database database) { + if (isNumericBoolean(database)) { + return "0"; + } + if (database instanceof InformixDatabase) { + return "'f'"; + } + return "FALSE"; + } + + /** + * The database-specific value to use for "true" "boolean" columns. + */ + public String getTrueBooleanValue(Database database) { + if (isNumericBoolean(database)) { + return "1"; + } + if (database instanceof InformixDatabase) { + return "'t'"; + } + return "TRUE"; + } + + @Override + public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() { + return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN; + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java new file mode 100644 index 0000000..2ac83d5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java @@ -0,0 +1,2094 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.common.engine.impl; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.Set; + +import javax.naming.InitialContext; +import javax.sql.DataSource; + +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.builder.xml.XMLConfigBuilder; +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.datasource.pooled.PooledDataSource; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.apache.ibatis.transaction.managed.ManagedTransactionFactory; +import org.apache.ibatis.type.ArrayTypeHandler; +import org.apache.ibatis.type.BigDecimalTypeHandler; +import org.apache.ibatis.type.BlobInputStreamTypeHandler; +import org.apache.ibatis.type.BlobTypeHandler; +import org.apache.ibatis.type.BooleanTypeHandler; +import org.apache.ibatis.type.ByteTypeHandler; +import org.apache.ibatis.type.ClobTypeHandler; +import org.apache.ibatis.type.DateOnlyTypeHandler; +import org.apache.ibatis.type.DateTypeHandler; +import org.apache.ibatis.type.DoubleTypeHandler; +import org.apache.ibatis.type.FloatTypeHandler; +import org.apache.ibatis.type.IntegerTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.LongTypeHandler; +import org.apache.ibatis.type.NClobTypeHandler; +import org.apache.ibatis.type.NStringTypeHandler; +import org.apache.ibatis.type.ShortTypeHandler; +import org.apache.ibatis.type.SqlxmlTypeHandler; +import org.apache.ibatis.type.StringTypeHandler; +import org.apache.ibatis.type.TimeOnlyTypeHandler; +import org.apache.ibatis.type.TypeHandlerRegistry; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.api.delegate.event.FlowableEventListener; +import org.flowable.common.engine.api.engine.EngineLifecycleListener; +import org.flowable.common.engine.impl.agenda.AgendaOperationExecutionListener; +import org.flowable.common.engine.impl.agenda.AgendaOperationRunner; +import org.flowable.common.engine.impl.cfg.CommandExecutorImpl; +import org.flowable.common.engine.impl.cfg.IdGenerator; +import org.flowable.common.engine.impl.cfg.TransactionContextFactory; +import org.flowable.common.engine.impl.cfg.standalone.StandaloneMybatisTransactionContextFactory; +import org.flowable.common.engine.impl.db.CommonDbSchemaManager; +import org.flowable.common.engine.impl.db.DbSqlSessionFactory; +import org.flowable.common.engine.impl.db.LogSqlExecutionTimePlugin; +import org.flowable.common.engine.impl.db.MybatisTypeAliasConfigurator; +import org.flowable.common.engine.impl.db.MybatisTypeHandlerConfigurator; +import org.flowable.common.engine.impl.db.SchemaManager; +import org.flowable.common.engine.impl.event.EventDispatchAction; +import org.flowable.common.engine.impl.event.FlowableEventDispatcherImpl; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandConfig; +import org.flowable.common.engine.impl.interceptor.CommandContextFactory; +import org.flowable.common.engine.impl.interceptor.CommandContextInterceptor; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.common.engine.impl.interceptor.CommandInterceptor; +import org.flowable.common.engine.impl.interceptor.CrDbRetryInterceptor; +import org.flowable.common.engine.impl.interceptor.DefaultCommandInvoker; +import org.flowable.common.engine.impl.interceptor.LogInterceptor; +import org.flowable.common.engine.impl.interceptor.SessionFactory; +import org.flowable.common.engine.impl.interceptor.TransactionContextInterceptor; +import org.flowable.common.engine.impl.lock.LockManager; +import org.flowable.common.engine.impl.lock.LockManagerImpl; +import org.flowable.common.engine.impl.logging.LoggingListener; +import org.flowable.common.engine.impl.logging.LoggingSession; +import org.flowable.common.engine.impl.logging.LoggingSessionFactory; +import org.flowable.common.engine.impl.persistence.GenericManagerFactory; +import org.flowable.common.engine.impl.persistence.StrongUuidGenerator; +import org.flowable.common.engine.impl.persistence.cache.EntityCache; +import org.flowable.common.engine.impl.persistence.cache.EntityCacheImpl; +import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManager; +import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.Entity; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManager; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.TableDataManager; +import org.flowable.common.engine.impl.persistence.entity.TableDataManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.data.ByteArrayDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.PropertyDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisByteArrayDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisPropertyDataManager; +import org.flowable.common.engine.impl.runtime.Clock; +import org.flowable.common.engine.impl.service.CommonEngineServiceImpl; +import org.flowable.common.engine.impl.util.DefaultClockImpl; +import org.flowable.common.engine.impl.util.IoUtil; +import org.flowable.common.engine.impl.util.ReflectUtil; +import org.flowable.eventregistry.api.EventRegistryEventConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +public abstract class AbstractEngineConfiguration { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** The tenant id indicating 'no tenant' */ + public static final String NO_TENANT_ID = ""; + + /** + * Checks the version of the DB schema against the library when the form engine is being created and throws an exception if the versions don't match. + */ + public static final String DB_SCHEMA_UPDATE_FALSE = "false"; + public static final String DB_SCHEMA_UPDATE_CREATE = "create"; + public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop"; + + /** + * Creates the schema when the form engine is being created and drops the schema when the form engine is being closed. + */ + public static final String DB_SCHEMA_UPDATE_DROP_CREATE = "drop-create"; + + /** + * Upon building of the process engine, a check is performed and an update of the schema is performed if it is necessary. + */ + public static final String DB_SCHEMA_UPDATE_TRUE = "true"; + + protected boolean forceCloseMybatisConnectionPool = true; + + protected String databaseType; + protected String jdbcDriver = "org.h2.Driver"; + protected String jdbcUrl = "jdbc:h2:tcp://localhost/~/flowable"; + protected String jdbcUsername = "sa"; + protected String jdbcPassword = ""; + protected String dataSourceJndiName; + protected int jdbcMaxActiveConnections = 16; + protected int jdbcMaxIdleConnections = 8; + protected int jdbcMaxCheckoutTime; + protected int jdbcMaxWaitTime; + protected boolean jdbcPingEnabled; + protected String jdbcPingQuery; + protected int jdbcPingConnectionNotUsedFor; + protected int jdbcDefaultTransactionIsolationLevel; + protected DataSource dataSource; + protected SchemaManager commonSchemaManager; + protected SchemaManager schemaManager; + protected Command schemaManagementCmd; + + protected String databaseSchemaUpdate = DB_SCHEMA_UPDATE_FALSE; + + /** + * Whether to use a lock when performing the database schema create or update operations. + */ + protected boolean useLockForDatabaseSchemaUpdate = false; + + protected String xmlEncoding = "UTF-8"; + + // COMMAND EXECUTORS /////////////////////////////////////////////// + + protected CommandExecutor commandExecutor; + protected Collection defaultCommandInterceptors; + protected CommandConfig defaultCommandConfig; + protected CommandConfig schemaCommandConfig; + protected CommandContextFactory commandContextFactory; + protected CommandInterceptor commandInvoker; + + protected AgendaOperationRunner agendaOperationRunner = (commandContext, runnable) -> runnable.run(); + protected Collection agendaOperationExecutionListeners; + + protected List customPreCommandInterceptors; + protected List customPostCommandInterceptors; + protected List commandInterceptors; + + protected Map engineConfigurations = new HashMap<>(); + protected Map serviceConfigurations = new HashMap<>(); + + protected ClassLoader classLoader; + /** + * Either use Class.forName or ClassLoader.loadClass for class loading. See http://forums.activiti.org/content/reflectutilloadclass-and-custom- classloader + */ + protected boolean useClassForNameClassLoading = true; + + protected List engineLifecycleListeners; + + // Event Registry ////////////////////////////////////////////////// + protected Map eventRegistryEventConsumers = new HashMap<>(); + + // MYBATIS SQL SESSION FACTORY ///////////////////////////////////// + + protected boolean isDbHistoryUsed = true; + protected DbSqlSessionFactory dbSqlSessionFactory; + protected SqlSessionFactory sqlSessionFactory; + protected TransactionFactory transactionFactory; + protected TransactionContextFactory transactionContextFactory; + + /** + * If set to true, enables bulk insert (grouping sql inserts together). Default true. + * For some databases (eg DB2+z/OS) needs to be set to false. + */ + protected boolean isBulkInsertEnabled = true; + + /** + * Some databases have a limit of how many parameters one sql insert can have (eg SQL Server, 2000 params (!= insert statements) ). Tweak this parameter in case of exceptions indicating too much + * is being put into one bulk insert, or make it higher if your database can cope with it and there are inserts with a huge amount of data. + *

+ * By default: 100 (55 for mssql server as it has a hard limit of 2000 parameters in a statement) + */ + protected int maxNrOfStatementsInBulkInsert = 100; + + public int DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER = 55; // currently Execution has most params (35). 2000 / 35 = 57. + + protected String mybatisMappingFile; + protected Set> customMybatisMappers; + protected Set customMybatisXMLMappers; + protected List customMybatisInterceptors; + + protected Set dependentEngineMyBatisXmlMappers; + protected List dependentEngineMybatisTypeAliasConfigs; + protected List dependentEngineMybatisTypeHandlerConfigs; + + // SESSION FACTORIES /////////////////////////////////////////////// + protected List customSessionFactories; + protected Map, SessionFactory> sessionFactories; + + protected boolean enableEventDispatcher = true; + protected FlowableEventDispatcher eventDispatcher; + protected List eventListeners; + protected Map> typedEventListeners; + protected List additionalEventDispatchActions; + + protected LoggingListener loggingListener; + + protected boolean transactionsExternallyManaged; + + /** + * Flag that can be set to configure or not a relational database is used. This is useful for custom implementations that do not use relational databases at all. + * + * If true (default), the {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will be used to determine what needs to happen wrt the database schema. + * + * If false, no validation or schema creation will be done. That means that the database schema must have been created 'manually' before but the engine does not validate whether the schema is + * correct. The {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will not be used. + */ + protected boolean usingRelationalDatabase = true; + + /** + * Flag that can be set to configure whether or not a schema is used. This is useful for custom implementations that do not use relational databases at all. + * Setting {@link #usingRelationalDatabase} to true will automatically imply using a schema. + */ + protected boolean usingSchemaMgmt = true; + + /** + * Allows configuring a database table prefix which is used for all runtime operations of the process engine. For example, if you specify a prefix named 'PRE1.', Flowable will query for executions + * in a table named 'PRE1.ACT_RU_EXECUTION_'. + * + *

+ * NOTE: the prefix is not respected by automatic database schema management. If you use {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_CREATE_DROP} or + * {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_TRUE}, Flowable will create the database tables using the default names, regardless of the prefix configured here. + */ + protected String databaseTablePrefix = ""; + + /** + * Escape character for doing wildcard searches. + * + * This will be added at then end of queries that include for example a LIKE clause. For example: SELECT * FROM table WHERE column LIKE '%\%%' ESCAPE '\'; + */ + protected String databaseWildcardEscapeCharacter; + + /** + * database catalog to use + */ + protected String databaseCatalog = ""; + + /** + * In some situations you want to set the schema to use for table checks / generation if the database metadata doesn't return that correctly, see https://jira.codehaus.org/browse/ACT-1220, + * https://jira.codehaus.org/browse/ACT-1062 + */ + protected String databaseSchema; + + /** + * Set to true in case the defined databaseTablePrefix is a schema-name, instead of an actual table name prefix. This is relevant for checking if Flowable-tables exist, the databaseTablePrefix + * will not be used here - since the schema is taken into account already, adding a prefix for the table-check will result in wrong table-names. + */ + protected boolean tablePrefixIsSchema; + + /** + * Set to true if the latest version of a definition should be retrieved, ignoring a possible parent deployment id value + */ + protected boolean alwaysLookupLatestDefinitionVersion; + + /** + * Set to true if by default lookups should fallback to the default tenant (an empty string by default or a defined tenant value) + */ + protected boolean fallbackToDefaultTenant; + + /** + * Default tenant provider that is executed when looking up definitions, in case the global or local fallback to default tenant value is true + */ + protected DefaultTenantProvider defaultTenantProvider = (tenantId, scope, scopeKey) -> NO_TENANT_ID; + + /** + * Enables the MyBatis plugin that logs the execution time of sql statements. + */ + protected boolean enableLogSqlExecutionTime; + + protected Properties databaseTypeMappings = getDefaultDatabaseTypeMappings(); + + /** + * Duration between the checks when acquiring a lock. + */ + protected Duration lockPollRate = Duration.ofSeconds(10); + + /** + * Duration to wait for the DB Schema lock before giving up. + */ + protected Duration schemaLockWaitTime = Duration.ofMinutes(5); + + // DATA MANAGERS ////////////////////////////////////////////////////////////////// + + protected PropertyDataManager propertyDataManager; + protected ByteArrayDataManager byteArrayDataManager; + protected TableDataManager tableDataManager; + + // ENTITY MANAGERS //////////////////////////////////////////////////////////////// + + protected PropertyEntityManager propertyEntityManager; + protected ByteArrayEntityManager byteArrayEntityManager; + + protected List customPreDeployers; + protected List customPostDeployers; + protected List deployers; + + // CONFIGURATORS //////////////////////////////////////////////////////////// + + protected boolean enableConfiguratorServiceLoader = true; // Enabled by default. In certain environments this should be set to false (eg osgi) + protected List configurators; // The injected configurators + protected List allConfigurators; // Including auto-discovered configurators + protected EngineConfigurator idmEngineConfigurator; + protected EngineConfigurator eventRegistryConfigurator; + + public static final String PRODUCT_NAME_POSTGRES = "PostgreSQL"; + public static final String PRODUCT_NAME_CRDB = "CockroachDB"; + + public static final String DATABASE_TYPE_H2 = "h2"; + public static final String DATABASE_TYPE_HSQL = "hsql"; + public static final String DATABASE_TYPE_MYSQL = "mysql"; + public static final String DATABASE_TYPE_ORACLE = "oracle"; + public static final String DATABASE_TYPE_POSTGRES = "postgres"; + public static final String DATABASE_TYPE_MSSQL = "mssql"; + public static final String DATABASE_TYPE_DB2 = "db2"; + public static final String DATABASE_TYPE_COCKROACHDB = "cockroachdb"; + + public static Properties getDefaultDatabaseTypeMappings() { + Properties databaseTypeMappings = new Properties(); + databaseTypeMappings.setProperty("H2", DATABASE_TYPE_H2); + databaseTypeMappings.setProperty("HSQL Database Engine", DATABASE_TYPE_HSQL); + databaseTypeMappings.setProperty("MySQL", DATABASE_TYPE_MYSQL); + databaseTypeMappings.setProperty("MariaDB", DATABASE_TYPE_MYSQL); + databaseTypeMappings.setProperty("Oracle", DATABASE_TYPE_ORACLE); + databaseTypeMappings.setProperty(PRODUCT_NAME_POSTGRES, DATABASE_TYPE_POSTGRES); + databaseTypeMappings.setProperty("Microsoft SQL Server", DATABASE_TYPE_MSSQL); + databaseTypeMappings.setProperty(DATABASE_TYPE_DB2, DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/NT", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/NT64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2 UDP", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUX", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUX390", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUXX8664", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUXZ64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUXPPC64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUXPPC64LE", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/400 SQL", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/6000", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2 UDB iSeries", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/AIX64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/HPUX", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/HP64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/SUN", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/SUN64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/PTX", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/2", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2 UDB AS400", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty(PRODUCT_NAME_CRDB, DATABASE_TYPE_COCKROACHDB); + databaseTypeMappings.setProperty("DM DBMS", DATABASE_TYPE_ORACLE);// 加入达梦支持 可以直接使用ORACLE或者MYSQL的 + return databaseTypeMappings; + } + + protected Map beans; + + protected IdGenerator idGenerator; + protected boolean usePrefixId; + + protected Clock clock; + protected ObjectMapper objectMapper; + + // Variables + + public static final int DEFAULT_GENERIC_MAX_LENGTH_STRING = 4000; + public static final int DEFAULT_ORACLE_MAX_LENGTH_STRING = 2000; + + /** + * Define a max length for storing String variable types in the database. Mainly used for the Oracle NVARCHAR2 limit of 2000 characters + */ + protected int maxLengthStringVariableType = -1; + + protected void initEngineConfigurations() { + addEngineConfiguration(getEngineCfgKey(), getEngineScopeType(), this); + } + + // DataSource + // /////////////////////////////////////////////////////////////// + + protected void initDataSource() { + if (dataSource == null) { + if (dataSourceJndiName != null) { + try { + dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName); + } catch (Exception e) { + throw new FlowableException("couldn't lookup datasource from " + dataSourceJndiName + ": " + e.getMessage(), e); + } + + } else if (jdbcUrl != null) { + if ((jdbcDriver == null) || (jdbcUsername == null)) { + throw new FlowableException("DataSource or JDBC properties have to be specified in a process engine configuration"); + } + + logger.debug("initializing datasource to db: {}", jdbcUrl); + + if (logger.isInfoEnabled()) { + logger.info("Configuring Datasource with following properties (omitted password for security)"); + logger.info("datasource driver : {}", jdbcDriver); + logger.info("datasource url : {}", jdbcUrl); + logger.info("datasource user name : {}", jdbcUsername); + } + + PooledDataSource pooledDataSource = new PooledDataSource(this.getClass().getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword); + + if (jdbcMaxActiveConnections > 0) { + pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections); + } + if (jdbcMaxIdleConnections > 0) { + pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections); + } + if (jdbcMaxCheckoutTime > 0) { + pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime); + } + if (jdbcMaxWaitTime > 0) { + pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime); + } + if (jdbcPingEnabled) { + pooledDataSource.setPoolPingEnabled(true); + if (jdbcPingQuery != null) { + pooledDataSource.setPoolPingQuery(jdbcPingQuery); + } + pooledDataSource.setPoolPingConnectionsNotUsedFor(jdbcPingConnectionNotUsedFor); + } + if (jdbcDefaultTransactionIsolationLevel > 0) { + pooledDataSource.setDefaultTransactionIsolationLevel(jdbcDefaultTransactionIsolationLevel); + } + dataSource = pooledDataSource; + } + } + + if (databaseType == null) { + initDatabaseType(); + } + } + + public void initDatabaseType() { + Connection connection = null; + try { + connection = dataSource.getConnection(); + DatabaseMetaData databaseMetaData = connection.getMetaData(); + String databaseProductName = databaseMetaData.getDatabaseProductName(); + logger.debug("database product name: '{}'", databaseProductName); + + // CRDB does not expose the version through the jdbc driver, so we need to fetch it through version(). + if (PRODUCT_NAME_POSTGRES.equalsIgnoreCase(databaseProductName)) { + try (PreparedStatement preparedStatement = connection.prepareStatement("select version() as version;"); + ResultSet resultSet = preparedStatement.executeQuery()) { + String version = null; + if (resultSet.next()) { + version = resultSet.getString("version"); + } + + if (StringUtils.isNotEmpty(version) && version.toLowerCase().startsWith(PRODUCT_NAME_CRDB.toLowerCase())) { + databaseProductName = PRODUCT_NAME_CRDB; + logger.info("CockroachDB version '{}' detected", version); + } + } + } + + databaseType = databaseTypeMappings.getProperty(databaseProductName); + if (databaseType == null) { + throw new FlowableException("couldn't deduct database type from database product name '" + databaseProductName + "'"); + } + logger.debug("using database type: {}", databaseType); + + } catch (SQLException e) { + throw new RuntimeException("Exception while initializing Database connection", e); + } finally { + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + logger.error("Exception while closing the Database connection", e); + } + } + + // Special care for MSSQL, as it has a hard limit of 2000 params per statement (incl bulk statement). + // Especially with executions, with 100 as default, this limit is passed. + if (DATABASE_TYPE_MSSQL.equals(databaseType)) { + maxNrOfStatementsInBulkInsert = DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER; + } + } + + public void initSchemaManager() { + if (this.commonSchemaManager == null) { + this.commonSchemaManager = new CommonDbSchemaManager(); + } + } + + // session factories //////////////////////////////////////////////////////// + + public void addSessionFactory(SessionFactory sessionFactory) { + sessionFactories.put(sessionFactory.getSessionType(), sessionFactory); + } + + public void initCommandContextFactory() { + if (commandContextFactory == null) { + commandContextFactory = new CommandContextFactory(); + } + } + + public void initTransactionContextFactory() { + if (transactionContextFactory == null) { + transactionContextFactory = new StandaloneMybatisTransactionContextFactory(); + } + } + + public void initCommandExecutors() { + initDefaultCommandConfig(); + initSchemaCommandConfig(); + initCommandInvoker(); + initCommandInterceptors(); + initCommandExecutor(); + } + + + public void initDefaultCommandConfig() { + if (defaultCommandConfig == null) { + defaultCommandConfig = new CommandConfig(); + } + } + + public void initSchemaCommandConfig() { + if (schemaCommandConfig == null) { + schemaCommandConfig = new CommandConfig(); + } + } + + public void initCommandInvoker() { + if (commandInvoker == null) { + commandInvoker = new DefaultCommandInvoker(); + } + } + + public void initCommandInterceptors() { + if (commandInterceptors == null) { + commandInterceptors = new ArrayList<>(); + if (customPreCommandInterceptors != null) { + commandInterceptors.addAll(customPreCommandInterceptors); + } + commandInterceptors.addAll(getDefaultCommandInterceptors()); + if (customPostCommandInterceptors != null) { + commandInterceptors.addAll(customPostCommandInterceptors); + } + commandInterceptors.add(commandInvoker); + } + } + + public Collection getDefaultCommandInterceptors() { + if (defaultCommandInterceptors == null) { + List interceptors = new ArrayList<>(); + interceptors.add(new LogInterceptor()); + + if (DATABASE_TYPE_COCKROACHDB.equals(databaseType)) { + interceptors.add(new CrDbRetryInterceptor()); + } + + CommandInterceptor transactionInterceptor = createTransactionInterceptor(); + if (transactionInterceptor != null) { + interceptors.add(transactionInterceptor); + } + + if (commandContextFactory != null) { + String engineCfgKey = getEngineCfgKey(); + CommandContextInterceptor commandContextInterceptor = new CommandContextInterceptor(commandContextFactory, + classLoader, useClassForNameClassLoading, clock, objectMapper); + engineConfigurations.put(engineCfgKey, this); + commandContextInterceptor.setEngineCfgKey(engineCfgKey); + commandContextInterceptor.setEngineConfigurations(engineConfigurations); + interceptors.add(commandContextInterceptor); + } + + if (transactionContextFactory != null) { + interceptors.add(new TransactionContextInterceptor(transactionContextFactory)); + } + + List additionalCommandInterceptors = getAdditionalDefaultCommandInterceptors(); + if (additionalCommandInterceptors != null) { + interceptors.addAll(additionalCommandInterceptors); + } + + defaultCommandInterceptors = interceptors; + } + return defaultCommandInterceptors; + } + + public abstract String getEngineCfgKey(); + + public abstract String getEngineScopeType(); + + public List getAdditionalDefaultCommandInterceptors() { + return null; + } + + public void initCommandExecutor() { + if (commandExecutor == null) { + CommandInterceptor first = initInterceptorChain(commandInterceptors); + commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first); + } + } + + public CommandInterceptor initInterceptorChain(List chain) { + if (chain == null || chain.isEmpty()) { + throw new FlowableException("invalid command interceptor chain configuration: " + chain); + } + for (int i = 0; i < chain.size() - 1; i++) { + chain.get(i).setNext(chain.get(i + 1)); + } + return chain.get(0); + } + + public abstract CommandInterceptor createTransactionInterceptor(); + + + public void initBeans() { + if (beans == null) { + beans = new HashMap<>(); + } + } + + // id generator + // ///////////////////////////////////////////////////////////// + + public void initIdGenerator() { + if (idGenerator == null) { + idGenerator = new StrongUuidGenerator(); + } + } + + public void initObjectMapper() { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + } + } + + public void initClock() { + if (clock == null) { + clock = new DefaultClockImpl(); + } + } + + // Data managers /////////////////////////////////////////////////////////// + + public void initDataManagers() { + if (propertyDataManager == null) { + propertyDataManager = new MybatisPropertyDataManager(idGenerator); + } + + if (byteArrayDataManager == null) { + byteArrayDataManager = new MybatisByteArrayDataManager(idGenerator); + } + } + + // Entity managers ////////////////////////////////////////////////////////// + + public void initEntityManagers() { + if (propertyEntityManager == null) { + propertyEntityManager = new PropertyEntityManagerImpl(this, propertyDataManager); + } + + if (byteArrayEntityManager == null) { + byteArrayEntityManager = new ByteArrayEntityManagerImpl(byteArrayDataManager, getEngineCfgKey(), this::getEventDispatcher); + } + + if (tableDataManager == null) { + tableDataManager = new TableDataManagerImpl(this); + } + } + + // services + // ///////////////////////////////////////////////////////////////// + + protected void initService(Object service) { + if (service instanceof CommonEngineServiceImpl) { + ((CommonEngineServiceImpl) service).setCommandExecutor(commandExecutor); + } + } + + // myBatis SqlSessionFactory + // //////////////////////////////////////////////// + + public void initSessionFactories() { + if (sessionFactories == null) { + sessionFactories = new HashMap<>(); + + if (usingRelationalDatabase) { + initDbSqlSessionFactory(); + } + + addSessionFactory(new GenericManagerFactory(EntityCache.class, EntityCacheImpl.class)); + + if (isLoggingSessionEnabled()) { + if (!sessionFactories.containsKey(LoggingSession.class)) { + LoggingSessionFactory loggingSessionFactory = new LoggingSessionFactory(); + loggingSessionFactory.setLoggingListener(loggingListener); + loggingSessionFactory.setObjectMapper(objectMapper); + sessionFactories.put(LoggingSession.class, loggingSessionFactory); + } + } + + commandContextFactory.setSessionFactories(sessionFactories); + + } else { + if (usingRelationalDatabase) { + initDbSqlSessionFactoryEntitySettings(); + } + } + + if (customSessionFactories != null) { + for (SessionFactory sessionFactory : customSessionFactories) { + addSessionFactory(sessionFactory); + } + } + } + + public void initDbSqlSessionFactory() { + if (dbSqlSessionFactory == null) { + dbSqlSessionFactory = createDbSqlSessionFactory(); + } + dbSqlSessionFactory.setDatabaseType(databaseType); + dbSqlSessionFactory.setSqlSessionFactory(sqlSessionFactory); + dbSqlSessionFactory.setDbHistoryUsed(isDbHistoryUsed); + dbSqlSessionFactory.setDatabaseTablePrefix(databaseTablePrefix); + dbSqlSessionFactory.setTablePrefixIsSchema(tablePrefixIsSchema); + dbSqlSessionFactory.setDatabaseCatalog(databaseCatalog); + dbSqlSessionFactory.setDatabaseSchema(databaseSchema); + dbSqlSessionFactory.setMaxNrOfStatementsInBulkInsert(maxNrOfStatementsInBulkInsert); + + initDbSqlSessionFactoryEntitySettings(); + + addSessionFactory(dbSqlSessionFactory); + } + + public DbSqlSessionFactory createDbSqlSessionFactory() { + return new DbSqlSessionFactory(usePrefixId); + } + + protected abstract void initDbSqlSessionFactoryEntitySettings(); + + protected void defaultInitDbSqlSessionFactoryEntitySettings(List> insertOrder, List> deleteOrder) { + if (insertOrder != null) { + for (Class clazz : insertOrder) { + dbSqlSessionFactory.getInsertionOrder().add(clazz); + + if (isBulkInsertEnabled) { + dbSqlSessionFactory.getBulkInserteableEntityClasses().add(clazz); + } + } + } + + if (deleteOrder != null) { + for (Class clazz : deleteOrder) { + dbSqlSessionFactory.getDeletionOrder().add(clazz); + } + } + } + + public void initTransactionFactory() { + if (transactionFactory == null) { + if (transactionsExternallyManaged) { + transactionFactory = new ManagedTransactionFactory(); + Properties properties = new Properties(); + properties.put("closeConnection", "false"); + this.transactionFactory.setProperties(properties); + } else { + transactionFactory = new JdbcTransactionFactory(); + } + } + } + + public void initSqlSessionFactory() { + if (sqlSessionFactory == null) { + InputStream inputStream = null; + try { + inputStream = getMyBatisXmlConfigurationStream(); + + Environment environment = new Environment("default", transactionFactory, dataSource); + Reader reader = new InputStreamReader(inputStream); + Properties properties = new Properties(); + properties.put("prefix", databaseTablePrefix); + + String wildcardEscapeClause = ""; + if ((databaseWildcardEscapeCharacter != null) && (databaseWildcardEscapeCharacter.length() != 0)) { + wildcardEscapeClause = " escape '" + databaseWildcardEscapeCharacter + "'"; + } + properties.put("wildcardEscapeClause", wildcardEscapeClause); + + // set default properties + properties.put("limitBefore", ""); + properties.put("limitAfter", ""); + properties.put("limitBetween", ""); + properties.put("limitBeforeNativeQuery", ""); + properties.put("limitAfterNativeQuery", ""); + properties.put("blobType", "BLOB"); + properties.put("boolValue", "TRUE"); + + if (databaseType != null) { + properties.load(getResourceAsStream(pathToEngineDbProperties())); + } + + Configuration configuration = initMybatisConfiguration(environment, reader, properties); + sqlSessionFactory = new DefaultSqlSessionFactory(configuration); + + } catch (Exception e) { + throw new FlowableException("Error while building ibatis SqlSessionFactory: " + e.getMessage(), e); + } finally { + IoUtil.closeSilently(inputStream); + } + } else { + // This is needed when the SQL Session Factory is created by another engine. + // When custom XML Mappers are registered with this engine they need to be loaded in the configuration as well + applyCustomMybatisCustomizations(sqlSessionFactory.getConfiguration()); + } + } + + public String pathToEngineDbProperties() { + return "org/flowable/common/db/properties/" + databaseType + ".properties"; + } + + public Configuration initMybatisConfiguration(Environment environment, Reader reader, Properties properties) { + XMLConfigBuilder parser = new XMLConfigBuilder(reader, "", properties); + Configuration configuration = parser.getConfiguration(); + + if (databaseType != null) { + configuration.setDatabaseId(databaseType); + } + + configuration.setEnvironment(environment); + + initMybatisTypeHandlers(configuration); + initCustomMybatisInterceptors(configuration); + if (isEnableLogSqlExecutionTime()) { + initMyBatisLogSqlExecutionTimePlugin(configuration); + } + + configuration = parseMybatisConfiguration(parser); + return configuration; + } + + public void initCustomMybatisMappers(Configuration configuration) { + if (getCustomMybatisMappers() != null) { + for (Class clazz : getCustomMybatisMappers()) { + if (!configuration.hasMapper(clazz)) { + configuration.addMapper(clazz); + } + } + } + } + + public void initMybatisTypeHandlers(Configuration configuration) { + // When mapping into Map there is currently a problem with MyBatis. + // It will return objects which are driver specific. + // Therefore we are registering the mappings between Object.class and the specific jdbc type here. + // see https://github.com/mybatis/mybatis-3/issues/2216 for more info + TypeHandlerRegistry handlerRegistry = configuration.getTypeHandlerRegistry(); + + handlerRegistry.register(Object.class, JdbcType.BOOLEAN, new BooleanTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.BIT, new BooleanTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.TINYINT, new ByteTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.SMALLINT, new ShortTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.INTEGER, new IntegerTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.FLOAT, new FloatTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.DOUBLE, new DoubleTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.CHAR, new StringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.CLOB, new ClobTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.VARCHAR, new StringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.LONGVARCHAR, new StringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.NVARCHAR, new NStringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.NCHAR, new NStringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.NCLOB, new NClobTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.BIGINT, new LongTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.REAL, new BigDecimalTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.DECIMAL, new BigDecimalTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.NUMERIC, new BigDecimalTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.BLOB, new BlobInputStreamTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.LONGVARBINARY, new BlobTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.DATE, new DateOnlyTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.TIME, new TimeOnlyTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.TIMESTAMP, new DateTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.SQLXML, new SqlxmlTypeHandler()); + } + + public void initCustomMybatisInterceptors(Configuration configuration) { + if (customMybatisInterceptors!=null){ + for (Interceptor interceptor :customMybatisInterceptors){ + configuration.addInterceptor(interceptor); + } + } + } + + public void initMyBatisLogSqlExecutionTimePlugin(Configuration configuration) { + configuration.addInterceptor(new LogSqlExecutionTimePlugin()); + } + + public Configuration parseMybatisConfiguration(XMLConfigBuilder parser) { + Configuration configuration = parser.parse(); + + applyCustomMybatisCustomizations(configuration); + return configuration; + } + + protected void applyCustomMybatisCustomizations(Configuration configuration) { + initCustomMybatisMappers(configuration); + + if (dependentEngineMybatisTypeAliasConfigs != null) { + for (MybatisTypeAliasConfigurator typeAliasConfig : dependentEngineMybatisTypeAliasConfigs) { + typeAliasConfig.configure(configuration.getTypeAliasRegistry()); + } + } + if (dependentEngineMybatisTypeHandlerConfigs != null) { + for (MybatisTypeHandlerConfigurator typeHandlerConfig : dependentEngineMybatisTypeHandlerConfigs) { + typeHandlerConfig.configure(configuration.getTypeHandlerRegistry()); + } + } + + parseDependentEngineMybatisXMLMappers(configuration); + parseCustomMybatisXMLMappers(configuration); + } + + public void parseCustomMybatisXMLMappers(Configuration configuration) { + if (getCustomMybatisXMLMappers() != null) { + for (String resource : getCustomMybatisXMLMappers()) { + parseMybatisXmlMapping(configuration, resource); + } + } + } + + public void parseDependentEngineMybatisXMLMappers(Configuration configuration) { + if (getDependentEngineMyBatisXmlMappers() != null) { + for (String resource : getDependentEngineMyBatisXmlMappers()) { + parseMybatisXmlMapping(configuration, resource); + } + } + } + + protected void parseMybatisXmlMapping(Configuration configuration, String resource) { + // see XMLConfigBuilder.mapperElement() + XMLMapperBuilder mapperParser = new XMLMapperBuilder(getResourceAsStream(resource), configuration, resource, configuration.getSqlFragments()); + mapperParser.parse(); + } + + protected InputStream getResourceAsStream(String resource) { + ClassLoader classLoader = getClassLoader(); + if (classLoader != null) { + return getClassLoader().getResourceAsStream(resource); + } else { + return this.getClass().getClassLoader().getResourceAsStream(resource); + } + } + + public void setMybatisMappingFile(String file) { + this.mybatisMappingFile = file; + } + + public String getMybatisMappingFile() { + return mybatisMappingFile; + } + + public abstract InputStream getMyBatisXmlConfigurationStream(); + + public void initConfigurators() { + + allConfigurators = new ArrayList<>(); + allConfigurators.addAll(getEngineSpecificEngineConfigurators()); + + // Configurators that are explicitly added to the config + if (configurators != null) { + allConfigurators.addAll(configurators); + } + + // Auto discovery through ServiceLoader + if (enableConfiguratorServiceLoader) { + ClassLoader classLoader = getClassLoader(); + if (classLoader == null) { + classLoader = ReflectUtil.getClassLoader(); + } + + ServiceLoader configuratorServiceLoader = ServiceLoader.load(EngineConfigurator.class, classLoader); + int nrOfServiceLoadedConfigurators = 0; + for (EngineConfigurator configurator : configuratorServiceLoader) { + allConfigurators.add(configurator); + nrOfServiceLoadedConfigurators++; + } + + if (nrOfServiceLoadedConfigurators > 0) { + logger.info("Found {} auto-discoverable Process Engine Configurator{}", nrOfServiceLoadedConfigurators, nrOfServiceLoadedConfigurators > 1 ? "s" : ""); + } + + if (!allConfigurators.isEmpty()) { + + // Order them according to the priorities (useful for dependent + // configurator) + allConfigurators.sort(new Comparator() { + + @Override + public int compare(EngineConfigurator configurator1, EngineConfigurator configurator2) { + int priority1 = configurator1.getPriority(); + int priority2 = configurator2.getPriority(); + + if (priority1 < priority2) { + return -1; + } else if (priority1 > priority2) { + return 1; + } + return 0; + } + }); + + // Execute the configurators + logger.info("Found {} Engine Configurators in total:", allConfigurators.size()); + for (EngineConfigurator configurator : allConfigurators) { + logger.info("{} (priority:{})", configurator.getClass(), configurator.getPriority()); + } + + } + + } + } + + public void close() { + if (forceCloseMybatisConnectionPool && dataSource instanceof PooledDataSource) { + /* + * When the datasource is created by a Flowable engine (i.e. it's an instance of PooledDataSource), + * the connection pool needs to be closed when closing the engine. + * Note that calling forceCloseAll() multiple times (as is the case when running with multiple engine) is ok. + */ + ((PooledDataSource) dataSource).forceCloseAll(); + } + } + + protected List getEngineSpecificEngineConfigurators() { + // meant to be overridden if needed + return Collections.emptyList(); + } + + public void configuratorsBeforeInit() { + for (EngineConfigurator configurator : allConfigurators) { + logger.info("Executing beforeInit() of {} (priority:{})", configurator.getClass(), configurator.getPriority()); + configurator.beforeInit(this); + } + } + + public void configuratorsAfterInit() { + for (EngineConfigurator configurator : allConfigurators) { + logger.info("Executing configure() of {} (priority:{})", configurator.getClass(), configurator.getPriority()); + configurator.configure(this); + } + } + + public LockManager getLockManager(String lockName) { + return new LockManagerImpl(commandExecutor, lockName, getLockPollRate(), getEngineCfgKey()); + } + + // getters and setters + // ////////////////////////////////////////////////////// + + public abstract String getEngineName(); + + public ClassLoader getClassLoader() { + return classLoader; + } + + public AbstractEngineConfiguration setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + return this; + } + + public boolean isUseClassForNameClassLoading() { + return useClassForNameClassLoading; + } + + public AbstractEngineConfiguration setUseClassForNameClassLoading(boolean useClassForNameClassLoading) { + this.useClassForNameClassLoading = useClassForNameClassLoading; + return this; + } + + public void addEngineLifecycleListener(EngineLifecycleListener engineLifecycleListener) { + if (this.engineLifecycleListeners == null) { + this.engineLifecycleListeners = new ArrayList<>(); + } + this.engineLifecycleListeners.add(engineLifecycleListener); + } + + public List getEngineLifecycleListeners() { + return engineLifecycleListeners; + } + + public AbstractEngineConfiguration setEngineLifecycleListeners(List engineLifecycleListeners) { + this.engineLifecycleListeners = engineLifecycleListeners; + return this; + } + + public String getDatabaseType() { + return databaseType; + } + + public AbstractEngineConfiguration setDatabaseType(String databaseType) { + this.databaseType = databaseType; + return this; + } + + public DataSource getDataSource() { + return dataSource; + } + + public AbstractEngineConfiguration setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + return this; + } + + public SchemaManager getSchemaManager() { + return schemaManager; + } + + public AbstractEngineConfiguration setSchemaManager(SchemaManager schemaManager) { + this.schemaManager = schemaManager; + return this; + } + + public SchemaManager getCommonSchemaManager() { + return commonSchemaManager; + } + + public AbstractEngineConfiguration setCommonSchemaManager(SchemaManager commonSchemaManager) { + this.commonSchemaManager = commonSchemaManager; + return this; + } + + public Command getSchemaManagementCmd() { + return schemaManagementCmd; + } + + public AbstractEngineConfiguration setSchemaManagementCmd(Command schemaManagementCmd) { + this.schemaManagementCmd = schemaManagementCmd; + return this; + } + + public String getJdbcDriver() { + return jdbcDriver; + } + + public AbstractEngineConfiguration setJdbcDriver(String jdbcDriver) { + this.jdbcDriver = jdbcDriver; + return this; + } + + public String getJdbcUrl() { + return jdbcUrl; + } + + public AbstractEngineConfiguration setJdbcUrl(String jdbcUrl) { + this.jdbcUrl = jdbcUrl; + return this; + } + + public String getJdbcUsername() { + return jdbcUsername; + } + + public AbstractEngineConfiguration setJdbcUsername(String jdbcUsername) { + this.jdbcUsername = jdbcUsername; + return this; + } + + public String getJdbcPassword() { + return jdbcPassword; + } + + public AbstractEngineConfiguration setJdbcPassword(String jdbcPassword) { + this.jdbcPassword = jdbcPassword; + return this; + } + + public int getJdbcMaxActiveConnections() { + return jdbcMaxActiveConnections; + } + + public AbstractEngineConfiguration setJdbcMaxActiveConnections(int jdbcMaxActiveConnections) { + this.jdbcMaxActiveConnections = jdbcMaxActiveConnections; + return this; + } + + public int getJdbcMaxIdleConnections() { + return jdbcMaxIdleConnections; + } + + public AbstractEngineConfiguration setJdbcMaxIdleConnections(int jdbcMaxIdleConnections) { + this.jdbcMaxIdleConnections = jdbcMaxIdleConnections; + return this; + } + + public int getJdbcMaxCheckoutTime() { + return jdbcMaxCheckoutTime; + } + + public AbstractEngineConfiguration setJdbcMaxCheckoutTime(int jdbcMaxCheckoutTime) { + this.jdbcMaxCheckoutTime = jdbcMaxCheckoutTime; + return this; + } + + public int getJdbcMaxWaitTime() { + return jdbcMaxWaitTime; + } + + public AbstractEngineConfiguration setJdbcMaxWaitTime(int jdbcMaxWaitTime) { + this.jdbcMaxWaitTime = jdbcMaxWaitTime; + return this; + } + + public boolean isJdbcPingEnabled() { + return jdbcPingEnabled; + } + + public AbstractEngineConfiguration setJdbcPingEnabled(boolean jdbcPingEnabled) { + this.jdbcPingEnabled = jdbcPingEnabled; + return this; + } + + public int getJdbcPingConnectionNotUsedFor() { + return jdbcPingConnectionNotUsedFor; + } + + public AbstractEngineConfiguration setJdbcPingConnectionNotUsedFor(int jdbcPingConnectionNotUsedFor) { + this.jdbcPingConnectionNotUsedFor = jdbcPingConnectionNotUsedFor; + return this; + } + + public int getJdbcDefaultTransactionIsolationLevel() { + return jdbcDefaultTransactionIsolationLevel; + } + + public AbstractEngineConfiguration setJdbcDefaultTransactionIsolationLevel(int jdbcDefaultTransactionIsolationLevel) { + this.jdbcDefaultTransactionIsolationLevel = jdbcDefaultTransactionIsolationLevel; + return this; + } + + public String getJdbcPingQuery() { + return jdbcPingQuery; + } + + public AbstractEngineConfiguration setJdbcPingQuery(String jdbcPingQuery) { + this.jdbcPingQuery = jdbcPingQuery; + return this; + } + + public String getDataSourceJndiName() { + return dataSourceJndiName; + } + + public AbstractEngineConfiguration setDataSourceJndiName(String dataSourceJndiName) { + this.dataSourceJndiName = dataSourceJndiName; + return this; + } + + public CommandConfig getSchemaCommandConfig() { + return schemaCommandConfig; + } + + public AbstractEngineConfiguration setSchemaCommandConfig(CommandConfig schemaCommandConfig) { + this.schemaCommandConfig = schemaCommandConfig; + return this; + } + + public boolean isTransactionsExternallyManaged() { + return transactionsExternallyManaged; + } + + public AbstractEngineConfiguration setTransactionsExternallyManaged(boolean transactionsExternallyManaged) { + this.transactionsExternallyManaged = transactionsExternallyManaged; + return this; + } + + public Map getBeans() { + return beans; + } + + public AbstractEngineConfiguration setBeans(Map beans) { + this.beans = beans; + return this; + } + + public IdGenerator getIdGenerator() { + return idGenerator; + } + + public AbstractEngineConfiguration setIdGenerator(IdGenerator idGenerator) { + this.idGenerator = idGenerator; + return this; + } + + public boolean isUsePrefixId() { + return usePrefixId; + } + + public AbstractEngineConfiguration setUsePrefixId(boolean usePrefixId) { + this.usePrefixId = usePrefixId; + return this; + } + + public String getXmlEncoding() { + return xmlEncoding; + } + + public AbstractEngineConfiguration setXmlEncoding(String xmlEncoding) { + this.xmlEncoding = xmlEncoding; + return this; + } + + public CommandConfig getDefaultCommandConfig() { + return defaultCommandConfig; + } + + public AbstractEngineConfiguration setDefaultCommandConfig(CommandConfig defaultCommandConfig) { + this.defaultCommandConfig = defaultCommandConfig; + return this; + } + + public CommandExecutor getCommandExecutor() { + return commandExecutor; + } + + public AbstractEngineConfiguration setCommandExecutor(CommandExecutor commandExecutor) { + this.commandExecutor = commandExecutor; + return this; + } + + public CommandContextFactory getCommandContextFactory() { + return commandContextFactory; + } + + public AbstractEngineConfiguration setCommandContextFactory(CommandContextFactory commandContextFactory) { + this.commandContextFactory = commandContextFactory; + return this; + } + + public CommandInterceptor getCommandInvoker() { + return commandInvoker; + } + + public AbstractEngineConfiguration setCommandInvoker(CommandInterceptor commandInvoker) { + this.commandInvoker = commandInvoker; + return this; + } + + public AgendaOperationRunner getAgendaOperationRunner() { + return agendaOperationRunner; + } + + public AbstractEngineConfiguration setAgendaOperationRunner(AgendaOperationRunner agendaOperationRunner) { + this.agendaOperationRunner = agendaOperationRunner; + return this; + } + + public Collection getAgendaOperationExecutionListeners() { + return agendaOperationExecutionListeners; + } + + public AbstractEngineConfiguration addAgendaOperationExecutionListener(AgendaOperationExecutionListener listener) { + if (this.agendaOperationExecutionListeners == null) { + this.agendaOperationExecutionListeners = new ArrayList<>(); + } + this.agendaOperationExecutionListeners.add(listener); + return this; + } + + public AbstractEngineConfiguration setAgendaOperationExecutionListeners(Collection agendaOperationExecutionListeners) { + this.agendaOperationExecutionListeners = agendaOperationExecutionListeners; + return this; + } + + public List getCustomPreCommandInterceptors() { + return customPreCommandInterceptors; + } + + public AbstractEngineConfiguration addCustomPreCommandInterceptor(CommandInterceptor commandInterceptor) { + if (this.customPreCommandInterceptors == null) { + this.customPreCommandInterceptors = new ArrayList<>(); + } + this.customPreCommandInterceptors.add(commandInterceptor); + return this; + } + + public AbstractEngineConfiguration setCustomPreCommandInterceptors(List customPreCommandInterceptors) { + this.customPreCommandInterceptors = customPreCommandInterceptors; + return this; + } + + public List getCustomPostCommandInterceptors() { + return customPostCommandInterceptors; + } + + public AbstractEngineConfiguration addCustomPostCommandInterceptor(CommandInterceptor commandInterceptor) { + if (this.customPostCommandInterceptors == null) { + this.customPostCommandInterceptors = new ArrayList<>(); + } + this.customPostCommandInterceptors.add(commandInterceptor); + return this; + } + + public AbstractEngineConfiguration setCustomPostCommandInterceptors(List customPostCommandInterceptors) { + this.customPostCommandInterceptors = customPostCommandInterceptors; + return this; + } + + public List getCommandInterceptors() { + return commandInterceptors; + } + + public AbstractEngineConfiguration setCommandInterceptors(List commandInterceptors) { + this.commandInterceptors = commandInterceptors; + return this; + } + + public Map getEngineConfigurations() { + return engineConfigurations; + } + + public AbstractEngineConfiguration setEngineConfigurations(Map engineConfigurations) { + this.engineConfigurations = engineConfigurations; + return this; + } + + public void addEngineConfiguration(String key, String scopeType, AbstractEngineConfiguration engineConfiguration) { + if (engineConfigurations == null) { + engineConfigurations = new HashMap<>(); + } + engineConfigurations.put(key, engineConfiguration); + engineConfigurations.put(scopeType, engineConfiguration); + } + + public Map getServiceConfigurations() { + return serviceConfigurations; + } + + public AbstractEngineConfiguration setServiceConfigurations(Map serviceConfigurations) { + this.serviceConfigurations = serviceConfigurations; + return this; + } + + public void addServiceConfiguration(String key, AbstractServiceConfiguration serviceConfiguration) { + if (serviceConfigurations == null) { + serviceConfigurations = new HashMap<>(); + } + serviceConfigurations.put(key, serviceConfiguration); + } + + public Map getEventRegistryEventConsumers() { + return eventRegistryEventConsumers; + } + + public AbstractEngineConfiguration setEventRegistryEventConsumers(Map eventRegistryEventConsumers) { + this.eventRegistryEventConsumers = eventRegistryEventConsumers; + return this; + } + + public void addEventRegistryEventConsumer(String key, EventRegistryEventConsumer eventRegistryEventConsumer) { + if (eventRegistryEventConsumers == null) { + eventRegistryEventConsumers = new HashMap<>(); + } + eventRegistryEventConsumers.put(key, eventRegistryEventConsumer); + } + + public AbstractEngineConfiguration setDefaultCommandInterceptors(Collection defaultCommandInterceptors) { + this.defaultCommandInterceptors = defaultCommandInterceptors; + return this; + } + + public SqlSessionFactory getSqlSessionFactory() { + return sqlSessionFactory; + } + + public AbstractEngineConfiguration setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { + this.sqlSessionFactory = sqlSessionFactory; + return this; + } + + public boolean isDbHistoryUsed() { + return isDbHistoryUsed; + } + + public AbstractEngineConfiguration setDbHistoryUsed(boolean isDbHistoryUsed) { + this.isDbHistoryUsed = isDbHistoryUsed; + return this; + } + + public DbSqlSessionFactory getDbSqlSessionFactory() { + return dbSqlSessionFactory; + } + + public AbstractEngineConfiguration setDbSqlSessionFactory(DbSqlSessionFactory dbSqlSessionFactory) { + this.dbSqlSessionFactory = dbSqlSessionFactory; + return this; + } + + public TransactionFactory getTransactionFactory() { + return transactionFactory; + } + + public AbstractEngineConfiguration setTransactionFactory(TransactionFactory transactionFactory) { + this.transactionFactory = transactionFactory; + return this; + } + + public TransactionContextFactory getTransactionContextFactory() { + return transactionContextFactory; + } + + public AbstractEngineConfiguration setTransactionContextFactory(TransactionContextFactory transactionContextFactory) { + this.transactionContextFactory = transactionContextFactory; + return this; + } + + public int getMaxNrOfStatementsInBulkInsert() { + return maxNrOfStatementsInBulkInsert; + } + + public AbstractEngineConfiguration setMaxNrOfStatementsInBulkInsert(int maxNrOfStatementsInBulkInsert) { + this.maxNrOfStatementsInBulkInsert = maxNrOfStatementsInBulkInsert; + return this; + } + + public boolean isBulkInsertEnabled() { + return isBulkInsertEnabled; + } + + public AbstractEngineConfiguration setBulkInsertEnabled(boolean isBulkInsertEnabled) { + this.isBulkInsertEnabled = isBulkInsertEnabled; + return this; + } + + public Set> getCustomMybatisMappers() { + return customMybatisMappers; + } + + public AbstractEngineConfiguration setCustomMybatisMappers(Set> customMybatisMappers) { + this.customMybatisMappers = customMybatisMappers; + return this; + } + + public Set getCustomMybatisXMLMappers() { + return customMybatisXMLMappers; + } + + public AbstractEngineConfiguration setCustomMybatisXMLMappers(Set customMybatisXMLMappers) { + this.customMybatisXMLMappers = customMybatisXMLMappers; + return this; + } + + public Set getDependentEngineMyBatisXmlMappers() { + return dependentEngineMyBatisXmlMappers; + } + + public AbstractEngineConfiguration setCustomMybatisInterceptors(List customMybatisInterceptors) { + this.customMybatisInterceptors = customMybatisInterceptors; + return this; + } + + public List getCustomMybatisInterceptors() { + return customMybatisInterceptors; + } + + public AbstractEngineConfiguration setDependentEngineMyBatisXmlMappers(Set dependentEngineMyBatisXmlMappers) { + this.dependentEngineMyBatisXmlMappers = dependentEngineMyBatisXmlMappers; + return this; + } + + public List getDependentEngineMybatisTypeAliasConfigs() { + return dependentEngineMybatisTypeAliasConfigs; + } + + public AbstractEngineConfiguration setDependentEngineMybatisTypeAliasConfigs(List dependentEngineMybatisTypeAliasConfigs) { + this.dependentEngineMybatisTypeAliasConfigs = dependentEngineMybatisTypeAliasConfigs; + return this; + } + + public List getDependentEngineMybatisTypeHandlerConfigs() { + return dependentEngineMybatisTypeHandlerConfigs; + } + + public AbstractEngineConfiguration setDependentEngineMybatisTypeHandlerConfigs(List dependentEngineMybatisTypeHandlerConfigs) { + this.dependentEngineMybatisTypeHandlerConfigs = dependentEngineMybatisTypeHandlerConfigs; + return this; + } + + public List getCustomSessionFactories() { + return customSessionFactories; + } + + public AbstractEngineConfiguration addCustomSessionFactory(SessionFactory sessionFactory) { + if (customSessionFactories == null) { + customSessionFactories = new ArrayList<>(); + } + customSessionFactories.add(sessionFactory); + return this; + } + + public AbstractEngineConfiguration setCustomSessionFactories(List customSessionFactories) { + this.customSessionFactories = customSessionFactories; + return this; + } + + public boolean isUsingRelationalDatabase() { + return usingRelationalDatabase; + } + + public AbstractEngineConfiguration setUsingRelationalDatabase(boolean usingRelationalDatabase) { + this.usingRelationalDatabase = usingRelationalDatabase; + return this; + } + + public boolean isUsingSchemaMgmt() { + return usingSchemaMgmt; + } + + public AbstractEngineConfiguration setUsingSchemaMgmt(boolean usingSchema) { + this.usingSchemaMgmt = usingSchema; + return this; + } + + public String getDatabaseTablePrefix() { + return databaseTablePrefix; + } + + public AbstractEngineConfiguration setDatabaseTablePrefix(String databaseTablePrefix) { + this.databaseTablePrefix = databaseTablePrefix; + return this; + } + + public String getDatabaseWildcardEscapeCharacter() { + return databaseWildcardEscapeCharacter; + } + + public AbstractEngineConfiguration setDatabaseWildcardEscapeCharacter(String databaseWildcardEscapeCharacter) { + this.databaseWildcardEscapeCharacter = databaseWildcardEscapeCharacter; + return this; + } + + public String getDatabaseCatalog() { + return databaseCatalog; + } + + public AbstractEngineConfiguration setDatabaseCatalog(String databaseCatalog) { + this.databaseCatalog = databaseCatalog; + return this; + } + + public String getDatabaseSchema() { + return databaseSchema; + } + + public AbstractEngineConfiguration setDatabaseSchema(String databaseSchema) { + this.databaseSchema = databaseSchema; + return this; + } + + public boolean isTablePrefixIsSchema() { + return tablePrefixIsSchema; + } + + public AbstractEngineConfiguration setTablePrefixIsSchema(boolean tablePrefixIsSchema) { + this.tablePrefixIsSchema = tablePrefixIsSchema; + return this; + } + + public boolean isAlwaysLookupLatestDefinitionVersion() { + return alwaysLookupLatestDefinitionVersion; + } + + public AbstractEngineConfiguration setAlwaysLookupLatestDefinitionVersion(boolean alwaysLookupLatestDefinitionVersion) { + this.alwaysLookupLatestDefinitionVersion = alwaysLookupLatestDefinitionVersion; + return this; + } + + public boolean isFallbackToDefaultTenant() { + return fallbackToDefaultTenant; + } + + public AbstractEngineConfiguration setFallbackToDefaultTenant(boolean fallbackToDefaultTenant) { + this.fallbackToDefaultTenant = fallbackToDefaultTenant; + return this; + } + + public AbstractEngineConfiguration setDefaultTenantValue(String defaultTenantValue) { + this.defaultTenantProvider = (tenantId, scope, scopeKey) -> defaultTenantValue; + return this; + } + + public DefaultTenantProvider getDefaultTenantProvider() { + return defaultTenantProvider; + } + + public AbstractEngineConfiguration setDefaultTenantProvider(DefaultTenantProvider defaultTenantProvider) { + this.defaultTenantProvider = defaultTenantProvider; + return this; + } + + public boolean isEnableLogSqlExecutionTime() { + return enableLogSqlExecutionTime; + } + + public void setEnableLogSqlExecutionTime(boolean enableLogSqlExecutionTime) { + this.enableLogSqlExecutionTime = enableLogSqlExecutionTime; + } + + public Map, SessionFactory> getSessionFactories() { + return sessionFactories; + } + + public AbstractEngineConfiguration setSessionFactories(Map, SessionFactory> sessionFactories) { + this.sessionFactories = sessionFactories; + return this; + } + + public String getDatabaseSchemaUpdate() { + return databaseSchemaUpdate; + } + + public AbstractEngineConfiguration setDatabaseSchemaUpdate(String databaseSchemaUpdate) { + this.databaseSchemaUpdate = databaseSchemaUpdate; + return this; + } + + public boolean isUseLockForDatabaseSchemaUpdate() { + return useLockForDatabaseSchemaUpdate; + } + + public AbstractEngineConfiguration setUseLockForDatabaseSchemaUpdate(boolean useLockForDatabaseSchemaUpdate) { + this.useLockForDatabaseSchemaUpdate = useLockForDatabaseSchemaUpdate; + return this; + } + + public boolean isEnableEventDispatcher() { + return enableEventDispatcher; + } + + public AbstractEngineConfiguration setEnableEventDispatcher(boolean enableEventDispatcher) { + this.enableEventDispatcher = enableEventDispatcher; + return this; + } + + public FlowableEventDispatcher getEventDispatcher() { + return eventDispatcher; + } + + public AbstractEngineConfiguration setEventDispatcher(FlowableEventDispatcher eventDispatcher) { + this.eventDispatcher = eventDispatcher; + return this; + } + + public List getEventListeners() { + return eventListeners; + } + + public AbstractEngineConfiguration setEventListeners(List eventListeners) { + this.eventListeners = eventListeners; + return this; + } + + public Map> getTypedEventListeners() { + return typedEventListeners; + } + + public AbstractEngineConfiguration setTypedEventListeners(Map> typedEventListeners) { + this.typedEventListeners = typedEventListeners; + return this; + } + + public List getAdditionalEventDispatchActions() { + return additionalEventDispatchActions; + } + + public AbstractEngineConfiguration setAdditionalEventDispatchActions(List additionalEventDispatchActions) { + this.additionalEventDispatchActions = additionalEventDispatchActions; + return this; + } + + public void initEventDispatcher() { + if (this.eventDispatcher == null) { + this.eventDispatcher = new FlowableEventDispatcherImpl(); + } + + initAdditionalEventDispatchActions(); + + this.eventDispatcher.setEnabled(enableEventDispatcher); + + initEventListeners(); + initTypedEventListeners(); + } + + protected void initEventListeners() { + if (eventListeners != null) { + for (FlowableEventListener listenerToAdd : eventListeners) { + this.eventDispatcher.addEventListener(listenerToAdd); + } + } + } + + protected void initAdditionalEventDispatchActions() { + if (this.additionalEventDispatchActions == null) { + this.additionalEventDispatchActions = new ArrayList<>(); + } + } + + protected void initTypedEventListeners() { + if (typedEventListeners != null) { + for (Map.Entry> listenersToAdd : typedEventListeners.entrySet()) { + // Extract types from the given string + FlowableEngineEventType[] types = FlowableEngineEventType.getTypesFromString(listenersToAdd.getKey()); + + for (FlowableEventListener listenerToAdd : listenersToAdd.getValue()) { + this.eventDispatcher.addEventListener(listenerToAdd, types); + } + } + } + } + + public boolean isLoggingSessionEnabled() { + return loggingListener != null; + } + + public LoggingListener getLoggingListener() { + return loggingListener; + } + + public void setLoggingListener(LoggingListener loggingListener) { + this.loggingListener = loggingListener; + } + + public Clock getClock() { + return clock; + } + + public AbstractEngineConfiguration setClock(Clock clock) { + this.clock = clock; + return this; + } + + public ObjectMapper getObjectMapper() { + return objectMapper; + } + + public AbstractEngineConfiguration setObjectMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + return this; + } + + public int getMaxLengthString() { + if (maxLengthStringVariableType == -1) { + if ("oracle".equalsIgnoreCase(databaseType)) { + return DEFAULT_ORACLE_MAX_LENGTH_STRING; + } else { + return DEFAULT_GENERIC_MAX_LENGTH_STRING; + } + } else { + return maxLengthStringVariableType; + } + } + + public int getMaxLengthStringVariableType() { + return maxLengthStringVariableType; + } + + public AbstractEngineConfiguration setMaxLengthStringVariableType(int maxLengthStringVariableType) { + this.maxLengthStringVariableType = maxLengthStringVariableType; + return this; + } + + public PropertyDataManager getPropertyDataManager() { + return propertyDataManager; + } + + public Duration getLockPollRate() { + return lockPollRate; + } + + public AbstractEngineConfiguration setLockPollRate(Duration lockPollRate) { + this.lockPollRate = lockPollRate; + return this; + } + + public Duration getSchemaLockWaitTime() { + return schemaLockWaitTime; + } + + public void setSchemaLockWaitTime(Duration schemaLockWaitTime) { + this.schemaLockWaitTime = schemaLockWaitTime; + } + + public AbstractEngineConfiguration setPropertyDataManager(PropertyDataManager propertyDataManager) { + this.propertyDataManager = propertyDataManager; + return this; + } + + public PropertyEntityManager getPropertyEntityManager() { + return propertyEntityManager; + } + + public AbstractEngineConfiguration setPropertyEntityManager(PropertyEntityManager propertyEntityManager) { + this.propertyEntityManager = propertyEntityManager; + return this; + } + + public ByteArrayDataManager getByteArrayDataManager() { + return byteArrayDataManager; + } + + public AbstractEngineConfiguration setByteArrayDataManager(ByteArrayDataManager byteArrayDataManager) { + this.byteArrayDataManager = byteArrayDataManager; + return this; + } + + public ByteArrayEntityManager getByteArrayEntityManager() { + return byteArrayEntityManager; + } + + public AbstractEngineConfiguration setByteArrayEntityManager(ByteArrayEntityManager byteArrayEntityManager) { + this.byteArrayEntityManager = byteArrayEntityManager; + return this; + } + + public TableDataManager getTableDataManager() { + return tableDataManager; + } + + public AbstractEngineConfiguration setTableDataManager(TableDataManager tableDataManager) { + this.tableDataManager = tableDataManager; + return this; + } + + public List getDeployers() { + return deployers; + } + + public AbstractEngineConfiguration setDeployers(List deployers) { + this.deployers = deployers; + return this; + } + + public List getCustomPreDeployers() { + return customPreDeployers; + } + + public AbstractEngineConfiguration setCustomPreDeployers(List customPreDeployers) { + this.customPreDeployers = customPreDeployers; + return this; + } + + public List getCustomPostDeployers() { + return customPostDeployers; + } + + public AbstractEngineConfiguration setCustomPostDeployers(List customPostDeployers) { + this.customPostDeployers = customPostDeployers; + return this; + } + + public boolean isEnableConfiguratorServiceLoader() { + return enableConfiguratorServiceLoader; + } + + public AbstractEngineConfiguration setEnableConfiguratorServiceLoader(boolean enableConfiguratorServiceLoader) { + this.enableConfiguratorServiceLoader = enableConfiguratorServiceLoader; + return this; + } + + public List getConfigurators() { + return configurators; + } + + public AbstractEngineConfiguration addConfigurator(EngineConfigurator configurator) { + if (configurators == null) { + configurators = new ArrayList<>(); + } + configurators.add(configurator); + return this; + } + + /** + * @return All {@link EngineConfigurator} instances. Will only contain values after init of the engine. + * Use the {@link #getConfigurators()} or {@link #addConfigurator(EngineConfigurator)} methods otherwise. + */ + public List getAllConfigurators() { + return allConfigurators; + } + + public AbstractEngineConfiguration setConfigurators(List configurators) { + this.configurators = configurators; + return this; + } + + public EngineConfigurator getIdmEngineConfigurator() { + return idmEngineConfigurator; + } + + public AbstractEngineConfiguration setIdmEngineConfigurator(EngineConfigurator idmEngineConfigurator) { + this.idmEngineConfigurator = idmEngineConfigurator; + return this; + } + + public EngineConfigurator getEventRegistryConfigurator() { + return eventRegistryConfigurator; + } + + public AbstractEngineConfiguration setEventRegistryConfigurator(EngineConfigurator eventRegistryConfigurator) { + this.eventRegistryConfigurator = eventRegistryConfigurator; + return this; + } + + public AbstractEngineConfiguration setForceCloseMybatisConnectionPool(boolean forceCloseMybatisConnectionPool) { + this.forceCloseMybatisConnectionPool = forceCloseMybatisConnectionPool; + return this; + } + + public boolean isForceCloseMybatisConnectionPool() { + return forceCloseMybatisConnectionPool; + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/META-INF/package-info.md b/zt-module-bpm/zt-module-bpm-server/src/main/resources/META-INF/package-info.md new file mode 100644 index 0000000..1932c7a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/META-INF/package-info.md @@ -0,0 +1 @@ +防止IDEA将`.`和`/`混为一谈 \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/META-INF/services/liquibase.database.Database b/zt-module-bpm/zt-module-bpm-server/src/main/resources/META-INF/services/liquibase.database.Database new file mode 100644 index 0000000..0ccf224 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/META-INF/services/liquibase.database.Database @@ -0,0 +1,20 @@ +liquibase.database.core.CockroachDatabase +liquibase.database.core.DB2Database +liquibase.database.core.Db2zDatabase +liquibase.database.core.DerbyDatabase +#liquibase.database.core.Firebird3Database +liquibase.database.core.FirebirdDatabase +liquibase.database.core.H2Database +liquibase.database.core.HsqlDatabase +liquibase.database.core.InformixDatabase +liquibase.database.core.Ingres9Database +liquibase.database.core.MSSQLDatabase +liquibase.database.core.MariaDBDatabase +liquibase.database.core.MockDatabase +liquibase.database.core.MySQLDatabase +liquibase.database.core.OracleDatabase +liquibase.database.core.PostgresDatabase +liquibase.database.core.SQLiteDatabase +liquibase.database.core.SybaseASADatabase +liquibase.database.core.SybaseDatabase +liquibase.database.core.UnsupportedDatabase diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/application-dev.yaml b/zt-module-bpm/zt-module-bpm-server/src/main/resources/application-dev.yaml new file mode 100644 index 0000000..b6b318c --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/application-dev.yaml @@ -0,0 +1,94 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 5 # 初始连接数 + min-idle: 10 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + url: jdbc:mysql://172.16.46.247:4787/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + username: jygk-test + password: Zgty@0527 + slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 + lazy: true # 开启懒加载,保证启动速度 + url: jdbc:mysql://172.16.46.247:4787/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + username: jygk-test + password: Zgty@0527 + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 172.16.46.63 # 地址 + port: 30379 # 端口 + database: 1 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### +xxl: + job: + admin: + addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +--- #################### 芋道相关配置 #################### + + diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/application-local.yaml b/zt-module-bpm/zt-module-bpm-server/src/main/resources/application-local.yaml new file mode 100644 index 0000000..2bf6df5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/application-local.yaml @@ -0,0 +1,111 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 1 # 初始连接数 + min-idle: 1 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + url: jdbc:mysql://172.16.46.247:4787/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + username: jygk-test + password: Zgty@0527 + slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 + lazy: true # 开启懒加载,保证启动速度 + url: jdbc:mysql://172.16.46.247:4787/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + username: jygk-test + password: Zgty@0527 + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 172.16.46.64 # 地址 + port: 30379 # 端口 + database: 0 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + enabled: false # 是否开启调度中心,默认为 true 开启 + admin: + addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +# 日志文件配置 +logging: + level: + # 配置自己写的 MyBatis Mapper 打印日志 + com.zt.plat.module.bpm.dal.mysql: debug + org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +zt: + env: # 多环境的配置项 + tag: ${HOSTNAME} + security: + mock-enable: true + access-log: # 访问日志的配置项 + enable: false \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml b/zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml new file mode 100644 index 0000000..07020c5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml @@ -0,0 +1,150 @@ +spring: + application: + name: bpm-server + + profiles: + active: ${env.name} + #统一nacos配置,使用 profile 管理 + cloud: + nacos: + server-addr: ${config.server-addr} # Nacos 服务器地址 + username: ${config.username} # Nacos 账号 + password: ${config.password} # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: ${config.namespace} # 命名空间。这里使用 maven Profile 资源过滤进行动态替换 + group: ${config.group} # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: ${config.namespace} # 命名空间。这里使用 maven Profile 资源过滤进行动态替换 + group: ${config.group} # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务 + + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 + + # Servlet 配置 + servlet: + # 文件上传相关配置项 + multipart: + max-file-size: 16MB # 单个文件大小 + max-request-size: 32MB # 设置总上传的文件大小 + + # Jackson 配置项 + jackson: + serialization: + write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳 + write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401 + write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳 + fail-on-empty-beans: false # 允许序列化无属性的 Bean + + # Cache 配置项 + cache: + type: REDIS + redis: + time-to-live: 1h # 设置过期时间为 1 小时 + +server: + port: 48083 + +logging: + file: + name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 + +--- #################### 接口文档配置 #################### + +springdoc: + api-docs: + enabled: true # 1. 是否开启 Swagger 接文档的元数据 + path: /v3/api-docs + swagger-ui: + enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面 + path: /swagger-ui + default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档 + +knife4j: + enable: false # TODO 芋艿:需要关闭增强,具体原因见:https://github.com/xiaoymin/knife4j/issues/874 + setting: + language: zh_cn + +# 工作流 Flowable 配置 +flowable: + # 1. false: 默认值,Flowable 启动时,对比数据库表中保存的版本,如果不匹配。将抛出异常 + # 2. true: 启动时会对数据库中所有表进行更新操作,如果表存在,不做处理,反之,自动创建表 + # 3. create_drop: 启动时自动创建表,关闭时自动删除表 + # 4. drop_create: 启动时,删除旧表,再创建新表 + database-schema-update: false # 设置为 false,可通过 https://github.com/flowable/flowable-sql 初始化 + db-history-used: true # flowable6 默认 true 生成信息表,无需手动设置 + check-process-definitions: false # 设置为 false,禁用 /resources/processes 自动部署 BPMN XML 流程 + history-level: audit # full:保存历史数据的最高级别,可保存全部流程相关细节,包括流程流转各节点参数 + +# MyBatis Plus 的配置项 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 + global-config: + db-config: + id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 + # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) + banner: false # 关闭控制台的 Banner 打印 + type-aliases-package: ${zt.info.base-package}.dal.dataobject + encryptor: + password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成 + +mybatis-plus-join: + banner: false # 关闭控制台的 Banner 打印 + +# Spring Data Redis 配置 +spring: + data: + redis: + repositories: + enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度 + +# VO 转换(数据翻译)相关 +easy-trans: + is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口 + +--- #################### RPC 远程调用相关配置 #################### + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + executor: + appname: ${spring.application.name} # 执行器 AppName + logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 + accessToken: default_token # 执行器通讯TOKEN + +--- #################### 芋道相关配置 #################### + +zt: + info: + version: 1.0.0 + base-package: com.zt.plat.module.bpm + web: + admin-ui: + url: http://dashboard.zt.iocoder.cn # Admin 管理后台 UI 的地址 + xss: + enable: false + exclude-urls: # 如下 url,仅仅是为了演示,去掉配置也没关系 + - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 + swagger: + title: 管理后台 + description: 提供管理员管理的所有功能 + version: ${zt.info.version} + tenant: # 多租户相关配置项 + enable: true + +debug: false diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml b/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..0e55141 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java new file mode 100644 index 0000000..83a42f8 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvokerTest.java @@ -0,0 +1,274 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.other.BpmTaskCandidateAssignEmptyStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.ExtensionElement; +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; +import org.mockito.internal.util.collections.Sets; + +import java.util.*; + +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomString; +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * {@link BpmTaskCandidateInvoker} 的单元测试 + * + * @author ZT + */ +public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest { + + private BpmTaskCandidateInvoker taskCandidateInvoker; + + @Mock + private AdminUserApi adminUserApi; + + @Mock + private BpmProcessInstanceService processInstanceService; + + @Spy + private BpmTaskCandidateStrategy userStrategy; + @Mock + private BpmTaskCandidateAssignEmptyStrategy emptyStrategy; + + @Spy + private List strategyList; + + @BeforeEach + public void setUp() { + userStrategy = new BpmTaskCandidateUserStrategy(); // 创建 strategy 实例 + when(emptyStrategy.getStrategy()).thenReturn(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY); + strategyList = List.of(userStrategy, emptyStrategy); // 创建 strategyList + taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi); + } + + /** + * 场景:成功计算到候选人,但是移除了发起人的用户 + */ + @Test + public void testCalculateUsersByTask_some() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + // 准备参数 + String param = "1,2"; + DelegateExecution execution = mock(DelegateExecution.class); + // mock 方法(DelegateExecution) + UserTask userTask = mock(UserTask.class); + String processInstanceId = randomString(); + when(execution.getProcessInstanceId()).thenReturn(processInstanceId); + when(execution.getCurrentFlowElement()).thenReturn(userTask); + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY))) + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString()); + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM))) + .thenReturn(param); + // mock 方法(adminUserApi) + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + Map userMap = MapUtil.builder(user1.getId(), user1) + .put(user2.getId(), user2).build(); + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); + // mock 移除发起人的用户 + springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class)) + .thenReturn(processInstanceService); + ProcessInstance processInstance = mock(ProcessInstance.class); + when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance); + when(processInstance.getStartUserId()).thenReturn("1"); + mockFlowElementExtensionElement(userTask, BpmnModelConstants.USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, + String.valueOf(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())); + + // 调用 + Set results = taskCandidateInvoker.calculateUsersByTask(execution); + // 断言 + assertEquals(asSet(2L), results); + } + } + + /** + * 场景:没有计算到候选人,但是被禁用移除,最终通过 empty 进行分配 + */ + @Test + public void testCalculateUsersByTask_none() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + // 准备参数 + String param = "1,2"; + DelegateExecution execution = mock(DelegateExecution.class); + // mock 方法(DelegateExecution) + UserTask userTask = mock(UserTask.class); + String processInstanceId = randomString(); + when(execution.getProcessInstanceId()).thenReturn(processInstanceId); + when(execution.getCurrentFlowElement()).thenReturn(userTask); + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY))) + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString()); + when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM))) + .thenReturn(param); + // mock 方法(adminUserApi) + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) + .setStatus(CommonStatusEnum.DISABLE.getStatus())); + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) + .setStatus(CommonStatusEnum.DISABLE.getStatus())); + Map userMap = MapUtil.builder(user1.getId(), user1) + .put(user2.getId(), user2).build(); + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); + // mock 方法(empty) + when(emptyStrategy.calculateUsersByTask(same(execution), same(param))) + .thenReturn(Sets.newSet(2L)); + // mock 移除发起人的用户 + springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class)) + .thenReturn(processInstanceService); + ProcessInstance processInstance = mock(ProcessInstance.class); + when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance); + when(processInstance.getStartUserId()).thenReturn("1"); + + // 调用 + Set results = taskCandidateInvoker.calculateUsersByTask(execution); + // 断言 + assertEquals(asSet(2L), results); + } + } + + /** + * 场景:没有计算到候选人,但是被禁用移除,最终通过 empty 进行分配 + */ + @Test + public void testCalculateUsersByActivity_some() { + try (MockedStatic bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) { + // 准备参数 + String param = "1,2"; + BpmnModel bpmnModel = mock(BpmnModel.class); + String activityId = randomString(); + Long startUserId = 1L; + String processDefinitionId = randomString(); + Map processVariables = new HashMap<>(); + // mock 方法(DelegateExecution) + UserTask userTask = mock(UserTask.class); + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask))) + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy()); + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask))) + .thenReturn(param); + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask); + // mock 方法(adminUserApi) + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + Map userMap = MapUtil.builder(user1.getId(), user1) + .put(user2.getId(), user2).build(); + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); + // mock 移除发起人的用户 + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignStartUserHandlerType(same(userTask))) + .thenReturn(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType()); + + // 调用 + Set results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, + startUserId, processDefinitionId, processVariables); + // 断言 + assertEquals(asSet(2L), results); + } + } + + /** + * 场景:成功计算到候选人,但是移除了发起人的用户 + */ + @Test + public void testCalculateUsersByActivity_none() { + try (MockedStatic bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) { + // 准备参数 + String param = "1,2"; + BpmnModel bpmnModel = mock(BpmnModel.class); + String activityId = randomString(); + Long startUserId = 1L; + String processDefinitionId = randomString(); + Map processVariables = new HashMap<>(); + // mock 方法(DelegateExecution) + UserTask userTask = mock(UserTask.class); + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask))) + .thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy()); + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask))) + .thenReturn(param); + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask); + // mock 方法(adminUserApi) + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) + .setStatus(CommonStatusEnum.DISABLE.getStatus())); + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) + .setStatus(CommonStatusEnum.DISABLE.getStatus())); + Map userMap = MapUtil.builder(user1.getId(), user1) + .put(user2.getId(), user2).build(); + when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap); + // mock 方法(empty) + when(emptyStrategy.calculateUsersByActivity(same(bpmnModel), eq(activityId), + eq(param), same(startUserId), same(processDefinitionId), same(processVariables))) + .thenReturn(Sets.newSet(2L)); + + // 调用 + Set results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, + startUserId, processDefinitionId, processVariables); + // 断言 + assertEquals(asSet(2L), results); + } + } + + private static void mockFlowElementExtensionElement(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); + // mock + Map> extensionElements = element.getExtensionElements(); + if (extensionElements == null) { + extensionElements = new LinkedHashMap<>(); + } + extensionElements.put(name, Collections.singletonList(extensionElement)); + when(element.getExtensionElements()).thenReturn(extensionElements); + } + + @Test + public void testRemoveDisableUsers() { + // 准备参数. 1L 可以找到;2L 是禁用的;3L 找不到 + Set assigneeUserIds = asSet(1L, 2L, 3L); + // mock 方法 + AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L) + .setStatus(CommonStatusEnum.ENABLE.getStatus())); + AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L) + .setStatus(CommonStatusEnum.DISABLE.getStatus())); + Map userMap = MapUtil.builder(user1.getId(), user1) + .put(user2.getId(), user2).build(); + when(adminUserApi.getUserMap(eq(assigneeUserIds))).thenReturn(userMap); + + // 调用 + taskCandidateInvoker.removeDisableUsers(assigneeUserIds); + // 断言 + assertEquals(asSet(1L), assigneeUserIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java new file mode 100644 index 0000000..d7d1151 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java @@ -0,0 +1,107 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.expression; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Collections; +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +public class BpmTaskAssignLeaderExpressionTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskAssignLeaderExpression expression; + + @Mock + private AdminUserApi adminUserApi; + @Mock + private DeptApi deptApi; + + @Mock + private BpmProcessInstanceService processInstanceService; + + @Test + public void testCalculateUsers_noDept() { + // 准备参数 + DelegateExecution execution = mockDelegateExecution(1L); + // mock 方法(startUser) + AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptIds(Collections.singletonList(10L))); + when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser)); + // mock 方法(getStartUserDept)没有部门 + when(deptApi.getDept(eq(10L))).thenReturn(success(null)); + + // 调用 + Set result = expression.calculateUsers(execution, 1); + // 断言 + assertEquals(0, result.size()); + } + + @Test + public void testCalculateUsers_noParentDept() { + // 准备参数 + DelegateExecution execution = mockDelegateExecution(1L); + // mock 方法(startUser) + AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptIds(Collections.singletonList(10L))); + when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser)); + DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L) + .setLeaderUserId(20L)); + // mock 方法(getDept) + when(deptApi.getDept(eq(10L))).thenReturn(success(startUserDept)); + when(deptApi.getDept(eq(100L))).thenReturn(success(null)); + + // 调用 + Set result = expression.calculateUsers(execution, 2); + // 断言 + assertEquals(asSet(20L), result); + } + + @Test + public void testCalculateUsers_existParentDept() { + // 准备参数 + DelegateExecution execution = mockDelegateExecution(1L); + // mock 方法(startUser) + AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptIds(Collections.singletonList(10L))); + when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser)); + DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L) + .setLeaderUserId(20L)); + when(deptApi.getDept(eq(10L))).thenReturn(success(startUserDept)); + // mock 方法(父 dept) + DeptRespDTO parentDept = randomPojo(DeptRespDTO.class, o -> o.setId(100L).setParentId(1000L) + .setLeaderUserId(200L)); + when(deptApi.getDept(eq(100L))).thenReturn(success(parentDept)); + + // 调用 + Set result = expression.calculateUsers(execution, 2); + // 断言 + assertEquals(asSet(200L), result); + } + + @SuppressWarnings("SameParameterValue") + private DelegateExecution mockDelegateExecution(Long startUserId) { + ExecutionEntityImpl execution = new ExecutionEntityImpl(); + execution.setProcessInstanceId(randomString()); + // mock 返回 startUserId + ExecutionEntityImpl processInstance = new ExecutionEntityImpl(); + processInstance.setStartUserId(String.valueOf(startUserId)); + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))) + .thenReturn(processInstance); + return execution; + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java new file mode 100644 index 0000000..63d4c39 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import org.assertj.core.util.Sets; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.stubbing.Answer; + +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +public class BpmTaskCandidateDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateDeptLeaderMultiStrategy strategy; + + @Mock + private DeptApi deptApi; + + @Test + public void testCalculateUsers() { + // 准备参数 + String param = "10,20|2"; + // mock 方法 + when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult>) invocationOnMock -> { + Long deptId = invocationOnMock.getArgument(0); + return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1))); + }); + + // 调用 + Set userIds = strategy.calculateUsers(param); + // 断言结果 + assertEquals(Sets.newLinkedHashSet(11L, 1001L, 21L, 2001L), userIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java new file mode 100644 index 0000000..a7bbbf1 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import com.zt.plat.framework.common.util.collection.SetUtils; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import org.assertj.core.util.Sets; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateDeptLeaderStrategy strategy; + + @Mock + private DeptApi deptApi; + + @Test + public void testCalculateUsers() { + // 准备参数 + String param = "10,20"; + // mock 方法 + when(deptApi.getDeptList(eq(SetUtils.asSet(10L, 20L)))).thenReturn(success(asList( + randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(10L).setLeaderUserId(11L)), + randomPojo(DeptRespDTO.class, o -> o.setId(20L).setParentId(20L).setLeaderUserId(21L))))); + + // 调用 + Set userIds = strategy.calculateUsers(param); + // 断言结果 + assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java new file mode 100644 index 0000000..443eeb7 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java @@ -0,0 +1,47 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import com.zt.plat.framework.common.util.collection.SetUtils; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import org.assertj.core.util.Sets; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateDeptMemberStrategy strategy; + + @Mock + private DeptApi deptApi; + @Mock + private AdminUserApi adminUserApi; + + @Test + public void testCalculateUsers() { + // 准备参数 + String param = "10,20"; + // mock 方法 + when(adminUserApi.getUserListByDeptIds(eq(SetUtils.asSet(10L, 20L)))).thenReturn(success(asList( + randomPojo(AdminUserRespDTO.class, o -> o.setId(11L)), + randomPojo(AdminUserRespDTO.class, o -> o.setId(21L))))); + + // 调用 + Set userIds = strategy.calculateUsers(param); + // 断言结果 + assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java new file mode 100644 index 0000000..4a34602 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java @@ -0,0 +1,85 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import org.assertj.core.util.Sets; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.stubbing.Answer; + +import java.util.Collections; +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateStartUserDeptLeaderMultiStrategy strategy; + + @Mock + private BpmProcessInstanceService processInstanceService; + + @Mock + private AdminUserApi adminUserApi; + @Mock + private DeptApi deptApi; + + @Test + public void testCalculateUsersByTask() { + // 准备参数 + String param = "2"; + // mock 方法(获得流程发起人) + Long startUserId = 1L; + ProcessInstance processInstance = mock(ProcessInstance.class); + DelegateExecution execution = mock(DelegateExecution.class); + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance); + when(processInstance.getStartUserId()).thenReturn(startUserId.toString()); + // mock 方法(获取发起人的 multi 部门负责人) + mockGetStartUserDept(startUserId); + + // 调用 + Set userIds = strategy.calculateUsersByTask(execution, param); + // 断言 + assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds); + } + + @Test + public void testCalculateUsersByActivity() { + // 准备参数 + String param = "2"; + // mock 方法 + Long startUserId = 1L; + mockGetStartUserDept(startUserId); + + // 调用 + Set userIds = strategy.calculateUsersByActivity(null, null, param, + startUserId, null, null); + // 断言 + assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds); + } + + private void mockGetStartUserDept(Long startUserId) { + when(adminUserApi.getUser(eq(startUserId))).thenReturn( + success(randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptIds(Collections.singletonList(10L))))); + when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult>) invocationOnMock -> { + Long deptId = invocationOnMock.getArgument(0); + return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1))); + }); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java new file mode 100644 index 0000000..f829b9e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java @@ -0,0 +1,85 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import org.assertj.core.util.Sets; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.stubbing.Answer; + +import java.util.Collections; +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BpmTaskCandidateStartUserDeptLeaderStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateStartUserDeptLeaderStrategy strategy; + + @Mock + private BpmProcessInstanceService processInstanceService; + + @Mock + private AdminUserApi adminUserApi; + @Mock + private DeptApi deptApi; + + @Test + public void testCalculateUsersByTask() { + // 准备参数 + String param = "2"; + // mock 方法(获得流程发起人) + Long startUserId = 1L; + ProcessInstance processInstance = mock(ProcessInstance.class); + DelegateExecution execution = mock(DelegateExecution.class); + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance); + when(processInstance.getStartUserId()).thenReturn(startUserId.toString()); + // mock 方法(获取发起人的部门负责人) + mockGetStartUserDeptLeader(startUserId); + + // 调用 + Set userIds = strategy.calculateUsersByTask(execution, param); + // 断言 + assertEquals(Sets.newLinkedHashSet(1001L), userIds); + } + + @Test + public void testGetStartUserDeptLeader() { + // 准备参数 + String param = "2"; + // mock 方法 + Long startUserId = 1L; + mockGetStartUserDeptLeader(startUserId); + + // 调用 + Set userIds = strategy.calculateUsersByActivity(null, null, param, + startUserId, null, null); + // 断言 + assertEquals(Sets.newLinkedHashSet(1001L), userIds); + } + + private void mockGetStartUserDeptLeader(Long startUserId) { + when(adminUserApi.getUser(eq(startUserId))).thenReturn( + success(randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptIds(Collections.singletonList(10L))))); + when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult>) invocationOnMock -> { + Long deptId = invocationOnMock.getArgument(0); + return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1))); + }); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java new file mode 100644 index 0000000..2769280 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategyTest.java @@ -0,0 +1,68 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.map.MapUtil; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import org.assertj.core.util.Sets; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BpmTaskCandidateStartUserSelectStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateStartUserSelectStrategy strategy; + + @Mock + private BpmProcessInstanceService processInstanceService; + + @Test + public void testCalculateUsersByTask() { + // 准备参数 + String param = "2"; + // mock 方法(获得流程发起人) + ProcessInstance processInstance = mock(ProcessInstance.class); + DelegateExecution execution = mock(DelegateExecution.class); + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance); + when(execution.getCurrentActivityId()).thenReturn("activity_001"); + // mock 方法(FlowableUtils) + Map processVariables = new HashMap<>(); + processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, + MapUtil.of("activity_001", List.of(1L, 2L))); + when(processInstance.getProcessVariables()).thenReturn(processVariables); + + // 调用 + Set userIds = strategy.calculateUsersByTask(execution, param); + // 断言 + assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds); + } + + @Test + public void testCalculateUsersByActivity() { + // 准备参数 + String activityId = "activity_001"; + Map processVariables = new HashMap<>(); + processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, + MapUtil.of("activity_001", List.of(1L, 2L))); + + // 调用 + Set userIds = strategy.calculateUsersByActivity(null, activityId, null, + null, null, processVariables); + // 断言 + assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategyTest.java new file mode 100644 index 0000000..1bf5725 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategyTest.java @@ -0,0 +1,88 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.other; + +import cn.hutool.core.collection.ListUtil; +import com.zt.plat.framework.common.util.collection.SetUtils; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.zt.plat.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; +import com.zt.plat.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.zt.plat.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.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; + +import java.util.Set; + +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +public class BpmTaskCandidateAssignEmptyStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateAssignEmptyStrategy strategy; + + @Mock + private BpmProcessDefinitionService processDefinitionService; + + @Test + public void testCalculateUsersByTask() { + try (MockedStatic flowableUtilMockedStatic = mockStatic(FlowableUtils.class); + MockedStatic bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) { + // 准备参数 + DelegateExecution execution = mock(DelegateExecution.class); + String param = randomString(); + // mock 方法(execution) + String processDefinitionId = randomString(); + when(execution.getProcessDefinitionId()).thenReturn(processDefinitionId); + FlowElement flowElement = mock(FlowElement.class); + when(execution.getCurrentFlowElement()).thenReturn(flowElement); + // mock 方法(parseAssignEmptyHandlerType) + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement))) + .thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType()); + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerUserIds(same(flowElement))) + .thenReturn(ListUtil.of(1L, 2L)); + + // 调用 + Set userIds = strategy.calculateUsersByTask(execution, param); + // 断言 + assertEquals(SetUtils.asSet(1L, 2L), userIds); + } + + } + + @Test + public void testCalculateUsersByActivity() { + try (MockedStatic bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) { + // 准备参数 + String processDefinitionId = randomString(); + String activityId = randomString(); + String param = randomString(); + // mock 方法(getFlowElementById) + FlowElement flowElement = mock(FlowElement.class); + BpmnModel bpmnModel = mock(BpmnModel.class); + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))) + .thenReturn(flowElement); + // mock 方法(parseAssignEmptyHandlerType) + bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement))) + .thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType()); + // mock 方法(getProcessDefinitionInfo) + BpmProcessDefinitionInfoDO processDefinition = randomPojo(BpmProcessDefinitionInfoDO.class, + o -> o.setManagerUserIds(ListUtil.of(1L, 2L))); + when(processDefinitionService.getProcessDefinitionInfo(eq(processDefinitionId))).thenReturn(processDefinition); + + // 调用 + Set userIds = strategy.calculateUsersByActivity(bpmnModel, activityId, param, + null, processDefinitionId, null); + // 断言 + assertEquals(SetUtils.asSet(1L, 2L), userIds); + } + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategyTest.java new file mode 100644 index 0000000..d9ad569 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategyTest.java @@ -0,0 +1,61 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.other; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.framework.flowable.core.util.FlowableUtils; +import org.flowable.engine.delegate.DelegateExecution; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@Disabled // TODO 芋艿:临时注释 +public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateExpressionStrategy strategy; + + @Test + public void testCalculateUsersByTask() { + try (MockedStatic flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) { + // 准备参数 + String param = "1,2"; + DelegateExecution execution = mock(DelegateExecution.class); + // mock 方法 + flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(execution), eq(param))) + .thenReturn(asSet(1L, 2L)); + + // 调用 + Set results = strategy.calculateUsersByTask(execution, param); + // 断言 + assertEquals(asSet(1L, 2L), results); + } + } + + @Test + public void testCalculateUsersByActivity() { + try (MockedStatic flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) { + // 准备参数 + String param = "1,2"; + Map processVariables = new HashMap<>(); + // mock 方法 + flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(processVariables), eq(param))) + .thenReturn(asSet(1L, 2L)); + + // 调用 + Set results = strategy.calculateUsersByActivity(null, null, param, + null, null, processVariables); + // 断言 + assertEquals(asSet(1L, 2L), results); + } + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategyTest.java new file mode 100644 index 0000000..1bb03b5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategyTest.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import com.zt.plat.module.bpm.service.definition.BpmUserGroupService; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Arrays; +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@Disabled // TODO 芋艿:临时注释 +public class BpmTaskCandidateGroupStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateGroupStrategy strategy; + + @Mock + private BpmUserGroupService userGroupService; + + @Test + public void testCalculateUsers() { + // 准备参数 + String param = "1,2"; + // mock 方法 + BpmUserGroupDO userGroup1 = randomPojo(BpmUserGroupDO.class, o -> o.setUserIds(asSet(11L, 12L))); + BpmUserGroupDO userGroup2 = randomPojo(BpmUserGroupDO.class, o -> o.setUserIds(asSet(21L, 22L))); + when(userGroupService.getUserGroupList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(userGroup1, userGroup2)); + + // 调用 + Set userIds = strategy.calculateUsersByTask(null, param); + // 断言 + assertEquals(asSet(11L, 12L, 21L, 22L), userIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java new file mode 100644 index 0000000..8560052 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.api.dept.PostApi; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.List; +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@Disabled // TODO 芋艿:临时注释 +public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidatePostStrategy strategy; + + @Mock + private PostApi postApi; + @Mock + private AdminUserApi adminUserApi; + + @Test + public void testCalculateUsers() { + // 准备参数 + String param = "1,2"; + // mock 方法 + List users = convertList(asSet(11L, 22L), + id -> new AdminUserRespDTO().setId(id)); + when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(success(users)); + + // 调用 + Set userIds = strategy.calculateUsersByTask(null, param); + // 断言 + assertEquals(asSet(11L, 22L), userIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategyTest.java new file mode 100644 index 0000000..9af421a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategyTest.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.api.permission.PermissionApi; +import com.zt.plat.module.system.api.permission.RoleApi; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@Disabled // TODO 芋艿:临时注释 +public class BpmTaskCandidateRoleStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateRoleStrategy strategy; + + @Mock + private RoleApi roleApi; + @Mock + private PermissionApi permissionApi; + + @Test + public void testCalculateUsers() { + // 准备参数 + String param = "1,2"; + // mock 方法 + when(permissionApi.getUserRoleIdListByRoleIds(eq(asSet(1L, 2L)))) + .thenReturn(success(asSet(11L, 22L))); + + // 调用 + Set userIds = strategy.calculateUsersByTask(null, param); + // 断言 + assertEquals(asSet(11L, 22L), userIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategyTest.java new file mode 100644 index 0000000..79c08ba --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategyTest.java @@ -0,0 +1,56 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import org.assertj.core.util.Sets; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BpmTaskCandidateStartUserStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateStartUserStrategy strategy; + + @Mock + private BpmProcessInstanceService processInstanceService; + + @Test + public void testCalculateUsersByTask() { + // 准备参数 + String param = "2"; + // mock 方法(获得流程发起人) + Long startUserId = 1L; + ProcessInstance processInstance = mock(ProcessInstance.class); + DelegateExecution execution = mock(DelegateExecution.class); + when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance); + when(processInstance.getStartUserId()).thenReturn(startUserId.toString()); + + // 调用 + Set userIds = strategy.calculateUsersByTask(execution, param); + // 断言 + assertEquals(Sets.newLinkedHashSet(startUserId), userIds); + } + + @Test + public void testCalculateUsersByActivity() { + // 准备参数 + Long startUserId = 1L; + + // 调用 + Set userIds = strategy.calculateUsersByActivity(null, null, null, + startUserId, null, null); + // 断言 + assertEquals(Sets.newLinkedHashSet(startUserId), userIds); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategyTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategyTest.java new file mode 100644 index 0000000..91e19c5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategyTest.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; + +import java.util.Set; + +import static com.zt.plat.framework.common.util.collection.SetUtils.asSet; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Disabled // TODO 芋艿:临时注释 +public class BpmTaskCandidateUserStrategyTest extends BaseMockitoUnitTest { + + @InjectMocks + private BpmTaskCandidateUserStrategy strategy; + + @Test + public void test() { + // 准备参数 + String param = "1,2"; + + // 调用 + Set userIds = strategy.calculateUsersByTask(null, param); + // 断言 + assertEquals(asSet(1L, 2L), userIds); + } + + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/category/BpmCategoryServiceImplTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/category/BpmCategoryServiceImplTest.java new file mode 100644 index 0000000..621d68e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/category/BpmCategoryServiceImplTest.java @@ -0,0 +1,136 @@ +package com.zt.plat.module.bpm.service.category; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; +import com.zt.plat.module.bpm.dal.mysql.category.BpmCategoryMapper; +import com.zt.plat.module.bpm.service.definition.BpmCategoryServiceImpl; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import static com.zt.plat.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; +import static com.zt.plat.framework.common.util.date.LocalDateTimeUtils.buildTime; +import static com.zt.plat.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static com.zt.plat.framework.test.core.util.AssertUtils.assertPojoEquals; +import static com.zt.plat.framework.test.core.util.AssertUtils.assertServiceException; +import static com.zt.plat.framework.test.core.util.RandomUtils.*; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.CATEGORY_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link BpmCategoryServiceImpl} 的单元测试类 + * + * @author ZT + */ +@Import(BpmCategoryServiceImpl.class) +public class BpmCategoryServiceImplTest extends BaseDbUnitTest { + + @Resource + private BpmCategoryServiceImpl categoryService; + + @Resource + private BpmCategoryMapper categoryMapper; + + @Test + public void testCreateCategory_success() { + // 准备参数 + BpmCategorySaveReqVO createReqVO = randomPojo(BpmCategorySaveReqVO.class).setId(null) + .setStatus(randomCommonStatus()); + + // 调用 + Long categoryId = categoryService.createCategory(createReqVO); + // 断言 + assertNotNull(categoryId); + // 校验记录的属性是否正确 + BpmCategoryDO category = categoryMapper.selectById(categoryId); + assertPojoEquals(createReqVO, category, "id"); + } + + @Test + public void testUpdateCategory_success() { + // mock 数据 + BpmCategoryDO dbCategory = randomPojo(BpmCategoryDO.class); + categoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据 + // 准备参数 + BpmCategorySaveReqVO updateReqVO = randomPojo(BpmCategorySaveReqVO.class, o -> { + o.setId(dbCategory.getId()); // 设置更新的 ID + o.setStatus(randomCommonStatus()); + }); + + // 调用 + categoryService.updateCategory(updateReqVO); + // 校验是否更新正确 + BpmCategoryDO category = categoryMapper.selectById(updateReqVO.getId()); // 获取最新的 + assertPojoEquals(updateReqVO, category); + } + + @Test + public void testUpdateCategory_notExists() { + // 准备参数 + BpmCategorySaveReqVO updateReqVO = randomPojo(BpmCategorySaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> categoryService.updateCategory(updateReqVO), CATEGORY_NOT_EXISTS); + } + + @Test + public void testDeleteCategory_success() { + // mock 数据 + BpmCategoryDO dbCategory = randomPojo(BpmCategoryDO.class); + categoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbCategory.getId(); + + // 调用 + categoryService.deleteCategory(id); + // 校验数据不存在了 + assertNull(categoryMapper.selectById(id)); + } + + @Test + public void testDeleteCategory_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> categoryService.deleteCategory(id), CATEGORY_NOT_EXISTS); + } + + @Test + public void testGetCategoryPage() { + // mock 数据 + BpmCategoryDO dbCategory = randomPojo(BpmCategoryDO.class, o -> { // 等会查询到 + o.setName("芋头"); + o.setCode("xiaodun"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setCreateTime(buildTime(2023, 2, 2)); + }); + categoryMapper.insert(dbCategory); + // 测试 name 不匹配 + categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName("小盾"))); + // 测试 code 不匹配 + categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCode("tudou"))); + // 测试 status 不匹配 + categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 createTime 不匹配 + categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCreateTime(buildTime(2024, 2, 2)))); + // 准备参数 + BpmCategoryPageReqVO reqVO = new BpmCategoryPageReqVO(); + reqVO.setName("芋"); + reqVO.setCode("xiao"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + + // 调用 + PageResult pageResult = categoryService.getCategoryPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbCategory, pageResult.getList().get(0)); + } + +} \ No newline at end of file diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/definition/BpmFormServiceTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/definition/BpmFormServiceTest.java new file mode 100644 index 0000000..ac03d9e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/definition/BpmFormServiceTest.java @@ -0,0 +1,144 @@ +package com.zt.plat.module.bpm.service.definition; + +import cn.hutool.core.util.RandomUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.zt.plat.module.bpm.dal.mysql.definition.BpmFormMapper; +import com.zt.plat.module.bpm.service.definition.dto.BpmFormFieldRespDTO; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.zt.plat.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static com.zt.plat.framework.test.core.util.AssertUtils.assertPojoEquals; +import static com.zt.plat.framework.test.core.util.AssertUtils.assertServiceException; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomLongId; +import static com.zt.plat.framework.test.core.util.RandomUtils.randomPojo; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.FORM_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link BpmFormServiceImpl} 的单元测试类 + * + * @author ZT + */ +@Import(BpmFormServiceImpl.class) +public class BpmFormServiceTest extends BaseDbUnitTest { + + @Resource + private BpmFormServiceImpl formService; + + @Resource + private BpmFormMapper formMapper; + + @Test + public void testCreateForm_success() { + // 准备参数 + BpmFormSaveReqVO reqVO = randomPojo(BpmFormSaveReqVO.class, o -> { + o.setConf("{}"); + o.setFields(randomFields()); + }); + + // 调用 + Long formId = formService.createForm(reqVO); + // 断言 + assertNotNull(formId); + // 校验记录的属性是否正确 + BpmFormDO form = formMapper.selectById(formId); + assertPojoEquals(reqVO, form); + } + + @Test + public void testUpdateForm_success() { + // mock 数据 + BpmFormDO dbForm = randomPojo(BpmFormDO.class, o -> { + o.setConf("{}"); + o.setFields(randomFields()); + }); + formMapper.insert(dbForm);// @Sql: 先插入出一条存在的数据 + // 准备参数 + BpmFormSaveReqVO reqVO = randomPojo(BpmFormSaveReqVO.class, o -> { + o.setId(dbForm.getId()); // 设置更新的 ID + o.setConf("{'zt': 'yuanma'}"); + o.setFields(randomFields()); + }); + + // 调用 + formService.updateForm(reqVO); + // 校验是否更新正确 + BpmFormDO form = formMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, form); + } + + @Test + public void testUpdateForm_notExists() { + // 准备参数 + BpmFormSaveReqVO reqVO = randomPojo(BpmFormSaveReqVO.class, o -> { + o.setConf("{'zt': 'yuanma'}"); + o.setFields(randomFields()); + }); + + // 调用, 并断言异常 + assertServiceException(() -> formService.updateForm(reqVO), FORM_NOT_EXISTS); + } + + @Test + public void testDeleteForm_success() { + // mock 数据 + BpmFormDO dbForm = randomPojo(BpmFormDO.class); + formMapper.insert(dbForm);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbForm.getId(); + + // 调用 + formService.deleteForm(id); + // 校验数据不存在了 + assertNull(formMapper.selectById(id)); + } + + @Test + public void testDeleteForm_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> formService.deleteForm(id), FORM_NOT_EXISTS); + } + + @Test + public void testGetFormPage() { + // mock 数据 + BpmFormDO dbForm = randomPojo(BpmFormDO.class, o -> { // 等会查询到 + o.setName("芋道源码"); + }); + formMapper.insert(dbForm); + // 测试 name 不匹配 + formMapper.insert(cloneIgnoreId(dbForm, o -> o.setName("源码"))); + // 准备参数 + BpmFormPageReqVO reqVO = new BpmFormPageReqVO(); + reqVO.setName("芋道"); + + // 调用 + PageResult pageResult = formService.getFormPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbForm, pageResult.getList().get(0)); + } + + private List randomFields() { + int size = RandomUtil.randomInt(1, 3); + return Stream.iterate(0, i -> i).limit(size) + .map(i -> JsonUtils.toJsonString(randomPojo(BpmFormFieldRespDTO.class))) + .collect(Collectors.toList()); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupServiceTest.java b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupServiceTest.java new file mode 100644 index 0000000..88b1d50 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/java/com/zt/plat/module/bpm/service/definition/BpmUserGroupServiceTest.java @@ -0,0 +1,130 @@ +package com.zt.plat.module.bpm.service.definition; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; +import com.zt.plat.framework.test.core.util.AssertUtils; +import com.zt.plat.framework.test.core.util.RandomUtils; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupSaveReqVO; +import com.zt.plat.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO; +import com.zt.plat.module.bpm.dal.dataobject.definition.BpmUserGroupDO; +import com.zt.plat.module.bpm.dal.mysql.definition.BpmUserGroupMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import jakarta.annotation.Resource; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.LocalDateTimeUtils.buildTime; +import static com.zt.plat.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static com.zt.plat.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS; + +/** + * {@link BpmUserGroupServiceImpl} 的单元测试类 + * + * @author ZT + */ +@Import(BpmUserGroupServiceImpl.class) +public class BpmUserGroupServiceTest extends BaseDbUnitTest { + + @Resource + private BpmUserGroupServiceImpl userGroupService; + + @Resource + private BpmUserGroupMapper userGroupMapper; + + @Test + public void testCreateUserGroup_success() { + // 准备参数 + BpmUserGroupSaveReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupSaveReqVO.class); + + // 调用 + Long userGroupId = userGroupService.createUserGroup(reqVO); + // 断言 + Assertions.assertNotNull(userGroupId); + // 校验记录的属性是否正确 + BpmUserGroupDO userGroup = userGroupMapper.selectById(userGroupId); + AssertUtils.assertPojoEquals(reqVO, userGroup); + } + + @Test + public void testUpdateUserGroup_success() { + // mock 数据 + BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class); + userGroupMapper.insert(dbUserGroup);// @Sql: 先插入出一条存在的数据 + // 准备参数 + BpmUserGroupSaveReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupSaveReqVO.class, o -> { + o.setId(dbUserGroup.getId()); // 设置更新的 ID + }); + + // 调用 + userGroupService.updateUserGroup(reqVO); + // 校验是否更新正确 + BpmUserGroupDO userGroup = userGroupMapper.selectById(reqVO.getId()); // 获取最新的 + AssertUtils.assertPojoEquals(reqVO, userGroup); + } + + @Test + public void testUpdateUserGroup_notExists() { + // 准备参数 + BpmUserGroupSaveReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupSaveReqVO.class); + + // 调用, 并断言异常 + AssertUtils.assertServiceException(() -> userGroupService.updateUserGroup(reqVO), USER_GROUP_NOT_EXISTS); + } + + @Test + public void testDeleteUserGroup_success() { + // mock 数据 + BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class); + userGroupMapper.insert(dbUserGroup);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbUserGroup.getId(); + + // 调用 + userGroupService.deleteUserGroup(id); + // 校验数据不存在了 + Assertions.assertNull(userGroupMapper.selectById(id)); + } + + @Test + public void testDeleteUserGroup_notExists() { + // 准备参数 + Long id = RandomUtils.randomLongId(); + + // 调用, 并断言异常 + AssertUtils.assertServiceException(() -> userGroupService.deleteUserGroup(id), USER_GROUP_NOT_EXISTS); + } + + @Test + public void testGetUserGroupPage() { + // mock 数据 + BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class, o -> { // 等会查询到 + o.setName("芋道源码"); + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setCreateTime(buildTime(2021, 11, 11)); + }); + userGroupMapper.insert(dbUserGroup); + // 测试 name 不匹配 + userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setName("芋道"))); + // 测试 status 不匹配 + userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); + // 测试 createTime 不匹配 + userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setCreateTime(buildTime(2021, 12, 12)))); + // 准备参数 + BpmUserGroupPageReqVO reqVO = new BpmUserGroupPageReqVO(); + reqVO.setName("源码"); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setCreateTime((new LocalDateTime[]{buildTime(2021, 11, 10),buildTime(2021, 11, 12)})); + + // 调用 + PageResult pageResult = userGroupService.getUserGroupPage(reqVO); + // 断言 + Assertions.assertEquals(1, pageResult.getTotal()); + Assertions.assertEquals(1, pageResult.getList().size()); + AssertUtils.assertPojoEquals(dbUserGroup, pageResult.getList().get(0)); + } + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/resources/application-unit-test.yaml b/zt-module-bpm/zt-module-bpm-server/src/test/resources/application-unit-test.yaml new file mode 100644 index 0000000..a669ee0 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/resources/application-unit-test.yaml @@ -0,0 +1,45 @@ +spring: + main: + lazy-initialization: true # 开启懒加载,加快速度 + banner-mode: off # 单元测试,禁用 Banner + +--- #################### 数据库相关配置 #################### + +spring: + # 数据源配置项 + datasource: + name: ruoyi-vue-pro + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 + initial-size: 1 # 单元测试,配置为 1,提升启动速度 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + +mybatis-plus: + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + type-aliases-package: ${zt.info.base-package}.dal.dataobject + global-config: + db-config: + id-type: AUTO # H2 主键递增 + +--- #################### 定时任务相关配置 #################### + +--- #################### 配置中心相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项(单元测试,禁用 Lock4j) + +--- #################### 监控相关配置 #################### + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +zt: + info: + base-package: com.zt.plat.module.bpm diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/resources/logback.xml b/zt-module-bpm/zt-module-bpm-server/src/test/resources/logback.xml new file mode 100644 index 0000000..daf756b --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/resources/logback.xml @@ -0,0 +1,4 @@ + + + + diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/resources/sql/clean.sql b/zt-module-bpm/zt-module-bpm-server/src/test/resources/sql/clean.sql new file mode 100644 index 0000000..d4f93bb --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/resources/sql/clean.sql @@ -0,0 +1,3 @@ +DELETE FROM "bpm_form"; +DELETE FROM "bpm_user_group"; +DELETE FROM "bpm_category"; diff --git a/zt-module-bpm/zt-module-bpm-server/src/test/resources/sql/create_tables.sql b/zt-module-bpm/zt-module-bpm-server/src/test/resources/sql/create_tables.sql new file mode 100644 index 0000000..1034962 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/test/resources/sql/create_tables.sql @@ -0,0 +1,43 @@ +CREATE TABLE IF NOT EXISTS "bpm_user_group" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(63) NOT NULL, + "description" varchar(255) NOT NULL, + "status" tinyint NOT NULL, + "user_ids" varchar(255) NOT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '用户组'; + +CREATE TABLE IF NOT EXISTS "bpm_category" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(63) NOT NULL, + "code" varchar(63) NOT NULL, + "description" varchar(255) NOT NULL, + "status" tinyint NOT NULL, + "sort" int NOT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '分类'; + +CREATE TABLE IF NOT EXISTS "bpm_form" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(63) NOT NULL, + "status" tinyint NOT NULL, + "fields" varchar(255) NOT NULL, + "conf" varchar(255) NOT NULL, + "remark" varchar(255), + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '动态表单';