From bd572ed0543ec3d732287581dd3460c833be1059 Mon Sep 17 00:00:00 2001 From: sunquan <287962685@qq.com> Date: Wed, 22 Jul 2026 14:05:41 +0800 Subject: [PATCH] =?UTF-8?q?add=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=8A=A0common-f?= =?UTF-8?q?low=20=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application-tenant/tenant-admin/pom.xml | 5 + common/common-flow/pom.xml | 44 + .../common/flow/Plugin/AbstractAssigneePlugin.java | 11 + .../common/flow/Plugin/DemoFlowNodePlugin.java | 10 + .../common/flow/Plugin/FlowNodeExpressionUtil.java | 49 + .../common/flow/advice/FlowExceptionHandler.java | 58 + .../flow/base/service/BaseFlowOnlineService.java | 26 + .../common/flow/base/service/BaseFlowService.java | 273 +++ .../java/apelet/common/flow/cmd/ExpressionCmd.java | 34 + .../common/flow/cmd/RestartProcessInstanceCmd.java | 162 ++ .../flow/config/CustomEngineConfigurator.java | 48 + .../apelet/common/flow/config/FlowAutoConfig.java | 13 + .../apelet/common/flow/config/FlowProperties.java | 20 + .../common/flow/constant/FlowApprovalType.java | 97 + .../apelet/common/flow/constant/FlowBackType.java | 25 + .../flow/constant/FlowBuiltinApprovalStatus.java | 33 + .../apelet/common/flow/constant/FlowConstant.java | 266 +++ .../common/flow/constant/FlowTaskStatus.java | 49 + .../apelet/common/flow/constant/FlowTaskType.java | 25 + .../flow/controller/FlowCategoryController.java | 229 ++ .../flow/controller/FlowEntryController.java | 469 ++++ .../controller/FlowEntryVariableController.java | 156 ++ .../flow/controller/FlowMessageController.java | 113 + .../flow/controller/FlowNodeTasksController.java | 56 + .../flow/controller/FlowOperationController.java | 1225 ++++++++++ .../apelet/common/flow/dao/FlowCategoryMapper.java | 26 + .../apelet/common/flow/dao/FlowEntryMapper.java | 26 + .../common/flow/dao/FlowEntryPublishMapper.java | 13 + .../flow/dao/FlowEntryPublishVariableMapper.java | 22 + .../common/flow/dao/FlowEntryVariableMapper.java | 27 + .../dao/FlowMessageCandidateIdentityMapper.java | 21 + .../dao/FlowMessageIdentityOperationMapper.java | 21 + .../apelet/common/flow/dao/FlowMessageMapper.java | 86 + .../flow/dao/FlowMultiInstanceTransMapper.java | 13 + .../common/flow/dao/FlowNodeTasksMapper.java | 9 + .../common/flow/dao/FlowTaskCommentMapper.java | 13 + .../apelet/common/flow/dao/FlowTaskExtMapper.java | 22 + .../common/flow/dao/FlowTaskTimeoutJobMapper.java | 14 + .../common/flow/dao/FlowTransProducerMapper.java | 13 + .../common/flow/dao/FlowVariableLogMapper.java | 14 + .../common/flow/dao/FlowWorkOrderExtMapper.java | 14 + .../common/flow/dao/FlowWorkOrderMapper.java | 49 + .../common/flow/dao/mapper/FlowCategoryMapper.xml | 56 + .../common/flow/dao/mapper/FlowEntryMapper.xml | 95 + .../flow/dao/mapper/FlowEntryPublishMapper.xml | 18 + .../dao/mapper/FlowEntryPublishVariableMapper.xml | 30 + .../flow/dao/mapper/FlowEntryVariableMapper.xml | 41 + .../mapper/FlowMessageCandidateIdentityMapper.xml | 16 + .../mapper/FlowMessageIdentityOperationMapper.xml | 17 + .../common/flow/dao/mapper/FlowMessageMapper.xml | 119 + .../dao/mapper/FlowMultiInstanceTransMapper.xml | 17 + .../flow/dao/mapper/FlowTaskCommentMapper.xml | 23 + .../common/flow/dao/mapper/FlowTaskExtMapper.xml | 36 + .../flow/dao/mapper/FlowTaskTimeoutJobMapper.xml | 19 + .../flow/dao/mapper/FlowTransProducerMapper.xml | 23 + .../flow/dao/mapper/FlowVariableLogMapper.xml | 13 + .../flow/dao/mapper/FlowWorkOrderExtMapper.xml | 15 + .../common/flow/dao/mapper/FlowWorkOrderMapper.xml | 82 + .../apelet/common/flow/dto/FlowCategoryDto.java | 47 + .../java/apelet/common/flow/dto/FlowEntryDto.java | 114 + .../common/flow/dto/FlowEntryVariableDto.java | 82 + .../apelet/common/flow/dto/FlowMessageDto.java | 51 + .../apelet/common/flow/dto/FlowTaskCommentDto.java | 38 + .../apelet/common/flow/dto/FlowWorkOrderDto.java | 39 + .../flow/exception/FlowEmptyUserException.java | 35 + .../flow/exception/FlowOperationException.java | 35 + .../common/flow/listener/AutoSkipListener.java | 161 ++ .../common/flow/listener/AutoSkipTaskListener.java | 156 ++ .../flow/listener/DeptPostLeaderListener.java | 27 + .../flow/listener/FlowEmptyUserHandleListener.java | 74 + .../common/flow/listener/FlowFinishedListener.java | 70 + .../flow/listener/FlowTaskNotifyListener.java | 80 + .../flow/listener/FlowTaskRejectBackListener.java | 26 + .../flow/listener/FlowTaskReviveListener.java | 55 + .../flow/listener/FlowTaskTimeoutListener.java | 48 + .../common/flow/listener/FlowUserTaskListener.java | 32 + .../flow/listener/UpDeptPostLeaderListener.java | 27 + .../UpdateLatestApprovalStatusListener.java | 44 + .../apelet/common/flow/model/FlowCategory.java | 88 + .../java/apelet/common/flow/model/FlowEntry.java | 192 ++ .../apelet/common/flow/model/FlowEntryPublish.java | 98 + .../flow/model/FlowEntryPublishVariable.java | 71 + .../common/flow/model/FlowEntryVariable.java | 88 + .../java/apelet/common/flow/model/FlowMessage.java | 178 ++ .../flow/model/FlowMessageCandidateIdentity.java | 41 + .../flow/model/FlowMessageIdentityOperation.java | 50 + .../common/flow/model/FlowMultiInstanceTrans.java | 99 + .../apelet/common/flow/model/FlowNodeTasks.java | 55 + .../apelet/common/flow/model/FlowTaskComment.java | 161 ++ .../java/apelet/common/flow/model/FlowTaskExt.java | 88 + .../common/flow/model/FlowTaskTimeoutJob.java | 97 + .../common/flow/model/FlowTransConsumer.java | 33 + .../common/flow/model/FlowTransProducer.java | 121 + .../apelet/common/flow/model/FlowVariableLog.java | 61 + .../apelet/common/flow/model/FlowWorkOrder.java | 172 ++ .../apelet/common/flow/model/FlowWorkOrderExt.java | 72 + .../flow/model/constant/FlowBindFormType.java | 44 + .../flow/model/constant/FlowEntryStatus.java | 44 + .../model/constant/FlowMessageOperationType.java | 21 + .../flow/model/constant/FlowMessageType.java | 26 + .../model/constant/FlowTaskTimeoutJobStatus.java | 29 + .../flow/model/constant/FlowVariableType.java | 44 + .../apelet/common/flow/object/AnalyzedNode.java | 86 + .../apelet/common/flow/object/FlowApiOption.java | 18 + .../common/flow/object/FlowElementExtProperty.java | 18 + .../common/flow/object/FlowEntryExtensionData.java | 37 + .../apelet/common/flow/object/FlowRejectData.java | 28 + .../flow/object/FlowTaskMultiSignAssign.java | 22 + .../common/flow/object/FlowTaskOperation.java | 34 + .../flow/object/FlowTaskPostCandidateGroup.java | 64 + .../common/flow/object/FlowUserTaskExtData.java | 63 + .../apelet/common/flow/service/FlowApiService.java | 637 +++++ .../common/flow/service/FlowCategoryService.java | 69 + .../common/flow/service/FlowEntryService.java | 134 ++ .../flow/service/FlowEntryVariableService.java | 68 + .../common/flow/service/FlowMessageService.java | 113 + .../service/FlowMultiInstanceTransService.java | 38 + .../common/flow/service/FlowNodeTasksService.java | 9 + .../flow/service/FlowTaskCommentService.java | 84 + .../common/flow/service/FlowTaskExtService.java | 121 + .../flow/service/FlowTaskTimeoutJobService.java | 52 + .../flow/service/FlowTransProducerService.java | 40 + .../flow/service/FlowVariableLogService.java | 32 + .../common/flow/service/FlowWorkOrderService.java | 172 ++ .../flow/service/impl/FlowApiServiceImpl.java | 2444 ++++++++++++++++++++ .../flow/service/impl/FlowCategoryServiceImpl.java | 132 ++ .../flow/service/impl/FlowEntryServiceImpl.java | 508 ++++ .../service/impl/FlowEntryVariableServiceImpl.java | 138 ++ .../flow/service/impl/FlowMessageServiceImpl.java | 395 ++++ .../impl/FlowMultiInstanceTransServiceImpl.java | 87 + .../service/impl/FlowNodeTasksServiceImpl.java | 30 + .../service/impl/FlowTaskCommentServiceImpl.java | 144 ++ .../flow/service/impl/FlowTaskExtServiceImpl.java | 616 +++++ .../impl/FlowTaskTimeoutJobServiceImpl.java | 137 ++ .../service/impl/FlowTransProducerServiceImpl.java | 70 + .../service/impl/FlowVariableLogServiceImpl.java | 58 + .../service/impl/FlowWorkOrderServiceImpl.java | 382 +++ .../common/flow/timer/FlowTaskTimeoutTimer.java | 71 + .../flow/util/BaseBusinessDataExtHelper.java | 124 + .../flow/util/BaseFlowIdentityExtHelper.java | 253 ++ .../common/flow/util/BaseFlowNotifyExtHelper.java | 28 + .../flow/util/BaseOnlineBusinessDataExtHelper.java | 51 + .../common/flow/util/FlowCustomExtFactory.java | 78 + .../common/flow/util/FlowOperationHelper.java | 430 ++++ .../apelet/common/flow/util/FlowRedisKeyUtil.java | 52 + .../java/apelet/common/flow/vo/FlowCategoryVo.java | 71 + .../apelet/common/flow/vo/FlowEntryPublishVo.java | 59 + .../apelet/common/flow/vo/FlowEntryVariableVo.java | 77 + .../java/apelet/common/flow/vo/FlowEntryVo.java | 163 ++ .../java/apelet/common/flow/vo/FlowMessageVo.java | 137 ++ .../apelet/common/flow/vo/FlowTaskCommentVo.java | 113 + .../java/apelet/common/flow/vo/FlowTaskVo.java | 125 + .../java/apelet/common/flow/vo/FlowUserInfoVo.java | 77 + .../apelet/common/flow/vo/FlowWorkOrderVo.java | 158 ++ .../java/apelet/common/flow/vo/TaskInfoVo.java | 79 + ....flowable.common.engine.impl.EngineConfigurator | 1 + .../src/main/resources/META-INF/spring.factories | 2 + common/pom.xml | 2 +- 158 files changed, 16963 insertions(+), 1 deletion(-) create mode 100644 common/common-flow/pom.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/Plugin/AbstractAssigneePlugin.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/Plugin/DemoFlowNodePlugin.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/Plugin/FlowNodeExpressionUtil.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/advice/FlowExceptionHandler.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/cmd/ExpressionCmd.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/cmd/RestartProcessInstanceCmd.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/config/CustomEngineConfigurator.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/config/FlowAutoConfig.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/config/FlowProperties.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/constant/FlowApprovalType.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/constant/FlowBackType.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/constant/FlowBuiltinApprovalStatus.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/constant/FlowTaskStatus.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/constant/FlowTaskType.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/controller/FlowCategoryController.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryVariableController.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/controller/FlowMessageController.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/controller/FlowNodeTasksController.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowCategoryMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryPublishMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryPublishVariableMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryVariableMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageCandidateIdentityMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageIdentityOperationMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowMultiInstanceTransMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowNodeTasksMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskCommentMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskExtMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskTimeoutJobMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowTransProducerMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowVariableLogMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowWorkOrderExtMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/FlowWorkOrderMapper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowCategoryMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryPublishMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryPublishVariableMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryVariableMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageCandidateIdentityMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageIdentityOperationMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMultiInstanceTransMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskCommentMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskTimeoutJobMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTransProducerMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowVariableLogMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowWorkOrderExtMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowWorkOrderMapper.xml create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dto/FlowCategoryDto.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dto/FlowEntryDto.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dto/FlowEntryVariableDto.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dto/FlowMessageDto.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dto/FlowTaskCommentDto.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/dto/FlowWorkOrderDto.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/exception/FlowEmptyUserException.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/exception/FlowOperationException.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/AutoSkipListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/AutoSkipTaskListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/DeptPostLeaderListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/FlowEmptyUserHandleListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/FlowFinishedListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskNotifyListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskRejectBackListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskReviveListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskTimeoutListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/FlowUserTaskListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/UpDeptPostLeaderListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/listener/UpdateLatestApprovalStatusListener.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowCategory.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryPublish.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryPublishVariable.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryVariable.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowMessage.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowMessageCandidateIdentity.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowMessageIdentityOperation.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowMultiInstanceTrans.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowNodeTasks.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskComment.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskTimeoutJob.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowTransConsumer.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowTransProducer.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowVariableLog.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrder.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrderExt.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowBindFormType.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowEntryStatus.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowMessageOperationType.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowMessageType.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowTaskTimeoutJobStatus.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowVariableType.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/AnalyzedNode.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/FlowApiOption.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/FlowElementExtProperty.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/FlowEntryExtensionData.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/FlowRejectData.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskMultiSignAssign.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskOperation.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskPostCandidateGroup.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/object/FlowUserTaskExtData.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowCategoryService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowEntryService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowEntryVariableService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowMessageService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowMultiInstanceTransService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowNodeTasksService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskCommentService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskTimeoutJobService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowTransProducerService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowVariableLogService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/FlowWorkOrderService.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowCategoryServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryVariableServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowMessageServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowMultiInstanceTransServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowNodeTasksServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskCommentServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskTimeoutJobServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTransProducerServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowVariableLogServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowWorkOrderServiceImpl.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/timer/FlowTaskTimeoutTimer.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/util/BaseBusinessDataExtHelper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/util/BaseFlowIdentityExtHelper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/util/BaseFlowNotifyExtHelper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/util/FlowCustomExtFactory.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/util/FlowOperationHelper.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/util/FlowRedisKeyUtil.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowCategoryVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryPublishVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryVariableVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowMessageVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskCommentVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowUserInfoVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/FlowWorkOrderVo.java create mode 100644 common/common-flow/src/main/java/apelet/common/flow/vo/TaskInfoVo.java create mode 100644 common/common-flow/src/main/resources/META-INF/services/org.flowable.common.engine.impl.EngineConfigurator create mode 100644 common/common-flow/src/main/resources/META-INF/spring.factories diff --git a/application-tenant/tenant-admin/pom.xml b/application-tenant/tenant-admin/pom.xml index 081c5b8..ef71c3a 100644 --- a/application-tenant/tenant-admin/pom.xml +++ b/application-tenant/tenant-admin/pom.xml @@ -57,6 +57,11 @@ apelet + common-flow + 1.0.0 + + + apelet common-flow-online 1.0.0 diff --git a/common/common-flow/pom.xml b/common/common-flow/pom.xml new file mode 100644 index 0000000..8018e84 --- /dev/null +++ b/common/common-flow/pom.xml @@ -0,0 +1,44 @@ + + + + common + apelet + 1.0.0 + + 4.0.0 + + common-flow + 1.0.0 + common-flow + jar + + + + apelet + common-datafilter + 1.0.0 + + + apelet + common-sequence + 1.0.0 + + + apelet + common-log + 1.0.0 + + + apelet + common-swagger + 1.0.0 + + + org.flowable + flowable-spring-boot-starter-basic + ${flowable.version} + + + \ No newline at end of file diff --git a/common/common-flow/src/main/java/apelet/common/flow/Plugin/AbstractAssigneePlugin.java b/common/common-flow/src/main/java/apelet/common/flow/Plugin/AbstractAssigneePlugin.java new file mode 100644 index 0000000..35fae03 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/Plugin/AbstractAssigneePlugin.java @@ -0,0 +1,11 @@ +package apelet.common.flow.Plugin; + +import java.util.ArrayList; +import java.util.List; + +public class AbstractAssigneePlugin { + + public List userStr = new ArrayList<>(); + + public void getFlowNodeUser(String parame){} +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/Plugin/DemoFlowNodePlugin.java b/common/common-flow/src/main/java/apelet/common/flow/Plugin/DemoFlowNodePlugin.java new file mode 100644 index 0000000..e799bb3 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/Plugin/DemoFlowNodePlugin.java @@ -0,0 +1,10 @@ +package apelet.common.flow.Plugin; + +public class DemoFlowNodePlugin extends AbstractAssigneePlugin{ + + @Override + public void getFlowNodeUser(String parame) { + System.out.println("-----------DemoFlowNodePlugin:"+parame); + this.userStr.add("650"); + } +} \ No newline at end of file diff --git a/common/common-flow/src/main/java/apelet/common/flow/Plugin/FlowNodeExpressionUtil.java b/common/common-flow/src/main/java/apelet/common/flow/Plugin/FlowNodeExpressionUtil.java new file mode 100644 index 0000000..b39b58b --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/Plugin/FlowNodeExpressionUtil.java @@ -0,0 +1,49 @@ +package apelet.common.flow.Plugin; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * + * 流程实例中表达式解析工具 + */ +@Component +@Slf4j +public class FlowNodeExpressionUtil { + +public String getAssigneeByPlugin( String parame){ + + List list = new ArrayList<>(); + String returnStr=","; + try { + //parame = URLDecoder.decode(parame,"UTF-8"); + JSONArray josnarr = JSONArray.parseArray(parame); + for(int i=0;i aClass = Class.forName(jsono.getString("pluginType")); + Method method = aClass.getMethod("getFlowNodeUser", String.class); + Object object = aClass.newInstance(); + method.invoke(object, jsono.toString()); + list.addAll((List) aClass.getSuperclass().getDeclaredField("userStr").get(object)); + } + if(list.size()>0){ + for(int i=0;i exceptionHandle(FlowableException ex, HttpServletRequest request) { + FlowEmptyUserException flowEmptyUserException = this.findCause(ex, FlowEmptyUserException.class); + if (flowEmptyUserException != null) { + FlowTaskComment comment = JSON.parseObject(flowEmptyUserException.getMessage(), FlowTaskComment.class); + flowTaskCommentService.saveNew(comment); + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "下一个任务节点的审批人为空,提交被自动驳回!"); + } + log.error("Unhandled FlowException from URL [" + request.getRequestURI() + "]", ex); + ContextUtil.getHttpResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + return ResponseResult.error(ErrorCodeEnum.UNHANDLED_EXCEPTION, ex.getMessage()); + } + + @SuppressWarnings("unchecked") + private T findCause(Throwable ex, Class clazz) { + if (ex.getCause() == null) { + return null; + } + if (ex.getCause().getClass().equals(clazz)) { + return (T) ex.getCause(); + } else { + return this.findCause(ex.getCause(), clazz); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java b/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java new file mode 100644 index 0000000..f86922f --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java @@ -0,0 +1,26 @@ +package apelet.common.flow.base.service; + +import apelet.common.flow.model.FlowWorkOrder; + +/** + * 工作流在线表单的服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface BaseFlowOnlineService { + + /** + * 更新在线表单主表数据的流程状态字段值。 + * + * @param workOrder 工单对象。 + */ + void updateFlowStatus(FlowWorkOrder workOrder); + + /** + * 根据工单对象级联删除业务数据。 + * + * @param workOrder 工单对象。 + */ + void deleteBusinessData(FlowWorkOrder workOrder); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowService.java b/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowService.java new file mode 100644 index 0000000..146ce9c --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowService.java @@ -0,0 +1,273 @@ +package apelet.common.flow.base.service; + +import apelet.common.core.base.service.BaseService; +import apelet.common.core.exception.MyRuntimeException; +import apelet.common.core.object.CallResult; +import apelet.common.core.object.MyRelationParam; +import apelet.common.core.util.MyDateUtil; +import apelet.common.core.util.MyModelUtil; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.constant.FlowTaskStatus; +import apelet.common.flow.exception.FlowOperationException; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.flow.model.FlowWorkOrderExt; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowWorkOrderService; +import apelet.common.flow.vo.FlowWorkOrderVo; +import apelet.common.redis.cache.SessionCacheHelper; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.ReflectUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 路由表单工作流服务抽象类。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public abstract class BaseFlowService extends BaseService { + + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowWorkOrderService flowWorkOrderService; + @Autowired + private SessionCacheHelper cacheHelper; + + @Transactional(rollbackFor = Exception.class) + public void startWithBusinessKey(String processDefinitionId, K dataId) { + ProcessInstance instance = flowApiService.start(processDefinitionId, dataId); + flowWorkOrderService.saveNew(instance, dataId, null, super.tableName); + } + + @Transactional(rollbackFor = Exception.class) + public void startAndTakeFirst( + String processDefinitionId, K dataId, FlowTaskComment comment, JSONObject variables) { + ProcessInstance instance = flowApiService.start(processDefinitionId, dataId); + flowWorkOrderService.saveNew(instance, dataId, null, super.tableName); + flowApiService.takeFirstTask(instance.getProcessInstanceId(), comment, variables); + // 这里需要在创建工单后再次更新一下工单状态,在flowApiService.completeTask中的更新, + // 因为当时没有创建工单对象,更新会不起任何作用,所以这里要补偿一下。 + Integer approvalStatus = MapUtil.getInt(variables, FlowConstant.LATEST_APPROVAL_STATUS_KEY); + flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(instance.getId(), approvalStatus); + } + + @Transactional(rollbackFor = Exception.class) + public FlowWorkOrder saveNewDraftAndStartProcess(String processDefinitionId, String masterData, String slaveData) { + ProcessInstance instance = flowApiService.start(processDefinitionId, null); + Map variableMap = flowApiService.initAndGetProcessInstanceVariables(processDefinitionId); + flowApiService.setProcessInstanceVariables(instance.getProcessInstanceId(), variableMap); + return flowWorkOrderService.saveNewWithDraft(instance, null, super.tableName, masterData, slaveData); + } + + @Transactional(rollbackFor = Exception.class) + public void takeFirstTask( + String processInstanceId, String taskId, K dataId, FlowTaskComment comment, JSONObject variables) { + Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + flowApiService.setBusinessKeyForProcessInstance(processInstanceId, dataId); + ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId); + FlowWorkOrder flowWorkOrder = + flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(instance.getProcessInstanceId()); + if (flowWorkOrder == null) { + flowWorkOrderService.saveNew(instance, dataId, null, super.tableName); + } else { + flowWorkOrder.setBusinessKey(dataId.toString()); + flowWorkOrder.setUpdateTime(new Date()); + flowWorkOrder.setFlowStatus(FlowTaskStatus.SUBMITTED); + flowWorkOrderService.updateById(flowWorkOrder); + } + flowApiService.completeTask(task, comment, variables, null); + } + + @Transactional(rollbackFor = Exception.class) + public void takeTask(Task task, K dataId, FlowTaskComment comment, JSONObject variables) { + int flowStatus = FlowTaskStatus.APPROVING; + if (comment.getApprovalType().equals(FlowApprovalType.REFUSE)) { + flowStatus = FlowTaskStatus.REFUSED; + } else if (comment.getApprovalType().equals(FlowApprovalType.STOP)) { + flowStatus = FlowTaskStatus.FINISHED; + } + if (comment.getApprovalType().equals(FlowApprovalType.STOP)) { + Integer s = MapUtil.getInt(variables, FlowConstant.LATEST_APPROVAL_STATUS_KEY); + flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(task.getProcessInstanceId(), s); + CallResult stopResult = flowApiService.stopProcessInstance( + task.getProcessInstanceId(), comment.getTaskComment(), flowStatus); + if (!stopResult.isSuccess()) { + throw new FlowOperationException(stopResult.getErrorMessage()); + } + } else { + flowWorkOrderService.updateFlowStatusByProcessInstanceId(task.getProcessInstanceId(), flowStatus); + flowApiService.completeTask(task, comment, variables, null); + } + } + + public void buildDraftData(List draftWorkOrderList) { + if (CollUtil.isEmpty(draftWorkOrderList)) { + return; + } + Set workOrderIdSet = draftWorkOrderList.stream() + .map(FlowWorkOrderVo::getWorkOrderId).collect(Collectors.toSet()); + Map workOrderExtMap = + flowWorkOrderService.getFlowWorkOrderExtByWorkOrderIds(workOrderIdSet) + .stream().collect(Collectors.toMap(FlowWorkOrderExt::getWorkOrderId, c -> c)); + for (FlowWorkOrderVo workOrder : draftWorkOrderList) { + FlowWorkOrderExt workOrderExt = workOrderExtMap.get(workOrder.getWorkOrderId()); + if (workOrderExt == null) { + continue; + } + JSONObject draftData = JSON.parseObject(workOrderExt.getDraftData()); + JSONObject masterData = draftData.getJSONObject(FlowConstant.MASTER_DATA_KEY); + JSONObject slaveData = draftData.getJSONObject(FlowConstant.SLAVE_DATA_KEY); + M model; + if (masterData != null) { + model = masterData.toJavaObject(modelClass); + super.buildRelationForData(model, MyRelationParam.dictOnly()); + } else { + model = ReflectUtil.newInstance(modelClass); + } + this.bindLocalOneToOneSlaveData(model, slaveData); + super.buildLocalOneToOneDictOnly(model); + workOrder.setMasterData(BeanUtil.beanToMap(model)); + } + } + + /** + * 在流程实例审批结束后,需要进行审批表到发布表数据同步的服务实现子类,需要实现该方法。 + * + * @param workOrder 工单对象。 + */ + public void syncBusinessData(FlowWorkOrder workOrder) { + // 请自行在子类中实现当前service的数据同步逻辑。 + } + + /** + * 在流程实例审批结束后,需要进行业务表的状态更新,业务Service需要实现该方法。 + * + * @param workOrder 工单对象。 + */ + public void updateFlowStatus(FlowWorkOrder workOrder) { + if (flowStatusField == null && flowLatestApprovalStatusField == null) { + return; + } + Serializable id = this.convertToKeyValue(workOrder.getBusinessKey()); + M data = this.getById(id); + if (data == null) { + String msg = StrFormatter.format( + "WorkOrderId [{}] don't find business data by key [{}] while calling [updateFlowStatus].", + workOrder.getWorkOrderId(), workOrder.getBusinessKey()); + log.warn(msg); + return; + } + if (flowStatusField != null) { + ReflectUtil.setFieldValue(data, flowStatusField, workOrder.getFlowStatus()); + } + if (flowLatestApprovalStatusField != null) { + ReflectUtil.setFieldValue(data, flowLatestApprovalStatusField, workOrder.getLatestApprovalStatus()); + } + mapper().updateById(data); + } + + /** + * 根据工单对象删除流程业务数据。 + * + * @param workOrder 工单对象。 + */ + @SuppressWarnings("unchecked") + public void removeByWorkOrder(FlowWorkOrder workOrder) { + Serializable id = this.convertToKeyValue(workOrder.getBusinessKey()); + M data = this.getById(id); + if (data == null) { + String msg = StrFormatter.format( + "WorkOrderId [{}] don't find business data by key [{}] while calling [removeByWorkOrder].", + workOrder.getWorkOrderId(), workOrder.getBusinessKey()); + log.warn(msg); + return; + } + // 级联删除业务数据,调用的是橙单默认生成的remove方法,参数类型为主键类型,如Long、String等。 + // 进一步补充说明,橙单默认生成的remove方法,会根据在生成器中的配置,生成关联从表数据的级联删除代码。 + this.remove((K) id); + } + + /** + * 获取业务详情数据。 + * + * @param processInstanceId 流程实例Id。 + * @param businessKey 业务主键Id。如果与实际主键值类型不同,需要在子类中自行完成类型转换。 + * @return 业务主表数据,以及关联从表数据。 + */ + @SuppressWarnings("unchecked") + public String getBusinessData(String processInstanceId, String businessKey) { + Serializable id = this.convertToKeyValue(businessKey); + M data = this.getByIdWithRelation((K) id, MyRelationParam.full()); + return JSON.toJSONStringWithDateFormat(data, MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); + } + + /** + * 规格化工单扩展表中保存的草稿数据。如果仅仅包含主表数据,可以使用当前的缺省实现。 + * + * @param processInstanceId 流程实例Id。 + * @param masterData 草稿中的主表数据。 + * @param slaveData 草稿中的全部关联从表数据。 + * @return 格式化后用于前端显示的业务草稿数据。 + */ + public String getNormalizedDraftData(String processInstanceId, JSONObject masterData, JSONObject slaveData) { + // 看到这个异常千万别慌,这是及时的提示您,如果草稿中包含了关联从表数据,就一定要在业务的ServiceImpl中实现该方法。 + // 在子类中实现主表和关联从表数据的数据组装。通用的方法中,只能解析主表数据,关联从表各式各样,所以需要在子类中实现。 + if (slaveData != null) { + throw new UnsupportedOperationException( + "Please implement getNormalizedDraftData by self, because draft data includes related slaveData"); + } + if (masterData == null) { + return JSON.toJSONString(ReflectUtil.newInstance(modelClass)); + } + M model = BeanUtil.toBean(masterData, modelClass); + // 这里会缺省缓存主表中上传字段的下载地址,以便在显示草稿数据时,这些上传字段对应的download方法,不会被数据权限过滤。 + Set cachedDownableFilename = + new HashSet<>(MyModelUtil.extractDownloadFileName(model, modelClass)); + cacheHelper.putSessionDownloadableFileNameSet(cachedDownableFilename); + return JSON.toJSONStringWithDateFormat(model, MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); + } + + private Serializable convertToKeyValue(String businessKey) { + if (idFieldClass.equals(String.class)) { + return businessKey; + } else if (idFieldClass.equals(Long.class)) { + return Long.valueOf(businessKey); + } else if (idFieldClass.equals(Integer.class)) { + return Integer.valueOf(businessKey); + } else { + throw new MyRuntimeException("Unsupported Primary Key Field Type."); + } + } + + private void bindLocalOneToOneSlaveData(M model, JSONObject slaveData) { + if (slaveData == null) { + return; + } + for (BaseService.RelationStruct relationStruct : super.localRelationOneToOneStructList) { + Field relationField = relationStruct.getRelationField(); + JSONObject slaveObject = slaveData.getJSONObject(relationField.getName()); + if (slaveObject != null) { + ReflectUtil.setFieldValue(model, relationField, + slaveObject.toJavaObject(relationStruct.getRelationOneToOne().slaveModelClass())); + } + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/cmd/ExpressionCmd.java b/common/common-flow/src/main/java/apelet/common/flow/cmd/ExpressionCmd.java new file mode 100644 index 0000000..99551cb --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/cmd/ExpressionCmd.java @@ -0,0 +1,34 @@ +package apelet.common.flow.cmd; + +import lombok.AllArgsConstructor; +import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.common.engine.impl.el.VariableContainerWrapper; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.util.Map; + +/** + * EL表达式解析和执行的命令。 + * + * @author guifc + * @date 2024-08-27 + */ +@AllArgsConstructor +public class ExpressionCmd implements Command { + + private String executionId; + private String exp; + + @Override + public Object execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl cfg = CommandContextUtil.getProcessEngineConfiguration(commandContext); + Expression expression = cfg.getExpressionManager().createExpression(exp); + Map variables = cfg.getRuntimeService().getVariables(executionId); + VariableContainer variableContainer = new VariableContainerWrapper(variables); + return cfg.getManagementService().executeCommand(cc -> expression.getValue(variableContainer)); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/cmd/RestartProcessInstanceCmd.java b/common/common-flow/src/main/java/apelet/common/flow/cmd/RestartProcessInstanceCmd.java new file mode 100644 index 0000000..96b1e61 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/cmd/RestartProcessInstanceCmd.java @@ -0,0 +1,162 @@ +package apelet.common.flow.cmd; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import lombok.AllArgsConstructor; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.HistoryService; +import org.flowable.engine.ProcessEngineConfiguration; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntityImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ChangeActivityStateBuilder; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.variable.api.history.HistoricVariableInstance; + +import java.util.*; + +/** + * 流程复活命令对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@AllArgsConstructor +public class RestartProcessInstanceCmd implements Command { + + /** + * 待复活的流程实例编号 + */ + protected String processInstanceId; + /** + * 要复活到的节点列表 + */ + protected List activityIds; + /** + * 流程任务标识和变量数据的Map。 + */ + protected Map variableMap; + + @Override + public ProcessInstance execute(CommandContext commandContext) { + ProcessEngineConfiguration engineConfig = CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoryService historyService = engineConfig.getHistoryService(); + //校验和查询历史流程实例 + HistoricProcessInstance historicProcessInstance = this.checkAndGetHistoricProcessInstance(historyService); + //校验待复活节点 + this.checkActivityIds(historicProcessInstance.getProcessDefinitionId()); + //校验流程定义 + ProcessDefinition processDefinition = + ProcessDefinitionUtil.getProcessDefinition(historicProcessInstance.getProcessDefinitionId()); + if (processDefinition == null) { + throw new FlowableException("流程定义不存在"); + } + //获取流程启动节点 + Process process = ProcessDefinitionUtil.getProcess(processDefinition.getId()); + StartEvent initialFlowElement = (StartEvent) process.getInitialFlowElement(); + //重建运行时流程实例的主执行实例 + ExecutionEntity processInstance = + this.recreateProcessInstance(processDefinition, initialFlowElement, historicProcessInstance); + //创建子执行实例 + ExecutionEntity childExecution = + CommandContextUtil.getExecutionEntityManager().createChildExecution(processInstance); + childExecution.setCurrentFlowElement(initialFlowElement); + //重建运行时流程变量 + //设置历史流程实例结束节点和结束时间为空 + ((HistoricProcessInstanceEntityImpl) historicProcessInstance).setEndActivityId(null); + ((HistoricProcessInstanceEntityImpl) historicProcessInstance).setEndTime(null); + Map gloablVariables = this.collectVariables(historyService, historicProcessInstance); + //执行动态跳转 + ChangeActivityStateBuilder builder = engineConfig.getRuntimeService() + .createChangeActivityStateBuilder() + .processInstanceId(processInstance.getProcessInstanceId()) + .moveSingleExecutionToActivityIds(childExecution.getId(), activityIds) + .processVariables(gloablVariables); + for (String activityId : activityIds) { + String variableData = variableMap.get(activityId); + if (StrUtil.isNotBlank(variableData)) { + builder.localVariables(activityId, JSON.parseObject(variableData)); + } + } + builder.changeState(); + return engineConfig.getRuntimeService() + .createProcessInstanceQuery().processInstanceId(processInstance.getId()).singleResult(); + } + + private HistoricProcessInstance checkAndGetHistoricProcessInstance(HistoryService historyService) { + if (StrUtil.isBlank(processInstanceId)) { + throw new FlowableIllegalArgumentException("processInstanceId不能为空"); + } + HistoricProcessInstance historicProcessInstance = historyService + .createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (historicProcessInstance == null) { + throw new FlowableException("id为" + processInstanceId + "的流程实例不存在"); + } else if (historicProcessInstance != null && historicProcessInstance.getEndTime() == null) { + throw new FlowableException("id为" + processInstanceId + "的流程实例没有结束"); + } + return historicProcessInstance; + } + + private void checkActivityIds(String processDefinitionId) { + if (CollUtil.isEmpty(activityIds)) { + throw new FlowableIllegalArgumentException("activityIds不能为空"); + } + BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinitionId); + List notExistedFlowElements = new ArrayList<>(); + for (String activityId : activityIds) { + if (!bpmnModel.getMainProcess().containsFlowElementId(activityId)) { + notExistedFlowElements.add(activityId); + } + } + if (!CollUtil.isEmpty(notExistedFlowElements)) { + throw new FlowableIllegalArgumentException("Id为" + String.join("、", notExistedFlowElements) + "节点不存在"); + } + } + + private ExecutionEntity recreateProcessInstance( + ProcessDefinition processDefinition, + StartEvent initialFlowElement, + HistoricProcessInstance historicProcessInstance) { + //创建流程实例 + ExecutionEntity executionEntity = CommandContextUtil.getExecutionEntityManager() + .createProcessInstanceExecution(processDefinition, + historicProcessInstance.getId(), + historicProcessInstance.getBusinessKey(), + historicProcessInstance.getBusinessStatus(), + historicProcessInstance.getName(), + historicProcessInstance.getCallbackId(), + historicProcessInstance.getCallbackType(), + historicProcessInstance.getReferenceId(), + historicProcessInstance.getReferenceType(), + null, + historicProcessInstance.getTenantId(), + initialFlowElement.getInitiator(), + historicProcessInstance.getStartActivityId()); + //重设流程开始事件 + executionEntity.setStartTime(historicProcessInstance.getStartTime()); + return executionEntity; + } + + private Map collectVariables( + HistoryService historyService, HistoricProcessInstance historicProcessInstance) { + Map variables = new HashMap<>(10); + List historicVariables = historyService + .createHistoricVariableInstanceQuery() + .processInstanceId(historicProcessInstance.getId()) + .executionId(historicProcessInstance.getId()).list(); + for (HistoricVariableInstance historicVariable : historicVariables) { + variables.put(historicVariable.getVariableName(), historicVariable.getValue()); + } + return variables; + } +} \ No newline at end of file diff --git a/common/common-flow/src/main/java/apelet/common/flow/config/CustomEngineConfigurator.java b/common/common-flow/src/main/java/apelet/common/flow/config/CustomEngineConfigurator.java new file mode 100644 index 0000000..ab1f231 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/config/CustomEngineConfigurator.java @@ -0,0 +1,48 @@ +package apelet.common.flow.config; + +import apelet.common.core.config.DynamicDataSource; +import apelet.common.core.constant.ApplicationConstant; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.AbstractEngineConfiguration; +import org.flowable.common.engine.impl.EngineConfigurator; +import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; + +import javax.sql.DataSource; +import java.util.Map; + +/** + * 服务启动过程中动态切换flowable引擎内置表所在的数据源。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class CustomEngineConfigurator implements EngineConfigurator { + + @Override + public void beforeInit(AbstractEngineConfiguration engineConfiguration) { + DataSource dataSource = engineConfiguration.getDataSource(); + if (dataSource instanceof TransactionAwareDataSourceProxy) { + TransactionAwareDataSourceProxy proxy = (TransactionAwareDataSourceProxy) dataSource; + DataSource targetDataSource = proxy.getTargetDataSource(); + if (targetDataSource instanceof DynamicDataSource) { + DynamicDataSource dynamicDataSource = (DynamicDataSource) targetDataSource; + Map dynamicDataSourceMap = dynamicDataSource.getResolvedDataSources(); + DataSource flowDataSource = dynamicDataSourceMap.get(ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE); + if (flowDataSource != null) { + engineConfiguration.setDataSource(flowDataSource); + } + } + } + } + + @Override + public void configure(AbstractEngineConfiguration engineConfiguration) { + // 默认实现。 + } + + @Override + public int getPriority() { + return 0; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/config/FlowAutoConfig.java b/common/common-flow/src/main/java/apelet/common/flow/config/FlowAutoConfig.java new file mode 100644 index 0000000..6f2d3da --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/config/FlowAutoConfig.java @@ -0,0 +1,13 @@ +package apelet.common.flow.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +/** + * common-flow模块的自动配置引导类。 + * + * @author guifc + * @date 2023-08-04 + */ +@EnableConfigurationProperties({FlowProperties.class}) +public class FlowAutoConfig { +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/config/FlowProperties.java b/common/common-flow/src/main/java/apelet/common/flow/config/FlowProperties.java new file mode 100644 index 0000000..aa820b6 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/config/FlowProperties.java @@ -0,0 +1,20 @@ +package apelet.common.flow.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 工作流的配置对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@ConfigurationProperties(prefix = "common-flow") +public class FlowProperties { + + /** + * 工作落工单操作接口的URL前缀。 + */ + private String urlPrefix; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/constant/FlowApprovalType.java b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowApprovalType.java new file mode 100644 index 0000000..b1d9e2d --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowApprovalType.java @@ -0,0 +1,97 @@ +package apelet.common.flow.constant; + +/** + * 工作流任务触发BUTTON。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowApprovalType { + + /** + * 保存。 + */ + public static final String SAVE = "save"; + /** + * 同意。 + */ + public static final String AGREE = "agree"; + /** + * 拒绝。 + */ + public static final String REFUSE = "refuse"; + /** + * 驳回。 + */ + public static final String REJECT = "reject"; + /** + * 撤销。 + */ + public static final String REVOKE = "revoke"; + /** + * 指派。 + */ + public static final String TRANSFER = "transfer"; + /** + * 多实例会签。 + */ + public static final String MULTI_SIGN = "multi_sign"; + /** + * 会签同意。 + */ + public static final String MULTI_AGREE = "multi_agree"; + /** + * 会签拒绝。 + */ + public static final String MULTI_REFUSE = "multi_refuse"; + /** + * 会签弃权。 + */ + public static final String MULTI_ABSTAIN = "multi_abstain"; + /** + * 多实例加签。 + */ + public static final String MULTI_CONSIGN = "multi_consign"; + /** + * 多实例减签。 + */ + public static final String MULTI_MINUS_SIGN = "multi_minus_sign"; + /** + * 中止。 + */ + public static final String STOP = "stop"; + /** + * 干预。 + */ + public static final String INTERVENE = "intervene"; + /** + * 自由跳转。 + */ + public static final String FREE_JUMP = "free_jump"; + /** + * 流程复活。 + */ + public static final String REUSED = "reused"; + /** + * 流程复活。 + */ + public static final String REVIVE = "revive"; + /** + * 超时自动审批。 + */ + public static final String TIMEOUT_AUTO_COMPLETE = "timeout_auto_complete"; + /** + * 空审批人自动审批。 + */ + public static final String EMPTY_USER_AUTO_COMPLETE = "empty_user_auto_complete"; + /** + * 空审批人自动退回。 + */ + public static final String EMPTY_USER_AUTO_REJECT = "empty_user_auto_reject"; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowApprovalType() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/constant/FlowBackType.java b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowBackType.java new file mode 100644 index 0000000..9ae123e --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowBackType.java @@ -0,0 +1,25 @@ +package apelet.common.flow.constant; + +/** + * 待办任务回退类型。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowBackType { + + /** + * 驳回。 + */ + public static final int REJECT = 0; + /** + * 撤回。 + */ + public static final int REVOKE = 1; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowBackType() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/constant/FlowBuiltinApprovalStatus.java b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowBuiltinApprovalStatus.java new file mode 100644 index 0000000..d04c37d --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowBuiltinApprovalStatus.java @@ -0,0 +1,33 @@ +package apelet.common.flow.constant; + +/** + * 内置的流程审批状态。 + * + * @author guifc + * @date 2023-08-04 + */ +public class FlowBuiltinApprovalStatus { + + /** + * 同意。 + */ + public static final int AGREED = 1; + /** + * 拒绝。 + */ + public static final int REFUSED = 2; + /** + * 会签同意。 + */ + public static final int MULTI_AGREED = 3; + /** + * 会签拒绝。 + */ + public static final int MULTI_REFUSED = 4; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowBuiltinApprovalStatus() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java new file mode 100644 index 0000000..0e0a96a --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java @@ -0,0 +1,266 @@ +package apelet.common.flow.constant; + +/** + * 工作流中的常量数据。 + * + * @author guifc + * @date 2023-08-04 + */ +public class FlowConstant { + + /** + * 标识流程实例启动用户的变量名。 + */ + public static final String START_USER_NAME_VAR = "${startUserName}"; + + /** + * 流程实例发起人变量名。 + */ + public static final String PROC_INSTANCE_INITIATOR_VAR = "initiator"; + + /** + * 流程实例中发起人用户的变量名。 + */ + public static final String PROC_INSTANCE_START_USER_NAME_VAR = "startUserName"; + + /** + * 流程任务的指定人变量。 + */ + public static final String TASK_APPOINTED_ASSIGNEE_VAR = "appointedAssignee"; + + /** + * 操作类型变量。 + */ + public static final String OPERATION_TYPE_VAR = "operationType"; + + /** + * 提交用户。 + */ + public static final String SUBMIT_USER_VAR = "submitUser"; + + /** + * 多任务拒绝数量变量。 + */ + public static final String MULTI_REFUSE_COUNT_VAR = "multiRefuseCount"; + + /** + * 多任务同意数量变量。 + */ + public static final String MULTI_AGREE_COUNT_VAR = "multiAgreeCount"; + + /** + * 多任务弃权数量变量。 + */ + public static final String MULTI_ABSTAIN_COUNT_VAR = "multiAbstainCount"; + + /** + * 会签发起任务。 + */ + public static final String MULTI_SIGN_START_TASK_VAR = "multiSignStartTask"; + + /** + * 会签任务总数量。 + */ + public static final String MULTI_SIGN_NUM_OF_INSTANCES_VAR = "multiNumOfInstances"; + + /** + * 会签任务执行的批次Id。 + */ + public static final String MULTI_SIGN_TASK_EXECUTION_ID_VAR = "taskExecutionId"; + + /** + * 多实例实例数量变量。 + */ + public static final String NUMBER_OF_INSTANCES_VAR = "nrOfInstances"; + + /** + * 多实例已完成实例数量变量。 + */ + public static final String NUMBER_OF_COMPLETED_INSTANCES_VAR = "nrOfCompletedInstances"; + + /** + * 多任务指派人列表变量。 + */ + public static final String MULTI_ASSIGNEE_LIST_VAR = "assigneeList"; + + /** + * 上级部门领导审批变量。 + */ + public static final String GROUP_TYPE_UP_DEPT_POST_LEADER_VAR = "upDeptPostLeader"; + + /** + * 本部门领导审批变量。 + */ + public static final String GROUP_TYPE_DEPT_POST_LEADER_VAR = "deptPostLeader"; + + /** + * 所有部门岗位审批变量。 + */ + public static final String GROUP_TYPE_ALL_DEPT_POST_VAR = "allDeptPost"; + + /** + * 本部门岗位审批变量。 + */ + public static final String GROUP_TYPE_SELF_DEPT_POST_VAR = "selfDeptPost"; + + /** + * 同级部门岗位审批变量。 + */ + public static final String GROUP_TYPE_SIBLING_DEPT_POST_VAR = "siblingDeptPost"; + + /** + * 上级部门岗位审批变量。 + */ + public static final String GROUP_TYPE_UP_DEPT_POST_VAR = "upDeptPost"; + + /** + * 任意部门关联的岗位审批变量。 + */ + public static final String GROUP_TYPE_DEPT_POST_VAR = "deptPost"; + + /** + * 指定角色分组变量。 + */ + public static final String GROUP_TYPE_ROLE_VAR = "role"; + + /** + * 指定部门分组变量。 + */ + public static final String GROUP_TYPE_DEPT_VAR = "dept"; + + /** + * 指定用户分组变量。 + */ + public static final String GROUP_TYPE_USER_VAR = "user"; + + /** + * 指定审批人。 + */ + public static final String GROUP_TYPE_ASSIGNEE = "ASSIGNEE"; + + /** + * 岗位。 + */ + public static final String GROUP_TYPE_POST = "POST"; + + /** + * 上级部门领导审批。 + */ + public static final String GROUP_TYPE_UP_DEPT_POST_LEADER = "UP_DEPT_POST_LEADER"; + + /** + * 本部门岗位领导审批。 + */ + public static final String GROUP_TYPE_DEPT_POST_LEADER = "DEPT_POST_LEADER"; + + /** + * 本部门岗位前缀。 + */ + public static final String SELF_DEPT_POST_PREFIX = "SELF_DEPT_"; + + /** + * 上级部门岗位前缀。 + */ + public static final String UP_DEPT_POST_PREFIX = "UP_DEPT_"; + + /** + * 同级部门岗位前缀。 + */ + public static final String SIBLING_DEPT_POST_PREFIX = "SIBLING_DEPT_"; + + /** + * 当前流程实例所有任务的抄送数据前缀。 + */ + public static final String COPY_DATA_MAP_PREFIX = "copyDataMap_"; + + /** + * 作为临时变量存入任务变量JSONObject对象时的key。 + */ + public static final String COPY_DATA_KEY = "copyDataKey"; + + /** + * 流程中业务快照数据中,主表数据的Key。 + */ + public static final String MASTER_DATA_KEY = "masterData"; + + /** + * 流程中业务快照数据中,关联从表数据的Key。 + */ + public static final String SLAVE_DATA_KEY = "slaveData"; + + /** + * 流程任务的最近更新状态的Key。 + */ + public static final String LATEST_APPROVAL_STATUS_KEY = "latestApprovalStatus"; + + /** + * 流程用户任务待办之前的通知类型的Key。 + */ + public static final String USER_TASK_NOTIFY_TYPES_KEY = "flowNotifyTypeList"; + + /** + * 流程用户任务自动跳过类型的Key。 + */ + public static final String USER_TASK_AUTO_SKIP_KEY = "autoSkipType"; + + /** + * 流程用户任务驳回类型的Key。 + */ + public static final String USER_TASK_REJECT_TYPE_KEY = "rejectType"; + + /** + * 驳回时携带的变量数据。 + */ + public static final String REJECT_TO_SOURCE_DATA_VAR = "rejectData"; + + /** + * 驳回时携带的变量数据。 + */ + public static final String REJECT_BACK_TO_SOURCE_DATA_VAR = "rejectBackData"; + + /** + * 指定审批人。 + */ + public static final String DELEGATE_ASSIGNEE_VAR = "defaultAssignee"; + + /** + * 业务主表对象的键。目前仅仅用户在线表单工作流。 + */ + public static final String MASTER_TABLE_KEY = "masterTable"; + + /** + * 不删除任务超时作业。 + */ + public static final String NOT_DELETE_TIMEOUT_TASK_JOB_KEY = "notDeleteTimeoutTaskJob"; + + /** + * 用户任务超时小时数。 + */ + public static final String TASK_TIMEOUT_HOURS = "timeoutHours"; + + /** + * 用户任务超时处理方式。 + */ + public static final String TASK_TIMEOUT_HANDLE_WAY = "timeoutHandleWay"; + + /** + * 用户任务超时指定审批人。 + */ + public static final String TASK_TIMEOUT_DEFAULT_ASSIGNEE = "defaultAssignee"; + + /** + * 空处理人处理方式。 + */ + public static final String EMPTY_USER_HANDLE_WAY = "emptyUserHandleWay"; + + /** + * 空处理人时指定的审批人。 + */ + public static final String EMPTY_USER_TO_ASSIGNEE = "emptyUserToAssignee"; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowConstant() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/constant/FlowTaskStatus.java b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowTaskStatus.java new file mode 100644 index 0000000..49aa2c4 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowTaskStatus.java @@ -0,0 +1,49 @@ +package apelet.common.flow.constant; + +/** + * 工作流任务类型。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowTaskStatus { + + /** + * 已提交。 + */ + public static final int SUBMITTED = 0; + /** + * 审批中。 + */ + public static final int APPROVING = 1; + /** + * 被拒绝。 + */ + public static final int REFUSED = 2; + /** + * 已结束。 + */ + public static final int FINISHED = 3; + /** + * 提前停止。 + */ + public static final Integer STOPPED = 4; + /** + * 已取消。 + */ + public static final Integer CANCELLED = 5; + /** + * 保存草稿。 + */ + public static final Integer DRAFT = 6; + /** + * 流程复活。 + */ + public static final Integer REVIVE = 7; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowTaskStatus() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/constant/FlowTaskType.java b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowTaskType.java new file mode 100644 index 0000000..dced5f3 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowTaskType.java @@ -0,0 +1,25 @@ +package apelet.common.flow.constant; + +/** + * 工作流任务类型。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowTaskType { + + /** + * 其他类型任务。 + */ + public static final int OTHER_TYPE = 0; + /** + * 用户任务。 + */ + public static final int USER_TYPE = 1; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowTaskType() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowCategoryController.java b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowCategoryController.java new file mode 100644 index 0000000..8cfdd52 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowCategoryController.java @@ -0,0 +1,229 @@ +package apelet.common.flow.controller; + +import apelet.common.core.annotation.MyRequestBody; +import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.object.*; +import apelet.common.core.util.MyCommonUtil; +import apelet.common.core.util.MyModelUtil; +import apelet.common.core.util.MyPageUtil; +import apelet.common.core.validator.UpdateGroup; +import apelet.common.flow.dto.FlowCategoryDto; +import apelet.common.flow.model.FlowCategory; +import apelet.common.flow.model.FlowEntry; +import apelet.common.flow.model.constant.FlowEntryStatus; +import apelet.common.flow.service.FlowCategoryService; +import apelet.common.flow.service.FlowEntryService; +import apelet.common.flow.vo.FlowCategoryVo; +import apelet.common.log.annotation.OperationLog; +import apelet.common.log.model.constant.SysOperationLogType; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.github.pagehelper.page.PageMethod; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springdoc.api.annotations.ParameterObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +import javax.validation.groups.Default; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * 工作流流程分类接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Tag(name = "工作流流程分类接口") +@Slf4j +@RestController +@RequestMapping("${common-flow.urlPrefix}/flowCategory") +@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true") +public class FlowCategoryController { + + @Autowired + private FlowCategoryService flowCategoryService; + @Autowired + private FlowEntryService flowEntryService; + + /** + * 新增FlowCategory数据。 + * + * @param flowCategoryDto 新增对象。 + * @return 应答结果对象,包含新增对象主键Id。 + */ + @ApiOperationSupport(ignoreParameters = {"flowCategoryDto.categoryId"}) + @OperationLog(type = SysOperationLogType.ADD) + @PostMapping("/add") + public ResponseResult add(@MyRequestBody FlowCategoryDto flowCategoryDto) { + String errorMessage = MyCommonUtil.getModelValidationError(flowCategoryDto); + if (errorMessage != null) { + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowCategory flowCategory = MyModelUtil.copyTo(flowCategoryDto, FlowCategory.class); + if (flowCategoryService.existByCode(flowCategory.getCode())) { + return ResponseResult.error(ErrorCodeEnum.DUPLICATED_UNIQUE_KEY, "数据验证失败,当前流程分类已经存在!"); + } + flowCategory = flowCategoryService.saveNew(flowCategory); + return ResponseResult.success(flowCategory.getCategoryId()); + } + + /** + * 更新FlowCategory数据。 + * + * @param flowCategoryDto 更新对象。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.UPDATE) + @PostMapping("/update") + public ResponseResult update(@MyRequestBody FlowCategoryDto flowCategoryDto) { + String errorMessage = MyCommonUtil.getModelValidationError(flowCategoryDto, Default.class, UpdateGroup.class); + if (errorMessage != null) { + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowCategory flowCategory = MyModelUtil.copyTo(flowCategoryDto, FlowCategory.class); + ResponseResult verifyResult = this.doVerifyAndGet(flowCategory.getCategoryId()); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowCategory originalFlowCategory = verifyResult.getData(); + if (!StrUtil.equals(flowCategory.getCode(), originalFlowCategory.getCode())) { + FlowEntry filter = new FlowEntry(); + filter.setCategoryId(flowCategory.getCategoryId()); + filter.setStatus(FlowEntryStatus.PUBLISHED); + List flowEntryList = flowEntryService.getListByFilter(filter); + if (CollUtil.isNotEmpty(flowEntryList)) { + errorMessage = "数据验证失败,当前流程分类存在已经发布的流程数据,因此分类标识不能修改!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (flowCategoryService.existByCode(flowCategory.getCode())) { + errorMessage = "数据验证失败,当前流程分类已经存在!"; + return ResponseResult.error(ErrorCodeEnum.DUPLICATED_UNIQUE_KEY, errorMessage); + } + } + if (!flowCategoryService.update(flowCategory, originalFlowCategory)) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + return ResponseResult.success(); + } + + /** + * 删除FlowCategory数据。 + * + * @param categoryId 删除对象主键Id。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.DELETE) + @PostMapping("/delete") + public ResponseResult delete(@MyRequestBody Long categoryId) { + String errorMessage; + ResponseResult verifyResult = this.doVerifyAndGet(categoryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntry filter = new FlowEntry(); + filter.setCategoryId(categoryId); + List flowEntryList = flowEntryService.getListByFilter(filter); + if (CollUtil.isNotEmpty(flowEntryList)) { + errorMessage = "数据验证失败,请先删除当前流程分类关联的流程数据!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowCategoryService.remove(categoryId)) { + errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + return ResponseResult.success(); + } + + /** + * 列出符合过滤条件的FlowCategory列表。 + * + * @param flowCategoryDtoFilter 过滤对象。 + * @param orderParam 排序参数。 + * @param pageParam 分页参数。 + * @return 应答结果对象,包含查询结果集。 + */ + @PostMapping("/list") + public ResponseResult> list( + @MyRequestBody FlowCategoryDto flowCategoryDtoFilter, + @MyRequestBody MyOrderParam orderParam, + @MyRequestBody MyPageParam pageParam) { + if (pageParam != null) { + PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize()); + } + FlowCategory flowCategoryFilter = MyModelUtil.copyTo(flowCategoryDtoFilter, FlowCategory.class); + String orderBy = MyOrderParam.buildOrderBy(orderParam, FlowCategory.class); + List flowCategoryList = flowCategoryService.getFlowCategoryListWithRelation(flowCategoryFilter, orderBy); + return ResponseResult.success(MyPageUtil.makeResponseData(flowCategoryList, FlowCategory.INSTANCE)); + } + + /** + * 查看指定FlowCategory对象详情。 + * + * @param categoryId 指定对象主键Id。 + * @return 应答结果对象,包含对象详情。 + */ + @GetMapping("/view") + public ResponseResult view(@RequestParam Long categoryId) { + ResponseResult verifyResult = this.doVerifyAndGet(categoryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowCategoryVo flowCategoryVo = FlowCategory.INSTANCE.fromModel(verifyResult.getData()); + return ResponseResult.success(flowCategoryVo); + } + + /** + * 以字典形式返回全部FlowCategory数据集合。字典的键值为[categoryId, name]。 + * 白名单接口,登录用户均可访问。 + * + * @param filter 过滤对象。 + * @return 应答结果对象,包含的数据为 List>,map中包含两条记录,key的值分别是id和name,value对应具体数据。 + */ + @GetMapping("/listDict") + public ResponseResult>> listDict(@ParameterObject FlowCategoryVo filter) { + List resultList = + flowCategoryService.getFlowCategoryList(MyModelUtil.copyTo(filter, FlowCategory.class), null); + return ResponseResult.success( + MyCommonUtil.toDictDataList(resultList, FlowCategory::getCategoryId, FlowCategory::getName)); + } + + /** + * 根据字典Id集合,获取查询后的字典数据。 + * + * @param dictIds 字典Id集合。 + * @return 应答结果对象,包含字典形式的数据集合。 + */ + @GetMapping("/listDictByIds") + public ResponseResult>> listDictByIds(@RequestParam List dictIds) { + List resultList = flowCategoryService.getInList(new HashSet<>(dictIds)); + return ResponseResult.success( + MyCommonUtil.toDictDataList(resultList, FlowCategory::getCategoryId, FlowCategory::getName)); + } + + private ResponseResult doVerifyAndGet(Long categoryId) { + String errorMessage; + if (MyCommonUtil.existBlankArgument(categoryId)) { + return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); + } + FlowCategory flowCategory = flowCategoryService.getById(categoryId); + if (flowCategory == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + TokenData tokenData = TokenData.takeFromRequest(); + if (!StrUtil.equals(flowCategory.getAppCode(), tokenData.getAppCode())) { + errorMessage = "数据验证失败,当前应用并不存在该流程分类的定义!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (ObjectUtil.notEqual(flowCategory.getTenantId(), tokenData.getTenantId())) { + errorMessage = "数据验证失败,当前租户并不存在该流程分类的定义!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(flowCategory); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java new file mode 100644 index 0000000..fc8ef22 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java @@ -0,0 +1,469 @@ +package apelet.common.flow.controller; + +import apelet.common.core.annotation.MyRequestBody; +import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.object.*; +import apelet.common.core.util.MyCommonUtil; +import apelet.common.core.util.MyModelUtil; +import apelet.common.core.util.MyPageUtil; +import apelet.common.core.validator.UpdateGroup; +import apelet.common.flow.constant.FlowTaskType; +import apelet.common.flow.dto.FlowEntryDto; +import apelet.common.flow.model.FlowEntry; +import apelet.common.flow.model.FlowEntryPublish; +import apelet.common.flow.model.constant.FlowEntryStatus; +import apelet.common.flow.service.*; +import apelet.common.flow.vo.FlowEntryPublishVo; +import apelet.common.flow.vo.FlowEntryVo; +import apelet.common.flow.vo.TaskInfoVo; +import apelet.common.log.annotation.OperationLog; +import apelet.common.log.model.constant.SysOperationLogType; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.github.pagehelper.page.PageMethod; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.*; +import org.springdoc.api.annotations.ParameterObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +import javax.validation.groups.Default; +import javax.xml.stream.XMLStreamException; +import java.util.*; + +/** + * 工作流流程定义接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Tag(name = "工作流流程定义接口") +@Slf4j +@RestController +@RequestMapping("${common-flow.urlPrefix}/flowEntry") +@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true") +public class FlowEntryController { + + @Autowired + private FlowEntryService flowEntryService; + @Autowired + private FlowCategoryService flowCategoryService; + @Autowired + private FlowEntryVariableService flowEntryVariableService; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowTaskExtService flowTaskExtService; + + /** + * 新增工作流对象数据。 + * + * @param flowEntryDto 新增对象。 + * @return 应答结果对象,包含新增对象主键Id。 + */ + @ApiOperationSupport(ignoreParameters = {"flowEntryDto.entryId"}) + @OperationLog(type = SysOperationLogType.ADD) + @PostMapping("/add") + public ResponseResult add(@MyRequestBody FlowEntryDto flowEntryDto) { + String errorMessage = MyCommonUtil.getModelValidationError(flowEntryDto); + if (errorMessage != null) { + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowEntry flowEntry = MyModelUtil.copyTo(flowEntryDto, FlowEntry.class); + if (flowEntryService.existByProcessDefinitionKey(flowEntry.getProcessDefinitionKey())) { + errorMessage = "数据验证失败,该流程定义标识已存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + // 验证关联Id的数据合法性 + CallResult callResult = flowEntryService.verifyRelatedData(flowEntry, null); + if (!callResult.isSuccess()) { + errorMessage = callResult.getErrorMessage(); + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + flowEntry = flowEntryService.saveNew(flowEntry); + return ResponseResult.success(flowEntry.getEntryId()); + } + + /** + * 更新工作流对象数据。 + * + * @param flowEntryDto 更新对象。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.UPDATE) + @PostMapping("/update") + public ResponseResult update(@MyRequestBody FlowEntryDto flowEntryDto) { + String errorMessage = MyCommonUtil.getModelValidationError(flowEntryDto, Default.class, UpdateGroup.class); + if (errorMessage != null) { + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowEntry flowEntry = MyModelUtil.copyTo(flowEntryDto, FlowEntry.class); + ResponseResult verifyResult = this.doVerifyAndGet(flowEntry.getEntryId()); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntry originalFlowEntry = verifyResult.getData(); + if (ObjectUtil.notEqual(flowEntry.getProcessDefinitionKey(), originalFlowEntry.getProcessDefinitionKey())) { + if (originalFlowEntry.getStatus().equals(FlowEntryStatus.PUBLISHED)) { + errorMessage = "数据验证失败,当前流程为发布状态,流程标识不能修改!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (flowEntryService.existByProcessDefinitionKey(flowEntry.getProcessDefinitionKey())) { + errorMessage = "数据验证失败,该流程定义标识已存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + } + // 验证关联Id的数据合法性 + CallResult callResult = flowEntryService.verifyRelatedData(flowEntry, originalFlowEntry); + if (!callResult.isSuccess()) { + errorMessage = callResult.getErrorMessage(); + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowEntryService.update(flowEntry, originalFlowEntry)) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + return ResponseResult.success(); + } + + /** + * 删除工作流对象数据。 + * + * @param entryId 删除对象主键Id。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.DELETE) + @PostMapping("/delete") + public ResponseResult delete(@MyRequestBody Long entryId) { + String errorMessage; + if (MyCommonUtil.existBlankArgument(entryId)) { + return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); + } + ResponseResult verifyResult = this.doVerifyAndGet(entryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntry originalFlowEntry = verifyResult.getData(); + if (originalFlowEntry.getStatus().equals(FlowEntryStatus.PUBLISHED)) { + errorMessage = "数据验证失败,当前流程为发布状态,不能删除!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowEntryService.remove(entryId)) { + errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + return ResponseResult.success(); + } + + /** + * 发布工作流。 + * + * @param entryId 流程主键Id。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.PUBLISH) + @PostMapping("/publish") + public ResponseResult publish(@MyRequestBody(required = true) Long entryId) throws XMLStreamException { + String errorMessage; + ResponseResult verifyResult = this.doVerifyAndGet(entryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntry flowEntry = verifyResult.getData(); + if (StrUtil.isBlank(flowEntry.getBpmnXml())) { + errorMessage = "数据验证失败,该流程没有流程图不能被发布!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + ResponseResult taskInfoResult = this.verifyAndGetInitialTaskInfo(flowEntry); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + String taskInfo = taskInfoResult.getData() == null ? null : JSON.toJSONString(taskInfoResult.getData()); + flowEntryService.publish(flowEntry, taskInfo); + return ResponseResult.success(); + } + + /** + * 列出符合过滤条件的工作流列表。 + * + * @param flowEntryDtoFilter 过滤对象。 + * @param orderParam 排序参数。 + * @param pageParam 分页参数。 + * @return 应答结果对象,包含查询结果集。 + */ + @PostMapping("/list") + public ResponseResult> list( + @MyRequestBody FlowEntryDto flowEntryDtoFilter, + @MyRequestBody MyOrderParam orderParam, + @MyRequestBody MyPageParam pageParam) { + if (pageParam != null) { + PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize()); + } + FlowEntry flowEntryFilter = MyModelUtil.copyTo(flowEntryDtoFilter, FlowEntry.class); + String orderBy = MyOrderParam.buildOrderBy(orderParam, FlowEntry.class); + List flowEntryList = flowEntryService.getFlowEntryListWithRelation(flowEntryFilter, orderBy); + return ResponseResult.success(MyPageUtil.makeResponseData(flowEntryList, FlowEntry.INSTANCE)); + } + + /** + * 查看指定工作流对象详情。 + * + * @param entryId 指定对象主键Id。 + * @return 应答结果对象,包含对象详情。 + */ + @GetMapping("/view") + public ResponseResult view(@RequestParam Long entryId) { + ResponseResult verifyResult = this.doVerifyAndGet(entryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntry flowEntry = flowEntryService.getByIdWithRelation(entryId, MyRelationParam.full()); + if (flowEntry == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + FlowEntryVo flowEntryVo = FlowEntry.INSTANCE.fromModel(flowEntry); + flowEntryVo.setStartConditions(flowEntry.getStartConditions()); + return ResponseResult.success(flowEntryVo); + } + + /** + * 列出指定流程的发布版本列表。 + * + * @param entryId 流程主键Id。 + * @return 应答结果对象,包含流程发布列表数据。 + */ + @GetMapping("/listFlowEntryPublish") + public ResponseResult> listFlowEntryPublish(@RequestParam Long entryId) { + ResponseResult verifyResult = this.doVerifyAndGet(entryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + List flowEntryPublishList = flowEntryService.getFlowEntryPublishList(entryId); + return ResponseResult.success(MyModelUtil.copyCollectionTo(flowEntryPublishList, FlowEntryPublishVo.class)); + } + + /** + * 以字典形式返回全部FlowEntry数据集合。字典的键值为[entryId, procDefinitionName]。 + * 白名单接口,登录用户均可访问。 + * + * @param filter 过滤对象。 + * @return 应答结果对象,包含的数据为 List>,map中包含两条记录,key的值分别是id和name,value对应具体数据。 + */ + @GetMapping("/listDict") + public ResponseResult>> listDict(@ParameterObject FlowEntryVo filter) { + List resultList = + flowEntryService.getFlowEntryList(MyModelUtil.copyTo(filter, FlowEntry.class), null); + return ResponseResult.success( + MyCommonUtil.toDictDataList(resultList, FlowEntry::getEntryId, FlowEntry::getProcessDefinitionName)); + } + + /** + * 获取所有流程分类和流程定义的列表。白名单接口。 + * + * @return 所有流程分类和流程定义的列表 + */ + @GetMapping("/listAll") + public ResponseResult listAll() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("flowEntryList", flowEntryService.getFlowEntryList(null, null)); + jsonObject.put("flowCategoryList", flowCategoryService.getFlowCategoryList(null, null)); + return ResponseResult.success(jsonObject); + } + + /** + * 白名单接口,根据流程Id,获取流程引擎需要的流程标识和流程名称。 + * + * @param entryId 流程Id。 + * @return 流程的部分数据。 + */ + @GetMapping("/viewDict") + public ResponseResult> viewDict(@RequestParam Long entryId) { + ResponseResult verifyResult = this.doVerifyAndGet(entryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntry flowEntry = verifyResult.getData(); + Map resultMap = new HashMap<>(2); + resultMap.put("processDefinitionKey", flowEntry.getProcessDefinitionKey()); + resultMap.put("processDefinitionName", flowEntry.getProcessDefinitionName()); + return ResponseResult.success(resultMap); + } + + /** + * 切换指定工作的发布主版本。 + * + * @param entryId 工作流主键Id。 + * @param newEntryPublishId 新的工作流发布主版本对象的主键Id。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.UPDATE) + @PostMapping("/updateMainVersion") + public ResponseResult updateMainVersion( + @MyRequestBody(required = true) Long entryId, + @MyRequestBody(required = true) Long newEntryPublishId) { + String errorMessage; + ResponseResult verifyResult = this.doVerifyAndGet(entryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntryPublish flowEntryPublish = flowEntryService.getFlowEntryPublishFromCache(newEntryPublishId); + if (flowEntryPublish == null) { + errorMessage = "数据验证失败,当前流程发布版本并不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + if (ObjectUtil.notEqual(entryId, flowEntryPublish.getEntryId())) { + errorMessage = "数据验证失败,当前工作流并不包含该工作流发布版本数据,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (BooleanUtil.isTrue(flowEntryPublish.getMainVersion())) { + errorMessage = "数据验证失败,该版本已经为当前工作流的发布主版本,不能重复设置!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + flowEntryService.updateFlowEntryMainVersion(flowEntryService.getById(entryId), flowEntryPublish); + return ResponseResult.success(); + } + + /** + * 挂起工作流的指定发布版本。 + * + * @param entryPublishId 工作发布Id。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.SUSPEND) + @PostMapping("/suspendFlowEntryPublish") + public ResponseResult suspendFlowEntryPublish(@MyRequestBody(required = true) Long entryPublishId) { + String errorMessage; + FlowEntryPublish flowEntryPublish = flowEntryService.getFlowEntryPublishFromCache(entryPublishId); + if (flowEntryPublish == null) { + errorMessage = "数据验证失败,当前流程发布版本并不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + ResponseResult verifyResult = this.doVerifyAndGet(flowEntryPublish.getEntryId()); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + if (BooleanUtil.isFalse(flowEntryPublish.getActiveStatus())) { + errorMessage = "数据验证失败,当前流程发布版本已处于挂起状态!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + flowEntryService.suspendFlowEntryPublish(flowEntryPublish); + return ResponseResult.success(); + } + + /** + * 激活工作流的指定发布版本。 + * + * @param entryPublishId 工作发布Id。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.RESUME) + @PostMapping("/activateFlowEntryPublish") + public ResponseResult activateFlowEntryPublish(@MyRequestBody(required = true) Long entryPublishId) { + String errorMessage; + FlowEntryPublish flowEntryPublish = flowEntryService.getFlowEntryPublishFromCache(entryPublishId); + if (flowEntryPublish == null) { + errorMessage = "数据验证失败,当前流程发布版本并不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + ResponseResult verifyResult = this.doVerifyAndGet(flowEntryPublish.getEntryId()); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + if (BooleanUtil.isTrue(flowEntryPublish.getActiveStatus())) { + errorMessage = "数据验证失败,当前流程发布版本已处于激活状态!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + flowEntryService.activateFlowEntryPublish(flowEntryPublish); + return ResponseResult.success(); + } + + private ResponseResult doVerifyAndGet(Long entryId) { + String errorMessage; + if (MyCommonUtil.existBlankArgument(entryId)) { + return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); + } + FlowEntry flowEntry = flowEntryService.getById(entryId); + if (flowEntry == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + TokenData tokenData = TokenData.takeFromRequest(); + if (!StrUtil.equals(flowEntry.getAppCode(), tokenData.getAppCode())) { + errorMessage = "数据验证失败,当前应用并不存在该流程定义!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (ObjectUtil.notEqual(flowEntry.getTenantId(), tokenData.getTenantId())) { + errorMessage = "数据验证失败,当前租户并不存在该流程定义!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(flowEntry); + } + + private ResponseResult verifyAndGetInitialTaskInfo(FlowEntry flowEntry) throws XMLStreamException { + String errorMessage; + BpmnModel bpmnModel = flowApiService.convertToBpmnModel(flowEntry.getBpmnXml()); + Process process = bpmnModel.getMainProcess(); + if (process == null) { + errorMessage = "数据验证失败,当前流程标识 [" + flowEntry.getProcessDefinitionKey() + "] 关联的流程模型并不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + Collection elementList = process.getFlowElements(); + FlowElement startEvent = null; + // 这里我们只定位流程模型中的第二个节点。 + for (FlowElement flowElement : elementList) { + if (flowElement instanceof StartEvent) { + startEvent = flowElement; + break; + } + } + if (startEvent == null) { + errorMessage = "数据验证失败,当前流程图没有包含 [开始事件] 节点,请修改流程图!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowElement firstTask = this.findFirstTask(elementList, startEvent); + if (firstTask == null) { + errorMessage = "数据验证失败,当前流程图没有包含 [开始事件] 节点没有任何连线,请修改流程图!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + TaskInfoVo taskInfoVo; + if (firstTask instanceof UserTask) { + UserTask userTask = (UserTask) firstTask; + String formKey = userTask.getFormKey(); + if (StrUtil.isNotBlank(formKey)) { + taskInfoVo = JSON.parseObject(formKey, TaskInfoVo.class); + } else { + taskInfoVo = new TaskInfoVo(); + } + taskInfoVo.setAssignee(userTask.getAssignee()); + taskInfoVo.setTaskKey(userTask.getId()); + taskInfoVo.setTaskType(FlowTaskType.USER_TYPE); + Map> extensionMap = userTask.getExtensionElements(); + if (MapUtil.isNotEmpty(extensionMap)) { + taskInfoVo.setOperationList(flowTaskExtService.buildOperationListExtensionElement(extensionMap)); + taskInfoVo.setVariableList(flowTaskExtService.buildVariableListExtensionElement(extensionMap)); + } + } else { + taskInfoVo = new TaskInfoVo(); + taskInfoVo.setTaskType(FlowTaskType.OTHER_TYPE); + } + return ResponseResult.success(taskInfoVo); + } + + private FlowElement findFirstTask(Collection elementList, FlowElement startEvent) { + for (FlowElement flowElement : elementList) { + if (flowElement instanceof SequenceFlow) { + SequenceFlow sequenceFlow = (SequenceFlow) flowElement; + if (sequenceFlow.getSourceFlowElement().equals(startEvent)) { + return sequenceFlow.getTargetFlowElement(); + } + } + } + return null; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryVariableController.java b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryVariableController.java new file mode 100644 index 0000000..9a11ac7 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryVariableController.java @@ -0,0 +1,156 @@ +package apelet.common.flow.controller; + +import apelet.common.core.annotation.MyRequestBody; +import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.object.*; +import apelet.common.core.util.MyCommonUtil; +import apelet.common.core.util.MyModelUtil; +import apelet.common.core.util.MyPageUtil; +import apelet.common.core.validator.UpdateGroup; +import apelet.common.flow.dto.FlowEntryVariableDto; +import apelet.common.flow.model.FlowEntryVariable; +import apelet.common.flow.service.FlowEntryVariableService; +import apelet.common.flow.vo.FlowEntryVariableVo; +import apelet.common.log.annotation.OperationLog; +import apelet.common.log.model.constant.SysOperationLogType; +import com.github.pagehelper.page.PageMethod; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +import javax.validation.groups.Default; +import java.util.List; + +/** + * 工作流流程变量接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Tag(name = "工作流流程变量接口") +@Slf4j +@RestController +@RequestMapping("${common-flow.urlPrefix}/flowEntryVariable") +@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true") +public class FlowEntryVariableController { + + @Autowired + private FlowEntryVariableService flowEntryVariableService; + + /** + * 新增流程变量数据。 + * + * @param flowEntryVariableDto 新增对象。 + * @return 应答结果对象,包含新增对象主键Id。 + */ + @ApiOperationSupport(ignoreParameters = {"flowEntryVariableDto.variableId"}) + @OperationLog(type = SysOperationLogType.ADD) + @PostMapping("/add") + public ResponseResult add(@MyRequestBody FlowEntryVariableDto flowEntryVariableDto) { + String errorMessage = MyCommonUtil.getModelValidationError(flowEntryVariableDto); + if (errorMessage != null) { + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowEntryVariable flowEntryVariable = MyModelUtil.copyTo(flowEntryVariableDto, FlowEntryVariable.class); + flowEntryVariable = flowEntryVariableService.saveNew(flowEntryVariable); + return ResponseResult.success(flowEntryVariable.getVariableId()); + } + + /** + * 更新流程变量数据。 + * + * @param flowEntryVariableDto 更新对象。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.UPDATE) + @PostMapping("/update") + public ResponseResult update(@MyRequestBody FlowEntryVariableDto flowEntryVariableDto) { + String errorMessage = MyCommonUtil.getModelValidationError(flowEntryVariableDto, Default.class, UpdateGroup.class); + if (errorMessage != null) { + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowEntryVariable flowEntryVariable = MyModelUtil.copyTo(flowEntryVariableDto, FlowEntryVariable.class); + FlowEntryVariable originalFlowEntryVariable = flowEntryVariableService.getById(flowEntryVariable.getVariableId()); + if (originalFlowEntryVariable == null) { + // NOTE: 修改下面方括号中的话述 + errorMessage = "数据验证失败,当前 [数据] 并不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + if (!flowEntryVariableService.update(flowEntryVariable, originalFlowEntryVariable)) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + return ResponseResult.success(); + } + + /** + * 删除流程变量数据。 + * + * @param variableId 删除对象主键Id。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.DELETE) + @PostMapping("/delete") + public ResponseResult delete(@MyRequestBody Long variableId) { + String errorMessage; + if (MyCommonUtil.existBlankArgument(variableId)) { + return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); + } + // 验证关联Id的数据合法性 + FlowEntryVariable originalFlowEntryVariable = flowEntryVariableService.getById(variableId); + if (originalFlowEntryVariable == null) { + // NOTE: 修改下面方括号中的话述 + errorMessage = "数据验证失败,当前 [对象] 并不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + if (!flowEntryVariableService.remove(variableId)) { + errorMessage = "数据操作失败,删除的对象不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + return ResponseResult.success(); + } + + /** + * 列出符合过滤条件的流程变量列表。 + * + * @param flowEntryVariableDtoFilter 过滤对象。 + * @param orderParam 排序参数。 + * @param pageParam 分页参数。 + * @return 应答结果对象,包含查询结果集。 + */ + @PostMapping("/list") + public ResponseResult> list( + @MyRequestBody FlowEntryVariableDto flowEntryVariableDtoFilter, + @MyRequestBody MyOrderParam orderParam, + @MyRequestBody MyPageParam pageParam) { + if (pageParam != null) { + PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize()); + } + FlowEntryVariable flowEntryVariableFilter = MyModelUtil.copyTo(flowEntryVariableDtoFilter, FlowEntryVariable.class); + String orderBy = MyOrderParam.buildOrderBy(orderParam, FlowEntryVariable.class); + List flowEntryVariableList = + flowEntryVariableService.getFlowEntryVariableListWithRelation(flowEntryVariableFilter, orderBy); + return ResponseResult.success(MyPageUtil.makeResponseData(flowEntryVariableList, FlowEntryVariable.INSTANCE)); + } + + /** + * 查看指定流程变量对象详情。 + * + * @param variableId 指定对象主键Id。 + * @return 应答结果对象,包含对象详情。 + */ + @GetMapping("/view") + public ResponseResult view(@RequestParam Long variableId) { + if (MyCommonUtil.existBlankArgument(variableId)) { + return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); + } + FlowEntryVariable flowEntryVariable = flowEntryVariableService.getByIdWithRelation(variableId, MyRelationParam.full()); + if (flowEntryVariable == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + FlowEntryVariableVo flowEntryVariableVo = FlowEntryVariable.INSTANCE.fromModel(flowEntryVariable); + return ResponseResult.success(flowEntryVariableVo); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowMessageController.java b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowMessageController.java new file mode 100644 index 0000000..10e824a --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowMessageController.java @@ -0,0 +1,113 @@ +package apelet.common.flow.controller; + +import apelet.common.core.annotation.MyRequestBody; +import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.object.MyPageData; +import apelet.common.core.object.MyPageParam; +import apelet.common.core.object.ResponseResult; +import apelet.common.core.util.MyPageUtil; +import apelet.common.flow.model.FlowMessage; +import apelet.common.flow.model.constant.FlowMessageType; +import apelet.common.flow.service.FlowMessageService; +import apelet.common.flow.vo.FlowMessageVo; +import com.alibaba.fastjson.JSONObject; +import com.github.pagehelper.page.PageMethod; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 工作流消息接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Tag(name = "工作流消息接口") +@Slf4j +@RestController +@RequestMapping("${common-flow.urlPrefix}/flowMessage") +@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true") +public class FlowMessageController { + + @Autowired + private FlowMessageService flowMessageService; + + /** + * 获取当前用户的未读消息总数。 + * NOTE:白名单接口。 + * + * @return 应答结果对象,包含当前用户的未读消息总数。 + */ + @GetMapping("/getMessageCount") + public ResponseResult getMessageCount() { + JSONObject resultData = new JSONObject(); + resultData.put("remindingMessageCount", flowMessageService.countRemindingMessageListByUser()); + resultData.put("copyMessageCount", flowMessageService.countCopyMessageByUser()); + resultData.put("sysMessageCount", flowMessageService.countSysMessageByUser()); + return ResponseResult.success(resultData); + } + + /** + * 获取当前用户的催办消息列表。 + * 不仅仅包含,其中包括当前用户所属角色、部门和岗位的候选组催办消息。 + * NOTE:白名单接口。 + * + * @return 应答结果对象,包含查询结果集。 + */ + @PostMapping("/listRemindingTask") + public ResponseResult> listRemindingTask(@MyRequestBody MyPageParam pageParam) { + if (pageParam != null) { + PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount()); + } + List flowMessageList = flowMessageService.getRemindingMessageListByUser(); + return ResponseResult.success(MyPageUtil.makeResponseData(flowMessageList, FlowMessage.INSTANCE)); + } + + /** + * 获取当前用户的抄送消息列表。 + * 不仅仅包含,其中包括当前用户所属角色、部门和岗位的候选组抄送消息。 + * NOTE:白名单接口。 + * + * @param read true表示已读,false表示未读。 + * @return 应答结果对象,包含查询结果集。 + */ + @PostMapping("/listCopyMessage") + public ResponseResult> listCopyMessage( + @MyRequestBody MyPageParam pageParam, @MyRequestBody Boolean read) { + if (pageParam != null) { + PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount()); + } + List flowMessageList = flowMessageService.getCopyMessageListByUser(read); + return ResponseResult.success(MyPageUtil.makeResponseData(flowMessageList, FlowMessage.INSTANCE)); + } + + /** + * 读取抄送消息,同时更新当前用户对指定抄送消息的读取状态。 + * + * @param messageId 消息Id。 + * @return 应答结果对象。 + */ + @PostMapping("/readCopyTask") + public ResponseResult readCopyTask(@MyRequestBody Long messageId) { + String errorMessage; + // 验证流程任务的合法性。 + FlowMessage flowMessage = flowMessageService.getById(messageId); + if (flowMessage == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + if (flowMessage.getMessageType() != FlowMessageType.COPY_TYPE) { + errorMessage = "数据验证失败,当前消息不是抄送类型消息!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowMessageService.isCandidateIdentityOnMessage(messageId)) { + errorMessage = "数据验证失败,当前用户没有权限访问该消息!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + flowMessageService.readCopyTask(messageId); + return ResponseResult.success(); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowNodeTasksController.java b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowNodeTasksController.java new file mode 100644 index 0000000..7f07387 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowNodeTasksController.java @@ -0,0 +1,56 @@ +package apelet.common.flow.controller; + +import apelet.common.core.annotation.MyRequestBody; +import apelet.common.core.object.ResponseResult; +import apelet.common.flow.model.FlowNodeTasks; +import apelet.common.flow.service.FlowNodeTasksService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 工作流审核设置接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Tag(name = "工作流审核设置接口") +@Slf4j +@RestController +@RequestMapping("${common-flow.urlPrefix}/flowNodeTasks") +@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true") +public class FlowNodeTasksController { + + @Autowired + FlowNodeTasksService flowNodeTasksService; + + + @PostMapping("/add") + public ResponseResult add(@RequestBody FlowNodeTasks flowNodeTasks) { + flowNodeTasksService.save(flowNodeTasks); + return ResponseResult.success(flowNodeTasks.getId()); + } + + @GetMapping("/list") + public ResponseResult getList(@RequestParam String flowId,@RequestParam String taskKey) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(FlowNodeTasks::getFlowId, flowId); + wrapper.eq(FlowNodeTasks::getTaskKey, taskKey); + List list = flowNodeTasksService.list(wrapper); + return ResponseResult.success(list); + } + + + + @PostMapping("/delete") + public ResponseResult delete(@MyRequestBody Long id) { + boolean b = flowNodeTasksService.removeById(id); + return ResponseResult.success(b); + } + +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java new file mode 100644 index 0000000..f9f488c --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java @@ -0,0 +1,1225 @@ +package apelet.common.flow.controller; + +import apelet.common.core.annotation.DisableDataFilter; +import apelet.common.core.annotation.MyRequestBody; +import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.object.*; +import apelet.common.core.util.MyPageUtil; +import apelet.common.flow.constant.*; +import apelet.common.flow.exception.FlowOperationException; +import apelet.common.flow.model.*; +import apelet.common.flow.model.constant.FlowMessageType; +import apelet.common.flow.object.FlowEntryExtensionData; +import apelet.common.flow.service.*; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.util.FlowOperationHelper; +import apelet.common.flow.vo.*; +import apelet.common.log.annotation.OperationLog; +import apelet.common.log.model.constant.SysOperationLogType; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.UserTask; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.task.api.TaskInfo; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 工作流流程操作接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Tag(name = "工作流流程操作接口") +@Slf4j +@RestController +@RequestMapping("${common-flow.urlPrefix}/flowOperation") +@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true") +public class FlowOperationController { + + @Autowired + private FlowEntryService flowEntryService; + @Autowired + private FlowTaskCommentService flowTaskCommentService; + @Autowired + private FlowTaskExtService flowTaskExtService; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowWorkOrderService flowWorkOrderService; + @Autowired + private FlowMessageService flowMessageService; + @Autowired + private FlowOperationHelper flowOperationHelper; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private FlowMultiInstanceTransService flowMultiInstanceTransService; + @Autowired + private FlowTransProducerService flowTransProducerService; + + private static final String ACTIVE_MULTI_INST_TASK = "activeMultiInstanceTask"; + private static final String SHOW_NAME = "showName"; + private static final String INSTANCE_ID = "processInstanceId"; + + /** + * 根据指定流程的主版本,发起一个流程实例。 + * + * @param processDefinitionKey 流程标识。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.START_FLOW) + @PostMapping("/startOnly") + public ResponseResult startOnly(@MyRequestBody(required = true) String processDefinitionKey) { + // 1. 验证流程数据的合法性。 + ResponseResult flowEntryResult = flowOperationHelper.verifyAndGetFlowEntry(processDefinitionKey); + if (!flowEntryResult.isSuccess()) { + return ResponseResult.errorFrom(flowEntryResult); + } + // 2. 验证流程一个用户任务的合法性。 + FlowEntryPublish flowEntryPublish = flowEntryResult.getData().getMainFlowEntryPublish(); + ResponseResult taskInfoResult = + flowOperationHelper.verifyAndGetInitialTaskInfo(flowEntryPublish, false); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + flowApiService.start(flowEntryPublish.getProcessDefinitionId(), null); + return ResponseResult.success(); + } + + /** + * 流程复活接口。 + * + * @param processInstanceId 待复活的流程实例Id。 + * @param taskKeys 复活跳转到的任务标识集合。 + * @param taskComment 流程复活备注。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.REVIVE_FLOW) + @PostMapping("/revive") + public ResponseResult revive( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) List taskKeys, + @MyRequestBody(required = true) String taskComment) { + String errorMessage; + HistoricProcessInstance instance = flowApiService.getHistoricProcessInstance(processInstanceId); + if (instance == null) { + errorMessage = "数据验证失败,当前流程实例不存在或尚未结束!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + List flowEntryPublishList = + flowEntryService.getFlowEntryPublishList(CollUtil.newHashSet(instance.getProcessDefinitionId())); + if (StrUtil.isBlank(flowEntryPublishList.get(0).getExtensionData())) { + errorMessage = "数据验证失败,当前流程不支持流程复活!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowEntryExtensionData extData = + JSON.parseObject(flowEntryPublishList.get(0).getExtensionData(), FlowEntryExtensionData.class); + if (BooleanUtil.isFalse(extData.getSupportRevive())) { + errorMessage = "数据验证失败,当前流程不支持流程复活!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (extData.getKeptReviveDays() > 0) { + Date expiredDate = DateUtil.offset(instance.getEndTime(), DateField.DAY_OF_MONTH, extData.getKeptReviveDays()); + if (expiredDate.before(new Date())) { + errorMessage = "数据验证失败,当前流程流程复活已过期!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + } + try { + flowApiService.revive(processInstanceId, taskKeys, taskComment); + return ResponseResult.success(); + } catch (FlowableException e) { + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, e.getMessage()); + } + } + + /** + * 获取开始节点之后的第一个任务节点的数据。 + * + * @param processDefinitionKey 流程标识。 + * @return 任务节点的自定义对象数据。 + */ + @GetMapping("/viewInitialTaskInfo") + public ResponseResult viewInitialTaskInfo(@RequestParam String processDefinitionKey) { + ResponseResult flowEntryResult = flowOperationHelper.verifyAndGetFlowEntry(processDefinitionKey); + if (!flowEntryResult.isSuccess()) { + return ResponseResult.errorFrom(flowEntryResult); + } + FlowEntryPublish flowEntryPublish = flowEntryResult.getData().getMainFlowEntryPublish(); + String initTaskInfo = flowEntryPublish.getInitTaskInfo(); + TaskInfoVo taskInfo = StrUtil.isBlank(initTaskInfo) + ? null : JSON.parseObject(initTaskInfo, TaskInfoVo.class); + if (taskInfo != null) { + String loginName = TokenData.takeFromRequest().getLoginName(); + taskInfo.setAssignedMe(StrUtil.equalsAny( + taskInfo.getAssignee(), loginName, FlowConstant.START_USER_NAME_VAR)); + } + return ResponseResult.success(taskInfo); + } + + /** + * 获取流程运行时指定任务的信息。 + * + * @param processDefinitionId 流程引擎的定义Id。 + * @param processInstanceId 流程引擎的实例Id。 + * @param taskId 流程引擎的任务Id。 + * @return 任务节点的自定义对象数据。 + */ + @GetMapping("/viewRuntimeTaskInfo") + public ResponseResult viewRuntimeTaskInfo( + @RequestParam String processDefinitionId, + @RequestParam String processInstanceId, + @RequestParam String taskId) { + Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + ResponseResult taskInfoResult = flowOperationHelper.verifyAndGetRuntimeTaskInfo(task); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + TaskInfoVo taskInfoVo = taskInfoResult.getData(); + FlowTaskExt flowTaskExt = + flowTaskExtService.getByProcessDefinitionIdAndTaskId(processDefinitionId, taskInfoVo.getTaskKey()); + if (flowTaskExt != null) { + if (StrUtil.isNotBlank(flowTaskExt.getOperationListJson())) { + taskInfoVo.setOperationList(JSON.parseArray(flowTaskExt.getOperationListJson(), JSONObject.class)); + } + if (StrUtil.isNotBlank(flowTaskExt.getVariableListJson())) { + taskInfoVo.setVariableList(JSON.parseArray(flowTaskExt.getVariableListJson(), JSONObject.class)); + } + } + return ResponseResult.success(taskInfoVo); + } + + /** + * 获取流程运行时指定任务的信息。 + * + * @param processDefinitionId 流程引擎的定义Id。 + * @param processInstanceId 流程引擎的实例Id。 + * @param taskId 流程引擎的任务Id。 + * @return 任务节点的自定义对象数据。 + */ + @GetMapping("/viewHistoricTaskInfo") + public ResponseResult viewHistoricTaskInfo( + @RequestParam String processDefinitionId, + @RequestParam String processInstanceId, + @RequestParam String taskId) { + String errorMessage; + HistoricTaskInstance taskInstance = flowApiService.getHistoricTaskInstance(processInstanceId, taskId); + String loginName = TokenData.takeFromRequest().getLoginName(); + if (!StrUtil.equals(taskInstance.getAssignee(), loginName)) { + errorMessage = "数据验证失败,当前用户不是指派人!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + TaskInfoVo taskInfoVo = JSON.parseObject(taskInstance.getFormKey(), TaskInfoVo.class); + FlowTaskExt flowTaskExt = + flowTaskExtService.getByProcessDefinitionIdAndTaskId(processDefinitionId, taskInstance.getTaskDefinitionKey()); + if (flowTaskExt != null) { + if (StrUtil.isNotBlank(flowTaskExt.getOperationListJson())) { + taskInfoVo.setOperationList(JSON.parseArray(flowTaskExt.getOperationListJson(), JSONObject.class)); + } + if (StrUtil.isNotBlank(flowTaskExt.getVariableListJson())) { + taskInfoVo.setVariableList(JSON.parseArray(flowTaskExt.getVariableListJson(), JSONObject.class)); + } + } + return ResponseResult.success(taskInfoVo); + } + + /** + * 获取第一个提交表单数据的任务信息。 + * + * @param processInstanceId 流程实例Id。 + * @return 任务节点的自定义对象数据。 + */ + @GetMapping("/viewInitialHistoricTaskInfo") + public ResponseResult viewInitialHistoricTaskInfo(@RequestParam String processInstanceId) { + String errorMessage; + List taskCommentList = + flowTaskCommentService.getFlowTaskCommentList(processInstanceId); + if (CollUtil.isEmpty(taskCommentList)) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + FlowTaskComment taskComment = taskCommentList.get(0); + HistoricTaskInstance task = flowApiService.getHistoricTaskInstance(processInstanceId, taskComment.getTaskId()); + if (StrUtil.isBlank(task.getFormKey())) { + errorMessage = "数据验证失败,指定任务的formKey属性不存在,请重新修改流程图!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + TaskInfoVo taskInfo = JSON.parseObject(task.getFormKey(), TaskInfoVo.class); + taskInfo.setTaskKey(task.getTaskDefinitionKey()); + return ResponseResult.success(taskInfo); + } + + /** + * 获取任务的用户信息列表。 + * + * @param processDefinitionId 流程定义Id。 + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @param historic 是否为历史任务。 + * @return 任务相关的用户信息列表。 + */ + @DisableDataFilter + @GetMapping("/viewTaskUserInfo") + public ResponseResult> viewTaskUserInfo( + @RequestParam String processDefinitionId, + @RequestParam String processInstanceId, + @RequestParam String taskId, + @RequestParam Boolean historic) { + TaskInfo taskInfo; + HistoricTaskInstance hisotricTask; + if (BooleanUtil.isFalse(historic)) { + taskInfo = flowApiService.getTaskById(taskId); + if (taskInfo == null) { + hisotricTask = flowApiService.getHistoricTaskInstance(processInstanceId, taskId); + taskInfo = hisotricTask; + historic = true; + } + } else { + hisotricTask = flowApiService.getHistoricTaskInstance(processInstanceId, taskId); + taskInfo = hisotricTask; + } + if (taskInfo == null) { + String errorMessage = "数据验证失败,任务Id不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + String taskKey = taskInfo.getTaskDefinitionKey(); + FlowTaskExt taskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId(processDefinitionId, taskKey); + boolean isMultiInstanceTask = flowApiService.isMultiInstanceTask(taskInfo.getProcessDefinitionId(), taskKey); + List resultUserInfoList = + flowTaskExtService.getCandidateUserInfoList(processInstanceId, taskExt, taskInfo, isMultiInstanceTask, historic); + if (BooleanUtil.isTrue(historic) || isMultiInstanceTask) { + List taskCommentList = buildApprovedFlowTaskCommentList(taskInfo, isMultiInstanceTask); + Map resultUserInfoMap = + resultUserInfoList.stream().collect(Collectors.toMap(FlowUserInfoVo::getLoginName, c -> c)); + for (FlowTaskComment taskComment : taskCommentList) { + FlowUserInfoVo flowUserInfoVo = resultUserInfoMap.get(taskComment.getCreateLoginName()); + if (flowUserInfoVo != null) { + flowUserInfoVo.setLastApprovalTime(taskComment.getCreateTime()); + } + } + } + return ResponseResult.success(resultUserInfoList); + } + + /** + * 查看指定流程实例的草稿数据。 + * NOTE:白名单接口。 + * + * @param processDefinitionKey 流程定义标识。 + * @param processInstanceId 流程实例Id。 + * @return 流程实例的草稿数据。 + */ + @DisableDataFilter + @GetMapping("/viewDraftData") + public ResponseResult viewDraftData( + @RequestParam String processDefinitionKey, @RequestParam String processInstanceId) { + String errorMessage; + ResponseResult flowWorkOrderResult = + flowOperationHelper.verifyAndGetFlowWorkOrderWithDraft(processDefinitionKey, processInstanceId); + if (!flowWorkOrderResult.isSuccess()) { + return ResponseResult.errorFrom(flowWorkOrderResult); + } + FlowWorkOrder flowWorkOrder = flowWorkOrderResult.getData(); + if (StrUtil.isBlank(flowWorkOrder.getTableName())) { + errorMessage = "数据验证失败,当前工单不是静态路由表单工单!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowWorkOrderExt flowWorkOrderExt = + flowWorkOrderService.getFlowWorkOrderExtByWorkOrderId(flowWorkOrder.getWorkOrderId()); + if (StrUtil.isBlank(flowWorkOrderExt.getDraftData())) { + return ResponseResult.success(null); + } + JSONObject masterData = JSON.parseObject( + flowWorkOrderExt.getDraftData()).getJSONObject(FlowConstant.MASTER_DATA_KEY); + JSONObject slaveData = JSON.parseObject( + flowWorkOrderExt.getDraftData()).getJSONObject(FlowConstant.SLAVE_DATA_KEY); + String normalizedDraftData = flowCustomExtFactory.getBusinessDataExtHelper() + .getNormalizedDraftData(processDefinitionKey, processInstanceId, masterData, slaveData); + JSONObject draftObject = null; + if (StrUtil.isNotBlank(normalizedDraftData)) { + draftObject = JSON.parseObject(normalizedDraftData); + } + return ResponseResult.success(draftObject); + } + + /** + * 根据消息Id,获取流程Id关联的业务数据。 + * NOTE:白名单接口。 + * + * @param messageId 抄送消息Id。 + * @param snapshot 是否获取抄送或传阅时任务的业务快照数据。如果为true,后续任务导致的业务数据修改,将不会返回给前端。 + * @return 抄送消息关联的流程实例业务数据。 + */ + @DisableDataFilter + @GetMapping("/viewCopyBusinessData") + public ResponseResult viewCopyBusinessData( + @RequestParam Long messageId, @RequestParam(required = false) Boolean snapshot) { + String errorMessage; + // 验证流程任务的合法性。 + FlowMessage flowMessage = flowMessageService.getById(messageId); + if (flowMessage == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + if (flowMessage.getMessageType() != FlowMessageType.COPY_TYPE) { + errorMessage = "数据验证失败,当前消息不是抄送类型消息!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (flowMessage.getOnlineFormData() == null || flowMessage.getOnlineFormData()) { + errorMessage = "数据验证失败,当前消息为在线表单数据,不能通过该接口获取!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowMessageService.isCandidateIdentityOnMessage(messageId)) { + errorMessage = "数据验证失败,当前用户没有权限访问该消息!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + JSONObject businessObject = null; + if (snapshot != null && snapshot) { + if (StrUtil.isNotBlank(flowMessage.getBusinessDataShot())) { + businessObject = JSON.parseObject(flowMessage.getBusinessDataShot()); + } + return ResponseResult.success(businessObject); + } + HistoricProcessInstance instance = + flowApiService.getHistoricProcessInstance(flowMessage.getProcessInstanceId()); + // 如果业务主数据为空,则直接返回。 + if (StrUtil.isBlank(instance.getBusinessKey())) { + errorMessage = "数据验证失败,当前消息为所属流程实例没有包含业务主键Id!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + String businessData = flowCustomExtFactory.getBusinessDataExtHelper().getBusinessData( + flowMessage.getProcessDefinitionKey(), flowMessage.getProcessInstanceId(), instance.getBusinessKey()); + if (StrUtil.isNotBlank(businessData)) { + businessObject = JSON.parseObject(businessData); + } + // 将当前消息更新为已读 + flowMessageService.readCopyTask(messageId); + return ResponseResult.success(businessObject); + } + + /** + * 获取多实例会签任务的指派人列表。 + * NOTE: 白名单接口。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 多实例任务的上一级任务Id。 + * @return 应答结果,指定会签任务的指派人列表。 + */ + @GetMapping("/listMultiSignAssignees") + public ResponseResult> listMultiSignAssignees( + @RequestParam String processInstanceId, @RequestParam String taskId) { + ResponseResult verifyResult = this.doVerifyMultiSign(processInstanceId, taskId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + Task activeMultiInstanceTask = + verifyResult.getData().getObject(ACTIVE_MULTI_INST_TASK, Task.class); + String multiInstanceExecId = flowApiService.getExecutionVariableStringWithSafe( + activeMultiInstanceTask.getExecutionId(), FlowConstant.MULTI_SIGN_TASK_EXECUTION_ID_VAR); + FlowMultiInstanceTrans trans = + flowMultiInstanceTransService.getWithAssigneeListByMultiInstanceExecId(multiInstanceExecId); + List commentList = + flowTaskCommentService.getFlowTaskCommentListByMultiInstanceExecId(multiInstanceExecId); + List assigneeList = StrUtil.split(trans.getAssigneeList(), ","); + Set approvedAssigneeSet = commentList.stream() + .map(FlowTaskComment::getCreateLoginName).collect(Collectors.toSet()); + List resultList = new LinkedList<>(); + Map usernameMap = + flowCustomExtFactory.getFlowIdentityExtHelper().mapUserShowNameByLoginName(new HashSet<>(assigneeList)); + for (String assignee : assigneeList) { + JSONObject resultData = new JSONObject(); + resultData.put("assignee", assignee); + resultData.put(SHOW_NAME, usernameMap.get(assignee)); + resultData.put("approved", approvedAssigneeSet.contains(assignee)); + resultList.add(resultData); + } + return ResponseResult.success(resultList); + } + + /** + * 提交多实例加签或减签。 + * NOTE: 白名单接口。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 多实例任务的上一级任务Id。 + * @param newAssignees 加签减签人列表,多个指派人之间逗号分隔。 + * @param isAdd 是否为加签,如果没有该参数,为了保持兼容性,缺省值为true。 + * @return 应答结果。 + */ + @PostMapping("/submitConsign") + public ResponseResult submitConsign( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) String taskId, + @MyRequestBody(required = true) String newAssignees, + @MyRequestBody Boolean isAdd) { + String errorMessage; + ResponseResult verifyResult = this.doVerifyMultiSign(processInstanceId, taskId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + HistoricTaskInstance taskInstance = + verifyResult.getData().getObject("taskInstance", HistoricTaskInstance.class); + Task activeMultiInstanceTask = + verifyResult.getData().getObject(ACTIVE_MULTI_INST_TASK, Task.class); + String multiInstanceExecId = flowApiService.getExecutionVariableStringWithSafe( + activeMultiInstanceTask.getExecutionId(), FlowConstant.MULTI_SIGN_TASK_EXECUTION_ID_VAR); + JSONArray assigneeArray = JSON.parseArray(newAssignees); + if (isAdd == null) { + isAdd = true; + } + if (!isAdd) { + List commentList = + flowTaskCommentService.getFlowTaskCommentListByMultiInstanceExecId(multiInstanceExecId); + if (CollUtil.isNotEmpty(commentList)) { + Set approvedAssigneeSet = commentList.stream() + .map(FlowTaskComment::getCreateLoginName).collect(Collectors.toSet()); + String loginName = this.findExistAssignee(approvedAssigneeSet, assigneeArray); + if (loginName != null) { + errorMessage = "数据验证失败,用户 [" + loginName + "] 已经审批,不能减签该用户!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + } + } else { + // 避免同一人被重复加签。 + FlowMultiInstanceTrans trans = + flowMultiInstanceTransService.getWithAssigneeListByMultiInstanceExecId(multiInstanceExecId); + Set assigneeSet = new HashSet<>(StrUtil.split(trans.getAssigneeList(), ",")); + String loginName = this.findExistAssignee(assigneeSet, assigneeArray); + if (loginName != null) { + errorMessage = "数据验证失败,用户 [" + loginName + "] 已经是会签人,不能重复指定!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + } + try { + flowApiService.submitConsign(taskInstance, activeMultiInstanceTask, newAssignees, isAdd); + } catch (FlowOperationException e) { + errorMessage = e.getMessage(); + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(); + } + + /** + * 返回当前用户待办的任务列表。 + * + * @param processDefinitionKey 流程标识。 + * @param processDefinitionName 流程定义名 (模糊查询)。 + * @param taskName 任务名称 (模糊查询)。 + * @param pageParam 分页对象。 + * @return 返回当前用户待办的任务列表。如果指定流程标识,则仅返回该流程的待办任务列表。 + */ + @DisableDataFilter + @PostMapping("/listRuntimeTask") + public ResponseResult> listRuntimeTask( + @MyRequestBody String processDefinitionKey, + @MyRequestBody String processDefinitionName, + @MyRequestBody String taskName, + @MyRequestBody(required = true) MyPageParam pageParam) { + String username = TokenData.takeFromRequest().getLoginName(); + MyPageData pageData = flowApiService.getTaskListByUserName( + username, processDefinitionKey, processDefinitionName, taskName, pageParam); + List flowTaskVoList = flowApiService.convertToFlowTaskList(pageData.getDataList()); + return ResponseResult.success(MyPageUtil.makeResponseData(flowTaskVoList, pageData.getTotalCount())); + } + + /** + * 返回当前用户待办的任务数量。 + * + * @return 返回当前用户待办的任务数量。 + */ + @PostMapping("/countRuntimeTask") + public ResponseResult countRuntimeTask() { + String username = TokenData.takeFromRequest().getLoginName(); + long totalCount = flowApiService.getTaskCountByUserName(username); + return ResponseResult.success(totalCount); + } + + /** + * 获取指定任务的可回退用户任务列表。 + * NOTE: 白名单接口。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 待办任务Id。 + * @return 指定任务的可回退用户任务列表。 + */ + @GetMapping("/listRejectCandidateUserTask") + public ResponseResult> listRejectCandidateUserTask( + @RequestParam String processInstanceId, @RequestParam String taskId) { + String errorMessage; + Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + if (task == null) { + errorMessage = "数据验证失败,指定的任务Id不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + List userTaskList = flowApiService.getRejectCandidateUserTaskList(task); + List resultList = new LinkedList<>(); + if (CollUtil.isNotEmpty(userTaskList)) { + for (UserTask userTask : userTaskList) { + FlowTaskVo flowTaskVo = new FlowTaskVo(); + flowTaskVo.setTaskKey(userTask.getId()); + flowTaskVo.setShowName(userTask.getName()); + resultList.add(flowTaskVo); + } + } + return ResponseResult.success(resultList); + } + + /** + * 自由跳转接口。 + * + * @param processInstanceId 流程实例Id。 + * @param sourceTaskId 当前待办任务Id。 + * @param targetTaskKey 跳转目标任务的定义标识。 + * @param taskComment 跳转注释说明。 + * @param delegateAssignee 指派人(多人之间逗号分割)。 + * @return 跳转应答结果。 + */ + @PostMapping("/freeJumpTo") + public ResponseResult freeJumpTo( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) String sourceTaskId, + @MyRequestBody(required = true) String targetTaskKey, + @MyRequestBody(required = true) String taskComment, + @MyRequestBody String delegateAssignee) { + ResponseResult taskResult = + flowOperationHelper.verifySubmitAndGetTask(processInstanceId, sourceTaskId, null); + if (!taskResult.isSuccess()) { + return ResponseResult.errorFrom(taskResult); + } + CallResult result = flowApiService.freeJumpTo(taskResult.getData(), targetTaskKey, taskComment, delegateAssignee); + return ResponseResult.from(result); + } + + /** + * 主动驳回当前的待办任务到开始节点,只用当前待办任务的指派人或者候选者才能完成该操作。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 待办任务Id。 + * @param taskComment 驳回备注。 + * @param taskVariableData 流程任务变量数据。 + * @return 操作应答结果。 + */ + @PostMapping("/rejectToStartUserTask") + public ResponseResult rejectToStartUserTask( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) String taskId, + @MyRequestBody(required = true) String taskComment, + @MyRequestBody JSONObject taskVariableData) { + ResponseResult taskResult = + flowOperationHelper.verifySubmitAndGetTask(processInstanceId, taskId, null); + if (!taskResult.isSuccess()) { + return ResponseResult.errorFrom(taskResult); + } + FlowTaskComment firstTaskComment = flowTaskCommentService.getFirstFlowTaskComment(processInstanceId); + CallResult result = flowApiService.backToRuntimeTask( + taskResult.getData(), firstTaskComment.getTaskKey(), FlowBackType.REJECT, taskComment, taskVariableData); + return ResponseResult.from(result); + } + + /** + * 主动驳回当前的待办任务,只用当前待办任务的指派人或者候选者才能完成该操作。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 待办任务Id。 + * @param taskComment 驳回备注。 + * @param targetTaskKey 驳回到的目标任务标识。 + * @param taskVariableData 流程任务变量数据。 + * @return 操作应答结果。 + */ + @PostMapping("/rejectRuntimeTask") + public ResponseResult rejectRuntimeTask( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) String taskId, + @MyRequestBody(required = true) String taskComment, + @MyRequestBody String targetTaskKey, + @MyRequestBody JSONObject taskVariableData) { + ResponseResult taskResult = + flowOperationHelper.verifySubmitAndGetTask(processInstanceId, taskId, null); + if (!taskResult.isSuccess()) { + return ResponseResult.errorFrom(taskResult); + } + CallResult result = flowApiService.backToRuntimeTask( + taskResult.getData(), targetTaskKey, FlowBackType.REJECT, taskComment, taskVariableData); + return ResponseResult.from(result); + } + + /** + * 撤回当前用户提交的,但是尚未被审批的待办任务。只有已办任务的指派人才能完成该操作。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 待撤回的已办任务Id。 + * @param taskComment 撤回备注。 + * @param taskVariableData 流程任务变量数据。 + * @return 操作应答结果。 + */ + @PostMapping("/revokeHistoricTask") + public ResponseResult revokeHistoricTask( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) String taskId, + @MyRequestBody(required = true) String taskComment, + @MyRequestBody JSONObject taskVariableData) { + String errorMessage; + if (!flowApiService.existActiveProcessInstance(processInstanceId)) { + errorMessage = "数据验证失败,当前流程实例已经结束,不能执行撤回!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + HistoricTaskInstance taskInstance = flowApiService.getHistoricTaskInstance(processInstanceId, taskId); + if (taskInstance == null) { + errorMessage = "数据验证失败,当前任务不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!StrUtil.equals(taskInstance.getAssignee(), TokenData.takeFromRequest().getLoginName())) { + errorMessage = "数据验证失败,任务指派人与当前用户不匹配!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowTaskComment latestComment = flowTaskCommentService.getLatestFlowTaskComment(processInstanceId); + if (latestComment == null) { + errorMessage = "数据验证失败,当前实例没有任何审批提交记录!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!latestComment.getTaskId().equals(taskId)) { + errorMessage = "数据验证失败,当前审批任务已被办理,不能撤回!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + List activeTaskList = flowApiService.getProcessInstanceActiveTaskList(processInstanceId); + if (CollUtil.isEmpty(activeTaskList)) { + errorMessage = "数据验证失败,当前流程没有任何待办任务!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (latestComment.getApprovalType().equals(FlowApprovalType.TRANSFER)) { + if (activeTaskList.size() > 1) { + errorMessage = "数据验证失败,转办任务数量不能多于1个!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + // 如果是转办任务,无需节点跳转,将指派人改为当前用户即可。 + Task task = activeTaskList.get(0); + task.setAssignee(TokenData.takeFromRequest().getLoginName()); + return ResponseResult.success(); + } + List activitiIds = activeTaskList.stream() + .map(Task::getTaskDefinitionKey).collect(Collectors.toList()); + List childActivitiIds = flowApiService.getChildActivitiIdList( + taskInstance.getProcessDefinitionId(), taskInstance.getTaskDefinitionKey(), activitiIds); + if (CollUtil.isEmpty(childActivitiIds)) { + errorMessage = "数据验证失败,当前任务的待撤销任务并不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + Task revokedTask = activeTaskList.stream() + .filter(t -> childActivitiIds.contains(t.getTaskDefinitionKey())).findFirst().orElse(null); + CallResult result = flowApiService.backToRuntimeTask( + revokedTask, null, FlowBackType.REVOKE, taskComment, taskVariableData); + return ResponseResult.from(result); + } + + /** + * 获取当前流程任务的审批列表。 + * + * @param processInstanceId 当前运行时的流程实例Id。 + * @return 当前流程实例的详情数据。 + */ + @GetMapping("/listFlowTaskComment") + public ResponseResult> listFlowTaskComment(@RequestParam String processInstanceId) { + List flowTaskCommentList = + flowTaskCommentService.getFlowTaskCommentList(processInstanceId); + List resultList = FlowTaskComment.INSTANCE.fromModelList(flowTaskCommentList); + return ResponseResult.success(resultList); + } + + /** + * 获取指定流程定义的流程图。 + * + * @param processDefinitionId 流程定义Id。 + * @return 流程图。 + */ + @GetMapping("/viewProcessBpmn") + public ResponseResult viewProcessBpmn(@RequestParam String processDefinitionId) throws IOException { + BpmnXMLConverter converter = new BpmnXMLConverter(); + BpmnModel bpmnModel = flowApiService.getBpmnModelByDefinitionId(processDefinitionId); + byte[] xmlBytes = converter.convertToXML(bpmnModel); + InputStream in = new ByteArrayInputStream(xmlBytes); + return ResponseResult.success(StreamUtils.copyToString(in, StandardCharsets.UTF_8)); + } + + /** + * 获取流程图高亮数据。 + * + * @param processInstanceId 流程实例Id。 + * @return 流程图高亮数据。 + */ + @GetMapping("/viewHighlightFlowData") + public ResponseResult viewHighlightFlowData(@RequestParam String processInstanceId) { + List activityInstanceList = + flowApiService.getHistoricActivityInstanceList(processInstanceId); + Set finishedTaskSet = activityInstanceList.stream() + .filter(s -> !StrUtil.equals(s.getActivityType(), "sequenceFlow")) + .map(HistoricActivityInstance::getActivityId).collect(Collectors.toSet()); + Set finishedSequenceFlowSet = activityInstanceList.stream() + .filter(s -> StrUtil.equals(s.getActivityType(), "sequenceFlow")) + .map(HistoricActivityInstance::getActivityId).collect(Collectors.toSet()); + //获取流程实例当前正在待办的节点 + List unfinishedInstanceList = + flowApiService.getHistoricUnfinishedInstanceList(processInstanceId); + Set unfinishedTaskSet = new LinkedHashSet<>(); + for (HistoricActivityInstance unfinishedActivity : unfinishedInstanceList) { + unfinishedTaskSet.add(unfinishedActivity.getActivityId()); + } + JSONObject jsonData = new JSONObject(); + jsonData.put("finishedTaskSet", finishedTaskSet); + jsonData.put("finishedSequenceFlowSet", finishedSequenceFlowSet); + jsonData.put("unfinishedTaskSet", unfinishedTaskSet); + return ResponseResult.success(jsonData); + } + + /** + * 获取当前用户的已办理的审批任务列表。 + * + * @param processDefinitionName 流程名。 + * @param beginDate 流程发起开始时间。 + * @param endDate 流程发起结束时间。 + * @param pageParam 分页对象。 + * @return 查询结果应答。 + */ + @DisableDataFilter + @PostMapping("/listHistoricTask") + public ResponseResult>> listHistoricTask( + @MyRequestBody String processDefinitionName, + @MyRequestBody String beginDate, + @MyRequestBody String endDate, + @MyRequestBody(required = true) MyPageParam pageParam) throws ParseException { + MyPageData pageData = + flowApiService.getHistoricTaskInstanceFinishedList(processDefinitionName, beginDate, endDate, pageParam); + List> resultList = new LinkedList<>(); + pageData.getDataList().forEach(instance -> resultList.add(BeanUtil.beanToMap(instance))); + List taskInstanceList = pageData.getDataList(); + if (CollUtil.isNotEmpty(taskInstanceList)) { + Set instanceIdSet = taskInstanceList.stream() + .map(HistoricTaskInstance::getProcessInstanceId).collect(Collectors.toSet()); + List instanceList = flowApiService.getHistoricProcessInstanceList(instanceIdSet); + Set loginNameSet = instanceList.stream() + .map(HistoricProcessInstance::getStartUserId).collect(Collectors.toSet()); + List userInfoList = flowCustomExtFactory + .getFlowIdentityExtHelper().getUserInfoListByUsernameSet(loginNameSet); + Map userInfoMap = + userInfoList.stream().collect(Collectors.toMap(FlowUserInfoVo::getLoginName, c -> c)); + Map instanceMap = + instanceList.stream().collect(Collectors.toMap(HistoricProcessInstance::getId, c -> c)); + List workOrderList = + flowWorkOrderService.getInList(INSTANCE_ID, instanceIdSet); + Map workOrderMap = + workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c)); + resultList.forEach(result -> { + String instanceId = result.get(INSTANCE_ID).toString(); + HistoricProcessInstance instance = instanceMap.get(instanceId); + result.put("processDefinitionKey", instance.getProcessDefinitionKey()); + result.put("processDefinitionName", instance.getProcessDefinitionName()); + result.put("startUser", instance.getStartUserId()); + FlowUserInfoVo userInfo = userInfoMap.get(instance.getStartUserId()); + result.put(SHOW_NAME, userInfo.getShowName()); + result.put("headImageUrl", userInfo.getHeadImageUrl()); + result.put("businessKey", instance.getBusinessKey()); + FlowWorkOrder flowWorkOrder = workOrderMap.get(instanceId); + if (flowWorkOrder != null) { + result.put("workOrderCode", flowWorkOrder.getWorkOrderCode()); + } + }); + Set taskIdSet = + taskInstanceList.stream().map(HistoricTaskInstance::getId).collect(Collectors.toSet()); + List commentList = flowTaskCommentService.getFlowTaskCommentListByTaskIds(taskIdSet); + Map> commentMap = + commentList.stream().collect(Collectors.groupingBy(FlowTaskComment::getTaskId)); + resultList.forEach(result -> { + List comments = commentMap.get(result.get("id").toString()); + if (CollUtil.isNotEmpty(comments)) { + result.put("approvalType", comments.get(0).getApprovalType()); + comments.remove(0); + } + }); + } + return ResponseResult.success(MyPageUtil.makeResponseData(resultList, pageData.getTotalCount())); + } + + /** + * 根据输入参数查询,当前用户的历史流程数据。 + * + * @param processDefinitionName 流程名。 + * @param beginDate 流程发起开始时间。 + * @param endDate 流程发起结束时间。 + * @param pageParam 分页对象。 + * @return 查询结果应答。 + */ + @DisableDataFilter + @PostMapping("/listHistoricProcessInstance") + public ResponseResult>> listHistoricProcessInstance( + @MyRequestBody String processDefinitionName, + @MyRequestBody String beginDate, + @MyRequestBody String endDate, + @MyRequestBody(required = true) MyPageParam pageParam) throws ParseException { + String loginName = TokenData.takeFromRequest().getLoginName(); + MyPageData pageData = flowApiService.getHistoricProcessInstanceList( + null, processDefinitionName, loginName, beginDate, endDate, pageParam, true); + Set loginNameSet = pageData.getDataList().stream() + .map(HistoricProcessInstance::getStartUserId).collect(Collectors.toSet()); + List userInfoList = flowCustomExtFactory + .getFlowIdentityExtHelper().getUserInfoListByUsernameSet(loginNameSet); + if (CollUtil.isEmpty(userInfoList)) { + userInfoList = new LinkedList<>(); + } + Map userInfoMap = + userInfoList.stream().collect(Collectors.toMap(FlowUserInfoVo::getLoginName, c -> c)); + Set instanceIdSet = pageData.getDataList().stream() + .map(HistoricProcessInstance::getId).collect(Collectors.toSet()); + List workOrderList = + flowWorkOrderService.getInList(INSTANCE_ID, instanceIdSet); + Map workOrderMap = + workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c)); + List> resultList = new LinkedList<>(); + pageData.getDataList().forEach(instance -> { + Map data = BeanUtil.beanToMap(instance); + FlowUserInfoVo userInfo = userInfoMap.get(instance.getStartUserId()); + if (userInfo != null) { + data.put(SHOW_NAME, userInfo.getShowName()); + data.put("headImageUrl", userInfo.getHeadImageUrl()); + } + FlowWorkOrder workOrder = workOrderMap.get(instance.getId()); + if (workOrder != null) { + data.put("workOrderCode", workOrder.getWorkOrderCode()); + data.put("flowStatus", workOrder.getFlowStatus()); + } + resultList.add(data); + }); + return ResponseResult.success(MyPageUtil.makeResponseData(resultList, pageData.getTotalCount())); + } + + /** + * 根据输入参数查询,所有历史流程数据。 + * + * @param processDefinitionName 流程名。 + * @param startUser 流程发起用户。 + * @param beginDate 流程发起开始时间。 + * @param endDate 流程发起结束时间。 + * @param pageParam 分页对象。 + * @return 查询结果。 + */ + @PostMapping("/listAllHistoricProcessInstance") + public ResponseResult>> listAllHistoricProcessInstance( + @MyRequestBody String processDefinitionName, + @MyRequestBody String startUser, + @MyRequestBody String beginDate, + @MyRequestBody String endDate, + @MyRequestBody(required = true) MyPageParam pageParam) throws ParseException { + MyPageData pageData = flowApiService.getHistoricProcessInstanceList( + null, processDefinitionName, startUser, beginDate, endDate, pageParam, false); + List> resultList = new LinkedList<>(); + pageData.getDataList().forEach(instance -> resultList.add(BeanUtil.beanToMap(instance))); + List unfinishedProcessInstanceIds = pageData.getDataList().stream() + .filter(c -> c.getEndTime() == null) + .map(HistoricProcessInstance::getId) + .collect(Collectors.toList()); + MyPageData> pageResultData = + MyPageUtil.makeResponseData(resultList, pageData.getTotalCount()); + if (CollUtil.isEmpty(unfinishedProcessInstanceIds)) { + return ResponseResult.success(pageResultData); + } + Set processInstanceIds = pageData.getDataList().stream() + .map(HistoricProcessInstance::getId).collect(Collectors.toSet()); + List transProducerList = + flowTransProducerService.getListByProcessInstanceIds(processInstanceIds); + Map> flowTransProducerMap = null; + if (CollUtil.isNotEmpty(transProducerList)) { + flowTransProducerMap = transProducerList.stream() + .collect(Collectors.groupingBy(FlowTransProducer::getProcessInstanceId)); + } + List taskList = flowApiService.getTaskListByProcessInstanceIds(unfinishedProcessInstanceIds); + Map> taskMap = + taskList.stream().collect(Collectors.groupingBy(Task::getProcessInstanceId)); + for (Map result : resultList) { + String processInstanceId = result.get(INSTANCE_ID).toString(); + List instanceTaskList = taskMap.get(processInstanceId); + if (instanceTaskList != null) { + JSONArray taskArray = new JSONArray(); + for (Task task : instanceTaskList) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("taskId", task.getId()); + jsonObject.put("taskName", task.getName()); + jsonObject.put("taskKey", task.getTaskDefinitionKey()); + jsonObject.put("assignee", task.getAssignee()); + taskArray.add(jsonObject); + } + if (flowTransProducerMap != null) { + List list = flowTransProducerMap.get(processInstanceId); + if (CollUtil.isNotEmpty(list)) { + result.put("flowTransList", list); + } + } + result.put("runtimeTaskInfoList", taskArray); + } + } + return ResponseResult.success(pageResultData); + } + + /** + * 催办工单,只有流程发起人才可以催办工单。 + * 催办场景必须要取消数据权限过滤,因为流程的指派很可能是跨越部门的。 + * 既然被指派和催办了,这里就应该禁用工单表的数据权限过滤约束。 + * 如果您的系统没有支持数据权限过滤,DisableDataFilter不会有任何影响,建议保留。 + * + * @param workOrderId 工单Id。 + * @return 应答结果。 + */ + @DisableDataFilter + @OperationLog(type = SysOperationLogType.REMIND_TASK) + @PostMapping("/remindRuntimeTask") + public ResponseResult remindRuntimeTask(@MyRequestBody(required = true) Long workOrderId) { + FlowWorkOrder flowWorkOrder = flowWorkOrderService.getById(workOrderId); + if (flowWorkOrder == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + String errorMessage; + if (!flowWorkOrder.getCreateUserId().equals(TokenData.takeFromRequest().getUserId())) { + errorMessage = "数据验证失败,只有流程发起人才能催办工单!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.FINISHED) + || flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.CANCELLED) + || flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.STOPPED)) { + errorMessage = "数据验证失败,已经结束的流程,不能催办工单!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT)) { + errorMessage = "数据验证失败,流程草稿不能催办工单!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + flowMessageService.saveNewRemindMessage(flowWorkOrder); + return ResponseResult.success(); + } + + /** + * 取消工作流工单,仅当没有进入任何审批流程之前,才可以取消工单。 + * + * @param workOrderId 工单Id。 + * @param cancelReason 取消原因。 + * @return 应答结果。 + */ + @OperationLog(type = SysOperationLogType.CANCEL_FLOW) + @DisableDataFilter + @PostMapping("/cancelWorkOrder") + public ResponseResult cancelWorkOrder( + @MyRequestBody(required = true) Long workOrderId, + @MyRequestBody(required = true) String cancelReason) { + FlowWorkOrder flowWorkOrder = flowWorkOrderService.getById(workOrderId); + if (flowWorkOrder == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + String errorMessage; + if (!flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.SUBMITTED) + && !flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT)) { + errorMessage = "数据验证失败,当前流程已经进入审批状态,不能撤销工单!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowWorkOrder.getCreateUserId().equals(TokenData.takeFromRequest().getUserId())) { + errorMessage = "数据验证失败,当前用户不是工单所有者,不能撤销工单!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + CallResult result; + // 草稿工单直接删除当前工单。 + if (flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT)) { + result = flowWorkOrderService.removeDraft(flowWorkOrder); + } else { + result = flowApiService.stopProcessInstance( + flowWorkOrder.getProcessInstanceId(), cancelReason, true); + } + if (!result.isSuccess()) { + return ResponseResult.errorFrom(result); + } + return ResponseResult.success(); + } + + /** + * 主动干预当前的待办任务,任何有该接口操作权限的用户均可执行该干预操作。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 待办任务Id。 + * @param taskComment 干预备注。 + * @param targetTaskKey 驳回到的目标任务标识。 + * @param delegateAssignee 指派人(多人之间逗号分割)。 + * @return 操作应答结果。 + */ + @OperationLog(type = SysOperationLogType.INTERVENE_FLOW) + @DisableDataFilter + @PostMapping("/interveneRuntimeTask") + public ResponseResult interveneRuntimeTask( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) String taskId, + @MyRequestBody(required = true) String taskComment, + @MyRequestBody String targetTaskKey, + @MyRequestBody String delegateAssignee) { + String errorMessage; + Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + if (task == null) { + errorMessage = "数据验证失败,该流程实例的待办任务Id不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (StrUtil.isAllBlank(targetTaskKey, delegateAssignee)) { + errorMessage = "数据验证失败,指派人和跳转任务不能同时为空!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + // 如果驳回到的任务是空,就可以直接走转办逻辑。 + if (StrUtil.isBlank(targetTaskKey)) { + FlowTaskComment flowTaskComment = new FlowTaskComment(); + flowTaskComment.setDelegateAssignee(delegateAssignee); + flowTaskComment.setApprovalType(FlowApprovalType.INTERVENE); + flowApiService.transferTo(task, flowTaskComment); + return ResponseResult.success(); + } + CallResult result = flowApiService.interveneTo(task, targetTaskKey, taskComment, delegateAssignee); + return ResponseResult.from(result); + } + + /** + * 终止流程实例,将任务从当前节点直接流转到主流程的结束事件。 + * + * @param processInstanceId 流程实例Id。 + * @param stopReason 停止原因。 + * @return 执行结果应答。 + */ + @OperationLog(type = SysOperationLogType.STOP_FLOW) + @DisableDataFilter + @PostMapping("/stopProcessInstance") + public ResponseResult stopProcessInstance( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) String stopReason) { + CallResult result = flowApiService.stopProcessInstance(processInstanceId, stopReason, false); + if (!result.isSuccess()) { + return ResponseResult.errorFrom(result); + } + return ResponseResult.success(); + } + + /** + * 删除流程实例。 + * + * @param processInstanceId 流程实例Id。 + * @return 执行结果应答。 + */ + @OperationLog(type = SysOperationLogType.DELETE_FLOW) + @PostMapping("/deleteProcessInstance") + public ResponseResult deleteProcessInstance(@MyRequestBody(required = true) String processInstanceId) { + flowApiService.deleteProcessInstance(processInstanceId); + return ResponseResult.success(); + } + + /** + * 获取指定流程定义Id的所有用户任务数据列表。 + * + * @param processDefinitionId 流程定义Id。 + * @return 查询结果。 + */ + @GetMapping("/listAllUserTask") + public ResponseResult> listAllUserTask(@RequestParam String processDefinitionId) { + Map taskMap = flowApiService.getAllUserTaskMap(processDefinitionId); + List resultList = new LinkedList<>(); + for (UserTask t : taskMap.values()) { + JSONObject data = new JSONObject(); + data.put("id", t.getId()); + data.put("name", t.getName()); + resultList.add(data); + } + return ResponseResult.success(resultList); + } + + private List buildApprovedFlowTaskCommentList(TaskInfo taskInfo, boolean isMultiInstanceTask) { + List taskCommentList; + if (isMultiInstanceTask) { + String multiInstanceExecId; + FlowMultiInstanceTrans trans = + flowMultiInstanceTransService.getByExecutionId(taskInfo.getExecutionId(), taskInfo.getId()); + if (trans != null) { + multiInstanceExecId = trans.getMultiInstanceExecId(); + } else { + multiInstanceExecId = flowApiService.getExecutionVariableStringWithSafe( + taskInfo.getExecutionId(), FlowConstant.MULTI_SIGN_TASK_EXECUTION_ID_VAR); + } + taskCommentList = flowTaskCommentService.getFlowTaskCommentListByMultiInstanceExecId(multiInstanceExecId); + } else { + taskCommentList = flowTaskCommentService.getFlowTaskCommentListByExecutionId( + taskInfo.getProcessInstanceId(), taskInfo.getId(), taskInfo.getExecutionId()); + } + return taskCommentList; + } + + private ResponseResult doVerifyMultiSign(String processInstanceId, String taskId) { + String errorMessage; + if (!flowApiService.existActiveProcessInstance(processInstanceId)) { + errorMessage = "数据验证失败,当前流程实例已经结束,不能执行加签!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + HistoricTaskInstance taskInstance = flowApiService.getHistoricTaskInstance(processInstanceId, taskId); + if (taskInstance == null) { + errorMessage = "数据验证失败,当前任务不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + String loginName = TokenData.takeFromRequest().getLoginName(); + if (!StrUtil.equals(taskInstance.getAssignee(), loginName)) { + errorMessage = "数据验证失败,任务指派人与当前用户不匹配!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + List activeTaskList = flowApiService.getProcessInstanceActiveTaskList(processInstanceId); + Task activeMultiInstanceTask = null; + Map userTaskMap = flowApiService.getAllUserTaskMap(taskInstance.getProcessDefinitionId()); + for (Task activeTask : activeTaskList) { + UserTask userTask = userTaskMap.get(activeTask.getTaskDefinitionKey()); + if (!userTask.hasMultiInstanceLoopCharacteristics()) { + errorMessage = "数据验证失败,指定加签任务不存在或已审批完毕!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + String startTaskId = flowApiService.getTaskVariableStringWithSafe( + activeTask.getId(), FlowConstant.MULTI_SIGN_START_TASK_VAR); + if (StrUtil.equals(startTaskId, taskId)) { + activeMultiInstanceTask = activeTask; + break; + } + } + if (activeMultiInstanceTask == null) { + errorMessage = "数据验证失败,指定加签任务不存在或已审批完毕!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + JSONObject resultData = new JSONObject(); + resultData.put("taskInstance", taskInstance); + resultData.put(ACTIVE_MULTI_INST_TASK, activeMultiInstanceTask); + return ResponseResult.success(resultData); + } + + private String findExistAssignee(Set assigneeSet, JSONArray assigneeArray) { + for (int i = 0; i < assigneeArray.size(); i++) { + String loginName = assigneeArray.getString(i); + if (assigneeSet.contains(loginName)) { + return loginName; + } + } + return null; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowCategoryMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowCategoryMapper.java new file mode 100644 index 0000000..2472ecc --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowCategoryMapper.java @@ -0,0 +1,26 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowCategory; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * FlowCategory数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowCategoryMapper extends BaseDaoMapper { + + /** + * 获取过滤后的对象列表。 + * + * @param flowCategoryFilter 主表过滤对象。 + * @param orderBy 排序字符串,order by从句的参数。 + * @return 对象列表。 + */ + List getFlowCategoryList( + @Param("flowCategoryFilter") FlowCategory flowCategoryFilter, @Param("orderBy") String orderBy); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryMapper.java new file mode 100644 index 0000000..ee1d1cd --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryMapper.java @@ -0,0 +1,26 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowEntry; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * FlowEntry数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowEntryMapper extends BaseDaoMapper { + + /** + * 获取过滤后的对象列表。 + * + * @param flowEntryFilter 主表过滤对象。 + * @param orderBy 排序字符串,order by从句的参数。 + * @return 对象列表。 + */ + List getFlowEntryList( + @Param("flowEntryFilter") FlowEntry flowEntryFilter, @Param("orderBy") String orderBy); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryPublishMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryPublishMapper.java new file mode 100644 index 0000000..41989aa --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryPublishMapper.java @@ -0,0 +1,13 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowEntryPublish; + +/** + * 数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowEntryPublishMapper extends BaseDaoMapper { +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryPublishVariableMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryPublishVariableMapper.java new file mode 100644 index 0000000..7c69000 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryPublishVariableMapper.java @@ -0,0 +1,22 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowEntryPublishVariable; + +import java.util.List; + +/** + * 数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowEntryPublishVariableMapper extends BaseDaoMapper { + + /** + * 批量插入流程发布的变量列表。 + * + * @param entryPublishVariableList 流程发布的变量列表。 + */ + void insertList(List entryPublishVariableList); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryVariableMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryVariableMapper.java new file mode 100644 index 0000000..832dd97 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowEntryVariableMapper.java @@ -0,0 +1,27 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowEntryVariable; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 流程变量数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowEntryVariableMapper extends BaseDaoMapper { + + /** + * 获取过滤后的对象列表。 + * + * @param flowEntryVariableFilter 主表过滤对象。 + * @param orderBy 排序字符串,order by从句的参数。 + * @return 对象列表。 + */ + List getFlowEntryVariableList( + @Param("flowEntryVariableFilter") FlowEntryVariable flowEntryVariableFilter, + @Param("orderBy") String orderBy); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageCandidateIdentityMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageCandidateIdentityMapper.java new file mode 100644 index 0000000..adaba4a --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageCandidateIdentityMapper.java @@ -0,0 +1,21 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowMessageCandidateIdentity; +import org.apache.ibatis.annotations.Param; + +/** + * 流程任务消息的候选身份数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowMessageCandidateIdentityMapper extends BaseDaoMapper { + + /** + * 删除指定流程实例的消息关联数据。 + * + * @param processInstanceId 流程实例Id。 + */ + void deleteByProcessInstanceId(@Param("processInstanceId") String processInstanceId); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageIdentityOperationMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageIdentityOperationMapper.java new file mode 100644 index 0000000..f87125b --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageIdentityOperationMapper.java @@ -0,0 +1,21 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowMessageIdentityOperation; +import org.apache.ibatis.annotations.Param; + +/** + * 流程任务消息所属用户的操作数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowMessageIdentityOperationMapper extends BaseDaoMapper { + + /** + * 删除指定流程实例的消息关联数据。 + * + * @param processInstanceId 流程实例Id。 + */ + void deleteByProcessInstanceId(@Param("processInstanceId") String processInstanceId); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageMapper.java new file mode 100644 index 0000000..43e34d9 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMessageMapper.java @@ -0,0 +1,86 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowMessage; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Set; + +/** + * 工作流消息数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowMessageMapper extends BaseDaoMapper { + + /** + * 获取指定用户和身份分组Id集合的催办消息列表。 + * + * @param tenantId 租户Id。 + * @param appCode 应用编码。 + * @param loginName 用户的登录名。与流程任务的assignee精确匹配。 + * @param groupIdSet 用户身份分组Id集合。 + * @return 查询后的催办消息列表。 + */ + List getRemindingMessageListByUser( + @Param("tenantId") Long tenantId, + @Param("appCode") String appCode, + @Param("loginName") String loginName, + @Param("groupIdSet") Set groupIdSet); + + /** + * 获取指定用户和身份分组Id集合的抄送消息列表。 + * + * @param tenantId 租户Id。 + * @param appCode 应用编码。 + * @param loginName 用户登录名。 + * @param groupIdSet 用户身份分组Id集合。 + * @param read true表示已读,false表示未读。 + * @return 查询后的抄送消息列表。 + */ + List getCopyMessageListByUser( + @Param("tenantId") Long tenantId, + @Param("appCode") String appCode, + @Param("loginName") String loginName, + @Param("groupIdSet") Set groupIdSet, + @Param("read") Boolean read); + + /** + * 计算当前用户催办消息的数量。 + * + * @param tenantId 租户Id。 + * @param appCode 应用编码。 + * @param loginName 用户登录名。 + * @param groupIdSet 用户身份分组Id集合。 + * @return 数据数量。 + */ + int countRemindingMessageListByUser( + @Param("tenantId") Long tenantId, + @Param("appCode") String appCode, + @Param("loginName") String loginName, + @Param("groupIdSet") Set groupIdSet); + + /** + * 计算当前用户未读抄送消息的数量。 + * + * @param tenantId 租户Id。 + * @param appCode 应用编码。 + * @param loginName 用户登录名。 + * @param groupIdSet 用户身份分组Id集合。 + * @return 数据数量 + */ + int countCopyMessageListByUser( + @Param("tenantId") Long tenantId, + @Param("appCode") String appCode, + @Param("loginName") String loginName, + @Param("groupIdSet") Set groupIdSet); + /** + * 计算当前用户未读抄送消息的数量。 + * + * @param userId 用户Id。 + * @return 数据数量 + */ + int countSysMessageListByUser(Long userId); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMultiInstanceTransMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMultiInstanceTransMapper.java new file mode 100644 index 0000000..49388e9 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowMultiInstanceTransMapper.java @@ -0,0 +1,13 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowMultiInstanceTrans; + +/** + * 流程多实例任务执行流水访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowMultiInstanceTransMapper extends BaseDaoMapper { +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowNodeTasksMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowNodeTasksMapper.java new file mode 100644 index 0000000..aeb2c73 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowNodeTasksMapper.java @@ -0,0 +1,9 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowNodeTasks; + + +public interface FlowNodeTasksMapper extends BaseDaoMapper { + +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskCommentMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskCommentMapper.java new file mode 100644 index 0000000..046a500 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskCommentMapper.java @@ -0,0 +1,13 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowTaskComment; + +/** + * 流程任务批注数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowTaskCommentMapper extends BaseDaoMapper { +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskExtMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskExtMapper.java new file mode 100644 index 0000000..51554de --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskExtMapper.java @@ -0,0 +1,22 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowTaskExt; + +import java.util.List; + +/** + * 流程任务扩展数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowTaskExtMapper extends BaseDaoMapper { + + /** + * 批量插入流程任务扩展信息列表。 + * + * @param flowTaskExtList 流程任务扩展信息列表。 + */ + void insertList(List flowTaskExtList); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskTimeoutJobMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskTimeoutJobMapper.java new file mode 100644 index 0000000..98d8610 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTaskTimeoutJobMapper.java @@ -0,0 +1,14 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowTaskTimeoutJob; + +/** + * 超时任务作业操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowTaskTimeoutJobMapper extends BaseDaoMapper { + +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTransProducerMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTransProducerMapper.java new file mode 100644 index 0000000..6ae5f0d --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowTransProducerMapper.java @@ -0,0 +1,13 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowTransProducer; + +/** + * 事务性业务数据生产者访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowTransProducerMapper extends BaseDaoMapper { +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowVariableLogMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowVariableLogMapper.java new file mode 100644 index 0000000..9be917d --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowVariableLogMapper.java @@ -0,0 +1,14 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowVariableLog; + +/** + * 流程请求变量操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowVariableLogMapper extends BaseDaoMapper { + +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowWorkOrderExtMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowWorkOrderExtMapper.java new file mode 100644 index 0000000..5967e81 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowWorkOrderExtMapper.java @@ -0,0 +1,14 @@ +package apelet.common.flow.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowWorkOrderExt; + +/** + * 工作流工单扩展数据操作访问接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowWorkOrderExtMapper extends BaseDaoMapper { + +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/FlowWorkOrderMapper.java b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowWorkOrderMapper.java new file mode 100644 index 0000000..0beaa8f --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/FlowWorkOrderMapper.java @@ -0,0 +1,49 @@ +package apelet.common.flow.dao; + +import apelet.common.core.annotation.EnableDataPerm; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.flow.model.FlowWorkOrder; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 工作流工单表数据操作访问接口。 + * 如果当前系统支持数据权限过滤,当前用户必须要能看自己的工单数据,所以需要把EnableDataPerm + * 的mustIncludeUserRule参数设置为true,即便当前用户的数据权限中并不包含DataPermRuleType.TYPE_USER_ONLY, + * 数据过滤拦截组件也会自动补偿该类型的数据权限,以便当前用户可以看到自己发起的工单。 + * + * @author guifc + * @date 2023-08-04 + */ +@EnableDataPerm(mustIncludeUserRule = true) +public interface FlowWorkOrderMapper extends BaseDaoMapper { + + /** + * 获取过滤后的对象列表。 + * + * @param flowWorkOrderFilter 主表过滤对象。 + * @param orderBy 排序字符串,order by从句的参数。 + * @return 对象列表。 + */ + List getFlowWorkOrderList( + @Param("flowWorkOrderFilter") FlowWorkOrder flowWorkOrderFilter, @Param("orderBy") String orderBy); + + /** + * 计算指定前缀工单编码的最大值。 + * + * @param prefix 工单编码前缀。 + * @return 该工单编码前缀的最大值。 + */ + @Select("SELECT MAX(work_order_code) FROM zz_flow_work_order WHERE work_order_code LIKE '${prefix}'") + String getMaxWorkOrderCodeByPrefix(@Param("prefix") String prefix); + + /** + * 根据工单编码查询指定工单,查询过程也会考虑逻辑删除的数据。 + * @param workOrderCode 工单编码。 + * @return + */ + @Select("SELECT COUNT(*) FROM zz_flow_work_order WHERE work_order_code = #{workOrderCode}") + int getCountByWorkOrderCode(@Param("workOrderCode") String workOrderCode); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowCategoryMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowCategoryMapper.xml new file mode 100644 index 0000000..d9e4d11 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowCategoryMapper.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + AND zz_flow_category.tenant_id IS NULL + + + AND zz_flow_category.tenant_id = #{flowCategoryFilter.tenantId} + + + AND zz_flow_category.app_code IS NULL + + + AND zz_flow_category.app_code = #{flowCategoryFilter.appCode} + + + AND zz_flow_category.name = #{flowCategoryFilter.name} + + + AND zz_flow_category.code = #{flowCategoryFilter.code} + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryMapper.xml new file mode 100644 index 0000000..4f4f9a2 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryMapper.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AND zz_flow_entry.tenant_id IS NULL + + + AND zz_flow_entry.tenant_id = #{flowEntryFilter.tenantId} + + + AND zz_flow_entry.app_code IS NULL + + + AND zz_flow_entry.app_code = #{flowEntryFilter.appCode} + + + AND zz_flow_entry.process_definition_name = #{flowEntryFilter.processDefinitionName} + + + AND zz_flow_entry.process_definition_key = #{flowEntryFilter.processDefinitionKey} + + + AND zz_flow_entry.category_id = #{flowEntryFilter.categoryId} + + + AND zz_flow_entry.status = #{flowEntryFilter.status} + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryPublishMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryPublishMapper.xml new file mode 100644 index 0000000..4648522 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryPublishMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryPublishVariableMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryPublishVariableMapper.xml new file mode 100644 index 0000000..d048f5f --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryPublishVariableMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + INSERT INTO zz_flow_entry_publish_variable VALUES + + (#{item.variableId}, + #{item.entryPublishId}, + #{item.variableName}, + #{item.showName}, + #{item.variableType}, + #{item.bindDatasourceId}, + #{item.bindRelationId}, + #{item.bindColumnId}, + #{item.builtin}) + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryVariableMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryVariableMapper.xml new file mode 100644 index 0000000..d3f2f43 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowEntryVariableMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + AND zz_flow_entry_variable.entry_id = #{flowEntryVariableFilter.entryId} + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageCandidateIdentityMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageCandidateIdentityMapper.xml new file mode 100644 index 0000000..47c0493 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageCandidateIdentityMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + DELETE FROM zz_flow_msg_candidate_identity a + WHERE EXISTS (SELECT * FROM zz_flow_message b + WHERE a.message_id = b.message_id AND b.process_instance_id = #{processInstanceId}) + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageIdentityOperationMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageIdentityOperationMapper.xml new file mode 100644 index 0000000..241a7a4 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageIdentityOperationMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + DELETE FROM zz_flow_msg_identity_operation a + WHERE EXISTS (SELECT * FROM zz_flow_message b + WHERE a.message_id = b.message_id AND b.process_instance_id = #{processInstanceId}) + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageMapper.xml new file mode 100644 index 0000000..6d3e4c9 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMessageMapper.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AND a.tenant_id IS NULL + + + AND a.tenant_id = #{tenantId} + + + AND a.app_code IS NULL + + + AND a.app_code = #{appCode} + + + + + + + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMultiInstanceTransMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMultiInstanceTransMapper.xml new file mode 100644 index 0000000..083679c --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowMultiInstanceTransMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskCommentMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskCommentMapper.xml new file mode 100644 index 0000000..6e6a62f --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskCommentMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml new file mode 100644 index 0000000..8ab8c5d --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + INSERT INTO zz_flow_task_ext VALUES + + (#{item.processDefinitionId}, + #{item.taskId}, + #{item.operationListJson}, + #{item.variableListJson}, + #{item.assigneeListJson}, + #{item.groupType}, + #{item.deptPostListJson}, + #{item.roleIds}, + #{item.deptIds}, + #{item.candidateUsernames}, + #{item.copyListJson}, + #{item.extraDataJson}) + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskTimeoutJobMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskTimeoutJobMapper.xml new file mode 100644 index 0000000..69a16c4 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskTimeoutJobMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTransProducerMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTransProducerMapper.xml new file mode 100644 index 0000000..c081808 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTransProducerMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowVariableLogMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowVariableLogMapper.xml new file mode 100644 index 0000000..8f2b0f7 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowVariableLogMapper.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowWorkOrderExtMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowWorkOrderExtMapper.xml new file mode 100644 index 0000000..34cb900 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowWorkOrderExtMapper.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowWorkOrderMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowWorkOrderMapper.xml new file mode 100644 index 0000000..9c627f0 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowWorkOrderMapper.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AND zz_flow_work_order.deleted_flag = ${@apelet.common.core.constant.GlobalDeletedFlag@NORMAL} + + + + + + + AND zz_flow_work_order.tenant_id IS NULL + + + AND zz_flow_work_order.tenant_id = #{flowWorkOrderFilter.tenantId} + + + AND zz_flow_work_order.app_code IS NULL + + + AND zz_flow_work_order.app_code = #{flowWorkOrderFilter.appCode} + + + AND zz_flow_work_order.work_order_code = #{flowWorkOrderFilter.workOrderCode} + + + AND zz_flow_work_order.process_definition_key = #{flowWorkOrderFilter.processDefinitionKey} + + + AND zz_flow_work_order.latest_approval_status = #{flowWorkOrderFilter.latestApprovalStatus} + + + AND zz_flow_work_order.flow_status = #{flowWorkOrderFilter.flowStatus} + + + AND zz_flow_work_order.create_time >= #{flowWorkOrderFilter.createTimeStart} + + + AND zz_flow_work_order.create_time <= #{flowWorkOrderFilter.createTimeEnd} + + + AND zz_flow_work_order.create_user_id = #{flowWorkOrderFilter.createUserId} + + + + + + diff --git a/common/common-flow/src/main/java/apelet/common/flow/dto/FlowCategoryDto.java b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowCategoryDto.java new file mode 100644 index 0000000..078f527 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowCategoryDto.java @@ -0,0 +1,47 @@ +package apelet.common.flow.dto; + +import apelet.common.core.validator.UpdateGroup; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 流程分类的Dto对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程分类的Dto对象") +@Data +public class FlowCategoryDto { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + @NotNull(message = "数据验证失败,主键Id不能为空!", groups = {UpdateGroup.class}) + private Long categoryId; + + /** + * 显示名称。 + */ + @Schema(description = "显示名称") + @NotBlank(message = "数据验证失败,显示名称不能为空!") + private String name; + + /** + * 分类编码。 + */ + @Schema(description = "分类编码") + @NotBlank(message = "数据验证失败,分类编码不能为空!") + private String code; + + /** + * 实现顺序。 + */ + @Schema(description = "实现顺序") + @NotNull(message = "数据验证失败,实现顺序不能为空!") + private Integer showOrder; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dto/FlowEntryDto.java b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowEntryDto.java new file mode 100644 index 0000000..7c5a8b8 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowEntryDto.java @@ -0,0 +1,114 @@ +package apelet.common.flow.dto; + +import apelet.common.core.validator.ConstDictRef; +import apelet.common.core.validator.UpdateGroup; +import apelet.common.flow.model.constant.FlowBindFormType; +import apelet.common.flow.model.constant.FlowEntryStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 流程的Dto对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程的Dto对象") +@Data +public class FlowEntryDto { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + @NotNull(message = "数据验证失败,主键不能为空!", groups = {UpdateGroup.class}) + private Long entryId; + + /** + * 流程名称。 + */ + @Schema(description = "流程名称") + @NotBlank(message = "数据验证失败,流程名称不能为空!") + private String processDefinitionName; + + /** + * 流程标识Key。 + */ + @Schema(description = "流程标识Key") + @NotBlank(message = "数据验证失败,流程标识Key不能为空!") + private String processDefinitionKey; + + /** + * 流程分类。 + */ + @Schema(description = "流程分类") + @NotNull(message = "数据验证失败,流程分类不能为空!") + private Long categoryId; + + /** + * 流程状态。 + */ + @Schema(description = "流程状态") + @ConstDictRef(constDictClass = FlowEntryStatus.class, message = "数据验证失败,工作流状态为无效值!") + private Integer status; + + /** + * 流程定义的xml。 + */ + @Schema(description = "流程定义的xml") + private String bpmnXml; + + /** + * 流程图类型。0: 普通流程图,1: 钉钉风格的流程图。 + */ + @Schema(description = "流程图类型。0: 普通流程图,1: 钉钉风格的流程图。") + private Integer diagramType; + + /** + * 绑定表单类型。 + */ + @Schema(description = "绑定表单类型") + @ConstDictRef(constDictClass = FlowBindFormType.class, message = "数据验证失败,工作流绑定表单类型为无效值!") + @NotNull(message = "数据验证失败,工作流绑定表单类型不能为空!") + private Integer bindFormType; + + /** + * 在线表单的页面Id。 + */ + @Schema(description = "在线表单的页面Id") + private Long pageId; + + /** + * 在线表单的缺省路由名称。 + */ + @Schema(description = "在线表单的缺省路由名称") + private String defaultRouterName; + + /** + * 在线表单Id。 + */ + @Schema(description = "在线表单Id") + private Long defaultFormId; + + /** + * 工单表编码字段的编码规则,如果为空则不计算工单编码。 + */ + @Schema(description = "工单表编码字段的编码规则") + private String encodedRule; + + /** + * 流程的自定义扩展数据(JSON格式)。 + */ + @Schema(description = "流程的自定义扩展数据") + private String extensionData; + + + /** + * 流程启动条件 + */ + @Schema(description = "流程启动条件") + private String startConditions; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dto/FlowEntryVariableDto.java b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowEntryVariableDto.java new file mode 100644 index 0000000..751067b --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowEntryVariableDto.java @@ -0,0 +1,82 @@ +package apelet.common.flow.dto; + +import apelet.common.core.validator.ConstDictRef; +import apelet.common.core.validator.UpdateGroup; +import apelet.common.flow.model.constant.FlowVariableType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 流程变量Dto对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程变量Dto对象") +@Data +public class FlowEntryVariableDto { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + @NotNull(message = "数据验证失败,主键Id不能为空!", groups = {UpdateGroup.class}) + private Long variableId; + + /** + * 流程Id。 + */ + @Schema(description = "流程Id") + @NotNull(message = "数据验证失败,流程Id不能为空!") + private Long entryId; + + /** + * 变量名。 + */ + @Schema(description = "变量名") + @NotBlank(message = "数据验证失败,变量名不能为空!") + private String variableName; + + /** + * 显示名。 + */ + @Schema(description = "显示名") + @NotBlank(message = "数据验证失败,显示名不能为空!") + private String showName; + + /** + * 流程变量类型。 + */ + @Schema(description = "流程变量类型") + @ConstDictRef(constDictClass = FlowVariableType.class, message = "数据验证失败,流程变量类型为无效值!") + @NotNull(message = "数据验证失败,流程变量类型不能为空!") + private Integer variableType; + + /** + * 绑定数据源Id。 + */ + @Schema(description = "绑定数据源Id") + private Long bindDatasourceId; + + /** + * 绑定数据源关联Id。 + */ + @Schema(description = "绑定数据源关联Id") + private Long bindRelationId; + + /** + * 绑定字段Id。 + */ + @Schema(description = "绑定字段Id") + private Long bindColumnId; + + /** + * 是否内置。 + */ + @Schema(description = "是否内置") + @NotNull(message = "数据验证失败,是否内置不能为空!") + private Boolean builtin; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dto/FlowMessageDto.java b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowMessageDto.java new file mode 100644 index 0000000..ca8aeab --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowMessageDto.java @@ -0,0 +1,51 @@ +package apelet.common.flow.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 工作流通知消息Dto对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "工作流通知消息Dto对象") +@Data +public class FlowMessageDto { + + /** + * 消息类型。 + */ + @Schema(description = "消息类型") + private Integer messageType; + + /** + * 工单Id。 + */ + @Schema(description = "工单Id") + private Long workOrderId; + + /** + * 流程名称。 + */ + @Schema(description = "流程名称") + private String processDefinitionName; + + /** + * 流程任务名称。 + */ + @Schema(description = "流程任务名称") + private String taskName; + + /** + * 更新时间范围过滤起始值(>=)。 + */ + @Schema(description = "updateTime 范围过滤起始值") + private String updateTimeStart; + + /** + * 更新时间范围过滤结束值(<=)。 + */ + @Schema(description = "updateTime 范围过滤结束值") + private String updateTimeEnd; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dto/FlowTaskCommentDto.java b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowTaskCommentDto.java new file mode 100644 index 0000000..d501a6e --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowTaskCommentDto.java @@ -0,0 +1,38 @@ +package apelet.common.flow.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 流程任务的批注。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程任务的批注") +@Data +public class FlowTaskCommentDto { + + /** + * 流程任务触发按钮类型,内置值可参考FlowTaskButton。 + */ + @Schema(description = "流程任务触发按钮类型") + @NotNull(message = "数据验证失败,任务的审批类型不能为空!") + private String approvalType; + + /** + * 流程任务的批注内容。 + */ + @Schema(description = "流程任务的批注内容") + @NotBlank(message = "数据验证失败,任务审批内容不能为空!") + private String taskComment; + + /** + * 委托指定人,比如加签、转办等。 + */ + @Schema(description = "委托指定人,比如加签、转办等") + private String delegateAssignee; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/dto/FlowWorkOrderDto.java b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowWorkOrderDto.java new file mode 100644 index 0000000..ee50d28 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/dto/FlowWorkOrderDto.java @@ -0,0 +1,39 @@ +package apelet.common.flow.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 工作流工单Dto对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "工作流工单Dto对象") +@Data +public class FlowWorkOrderDto { + + /** + * 工单编码。 + */ + @Schema(description = "工单编码") + private String workOrderCode; + + /** + * 流程状态。参考FlowTaskStatus常量值对象。 + */ + @Schema(description = "流程状态") + private Integer flowStatus; + + /** + * createTime 范围过滤起始值(>=)。 + */ + @Schema(description = "createTime 范围过滤起始值") + private String createTimeStart; + + /** + * createTime 范围过滤结束值(<=)。 + */ + @Schema(description = "createTime 范围过滤结束值") + private String createTimeEnd; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/exception/FlowEmptyUserException.java b/common/common-flow/src/main/java/apelet/common/flow/exception/FlowEmptyUserException.java new file mode 100644 index 0000000..3a5aa2b --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/exception/FlowEmptyUserException.java @@ -0,0 +1,35 @@ +package apelet.common.flow.exception; + +/** + * 流程空用户异常。 + * + * @author guifc + * @date 2023-08-04 + */ +public class FlowEmptyUserException extends RuntimeException { + + /** + * 构造函数。 + */ + public FlowEmptyUserException() { + + } + + /** + * 构造函数。 + * + * @param throwable 引发异常对象。 + */ + public FlowEmptyUserException(Throwable throwable) { + super(throwable); + } + + /** + * 构造函数。 + * + * @param msg 错误信息。 + */ + public FlowEmptyUserException(String msg) { + super(msg); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/exception/FlowOperationException.java b/common/common-flow/src/main/java/apelet/common/flow/exception/FlowOperationException.java new file mode 100644 index 0000000..5e0e8fc --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/exception/FlowOperationException.java @@ -0,0 +1,35 @@ +package apelet.common.flow.exception; + +/** + * 流程操作异常。 + * + * @author guifc + * @date 2023-08-04 + */ +public class FlowOperationException extends RuntimeException { + + /** + * 构造函数。 + */ + public FlowOperationException() { + + } + + /** + * 构造函数。 + * + * @param throwable 引发异常对象。 + */ + public FlowOperationException(Throwable throwable) { + super(throwable); + } + + /** + * 构造函数。 + * + * @param msg 错误信息。 + */ + public FlowOperationException(String msg) { + super(msg); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/AutoSkipListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/AutoSkipListener.java new file mode 100644 index 0000000..af85eb3 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/AutoSkipListener.java @@ -0,0 +1,161 @@ +package apelet.common.flow.listener; + +import apelet.common.core.object.TokenData; +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowTaskCommentService; +import apelet.common.flow.service.FlowTaskExtService; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.ExtensionAttribute; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; + +import java.util.*; + +/** + * 流程任务自动审批跳过的监听器。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class AutoSkipListener implements ExecutionListener { + + private final transient FlowTaskCommentService flowTaskCommentService = + ApplicationContextHolder.getBean(FlowTaskCommentService.class); + private final transient FlowApiService flowApiService = + ApplicationContextHolder.getBean(FlowApiService.class); + private final transient FlowTaskExtService flowTaskExtService = + ApplicationContextHolder.getBean(FlowTaskExtService.class); + + /** + * 流程的发起者等于当前任务的Assignee。 + */ + private static final String EQ_START_USER = "0"; + /** + * 上一步的提交者等于当前任务的Assignee。 + */ + private static final String EQ_PREV_SUBMIT_USER = "1"; + /** + * 当前任务的Assignee之前提交过审核。 + */ + private static final String EQ_HISTORIC_SUBMIT_USER = "2"; + + @Override + public void notify(DelegateExecution execution) { + UserTask userTask = (UserTask) execution.getCurrentFlowElement(); + List attributes = userTask.getAttributes().get(FlowConstant.USER_TASK_AUTO_SKIP_KEY); + Set skipTypes = new HashSet<>(StrUtil.split(attributes.get(0).getValue(), ",")); + String assignedUser = this.getAssignedUser(execution); + if (StrUtil.isBlank(assignedUser)) { + return; + } + for (String skipType : skipTypes) { + if (this.verifyAndHandle(execution, skipType, assignedUser)) { + return; + } + } + } + + private boolean verifyAndHandle(DelegateExecution execution, String skipType, String assignedUser) { + FlowTaskComment comment = null; + switch (skipType) { + case EQ_START_USER: + Object v = execution.getVariable(FlowConstant.PROC_INSTANCE_START_USER_NAME_VAR); + if (ObjectUtil.equal(v, assignedUser)) { + comment = flowTaskCommentService.getFirstFlowTaskComment(execution.getProcessInstanceId()); + } + break; + case EQ_PREV_SUBMIT_USER: + Object v2 = execution.getVariable(FlowConstant.SUBMIT_USER_VAR); + if (ObjectUtil.equal(v2, assignedUser)) { + TokenData tokenData = TokenData.takeFromRequest(); + comment = new FlowTaskComment(); + comment.setCreateUserId(tokenData.getUserId()); + comment.setCreateLoginName(tokenData.getLoginName()); + comment.setCreateUsername(tokenData.getShowName()); + } + break; + case EQ_HISTORIC_SUBMIT_USER: + List comments = + flowTaskCommentService.getFlowTaskCommentList(execution.getProcessInstanceId()); + List resultComments = new LinkedList<>(); + for (FlowTaskComment c : comments) { + if (StrUtil.equals(c.getCreateLoginName(), assignedUser)) { + resultComments.add(c); + } + } + if (CollUtil.isNotEmpty(resultComments)) { + comment = resultComments.get(0); + } + break; + default: + break; + } + UserTask userTask = (UserTask) execution.getCurrentFlowElement(); + if (comment != null) { + execution.setVariable("_FLOWABLE_SKIP_EXPRESSION_ENABLED", true); + userTask.setSkipExpression("${1==1}"); + comment.setTaskId(userTask.getId()); + comment.setTaskKey(userTask.getId()); + comment.setTaskName(userTask.getName()); + comment.setProcessInstanceId(execution.getProcessInstanceId()); + comment.setExecutionId(execution.getId()); + comment.setApprovalType(FlowApprovalType.AGREE); + comment.setTaskComment(StrFormatter.format("自动跳过审批。审批人 [{}], 跳过原因 [{}]。", + userTask.getAssignee(), this.getMessageBySkipType(skipType))); + flowTaskCommentService.saveNew(comment); + } + return comment != null; + } + + private String getAssignedUser(DelegateExecution execution) { + UserTask userTask = (UserTask) execution.getCurrentFlowElement(); + String assignedUser = userTask.getAssignee(); + if (StrUtil.isNotBlank(assignedUser)) { + if (assignedUser.startsWith("${") && assignedUser.endsWith("}")) { + String variableName = assignedUser.substring(2, assignedUser.length() - 1); + assignedUser = flowApiService.getExecutionVariableStringWithSafe(execution.getId(), variableName); + } + } else { + FlowTaskExt flowTaskExt = flowTaskExtService + .getByProcessDefinitionIdAndTaskId(execution.getProcessDefinitionId(), userTask.getId()); + List candidateUsernames; + if (StrUtil.isBlank(flowTaskExt.getCandidateUsernames())) { + candidateUsernames = Collections.emptyList(); + } else if (!StrUtil.equals(flowTaskExt.getCandidateUsernames(), "${" + FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR + "}")) { + candidateUsernames = StrUtil.split(flowTaskExt.getCandidateUsernames(), ","); + } else { + String value = flowApiService + .getExecutionVariableStringWithSafe(execution.getId(), FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR); + candidateUsernames = value == null ? null : StrUtil.split(value, ","); + } + if (candidateUsernames != null && candidateUsernames.size() == 1) { + assignedUser = candidateUsernames.get(0); + } + } + return assignedUser; + } + + private String getMessageBySkipType(String skipType) { + switch (skipType) { + case EQ_PREV_SUBMIT_USER: + return "审批人与上一审批节点处理人相同"; + case EQ_START_USER: + return "审批人为发起人"; + case EQ_HISTORIC_SUBMIT_USER: + return "审批人审批过"; + default: + return ""; + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/AutoSkipTaskListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/AutoSkipTaskListener.java new file mode 100644 index 0000000..62f7043 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/AutoSkipTaskListener.java @@ -0,0 +1,156 @@ +package apelet.common.flow.listener; + +import apelet.common.core.object.TokenData; +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowTaskCommentService; +import apelet.common.flow.service.FlowTaskExtService; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.ExtensionAttribute; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.api.Task; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.*; + +/** + * 流程任务自动审批跳过的监听器。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class AutoSkipTaskListener implements TaskListener { + + private final transient FlowTaskCommentService flowTaskCommentService = + ApplicationContextHolder.getBean(FlowTaskCommentService.class); + private final transient FlowApiService flowApiService = + ApplicationContextHolder.getBean(FlowApiService.class); + private final transient FlowTaskExtService flowTaskExtService = + ApplicationContextHolder.getBean(FlowTaskExtService.class); + + /** + * 流程的发起者等于当前任务的Assignee。 + */ + private static final String EQ_START_USER = "0"; + /** + * 上一步的提交者等于当前任务的Assignee。 + */ + private static final String EQ_PREV_SUBMIT_USER = "1"; + /** + * 当前任务的Assignee之前提交过审核。 + */ + private static final String EQ_HISTORIC_SUBMIT_USER = "2"; + + @Override + public void notify(DelegateTask t) { + UserTask userTask = flowApiService.getUserTask(t.getProcessDefinitionId(), t.getTaskDefinitionKey()); + List attributes = userTask.getAttributes().get(FlowConstant.USER_TASK_AUTO_SKIP_KEY); + Set skipTypes = new HashSet<>(StrUtil.split(attributes.get(0).getValue(), ",")); + String assignedUser = this.getAssignedUser(userTask, t.getProcessDefinitionId(), t.getExecutionId()); + if (StrUtil.isBlank(assignedUser)) { + return; + } + for (String skipType : skipTypes) { + if (this.verifyAndHandle(userTask, t, skipType, assignedUser)) { + return; + } + } + + } + + private boolean verifyAndHandle(UserTask userTask, DelegateTask task, String skipType, String assignedUser) { + FlowTaskComment comment = null; + switch (skipType) { + case EQ_START_USER: + Object v = task.getVariable(FlowConstant.PROC_INSTANCE_START_USER_NAME_VAR); + if (ObjectUtil.equal(v, assignedUser)) { + comment = flowTaskCommentService.getFirstFlowTaskComment(task.getProcessInstanceId()); + } + break; + case EQ_PREV_SUBMIT_USER: + Object v2 = task.getVariable(FlowConstant.SUBMIT_USER_VAR); + if (ObjectUtil.equal(v2, assignedUser)) { + TokenData tokenData = TokenData.takeFromRequest(); + comment = new FlowTaskComment(); + comment.setCreateUserId(tokenData.getUserId()); + comment.setCreateLoginName(tokenData.getLoginName()); + comment.setCreateUsername(tokenData.getShowName()); + } + break; + case EQ_HISTORIC_SUBMIT_USER: + List comments = + flowTaskCommentService.getFlowTaskCommentList(task.getProcessInstanceId()); + List resultComments = new LinkedList<>(); + for (FlowTaskComment c : comments) { + if (StrUtil.equals(c.getCreateLoginName(), assignedUser)) { + resultComments.add(c); + } + } + if (CollUtil.isNotEmpty(resultComments)) { + comment = resultComments.get(0); + } + break; + default: + break; + } + if (comment != null) { + Task t = flowApiService.getTaskById(task.getId()); + comment.fillWith(t); + comment.setApprovalType(FlowApprovalType.AGREE); + comment.setTaskComment(StrFormatter.format("自动跳过审批。审批人 [{}], 跳过原因 [{}]。", + userTask.getAssignee(), this.getMessageBySkipType(skipType))); + flowApiService.completeTask(t, comment, null, null); + } + return comment != null; + } + + private String getAssignedUser(UserTask userTask, String processDefinitionId, String executionId) { + String assignedUser = userTask.getAssignee(); + if (StrUtil.isNotBlank(assignedUser)) { + if (assignedUser.startsWith("${") && assignedUser.endsWith("}")) { + String variableName = assignedUser.substring(2, assignedUser.length() - 1); + assignedUser = flowApiService.getExecutionVariableStringWithSafe(executionId, variableName); + } + } else { + FlowTaskExt flowTaskExt = flowTaskExtService + .getByProcessDefinitionIdAndTaskId(processDefinitionId, userTask.getId()); + List candidateUsernames; + if (StrUtil.isBlank(flowTaskExt.getCandidateUsernames())) { + candidateUsernames = Collections.emptyList(); + } else if (!StrUtil.equals(flowTaskExt.getCandidateUsernames(), "${" + FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR + "}")) { + candidateUsernames = StrUtil.split(flowTaskExt.getCandidateUsernames(), ","); + } else { + String value = flowApiService + .getExecutionVariableStringWithSafe(executionId, FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR); + candidateUsernames = value == null ? null : StrUtil.split(value, ","); + } + if (candidateUsernames != null && candidateUsernames.size() == 1) { + assignedUser = candidateUsernames.get(0); + } + } + return assignedUser; + } + + private String getMessageBySkipType(String skipType) { + switch (skipType) { + case EQ_PREV_SUBMIT_USER: + return "审批人与上一审批节点处理人相同"; + case EQ_START_USER: + return "审批人为发起人"; + case EQ_HISTORIC_SUBMIT_USER: + return "审批人审批过"; + default: + return ""; + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/DeptPostLeaderListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/DeptPostLeaderListener.java new file mode 100644 index 0000000..7aeb019 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/DeptPostLeaderListener.java @@ -0,0 +1,27 @@ +package apelet.common.flow.listener; + +import apelet.common.flow.constant.FlowConstant; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.Map; + +/** + * 当用户任务的候选组为本部门领导岗位时,该监听器会在任务创建时,获取当前流程实例发起人的部门领导。 + * 并将其指派为当前任务的候选组。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class DeptPostLeaderListener implements TaskListener { + + @Override + public void notify(DelegateTask delegateTask) { + Map variables = delegateTask.getVariables(); + if (variables.get(FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR) == null) { + delegateTask.setAssignee(variables.get(FlowConstant.PROC_INSTANCE_START_USER_NAME_VAR).toString()); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/FlowEmptyUserHandleListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowEmptyUserHandleListener.java new file mode 100644 index 0000000..d2f76a9 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowEmptyUserHandleListener.java @@ -0,0 +1,74 @@ +package apelet.common.flow.listener; + +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.exception.FlowEmptyUserException; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.object.FlowUserTaskExtData; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowTaskExtService; +import apelet.common.flow.vo.FlowUserInfoVo; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.api.Task; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.List; + +/** + * 空审批人处理监听器。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class FlowEmptyUserHandleListener implements TaskListener { + + private final transient FlowTaskExtService flowTaskExtService = + ApplicationContextHolder.getBean(FlowTaskExtService.class); + private final transient FlowApiService flowApiService = + ApplicationContextHolder.getBean(FlowApiService.class); + + @Override + public void notify(DelegateTask t) { + FlowTaskExt flowTaskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId( + t.getProcessDefinitionId(), t.getTaskDefinitionKey()); + Task task = flowApiService.getTaskById(t.getId()); + List userInfoVoList = + flowTaskExtService.getCandidateUserInfoList(t.getProcessInstanceId(), flowTaskExt, task, + flowApiService.isMultiInstanceTask(t.getProcessDefinitionId(), t.getTaskDefinitionKey()), false); + if (CollUtil.isNotEmpty(userInfoVoList)) { + return; + } + FlowUserTaskExtData taskExtData = + JSON.parseObject(flowTaskExt.getExtraDataJson(), FlowUserTaskExtData.class); + if (StrUtil.isBlank(taskExtData.getEmptyUserHandleWay())) { + return; + } + switch (taskExtData.getEmptyUserHandleWay()) { + case FlowUserTaskExtData.EMPTY_USER_TO_ASSIGNEE: + t.setAssignee(taskExtData.getEmptyUserToAssignee()); + break; + case FlowUserTaskExtData.EMPTY_USER_AUTO_REJECT: + case FlowUserTaskExtData.EMPTY_USER_AUTO_COMPLETE: + FlowTaskComment comment = new FlowTaskComment(); + comment.fillWith(task); + if (taskExtData.getEmptyUserHandleWay().equals(FlowUserTaskExtData.EMPTY_USER_AUTO_REJECT)) { + comment.setApprovalType(FlowApprovalType.EMPTY_USER_AUTO_REJECT); + comment.setTaskComment("空审批人自动回退审批。"); + throw new FlowEmptyUserException(JSON.toJSONString(comment)); + } else { + comment.setApprovalType(FlowApprovalType.EMPTY_USER_AUTO_COMPLETE); + comment.setTaskComment("空审批人自动跳过审批。"); + flowApiService.completeTask(task, comment, null, null); + } + break; + default: + break; + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/FlowFinishedListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowFinishedListener.java new file mode 100644 index 0000000..75d9b96 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowFinishedListener.java @@ -0,0 +1,70 @@ +package apelet.common.flow.listener; + +import apelet.common.core.object.GlobalThreadLocal; +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.constant.FlowTaskStatus; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.flow.service.FlowTaskTimeoutJobService; +import apelet.common.flow.service.FlowWorkOrderService; +import apelet.common.flow.util.FlowCustomExtFactory; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; + +/** + * 流程实例监听器,在流程实例结束的时候,需要完成一些自定义的业务行为。如: + * 1. 更新流程工单表的审批状态字段。 + * 2. 业务数据同步。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class FlowFinishedListener implements ExecutionListener { + + private final transient FlowWorkOrderService flowWorkOrderService = + ApplicationContextHolder.getBean(FlowWorkOrderService.class); + private final transient FlowCustomExtFactory flowCustomExtFactory = + ApplicationContextHolder.getBean(FlowCustomExtFactory.class); + private final transient FlowTaskTimeoutJobService flowTaskTimeoutJobService = + ApplicationContextHolder.getBean(FlowTaskTimeoutJobService.class); + + @Override + public void notify(DelegateExecution execution) { + if (!StrUtil.equals("end", execution.getEventName())) { + return; + } + boolean enabled = GlobalThreadLocal.setDataFilter(false); + try { + String processInstanceId = execution.getProcessInstanceId(); + FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId); + if (workOrder == null) { + return; + } + int flowStatus = FlowTaskStatus.FINISHED; + if (workOrder.getFlowStatus().equals(FlowTaskStatus.CANCELLED) + || workOrder.getFlowStatus().equals(FlowTaskStatus.STOPPED)) { + flowStatus = workOrder.getFlowStatus(); + } + workOrder.setFlowStatus(flowStatus); + // 更新流程工单中的流程状态。 + flowWorkOrderService.updateFlowStatusByProcessInstanceId(processInstanceId, flowStatus); + if (workOrder.getOnlineTableId() != null) { + // 处理在线表单工作流的自定义状态更新。 + flowCustomExtFactory.getOnlineBusinessDataExtHelper().updateFlowStatus(workOrder); + } else { + // 处理路由表单工作里的自定义状态更新。 + flowCustomExtFactory.getBusinessDataExtHelper().updateFlowStatus(workOrder); + } + if (flowStatus == FlowTaskStatus.FINISHED) { + // 在线表单不支持该功能,仅限于路由表单工作流可用。 + // 可以自行实现审批完成后,将审批中已经通过的数据,同步到业务发布表。 + flowCustomExtFactory.getBusinessDataExtHelper().triggerSync(workOrder); + } + flowTaskTimeoutJobService.deleteByProcessInstanceId(execution.getProcessInstanceId()); + } finally { + GlobalThreadLocal.setDataFilter(enabled); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskNotifyListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskNotifyListener.java new file mode 100644 index 0000000..0228b84 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskNotifyListener.java @@ -0,0 +1,80 @@ +package apelet.common.flow.listener; + +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.object.FlowUserTaskExtData; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowTaskExtService; +import apelet.common.flow.util.BaseFlowNotifyExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.vo.FlowTaskVo; +import apelet.common.flow.vo.FlowUserInfoVo; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.List; + +/** + * 任务进入待办状态时的通知监听器。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class FlowTaskNotifyListener implements TaskListener { + + private final transient FlowTaskExtService flowTaskExtService = + ApplicationContextHolder.getBean(FlowTaskExtService.class); + private final transient FlowApiService flowApiService = + ApplicationContextHolder.getBean(FlowApiService.class); + private final transient FlowCustomExtFactory flowCustomExtFactory = + ApplicationContextHolder.getBean(FlowCustomExtFactory.class); + + @Override + public void notify(DelegateTask delegateTask) { + String definitionId = delegateTask.getProcessDefinitionId(); + String instanceId = delegateTask.getProcessInstanceId(); + String taskId = delegateTask.getId(); + String taskKey = delegateTask.getTaskDefinitionKey(); + FlowTaskExt taskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId(definitionId, taskKey); + if (StrUtil.isBlank(taskExt.getExtraDataJson())) { + return; + } + FlowUserTaskExtData extData = JSON.parseObject(taskExt.getExtraDataJson(), FlowUserTaskExtData.class); + if (CollUtil.isEmpty(extData.getFlowNotifyTypeList())) { + return; + } + ProcessInstance instance = flowApiService.getProcessInstance(instanceId); + Object initiator = flowApiService.getProcessInstanceVariable(instanceId, FlowConstant.PROC_INSTANCE_INITIATOR_VAR); + boolean isMultiInstanceTask = flowApiService.isMultiInstanceTask(definitionId, taskKey); + Task task = flowApiService.getProcessInstanceActiveTask(instanceId, taskId); + List userInfoList = + flowTaskExtService.getCandidateUserInfoList(instanceId, taskExt, task, isMultiInstanceTask, false); + if (CollUtil.isEmpty(userInfoList)) { + log.warn("ProcessDefinition [{}] Task [{}] don't find the candidate users for notification.", + instance.getProcessDefinitionName(), task.getName()); + return; + } + BaseFlowNotifyExtHelper helper = flowCustomExtFactory.getFlowNotifyExtHelper(); + Assert.notNull(helper); + for (String notifyType : extData.getFlowNotifyTypeList()) { + FlowTaskVo flowTaskVo = new FlowTaskVo(); + flowTaskVo.setProcessDefinitionId(definitionId); + flowTaskVo.setProcessInstanceId(instanceId); + flowTaskVo.setTaskKey(taskKey); + flowTaskVo.setTaskName(delegateTask.getName()); + flowTaskVo.setTaskId(delegateTask.getId()); + flowTaskVo.setBusinessKey(instance.getBusinessKey()); + flowTaskVo.setProcessInstanceInitiator(initiator.toString()); + helper.doNotify(notifyType, userInfoList, flowTaskVo); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskRejectBackListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskRejectBackListener.java new file mode 100644 index 0000000..43ab717 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskRejectBackListener.java @@ -0,0 +1,26 @@ +package apelet.common.flow.listener; + +import apelet.common.flow.constant.FlowConstant; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.Map; + +/** + * 任务驳回后重新提交到该任务的通知监听器。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class FlowTaskRejectBackListener implements TaskListener { + + @Override + public void notify(DelegateTask delegateTask) { + Map variables = delegateTask.getVariables(); + if (variables.get(FlowConstant.REJECT_BACK_TO_SOURCE_DATA_VAR) != null) { + delegateTask.setAssignee(variables.get(FlowConstant.REJECT_BACK_TO_SOURCE_DATA_VAR).toString()); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskReviveListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskReviveListener.java new file mode 100644 index 0000000..457f14b --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskReviveListener.java @@ -0,0 +1,55 @@ +package apelet.common.flow.listener; + +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.model.FlowEntryPublish; +import apelet.common.flow.model.FlowVariableLog; +import apelet.common.flow.object.FlowEntryExtensionData; +import apelet.common.flow.service.FlowEntryService; +import apelet.common.flow.service.FlowVariableLogService; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.map.MapUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 支持流程复活的任务监听器。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class FlowTaskReviveListener implements TaskListener { + + private final transient FlowVariableLogService flowVariableLogService = + ApplicationContextHolder.getBean(FlowVariableLogService.class); + private final transient FlowEntryService flowEntryService = + ApplicationContextHolder.getBean(FlowEntryService.class); + + @Override + public void notify(DelegateTask delegateTask) { + Map variables = delegateTask.getVariables(); + if (MapUtil.isNotEmpty(variables)) { + List flowEntryPublishList = + flowEntryService.getFlowEntryPublishList(CollUtil.newHashSet(delegateTask.getProcessDefinitionId())); + FlowEntryExtensionData flowEntryExtensionData = + JSON.parseObject(flowEntryPublishList.get(0).getExtensionData(), FlowEntryExtensionData.class); + FlowVariableLog o = new FlowVariableLog(); + if (flowEntryExtensionData.getKeptReviveDays() > 0) { + o.setExpiredTime(DateUtil.offset(new Date(), DateField.DAY_OF_MONTH, flowEntryExtensionData.getKeptReviveDays())); + } + o.setProcessDefinitionKey(delegateTask.getProcessDefinitionId()); + o.setProcessInstanceId(delegateTask.getProcessInstanceId()); + o.setTaskKey(delegateTask.getTaskDefinitionKey()); + o.setVariableData(JSON.toJSONString(variables)); + flowVariableLogService.saveNew(o); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskTimeoutListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskTimeoutListener.java new file mode 100644 index 0000000..2547289 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowTaskTimeoutListener.java @@ -0,0 +1,48 @@ +package apelet.common.flow.listener; + +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.model.FlowTaskTimeoutJob; +import apelet.common.flow.object.FlowUserTaskExtData; +import apelet.common.flow.service.FlowTaskExtService; +import apelet.common.flow.service.FlowTaskTimeoutJobService; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; + +/** + * 流程任务超时监听器。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class FlowTaskTimeoutListener implements TaskListener { + + private final transient FlowTaskExtService flowTaskExtService = + ApplicationContextHolder.getBean(FlowTaskExtService.class); + private final transient FlowTaskTimeoutJobService flowTaskTimeoutJobService = + ApplicationContextHolder.getBean(FlowTaskTimeoutJobService.class); + + @Override + public void notify(DelegateTask delegateTask) { + FlowTaskExt taskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId( + delegateTask.getProcessDefinitionId(), delegateTask.getTaskDefinitionKey()); + if (StrUtil.isNotBlank(taskExt.getExtraDataJson())) { + FlowUserTaskExtData taskExtData = JSON.parseObject(taskExt.getExtraDataJson(), FlowUserTaskExtData.class); + if (StrUtil.isNotBlank(taskExtData.getTimeoutHandleWay())) { + FlowTaskTimeoutJob job = new FlowTaskTimeoutJob(); + job.setProcessDefinitionId(delegateTask.getProcessDefinitionId()); + job.setProcessInstanceId(delegateTask.getProcessInstanceId()); + job.setTaskKey(delegateTask.getTaskDefinitionKey()); + job.setTaskId(delegateTask.getId()); + job.setTimeoutHours(taskExtData.getTimeoutHours()); + job.setHandleWay(taskExtData.getTimeoutHandleWay()); + job.setDefaultAssignee(taskExtData.getDefaultAssignee()); + flowTaskTimeoutJobService.saveNew(job); + } + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/FlowUserTaskListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowUserTaskListener.java new file mode 100644 index 0000000..f818bf0 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/FlowUserTaskListener.java @@ -0,0 +1,32 @@ +package apelet.common.flow.listener; + +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.constant.FlowConstant; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.Map; + +/** + * 流程任务通用监听器。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class FlowUserTaskListener implements TaskListener { + + private final transient RuntimeService runtimeService = + ApplicationContextHolder.getBean(RuntimeService.class); + + @Override + public void notify(DelegateTask delegateTask) { + Map variables = delegateTask.getVariables(); + if (variables.get(FlowConstant.DELEGATE_ASSIGNEE_VAR) != null) { + delegateTask.setAssignee(variables.get(FlowConstant.DELEGATE_ASSIGNEE_VAR).toString()); + runtimeService.removeVariableLocal(delegateTask.getExecutionId(), FlowConstant.DELEGATE_ASSIGNEE_VAR); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/UpDeptPostLeaderListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/UpDeptPostLeaderListener.java new file mode 100644 index 0000000..d56a965 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/UpDeptPostLeaderListener.java @@ -0,0 +1,27 @@ +package apelet.common.flow.listener; + +import apelet.common.flow.constant.FlowConstant; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.Map; + +/** + * 当用户任务的候选组为上级部门领导岗位时,该监听器会在任务创建时,获取当前流程实例发起人的部门领导。 + * 并将其指派为当前任务的候选组。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class UpDeptPostLeaderListener implements TaskListener { + + @Override + public void notify(DelegateTask delegateTask) { + Map variables = delegateTask.getVariables(); + if (variables.get(FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR) == null) { + delegateTask.setAssignee(variables.get(FlowConstant.PROC_INSTANCE_START_USER_NAME_VAR).toString()); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/UpdateLatestApprovalStatusListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/UpdateLatestApprovalStatusListener.java new file mode 100644 index 0000000..7069d29 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/UpdateLatestApprovalStatusListener.java @@ -0,0 +1,44 @@ +package apelet.common.flow.listener; + +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.flow.service.FlowWorkOrderService; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.flowable.engine.impl.el.FixedValue; + +/** + * 更新流程的最后审批状态的监听器,目前用于排他网关到任务结束节点的连线上, + * 以便于准确的判断流程实例的最后审批状态。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class UpdateLatestApprovalStatusListener implements ExecutionListener { + + private FixedValue latestApprovalStatus; + + private final transient FlowWorkOrderService flowWorkOrderService = + ApplicationContextHolder.getBean(FlowWorkOrderService.class); + + public void setAutoStoreVariablesExp(FixedValue approvalStatus) { + this.latestApprovalStatus = approvalStatus; + } + + @Override + public void notify(DelegateExecution execution) { + if (StrUtil.isNotBlank(latestApprovalStatus.getExpressionText())) { + FlowWorkOrder workOrder = + flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(execution.getProcessInstanceId()); + if (workOrder == null) { + return; + } + Integer approvalStatus = Integer.valueOf(latestApprovalStatus.getExpressionText()); + String processInstanceId = execution.getProcessInstanceId(); + flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(processInstanceId, approvalStatus); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowCategory.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowCategory.java new file mode 100644 index 0000000..be3a1af --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowCategory.java @@ -0,0 +1,88 @@ +package apelet.common.flow.model; + +import apelet.common.core.base.mapper.BaseModelMapper; +import apelet.common.flow.vo.FlowCategoryVo; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Date; + +/** + * 流程分类的实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_category") +public class FlowCategory { + + /** + * 主键Id。 + */ + @TableId(value = "category_id") + private Long categoryId; + + /** + * 租户Id。 + */ + @TableField(value = "tenant_id") + private Long tenantId; + + /** + * 应用编码。为空时,表示非第三方应用接入。 + */ + @TableField(value = "app_code") + private String appCode; + + /** + * 显示名称。 + */ + @TableField(value = "name") + private String name; + + /** + * 分类编码。 + */ + @TableField(value = "code") + private String code; + + /** + * 实现顺序。 + */ + @TableField(value = "show_order") + private Integer showOrder; + + /** + * 更新时间。 + */ + @TableField(value = "update_time") + private Date updateTime; + + /** + * 更新者Id。 + */ + @TableField(value = "update_user_id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + /** + * 创建者Id。 + */ + @TableField(value = "create_user_id") + private Long createUserId; + + @Mapper + public interface FlowCategoryModelMapper extends BaseModelMapper { + } + public static final FlowCategoryModelMapper INSTANCE = Mappers.getMapper(FlowCategoryModelMapper.class); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java new file mode 100644 index 0000000..27b2045 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java @@ -0,0 +1,192 @@ +package apelet.common.flow.model; + +import apelet.common.core.annotation.RelationOneToOne; +import apelet.common.core.base.mapper.BaseModelMapper; +import apelet.common.flow.vo.FlowEntryVo; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.Date; + +/** + * 流程的实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_entry") +public class FlowEntry { + + /** + * 主键。 + */ + @TableId(value = "entry_id") + private Long entryId; + + /** + * 租户Id。 + */ + @TableField(value = "tenant_id") + private Long tenantId; + + /** + * 应用编码。为空时,表示非第三方应用接入。 + */ + @TableField(value = "app_code") + private String appCode; + + /** + * 流程名称。 + */ + @TableField(value = "process_definition_name") + private String processDefinitionName; + + /** + * 流程标识Key。 + */ + @TableField(value = "process_definition_key") + private String processDefinitionKey; + + /** + * 流程分类。 + */ + @TableField(value = "category_id") + private Long categoryId; + + /** + * 工作流部署的发布主版本Id。 + */ + @TableField(value = "main_entry_publish_id") + private Long mainEntryPublishId; + + /** + * 最新发布时间。 + */ + @TableField(value = "latest_publish_time") + private Date latestPublishTime; + + /** + * 流程状态。 + */ + @TableField(value = "status") + private Integer status; + + /** + * 流程定义的xml。 + */ + @TableField(value = "bpmn_xml") + private String bpmnXml; + + /** + * 流程图类型。0: 普通流程图,1: 钉钉风格的流程图。 + */ + @TableField(value = "diagram_type") + private Integer diagramType; + + /** + * 绑定表单类型。 + */ + @TableField(value = "bind_form_type") + private Integer bindFormType; + + /** + * 在线表单的页面Id。 + */ + @TableField(value = "page_id") + private Long pageId; + + /** + * 在线表单Id。 + */ + @TableField(value = "default_form_id") + private Long defaultFormId; + + /** + * 静态表单的缺省路由名称。 + */ + @TableField(value = "default_router_name") + private String defaultRouterName; + + /** + * 工单表编码字段的编码规则,如果为空则不计算工单编码。 + */ + @TableField(value = "encoded_rule") + private String encodedRule; + + /** + * 流程的自定义扩展数据(JSON格式)。 + */ + @TableField(value = "extension_data") + private String extensionData; + + /** + * 更新时间。 + */ + @TableField(value = "update_time") + private Date updateTime; + + /** + * 更新者Id。 + */ + @TableField(value = "update_user_id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + /** + * 创建者Id。 + */ + @TableField(value = "create_user_id") + private Long createUserId; + + /** + * 流程启动条件 + */ + @TableField(value = "start_conditions") + private String startConditions; + + @TableField(exist = false) + private FlowEntryPublish mainFlowEntryPublish; + + @RelationOneToOne( + masterIdField = "categoryId", + slaveModelClass = FlowCategory.class, + slaveIdField = "categoryId") + @TableField(exist = false) + private FlowCategory flowCategory; + + @Mapper + public interface FlowEntryModelMapper extends BaseModelMapper { + /** + * 转换Vo对象到实体对象。 + * + * @param flowEntryVo 域对象。 + * @return 实体对象。 + */ + @Mapping(target = "mainFlowEntryPublish", expression = "java(mapToBean(flowEntryVo.getMainFlowEntryPublish(), apelet.common.flow.model.FlowEntryPublish.class))") + @Mapping(target = "flowCategory", expression = "java(mapToBean(flowEntryVo.getFlowCategory(), apelet.common.flow.model.FlowCategory.class))") + @Override + FlowEntry toModel(FlowEntryVo flowEntryVo); + /** + * 转换实体对象到VO对象。 + * + * @param flowEntry 实体对象。 + * @return 域对象。 + */ + @Mapping(target = "mainFlowEntryPublish", expression = "java(beanToMap(flowEntry.getMainFlowEntryPublish(), false))") + @Mapping(target = "flowCategory", expression = "java(beanToMap(flowEntry.getFlowCategory(), false))") + @Override + FlowEntryVo fromModel(FlowEntry flowEntry); + } + public static final FlowEntryModelMapper INSTANCE = Mappers.getMapper(FlowEntryModelMapper.class); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryPublish.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryPublish.java new file mode 100644 index 0000000..764c3da --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryPublish.java @@ -0,0 +1,98 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 流程发布数据的实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_entry_publish") +public class FlowEntryPublish { + + /** + * 主键Id。 + */ + @TableId(value = "entry_publish_id") + private Long entryPublishId; + + /** + * 流程Id。 + */ + @TableField(value = "entry_id") + private Long entryId; + + /** + * 流程引擎的部署Id。 + */ + @TableField(value = "deploy_id") + private String deployId; + + /** + * 流程引擎中的流程定义Id。 + */ + @TableField(value = "process_definition_id") + private String processDefinitionId; + + /** + * 发布版本。 + */ + @TableField(value = "publish_version") + private Integer publishVersion; + + /** + * 激活状态。 + */ + @TableField(value = "active_status") + private Boolean activeStatus; + + /** + * 是否为主版本。 + */ + @TableField(value = "main_version") + private Boolean mainVersion; + + /** + * 创建者Id。 + */ + @TableField(value = "create_user_id") + private Long createUserId; + + /** + * 发布时间。 + */ + @TableField(value = "publish_time") + private Date publishTime; + + /** + * 第一个非开始节点任务的附加信息。 + */ + @TableField(value = "init_task_info") + private String initTaskInfo; + + /** + * 分析后的节点JSON信息。 + */ + @TableField(value = "analyzed_node_json") + private String analyzedNodeJson; + + /** + * 流程的自定义扩展数据(JSON格式)。 + */ + @TableField(value = "extension_data") + private String extensionData; + + + /** + * 流程启动条件 + */ + @TableField(value = "start_conditions") + private String startConditions; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryPublishVariable.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryPublishVariable.java new file mode 100644 index 0000000..984fc74 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryPublishVariable.java @@ -0,0 +1,71 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * FlowEntryPublishVariable实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_entry_publish_variable") +public class FlowEntryPublishVariable { + + /** + * 主键Id。 + */ + @TableId(value = "variable_id") + private Long variableId; + + /** + * 流程Id。 + */ + @TableField(value = "entry_publish_id") + private Long entryPublishId; + + /** + * 变量名。 + */ + @TableField(value = "variable_name") + private String variableName; + + /** + * 显示名。 + */ + @TableField(value = "show_name") + private String showName; + + /** + * 变量类型。 + */ + @TableField(value = "variable_type") + private Integer variableType; + + /** + * 是否内置。 + */ + @TableField(value = "builtin") + private Boolean builtin; + + /** + * 绑定数据源Id。 + */ + @TableField(value = "bind_datasource_id") + private Long bindDatasourceId; + + /** + * 绑定数据源关联Id。 + */ + @TableField(value = "bind_relation_id") + private Long bindRelationId; + + /** + * 绑定字段Id。 + */ + @TableField(value = "bind_column_id") + private Long bindColumnId; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryVariable.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryVariable.java new file mode 100644 index 0000000..24777ce --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntryVariable.java @@ -0,0 +1,88 @@ +package apelet.common.flow.model; + +import apelet.common.core.base.mapper.BaseModelMapper; +import apelet.common.flow.vo.FlowEntryVariableVo; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Date; + +/** + * 流程变量实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_entry_variable") +public class FlowEntryVariable { + + /** + * 主键Id。 + */ + @TableId(value = "variable_id") + private Long variableId; + + /** + * 流程Id。 + */ + @TableField(value = "entry_id") + private Long entryId; + + /** + * 变量名。 + */ + @TableField(value = "variable_name") + private String variableName; + + /** + * 显示名。 + */ + @TableField(value = "show_name") + private String showName; + + /** + * 流程变量类型。 + */ + @TableField(value = "variable_type") + private Integer variableType; + + /** + * 绑定数据源Id。 + */ + @TableField(value = "bind_datasource_id") + private Long bindDatasourceId; + + /** + * 绑定数据源关联Id。 + */ + @TableField(value = "bind_relation_id") + private Long bindRelationId; + + /** + * 绑定字段Id。 + */ + @TableField(value = "bind_column_id") + private Long bindColumnId; + + /** + * 是否内置。 + */ + @TableField(value = "builtin") + private Boolean builtin; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + @Mapper + public interface FlowEntryVariableModelMapper extends BaseModelMapper { + } + public static final FlowEntryVariableModelMapper INSTANCE = Mappers.getMapper(FlowEntryVariableModelMapper.class); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessage.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessage.java new file mode 100644 index 0000000..4334c80 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessage.java @@ -0,0 +1,178 @@ +package apelet.common.flow.model; + +import apelet.common.core.base.mapper.BaseModelMapper; +import apelet.common.flow.vo.FlowMessageVo; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Date; + +/** + * 工作流通知消息实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_message") +public class FlowMessage { + + /** + * 主键Id。 + */ + @TableId(value = "message_id") + private Long messageId; + + /** + * 租户Id。 + */ + @TableField(value = "tenant_id") + private Long tenantId; + + /** + * 应用编码。为空时,表示非第三方应用接入。 + */ + @TableField(value = "app_code") + private String appCode; + + /** + * 消息类型。 + */ + @TableField(value = "message_type") + private Integer messageType; + + /** + * 消息内容。 + */ + @TableField(value = "message_content") + private String messageContent; + + /** + * 催办次数。 + */ + @TableField(value = "remind_count") + private Integer remindCount; + + /** + * 工单Id。 + */ + @TableField(value = "work_order_id") + private Long workOrderId; + + /** + * 流程定义Id。 + */ + @TableField(value = "process_definition_id") + private String processDefinitionId; + + /** + * 流程定义标识。 + */ + @TableField(value = "process_definition_key") + private String processDefinitionKey; + + /** + * 流程名称。 + */ + @TableField(value = "process_definition_name") + private String processDefinitionName; + + /** + * 流程实例Id。 + */ + @TableField(value = "process_instance_id") + private String processInstanceId; + + /** + * 流程实例发起者。 + */ + @TableField(value = "process_instance_initiator") + private String processInstanceInitiator; + + /** + * 流程任务Id。 + */ + @TableField(value = "task_id") + private String taskId; + + /** + * 流程任务定义标识。 + */ + @TableField(value = "task_definition_key") + private String taskDefinitionKey; + + /** + * 流程任务名称。 + */ + @TableField(value = "task_name") + private String taskName; + + /** + * 创建时间。 + */ + @TableField(value = "task_start_time") + private Date taskStartTime; + + /** + * 任务指派人登录名。 + */ + @TableField(value = "task_assignee") + private String taskAssignee; + + /** + * 任务是否已完成。 + */ + @TableField(value = "task_finished") + private Boolean taskFinished; + + /** + * 业务数据快照。 + */ + @TableField(value = "business_data_shot") + private String businessDataShot; + + /** + * 是否为在线表单消息数据。 + */ + @TableField(value = "online_form_data") + private Boolean onlineFormData; + + /** + * 更新时间。 + */ + @TableField(value = "update_time") + private Date updateTime; + + /** + * 更新者Id。 + */ + @TableField(value = "update_user_id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + /** + * 创建者Id。 + */ + @TableField(value = "create_user_id") + private Long createUserId; + + /** + * 创建者显示名。 + */ + @TableField(value = "create_username") + private String createUsername; + + @Mapper + public interface FlowMessageModelMapper extends BaseModelMapper { + } + public static final FlowMessage.FlowMessageModelMapper INSTANCE = Mappers.getMapper(FlowMessage.FlowMessageModelMapper.class); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessageCandidateIdentity.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessageCandidateIdentity.java new file mode 100644 index 0000000..e86181a --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessageCandidateIdentity.java @@ -0,0 +1,41 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 流程任务消息的候选身份实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_msg_candidate_identity") +public class FlowMessageCandidateIdentity { + + /** + * 主键Id。 + */ + @TableId(value = "id") + private Long id; + + /** + * 任务消息Id。 + */ + @TableField(value = "message_id") + private Long messageId; + + /** + * 候选身份类型。 + */ + @TableField(value = "candidate_type") + private String candidateType; + + /** + * 候选身份Id。 + */ + @TableField(value = "candidate_id") + private String candidateId; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessageIdentityOperation.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessageIdentityOperation.java new file mode 100644 index 0000000..2698f2f --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowMessageIdentityOperation.java @@ -0,0 +1,50 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 流程任务消息所属用户的操作表。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_msg_identity_operation") +public class FlowMessageIdentityOperation { + + /** + * 主键Id。 + */ + @TableId(value = "id") + private Long id; + + /** + * 任务消息Id。 + */ + @TableField(value = "message_id") + private Long messageId; + + /** + * 用户登录名。 + */ + @TableField(value = "login_name") + private String loginName; + + /** + * 操作类型。 + * 常量值参考FlowMessageOperationType对象。 + */ + @TableField(value = "operation_type") + private Integer operationType; + + /** + * 操作时间。 + */ + @TableField(value = "operation_time") + private Date operationTime; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowMultiInstanceTrans.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowMultiInstanceTrans.java new file mode 100644 index 0000000..a84210c --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowMultiInstanceTrans.java @@ -0,0 +1,99 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.flowable.task.api.TaskInfo; + +import java.util.Date; + +/** + * 流程多实例任务执行流水对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@NoArgsConstructor +@TableName(value = "zz_flow_multi_instance_trans") +public class FlowMultiInstanceTrans { + + /** + * 主键Id。 + */ + @TableId(value = "id") + private Long id; + + /** + * 流程实例Id。 + */ + @TableField(value = "process_instance_id") + private String processInstanceId; + + /** + * 任务Id。 + */ + @TableField(value = "task_id") + private String taskId; + + /** + * 任务标识。 + */ + @TableField(value = "task_key") + private String taskKey; + + /** + * 会签任务的执行Id。 + */ + @TableField(value = "multi_instance_exec_id") + private String multiInstanceExecId; + + /** + * 任务的执行Id。 + */ + @TableField(value = "execution_id") + private String executionId; + + /** + * 会签指派人列表。 + */ + @TableField(value = "assignee_list") + private String assigneeList; + + /** + * 创建者Id。 + */ + @TableField(value = "create_user_id") + private Long createUserId; + + /** + * 创建者登录名。 + */ + @TableField(value = "create_login_name") + private String createLoginName; + + /** + * 创建者显示名。 + */ + @TableField(value = "create_username") + private String createUsername; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + public FlowMultiInstanceTrans(TaskInfo task) { + this.fillWith(task); + } + + public void fillWith(TaskInfo task) { + this.taskId = task.getId(); + this.taskKey = task.getTaskDefinitionKey(); + this.processInstanceId = task.getProcessInstanceId(); + this.executionId = task.getExecutionId(); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowNodeTasks.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowNodeTasks.java new file mode 100644 index 0000000..7c3e434 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowNodeTasks.java @@ -0,0 +1,55 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * 工作流 审批设置。 + * + * @author + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_node_tasks") +public class FlowNodeTasks implements Serializable { + + /** + * 主键Id。 + */ + @TableId(value = "id") + private Long id; + + /** + * 流程ID + */ + @TableField(value = "flow_id") + private Long flowId; + + /** + * 执行时机 + */ + @TableField(value = "execution_timing") + private String executionTiming; + + /** + * 类型 + */ + @TableField(value = "type") + private Integer type; + + /** + * 操作。 + */ + @TableField(value = "operation") + private String operation; + + /** + * task_key。 + */ + @TableField(value = "task_key") + private String taskKey; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskComment.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskComment.java new file mode 100644 index 0000000..2f131c8 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskComment.java @@ -0,0 +1,161 @@ +package apelet.common.flow.model; + +import apelet.common.core.base.mapper.BaseModelMapper; +import apelet.common.core.util.ContextUtil; +import apelet.common.flow.vo.FlowTaskCommentVo; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.flowable.task.api.TaskInfo; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Date; + +/** + * FlowTaskComment实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@NoArgsConstructor +@TableName(value = "zz_flow_task_comment") +public class FlowTaskComment { + + /** + * 主键Id。 + */ + @TableId(value = "id") + private Long id; + + /** + * 流程实例Id。 + */ + @TableField(value = "process_instance_id") + private String processInstanceId; + + /** + * 任务Id。 + */ + @TableField(value = "task_id") + private String taskId; + + /** + * 任务标识。 + */ + @TableField(value = "task_key") + private String taskKey; + + /** + * 任务名称。 + */ + @TableField(value = "task_name") + private String taskName; + + /** + * 用于驳回和自由跳的目标任务标识。 + */ + @TableField(value = "target_task_key") + private String targetTaskKey; + + /** + * 任务的执行Id。 + */ + @TableField(value = "execution_id") + private String executionId; + + /** + * 会签任务的执行Id。 + */ + @TableField(value = "multi_instance_exec_id") + private String multiInstanceExecId; + + /** + * 审批类型。 + */ + @TableField(value = "approval_type") + private String approvalType; + + /** + * 批注内容。 + */ + @TableField(value = "task_comment") + private String taskComment; + + /** + * 委托指定人,比如加签、转办等。 + */ + @TableField(value = "delegate_assignee") + private String delegateAssignee; + + /** + * 自定义数据。开发者可自行扩展,推荐使用JSON格式数据。 + */ + @TableField(value = "custom_business_data") + private String customBusinessData; + + /** + * 审批人头像。 + */ + @TableField(value = "head_image_url") + private String headImageUrl; + + /** + * 创建者Id。 + */ + @TableField(value = "create_user_id") + private Long createUserId; + + /** + * 创建者登录名。 + */ + @TableField(value = "create_login_name") + private String createLoginName; + + /** + * 创建者显示名。 + */ + @TableField(value = "create_username") + private String createUsername; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + private static final String REQ_ATTRIBUTE_KEY = "flowTaskComment"; + + public FlowTaskComment(TaskInfo task) { + this.fillWith(task); + } + + public static void setToRequest(FlowTaskComment comment) { + if (ContextUtil.getHttpRequest() != null) { + ContextUtil.getHttpRequest().setAttribute(REQ_ATTRIBUTE_KEY, comment); + } + } + + public static FlowTaskComment getFromRequest() { + if (ContextUtil.getHttpRequest() == null) { + return null; + } + return (FlowTaskComment) ContextUtil.getHttpRequest().getAttribute(REQ_ATTRIBUTE_KEY); + } + + public void fillWith(TaskInfo task) { + this.taskId = task.getId(); + this.taskKey = task.getTaskDefinitionKey(); + this.taskName = task.getName(); + this.processInstanceId = task.getProcessInstanceId(); + this.executionId = task.getExecutionId(); + } + + @Mapper + public interface FlowTaskCommentModelMapper extends BaseModelMapper { + } + public static final FlowTaskCommentModelMapper INSTANCE = Mappers.getMapper(FlowTaskCommentModelMapper.class); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java new file mode 100644 index 0000000..652f45a --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java @@ -0,0 +1,88 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 流程任务扩展实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_task_ext") +public class FlowTaskExt { + + /** + * 流程引擎的定义Id。 + */ + @TableField(value = "process_definition_id") + private String processDefinitionId; + + /** + * 流程引擎任务Id。 + */ + @TableField(value = "task_id") + private String taskId; + + /** + * 操作列表JSON。 + */ + @TableField(value = "operation_list_json") + private String operationListJson; + + /** + * 变量列表JSON。 + */ + @TableField(value = "variable_list_json") + private String variableListJson; + + /** + * 存储多实例的assigneeList的JSON。 + */ + @TableField(value = "assignee_list_json") + private String assigneeListJson; + + /** + * 分组类型。 + */ + @TableField(value = "group_type") + private String groupType; + + /** + * 保存岗位相关的数据。 + */ + @TableField(value = "dept_post_list_json") + private String deptPostListJson; + + /** + * 逗号分隔的角色Id。 + */ + @TableField(value = "role_ids") + private String roleIds; + + /** + * 逗号分隔的部门Id。 + */ + @TableField(value = "dept_ids") + private String deptIds; + + /** + * 逗号分隔候选用户名。 + */ + @TableField(value = "candidate_usernames") + private String candidateUsernames; + + /** + * 抄送相关的数据。 + */ + @TableField(value = "copy_list_json") + private String copyListJson; + + /** + * 用户任务的扩展属性,存储为JSON的字符串格式。 + */ + @TableField(value = "extra_data_json") + private String extraDataJson; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskTimeoutJob.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskTimeoutJob.java new file mode 100644 index 0000000..b04ee0b --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskTimeoutJob.java @@ -0,0 +1,97 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 超时任务作业实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_task_timeout_job") +public class FlowTaskTimeoutJob { + + /** + * 主键Id。 + */ + @TableId(value = "id") + private Long id; + + /** + * 流程定义Id。 + */ + @TableField(value = "process_definition_id") + private String processDefinitionId; + + /** + * 流程实例Id。 + */ + @TableField(value = "process_instance_id") + private String processInstanceId; + + /** + * 流程任务标识。 + */ + @TableField(value = "task_key") + private String taskKey; + + /** + * 流程任务Id。 + */ + @TableField(value = "task_id") + private String taskId; + + /** + * 超时的小时数。 + */ + @TableField(value = "timeout_hours") + private Integer timeoutHours; + + /** + * 超时处理方式。 + */ + @TableField(value = "handle_way") + private String handleWay; + + /** + * 超时处理缺省用户名。 + */ + @TableField(value = "default_assignee") + private String defaultAssignee; + + /** + * 处理错误信息。 + */ + @TableField(value = "error_message") + private String errorMessage; + + /** + * 执行状态。参考常量类FlowTaskTimeoutJobStatus的值。 + */ + @TableField(value = "status") + private Integer status; + + /** + * 执行时间。 + */ + @TableField(value = "exec_time") + private Date execTime; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + /** + * 更新时间。 + */ + @TableField(value = "update_time") + private Date updateTime; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowTransConsumer.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTransConsumer.java new file mode 100644 index 0000000..747aec5 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTransConsumer.java @@ -0,0 +1,33 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.Date; + +/** + * 流程处理事务事件消费者流水实体。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@AllArgsConstructor +@TableName(value = "zz_flow_trans_consumer") +public class FlowTransConsumer { + + /** + * 流水Id。 + */ + @TableId(value = "trans_id") + private Long transId; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowTransProducer.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTransProducer.java new file mode 100644 index 0000000..2b3093f --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTransProducer.java @@ -0,0 +1,121 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 流程处理事务事件生产者流水实体。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_trans_producer") +public class FlowTransProducer { + + /** + * 流水Id。 + */ + @TableId(value = "trans_id") + private Long transId; + + /** + * 应用编码。为空时,表示非第三方应用接入。 + */ + @TableField(value = "app_code") + private String appCode; + + /** + * 业务数据库链接Id。 + */ + @TableField(value = "dblink_id") + private Long dblinkId; + + /** + * 流程实例Id。 + */ + @TableField(value = "process_instance_id") + private String processInstanceId; + + /** + * 任务Id。 + */ + @TableField(value = "task_id") + private String taskId; + + /** + * 任务标识。 + */ + @TableField(value = "task_key") + private String taskKey; + + /** + * 任务名称。 + */ + @TableField(value = "task_name") + private String taskName; + + /** + * 审批批注。 + */ + @TableField(value = "task_comment") + private String taskComment; + + /** + * 当前请求的url。 + */ + @TableField(value = "url") + private String url; + + /** + * 创建该事务性事件对象的初始方法。格式为:方法名(参数类型1,参数类型2)。 + */ + @TableField(value = "init_method") + private String initMethod; + + /** + * 当前请求的traceId。 + */ + @TableField(value = "trace_id") + private String traceId; + + /** + * 和SQL操作相关的数据。值类型为TransactionalBusinessData.BusinessSqlData对象。 + */ + @TableField(value = "sql_data") + private String sqlData; + + /** + * 尝试次数。默认的插入值为1。 + */ + @TableField(value = "try_times") + private Integer tryTimes; + + /** + * 提交业务数据时的错误信息。如果正常提交,该值为空。 + */ + @TableField(value = "error_reason") + private String errorReason; + + /** + * 创建者登录名。 + */ + @TableField(value = "create_login_name") + private String createLoginName; + + /** + * 创建者中文用户名。 + */ + @TableField(value = "create_username") + private String createUsername; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowVariableLog.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowVariableLog.java new file mode 100644 index 0000000..de4fa44 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowVariableLog.java @@ -0,0 +1,61 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 流程变量日志实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_variable_log") +public class FlowVariableLog { + + /** + * 主键Id。 + */ + @TableId(value = "id") + private Long id; + + /** + * 流程定义键。 + */ + @TableField(value = "process_definition_key") + private String processDefinitionKey; + + /** + * 流程实例Id。 + */ + @TableField(value = "process_instance_id") + private String processInstanceId; + + /** + * 流程任务标识。 + */ + @TableField(value = "task_key") + private String taskKey; + + /** + * 全部变量数据。 + */ + @TableField(value = "variable_data") + private String variableData; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + /** + * 过期时间。 + */ + @TableField(value = "expired_time") + private Date expiredTime; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrder.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrder.java new file mode 100644 index 0000000..a8d9c75 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrder.java @@ -0,0 +1,172 @@ +package apelet.common.flow.model; + +import apelet.common.core.annotation.DeptFilterColumn; +import apelet.common.core.annotation.RelationConstDict; +import apelet.common.core.annotation.UserFilterColumn; +import apelet.common.core.base.mapper.BaseModelMapper; +import apelet.common.flow.constant.FlowTaskStatus; +import apelet.common.flow.vo.FlowWorkOrderVo; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Date; +import java.util.Map; + +/** + * 工作流工单实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_work_order") +public class FlowWorkOrder { + + /** + * 主键Id。 + */ + @TableId(value = "work_order_id") + private Long workOrderId; + + /** + * 租户Id。 + */ + @TableField(value = "tenant_id") + private Long tenantId; + + /** + * 应用编码。为空时,表示非第三方应用接入。 + */ + @TableField(value = "app_code") + private String appCode; + + /** + * 工单编码字段。 + */ + @TableField(value = "work_order_code") + private String workOrderCode; + + /** + * 流程定义标识。 + */ + @TableField(value = "process_definition_key") + private String processDefinitionKey; + + /** + * 流程名称。 + */ + @TableField(value = "process_definition_name") + private String processDefinitionName; + + /** + * 流程引擎的定义Id。 + */ + @TableField(value = "process_definition_id") + private String processDefinitionId; + + /** + * 流程实例Id。 + */ + @TableField(value = "process_instance_id") + private String processInstanceId; + + /** + * 在线表单的主表Id。 + */ + @TableField(value = "online_table_id") + private Long onlineTableId; + + /** + * 静态表单所使用的数据表名。 + */ + @TableField(value = "table_name") + private String tableName; + + /** + * 业务主键值。 + */ + @TableField(value = "business_key") + private String businessKey; + + /** + * 最近的审批状态。 + */ + @TableField(value = "latest_approval_status") + private Integer latestApprovalStatus; + + /** + * 流程状态。参考FlowTaskStatus常量值对象。 + */ + @TableField(value = "flow_status") + private Integer flowStatus; + + /** + * 提交用户登录名称。 + */ + @TableField(value = "submit_username") + private String submitUsername; + + /** + * 提交用户所在部门Id。 + */ + @DeptFilterColumn + @TableField(value = "dept_id") + private Long deptId; + + /** + * 更新时间。 + */ + @TableField(value = "update_time") + private Date updateTime; + + /** + * 更新者Id。 + */ + @TableField(value = "update_user_id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + /** + * 创建者Id。 + */ + @UserFilterColumn + @TableField(value = "create_user_id") + private Long createUserId; + + /** + * 逻辑删除标记字段(1: 正常 -1: 已删除)。 + */ + @TableLogic + @TableField(value = "deleted_flag") + private Integer deletedFlag; + + /** + * createTime 范围过滤起始值(>=)。 + */ + @TableField(exist = false) + private String createTimeStart; + + /** + * createTime 范围过滤结束值(<=)。 + */ + @TableField(exist = false) + private String createTimeEnd; + + @RelationConstDict( + masterIdField = "flowStatus", + constantDictClass = FlowTaskStatus.class) + @TableField(exist = false) + private Map flowStatusDictMap; + + @Mapper + public interface FlowWorkOrderModelMapper extends BaseModelMapper { + } + public static final FlowWorkOrderModelMapper INSTANCE = Mappers.getMapper(FlowWorkOrderModelMapper.class); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrderExt.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrderExt.java new file mode 100644 index 0000000..4d571d1 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrderExt.java @@ -0,0 +1,72 @@ +package apelet.common.flow.model; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.util.Date; + +/** + * 工作流工单扩展数据实体对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@TableName(value = "zz_flow_work_order_ext") +public class FlowWorkOrderExt { + + /** + * 主键Id。 + */ + @TableId(value = "id") + private Long id; + + /** + * 流程工单Id。 + */ + @TableField(value = "work_order_id") + private Long workOrderId; + + /** + * 草稿数据。 + */ + @TableField(value = "draft_data") + private String draftData; + + /** + * 业务数据。 + */ + @TableField(value = "business_data") + private String businessData; + + /** + * 更新时间。 + */ + @TableField(value = "update_time") + private Date updateTime; + + /** + * 更新者Id。 + */ + @TableField(value = "update_user_id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @TableField(value = "create_time") + private Date createTime; + + /** + * 创建者Id。 + */ + @TableField(value = "create_user_id") + private Long createUserId; + + /** + * 逻辑删除标记字段(1: 正常 -1: 已删除)。 + */ + @TableLogic + @TableField(value = "deleted_flag") + private Integer deletedFlag; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowBindFormType.java b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowBindFormType.java new file mode 100644 index 0000000..01271f4 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowBindFormType.java @@ -0,0 +1,44 @@ +package apelet.common.flow.model.constant; + +import java.util.HashMap; +import java.util.Map; + +/** + * 工作流绑定表单类型。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowBindFormType { + + /** + * 在线表单。 + */ + public static final int ONLINE_FORM = 0; + /** + * 路由表单。 + */ + public static final int ROUTER_FORM = 1; + + private static final Map DICT_MAP = new HashMap<>(2); + static { + DICT_MAP.put(ONLINE_FORM, "在线表单"); + DICT_MAP.put(ROUTER_FORM, "路由表单"); + } + + /** + * 判断参数是否为当前常量字典的合法值。 + * + * @param value 待验证的参数值。 + * @return 合法返回true,否则false。 + */ + public static boolean isValid(Integer value) { + return value != null && DICT_MAP.containsKey(value); + } + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowBindFormType() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowEntryStatus.java b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowEntryStatus.java new file mode 100644 index 0000000..b99cb30 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowEntryStatus.java @@ -0,0 +1,44 @@ +package apelet.common.flow.model.constant; + +import java.util.HashMap; +import java.util.Map; + +/** + * 工作流状态。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowEntryStatus { + + /** + * 未发布。 + */ + public static final int UNPUBLISHED = 0; + /** + * 已发布。 + */ + public static final int PUBLISHED = 1; + + private static final Map DICT_MAP = new HashMap<>(2); + static { + DICT_MAP.put(UNPUBLISHED, "未发布"); + DICT_MAP.put(PUBLISHED, "已发布"); + } + + /** + * 判断参数是否为当前常量字典的合法值。 + * + * @param value 待验证的参数值。 + * @return 合法返回true,否则false。 + */ + public static boolean isValid(Integer value) { + return value != null && DICT_MAP.containsKey(value); + } + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowEntryStatus() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowMessageOperationType.java b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowMessageOperationType.java new file mode 100644 index 0000000..4b163a0 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowMessageOperationType.java @@ -0,0 +1,21 @@ +package apelet.common.flow.model.constant; + +/** + * 工作流消息操作类型。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowMessageOperationType { + + /** + * 已读操作。 + */ + public static final int READ_FINISHED = 0; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowMessageOperationType() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowMessageType.java b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowMessageType.java new file mode 100644 index 0000000..9e48702 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowMessageType.java @@ -0,0 +1,26 @@ +package apelet.common.flow.model.constant; + +/** + * 工作流消息类型。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowMessageType { + + /** + * 催办消息。 + */ + public static final int REMIND_TYPE = 0; + + /** + * 抄送消息。 + */ + public static final int COPY_TYPE = 1; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowMessageType() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowTaskTimeoutJobStatus.java b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowTaskTimeoutJobStatus.java new file mode 100644 index 0000000..d365ec5 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowTaskTimeoutJobStatus.java @@ -0,0 +1,29 @@ +package apelet.common.flow.model.constant; + +/** + * 超时任务作业状态。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowTaskTimeoutJobStatus { + + /** + * 未执行。 + */ + public static final int NO_EXEC = 0; + /** + * 执行成功。 + */ + public static final int SUCCESS = 1; + /** + * 执行失败。 + */ + public static final int FAIL = 2; + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowTaskTimeoutJobStatus() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowVariableType.java b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowVariableType.java new file mode 100644 index 0000000..6c4dc96 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/model/constant/FlowVariableType.java @@ -0,0 +1,44 @@ +package apelet.common.flow.model.constant; + +import java.util.HashMap; +import java.util.Map; + +/** + * 流程变量类型。 + * + * @author guifc + * @date 2023-08-04 + */ +public final class FlowVariableType { + + /** + * 流程实例变量。 + */ + public static final int INSTANCE = 0; + /** + * 任务变量。 + */ + public static final int TASK = 1; + + private static final Map DICT_MAP = new HashMap<>(2); + static { + DICT_MAP.put(INSTANCE, "流程实例变量"); + DICT_MAP.put(TASK, "任务变量"); + } + + /** + * 判断参数是否为当前常量字典的合法值。 + * + * @param value 待验证的参数值。 + * @return 合法返回true,否则false。 + */ + public static boolean isValid(Integer value) { + return value != null && DICT_MAP.containsKey(value); + } + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowVariableType() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/AnalyzedNode.java b/common/common-flow/src/main/java/apelet/common/flow/object/AnalyzedNode.java new file mode 100644 index 0000000..a74a949 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/AnalyzedNode.java @@ -0,0 +1,86 @@ +package apelet.common.flow.object; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.LinkedList; +import java.util.List; + +/** + * BPMN 执行路径分析后的节点对象。通常表示为一个并行网关、排他网关和开始节点。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class AnalyzedNode { + + /** + * 主流程Id或者子流程Id。 + */ + private String containerId; + /** + * 并行网关的开始节点Id,排他网关的Id,StartEvent节点的Id。 + */ + private String startId; + /** + * 并行网关的结束节点Id,其他场景为NULL。 + */ + private String endId; + /** + * 上级节点的StartId。 + */ + private String parentId; + /** + * 是否为启动任务。 + */ + private boolean startEvent = false; + /** + * 是否为并行网关。 + */ + private boolean parallelGateway = true; + /** + * 只有根节点包含该数据。 + */ + List> allRoads; + /** + * 所有子节点的列表。 + */ + private List childList = new LinkedList<>(); + /** + * 每个节点包含的用户任务的路径列表。 + */ + private List> userTaskRoads = new LinkedList<>(); + /** + * 节点下所有用户任务的列表。 + */ + @JSONField(serialize = false, deserialize = false) + private List allUserTasks = new LinkedList<>(); + /** + * 子流程节点列表。 + */ + List subProcessNodes = new LinkedList<>(); + + @Data + @AllArgsConstructor + public static class UserTaskInfo { + /** + * 任务定义的id。 + */ + private String taskId; + /** + * 是否为多实例任务。 + */ + private boolean isMultiInstance; + /** + * 所属AnalyzedNode对象的startId值。 + */ + private String nodeId; + /** + * 路径索引。 + */ + private int roadIndex; + } +} + diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/FlowApiOption.java b/common/common-flow/src/main/java/apelet/common/flow/object/FlowApiOption.java new file mode 100644 index 0000000..c18cc3e --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/FlowApiOption.java @@ -0,0 +1,18 @@ +package apelet.common.flow.object; + +import lombok.Data; + +/** + * 流程接口的选项对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class FlowApiOption { + + /** + * 是否删除任务超时作业。 + */ + private boolean deleteTimeoutTaskJob = false; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/FlowElementExtProperty.java b/common/common-flow/src/main/java/apelet/common/flow/object/FlowElementExtProperty.java new file mode 100644 index 0000000..0d4b988 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/FlowElementExtProperty.java @@ -0,0 +1,18 @@ +package apelet.common.flow.object; + +import lombok.Data; + +/** + * 流程任务的扩展属性。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class FlowElementExtProperty { + + /** + * 最近的审批状态,该值目前仅仅用于流程线元素,即SequenceElement。 + */ + private Integer latestApprovalStatus; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/FlowEntryExtensionData.java b/common/common-flow/src/main/java/apelet/common/flow/object/FlowEntryExtensionData.java new file mode 100644 index 0000000..66bd5ee --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/FlowEntryExtensionData.java @@ -0,0 +1,37 @@ +package apelet.common.flow.object; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 流程扩展数据对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class FlowEntryExtensionData { + + /** + * 通知类型。 + */ + private List notifyTypes; + /** + * 流程审批状态字典数据列表。Map的key是id和name。 + */ + private List> approvalStatusDict; + /** + * 级联删除业务数据。 + */ + private Boolean cascadeDeleteBusinessData = false; + /** + * 是否支持流程复活。 + */ + private Boolean supportRevive = false; + /** + * 复活数据保留天数。0表示永久保留。 + */ + private Integer keptReviveDays = 0; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/FlowRejectData.java b/common/common-flow/src/main/java/apelet/common/flow/object/FlowRejectData.java new file mode 100644 index 0000000..4be377b --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/FlowRejectData.java @@ -0,0 +1,28 @@ +package apelet.common.flow.object; + +import lombok.Data; + +import java.util.List; + +/** + * 流程驳回数据。主要用于RejectType为1,既重新提交会驳回人的驳回类型。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class FlowRejectData { + + /** + * 驳回用户名。 + */ + private String sourceUser; + /** + * 从哪个任务驳回的任务定义标识。 + */ + private String sourceTaskKey; + /** + * 驳回到的目标任务定义标识。 + */ + private List targetTaskKeys; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskMultiSignAssign.java b/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskMultiSignAssign.java new file mode 100644 index 0000000..5f2d754 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskMultiSignAssign.java @@ -0,0 +1,22 @@ +package apelet.common.flow.object; + +import lombok.Data; + +/** + * 表示多实例任务的指派人信息。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class FlowTaskMultiSignAssign { + + /** + * 指派人类型。参考常量类 UserFilterGroup。 + */ + private String assigneeType; + /** + * 逗号分隔的指派人列表。 + */ + private String assigneeList; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskOperation.java b/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskOperation.java new file mode 100644 index 0000000..6147378 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskOperation.java @@ -0,0 +1,34 @@ +package apelet.common.flow.object; + +import lombok.Data; + +/** + * 流程图中的用户任务操作数据。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class FlowTaskOperation { + + /** + * 操作Id。 + */ + private String id; + /** + * 操作的标签名。 + */ + private String label; + /** + * 操作类型。 + */ + private String type; + /** + * 显示顺序。 + */ + private Integer showOrder; + /** + * 在流程图中定义的多实例会签的指定人员信息。 + */ + private FlowTaskMultiSignAssign multiSignAssignee; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskPostCandidateGroup.java b/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskPostCandidateGroup.java new file mode 100644 index 0000000..b47d9bf --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/FlowTaskPostCandidateGroup.java @@ -0,0 +1,64 @@ +package apelet.common.flow.object; + +import apelet.common.flow.constant.FlowConstant; +import lombok.Data; + +import java.util.LinkedList; +import java.util.List; + +/** + * 流程任务岗位候选组数据。仅用于流程任务的候选组类型为岗位时。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class FlowTaskPostCandidateGroup { + + /** + * 唯一值,目前仅前端使用。 + */ + private String id; + /** + * 岗位类型。 + * 1. 所有部门岗位审批变量,值为 (allDeptPost)。 + * 2. 本部门岗位审批变量,值为 (selfDeptPost)。 + * 3. 上级部门岗位审批变量,值为 (upDeptPost)。 + * 4. 任意部门关联的岗位审批变量,值为 (deptPost)。 + */ + private String type; + /** + * 岗位Id。type为(1,2,3)时使用该值。 + */ + private String postId; + /** + * 部门岗位Id。type为(4)时使用该值。 + */ + private String deptPostId; + + public static List buildCandidateGroupList(List groupDataList) { + List candidateGroupList = new LinkedList<>(); + for (FlowTaskPostCandidateGroup groupData : groupDataList) { + switch (groupData.getType()) { + case FlowConstant.GROUP_TYPE_ALL_DEPT_POST_VAR: + candidateGroupList.add(groupData.getPostId()); + break; + case FlowConstant.GROUP_TYPE_DEPT_POST_VAR: + candidateGroupList.add(groupData.getDeptPostId()); + break; + case FlowConstant.GROUP_TYPE_SELF_DEPT_POST_VAR: + candidateGroupList.add("${" + FlowConstant.SELF_DEPT_POST_PREFIX + groupData.getPostId() + "}"); + break; + case FlowConstant.GROUP_TYPE_SIBLING_DEPT_POST_VAR: + candidateGroupList.add("${" + FlowConstant.SIBLING_DEPT_POST_PREFIX + groupData.getPostId() + "}"); + break; + case FlowConstant.GROUP_TYPE_UP_DEPT_POST_VAR: + candidateGroupList.add("${" + FlowConstant.UP_DEPT_POST_PREFIX + groupData.getPostId() + "}"); + break; + default: + break; + } + } + return candidateGroupList; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/object/FlowUserTaskExtData.java b/common/common-flow/src/main/java/apelet/common/flow/object/FlowUserTaskExtData.java new file mode 100644 index 0000000..b09def7 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/object/FlowUserTaskExtData.java @@ -0,0 +1,63 @@ +package apelet.common.flow.object; + +import lombok.Data; + +import java.util.List; + +/** + * 流程用户任务扩展数据对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +public class FlowUserTaskExtData { + + public static final String NOTIFY_TYPE_MSG = "message"; + public static final String NOTIFY_TYPE_EMAIL = "email"; + + public static final String TIMEOUT_AUTO_COMPLETE = "autoComplete"; + public static final String TIMEOUT_SEND_MSG = "sendMessage"; + + public static final String EMPTY_USER_TO_ASSIGNEE = "toAssignee"; + public static final String EMPTY_USER_AUTO_REJECT = "autoReject"; + public static final String EMPTY_USER_AUTO_COMPLETE = "autoComplete"; + + /** + * 拒绝后再提交,走重新审批。 + */ + public static final String REJECT_TYPE_REDO = "0"; + /** + * 拒绝后再提交,直接回到驳回前的节点。 + */ + public static final String REJECT_TYPE_BACK_TO_SOURCE = "1"; + + /** + * 任务通知类型列表。 + */ + private List flowNotifyTypeList; + /** + * 拒绝后再次提交的审批类型。 + */ + private String rejectType = REJECT_TYPE_REDO; + /** + * 到期提醒的小时数(从待办任务被创建的时候开始计算)。 + */ + private Integer timeoutHours; + /** + * 任务超时的处理方式。 + */ + private String timeoutHandleWay; + /** + * 默认审批人。 + */ + private String defaultAssignee; + /** + * 空用户审批处理方式。 + */ + private String emptyUserHandleWay; + /** + * 空用户审批时设定的审批人。 + */ + private String emptyUserToAssignee; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java new file mode 100644 index 0000000..b71f6b2 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java @@ -0,0 +1,637 @@ +package apelet.common.flow.service; + +import apelet.common.core.object.*; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.object.AnalyzedNode; +import apelet.common.flow.object.FlowApiOption; +import apelet.common.flow.vo.FlowTaskVo; +import com.alibaba.fastjson.JSONObject; +import org.flowable.bpmn.model.*; +import org.flowable.engine.delegate.ExecutionListener; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.history.HistoricActivityInstance; +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.TaskInfo; +import org.flowable.task.api.history.HistoricTaskInstance; + +import javax.xml.stream.XMLStreamException; +import java.text.ParseException; +import java.util.*; + +/** + * 流程引擎API的接口封装服务。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowApiService { + + /** + * 启动流程实例。 + * + * @param processDefinitionId 流程定义Id。 + * @param dataId 业务主键Id。 + * @return 新启动的流程实例。 + */ + ProcessInstance start(String processDefinitionId, Object dataId); + + /** + * 复活流程实例。 + * + * @param processInstanceId 流程实例Id。 + * @param taskKeys 复活后的任务标识集合。 + * @param taskComment 流程复活备注。 + * @return 新复活的流程实例。 + */ + ProcessInstance revive(String processInstanceId, List taskKeys, String taskComment); + + /** + * 完成第一个用户任务。 + * + * @param processInstanceId 流程实例Id。 + * @param flowTaskComment 审批对象。 + * @param taskVariableData 流程任务的变量数据。 + * @return 新完成的任务对象。 + */ + Task takeFirstTask(String processInstanceId, FlowTaskComment flowTaskComment, JSONObject taskVariableData); + + /** + * 启动流程实例,如果当前登录用户为第一个用户任务的指派者,或者Assginee为流程启动人变量时, + * 则自动完成第一个用户任务。 + * + * @param processDefinitionId 流程定义Id。 + * @param dataId 当前流程主表的主键数据。 + * @param flowTaskComment 审批对象。 + * @param taskVariableData 流程任务的变量数据。 + * @return 新启动的流程实例。 + */ + ProcessInstance startAndTakeFirst( + String processDefinitionId, Object dataId, FlowTaskComment flowTaskComment, JSONObject taskVariableData); + + /** + * 多实例加签减签。 + * + * @param startTaskInstance 会签对象的发起任务实例。 + * @param multiInstanceActiveTask 正在执行的多实例任务对象。 + * @param newAssignees 新指派人,多个指派人之间逗号分隔。 + * @param isAdd 是否为加签。 + */ + void submitConsign(HistoricTaskInstance startTaskInstance, Task multiInstanceActiveTask, String newAssignees, boolean isAdd); + + /** + * 完成任务,同时提交审批数据。 + * + * @param task 工作流任务对象。 + * @param flowTaskComment 审批对象。 + * @param taskVariableData 流程任务的变量数据。 + * @param option 操作选项。 + */ + void completeTask(Task task, FlowTaskComment flowTaskComment, JSONObject taskVariableData, FlowApiOption option); + + /** + * 判断当前登录用户是否为流程实例中的用户任务的指派人。或是候选人之一,如果是候选人则拾取该任务并成为指派人。 + * 如果都不是,就会返回具体的错误信息。 + * + * @param task 流程实例中的用户任务。 + * @return 调用结果。 + */ + CallResult verifyAssigneeOrCandidateAndClaim(Task task); + + /** + * 初始化并返回流程实例的变量Map。 + * + * @param processDefinitionId 流程定义Id。 + * @return 初始化后的流程实例变量Map。 + */ + Map initAndGetProcessInstanceVariables(String processDefinitionId); + + /** + * 判断当前登录用户是否为流程实例中的用户任务的指派人。或是候选人之一。 + * + * @param task 流程实例中的用户任务。 + * @return 是返回true,否则false。 + */ + boolean isAssigneeOrCandidate(TaskInfo task); + + /** + * 获取指定流程定义的全部流程节点。 + * + * @param processDefinitionId 流程定义Id。 + * @return 当前流程定义的全部节点集合。 + */ + Collection getProcessAllElements(String processDefinitionId); + + /** + * 判断当前登录用户是否为流程实例的发起人。 + * + * @param processInstanceId 流程实例Id。 + * @return 是返回true,否则false。 + */ + boolean isProcessInstanceStarter(String processInstanceId); + + /** + * 为流程实例设置BusinessKey。 + * + * @param processInstanceId 流程实例Id。 + * @param dataId 通常为主表的主键Id。 + */ + void setBusinessKeyForProcessInstance(String processInstanceId, Object dataId); + + /** + * 判断指定的流程实例Id是否存在。 + * + * @param processInstanceId 流程实例Id。 + * @return 存在返回true,否则false。 + */ + boolean existActiveProcessInstance(String processInstanceId); + + /** + * 获取指定的流程实例对象。 + * + * @param processInstanceId 流程实例Id。 + * @return 流程实例对象。 + */ + ProcessInstance getProcessInstance(String processInstanceId); + + /** + * 获取指定的流程实例对象。 + * + * @param processDefinitionId 流程定义Id。 + * @param businessKey 业务主键Id。 + * @return 流程实例对象。 + */ + ProcessInstance getProcessInstanceByBusinessKey(String processDefinitionId, String businessKey); + + /** + * 获取流程实例的列表。 + * + * @param processInstanceIdSet 流程实例Id集合。 + * @return 流程实例列表。 + */ + List getProcessInstanceList(Set processInstanceIdSet); + + /** + * 根据流程定义Id查询流程定义对象。 + * + * @param processDefinitionId 流程定义Id。 + * @return 流程定义对象。 + */ + ProcessDefinition getProcessDefinitionById(String processDefinitionId); + + /** + * 根据流程部署Id查询流程定义对象。 + * + * @param deployId 流程部署Id。 + * @return 流程定义对象。 + */ + ProcessDefinition getProcessDefinitionByDeployId(String deployId); + + /** + * 获取流程定义的列表。 + * + * @param processDefinitionIdSet 流程定义Id集合。 + * @return 流程定义列表。 + */ + List getProcessDefinitionList(Set processDefinitionIdSet); + + /** + * 挂起流程定义对象。 + * + * @param processDefinitionId 流程定义Id。 + */ + void suspendProcessDefinition(String processDefinitionId); + + /** + * 激活流程定义对象。 + * + * @param processDefinitionId 流程定义Id。 + */ + void activateProcessDefinition(String processDefinitionId); + + /** + * 获取指定流程定义的BpmnModel。 + * + * @param processDefinitionId 流程定义Id。 + * @return 关联的BpmnModel。 + */ + BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); + + /** + * 判断任务是否为多实例任务。 + * + * @param processDefinitionId 流程定义Id。 + * @param taskKey 流程任务标识。 + * @return true为多实例,否则false。 + */ + boolean isMultiInstanceTask(String processDefinitionId, String taskKey); + + /** + * 设置流程实例的变量集合。 + * + * @param processInstanceId 流程实例Id。 + * @param variableMap 变量名。 + */ + void setProcessInstanceVariables(String processInstanceId, Map variableMap); + + /** + * 获取流程实例的变量。 + * + * @param processInstanceId 流程实例Id。 + * @param variableName 变量名。 + * @return 变量值。 + */ + Object getProcessInstanceVariable(String processInstanceId, String variableName); + + /** + * 获取指定流程实例和任务Id的当前活动任务。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @return 当前流程实例的活动任务。 + */ + Task getProcessInstanceActiveTask(String processInstanceId, String taskId); + + /** + * 获取指定流程实例的当前活动任务列表。 + * + * @param processInstanceId 流程实例Id。 + * @return 当前流程实例的活动任务。 + */ + List getProcessInstanceActiveTaskList(String processInstanceId); + + /** + * 根据任务Id,获取当前运行时任务。 + * + * @param taskId 任务Id。 + * @return 运行时任务对象。 + */ + Task getTaskById(String taskId); + + /** + * 获取用户的任务列表。这其中包括当前用户作为指派人和候选人。 + * + * @param username 指派人。 + * @param definitionKey 流程定义的标识。 + * @param definitionName 流程定义名。 + * @param taskName 任务名称。 + * @param pageParam 分页对象。 + * @return 用户的任务列表。 + */ + MyPageData getTaskListByUserName( + String username, String definitionKey, String definitionName, String taskName, MyPageParam pageParam); + + /** + * 获取用户的任务数量。这其中包括当前用户作为指派人和候选人。 + * + * @param username 指派人。 + * @return 用户的任务数量。 + */ + long getTaskCountByUserName(String username); + + /** + * 获取流程实例Id集合的运行时任务列表。 + * + * @param processInstanceIdSet 流程实例Id集合。 + * @return 运行时任务列表。 + */ + List getTaskListByProcessInstanceIds(List processInstanceIdSet); + + /** + * 将流程任务列表数据,转换为前端可以显示的流程对象。 + * + * @param taskList 流程引擎中的任务列表。 + * @return 前端可以显示的流程任务列表。 + */ + List convertToFlowTaskList(List taskList); + + /** + * 添加流程实例结束的监听器。 + * + * @param bpmnModel 流程模型。 + * @param listenerClazz 流程监听器的Class对象。 + */ + void addProcessInstanceEndListener(BpmnModel bpmnModel, Class listenerClazz); + + /** + * 添加流程任务的执行监听器。 + * + * @param flowElement 指定任务节点。 + * @param listenerClazz 执行监听器。 + * @param event 事件。 + * @param fieldExtensions 执行监听器的扩展变量列表。 + */ + void addExecutionListener( + FlowElement flowElement, + Class listenerClazz, + String event, + List fieldExtensions); + + /** + * 添加流程任务创建的任务监听器。 + * + * @param userTask 用户任务。 + * @param listenerClazz 任务监听器。 + */ + void addTaskCreateListener(UserTask userTask, Class listenerClazz); + + + /** + * 根据事件类型添加流程任务创建的任务监听器。 + * + * @param userTask 用户任务。 + * @param listenerClazz 任务监听器。 + * @param event 事件类型 + */ + void addTaskListenerByEvent(UserTask userTask, Class listenerClazz, String event); + + /** + * 获取流程实例的历史流程实例。 + * + * @param processInstanceId 流程实例Id。 + * @return 历史流程实例。 + */ + HistoricProcessInstance getHistoricProcessInstance(String processInstanceId); + + /** + * 获取流程实例的历史流程实例列表。 + * + * @param processInstanceIdSet 流程实例Id集合。 + * @return 历史流程实例列表。 + */ + List getHistoricProcessInstanceList(Set processInstanceIdSet); + + /** + * 查询历史流程实例的列表。 + * + * @param processDefinitionKey 流程标识名。 + * @param processDefinitionName 流程名。 + * @param startUser 流程发起用户。 + * @param beginDate 流程发起开始时间。 + * @param endDate 流程发起结束时间。 + * @param pageParam 分页对象。 + * @param finishedOnly 仅仅返回已经结束的流程。 + * @return 分页后的查询列表对象。 + * @throws ParseException 日期参数解析失败。 + */ + MyPageData getHistoricProcessInstanceList( + String processDefinitionKey, + String processDefinitionName, + String startUser, + String beginDate, + String endDate, + MyPageParam pageParam, + boolean finishedOnly) throws ParseException; + + /** + * 获取流程实例的已完成历史任务列表。 + * + * @param processInstanceId 流程实例Id。 + * @return 流程实例已完成的历史任务列表。 + */ + List getHistoricActivityInstanceList(String processInstanceId); + + /** + * 获取流程实例的已完成历史任务列表,同时按照每个活动实例的开始时间升序排序。 + * + * @param processInstanceId 流程实例Id。 + * @return 流程实例已完成的历史任务列表。 + */ + List getHistoricActivityInstanceListOrderByStartTime(String processInstanceId); + + /** + * 获取当前用户的历史已办理任务列表。 + * + * @param processDefinitionName 流程名。 + * @param beginDate 流程发起开始时间。 + * @param endDate 流程发起结束时间。 + * @param pageParam 分页对象。 + * @return 分页后的查询列表对象。 + * @throws ParseException 日期参数解析失败。 + */ + MyPageData getHistoricTaskInstanceFinishedList( + String processDefinitionName, + String beginDate, + String endDate, + MyPageParam pageParam) throws ParseException; + + /** + * 获取指定的历史任务实例。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 任务Id。 + * @return 历史任务实例。 + */ + HistoricTaskInstance getHistoricTaskInstance(String processInstanceId, String taskId); + + /** + * 获取流程实例的待完成任务列表。 + * + * @param processInstanceId 流程实例Id。 + * @return 流程实例待完成的任务列表。 + */ + List getHistoricUnfinishedInstanceList(String processInstanceId); + + /** + * 终止流程实例,将任务从当前节点直接流转到主流程的结束事件。 + * + * @param processInstanceId 流程实例Id。 + * @param stopReason 停止原因。 + * @param forCancel 是否由取消工单触发。 + * @return 执行结果。 + */ + CallResult stopProcessInstance(String processInstanceId, String stopReason, boolean forCancel); + + /** + * 终止流程实例,将任务从当前节点直接流转到主流程的结束事件。 + * + * @param processInstanceId 流程实例Id。 + * @param stopReason 停止原因。 + * @param status 流程状态。 + * @return 执行结果。 + */ + CallResult stopProcessInstance(String processInstanceId, String stopReason, int status); + + /** + * 删除流程实例。 + * + * @param processInstanceId 流程实例Id。 + */ + void deleteProcessInstance(String processInstanceId); + + /** + * 获取任务的指定本地变量。 + * + * @param taskId 任务Id。 + * @param variableName 变量名。 + * @return 变量值。 + */ + Object getTaskVariable(String taskId, String variableName); + + /** + * 安全的获取任务变量,并返回字符型的变量值。 + * + * @param taskId 任务Id。 + * @param variableName 变量名。 + * @return 返回变量值的字符串形式,如果变量不存在不会抛异常,返回null。 + */ + String getTaskVariableStringWithSafe(String taskId, String variableName); + + /** + * 获取任务执行时的指定本地变量。 + * + * @param executionId 任务执行时Id。 + * @param variableName 变量名。 + * @return 变量值。 + */ + Object getExecutionVariable(String executionId, String variableName); + + /** + * 安全的获取任务执行时变量,并返回字符型的变量值。 + * + * @param executionId 任务执行时Id。 + * @param variableName 变量名。 + * @return 返回变量值的字符串形式,如果变量不存在不会抛异常,返回null。 + */ + String getExecutionVariableStringWithSafe(String executionId, String variableName); + + /** + * 获取历史流程变量。 + * + * @param processInstanceId 流程实例Id。 + * @param variableName 变量名。 + * @return 获取历史流程变量。 + */ + Object getHistoricProcessInstanceVariable(String processInstanceId, String variableName); + + /** + * 将xml格式的流程模型字符串,转换为标准的流程模型。 + * + * @param bpmnXml xml格式的流程模型字符串。 + * @return 转换后的标准的流程模型。 + * @throws XMLStreamException XML流处理异常 + */ + BpmnModel convertToBpmnModel(String bpmnXml) throws XMLStreamException; + + /** + * 回退到指定用户任务节点。如果没有指定,则回退到上一个任务。 + * + * @param task 当前活动任务。 + * @param targetKey 指定回退到的任务标识。如果为null,则回退到上一个任务。 + * @param backType 回退类型。 + * @param reason 驳回或者撤销的原因。 + * @param taskVariableData 流程任务变量。 + * @return 回退结果。 + */ + CallResult backToRuntimeTask( + Task task, String targetKey, Integer backType, String reason, JSONObject taskVariableData); + + /** + * 回退到指定用户任务节点。同时将回退任务的候选人改为指定人。 + * + * @param task 当前活动任务。 + * @param targetKey 指定回退到的任务标识。如果为null,则回退到上一个任务。 + * @param backType 回退类型。 + * @param reason 驳回或者撤销的原因。 + * @param delegateAssignee 转办人,多人之间逗号分割。 + * @return 回退结果。 + */ + CallResult backToRuntimeTaskAndTransfer( + Task task, String targetKey, Integer backType, String reason, String delegateAssignee); + + /** + * 根据流程任务,获取可以回退的用户任务列表。 + * + * @param task 流程任务。 + * @return 可以回退的用户任务列表。 + */ + List getRejectCandidateUserTaskList(Task task); + + /** + * 转办任务给他人。 + * + * @param task 流程任务。 + * @param flowTaskComment 审批对象。 + */ + void transferTo(Task task, FlowTaskComment flowTaskComment); + + /** + * 获取当前任务在流程图中配置候选用户组数据。 + * + * @param flowTaskExt 流程任务扩展对象。 + * @param taskId 运行时任务Id。 + * @return 候选用户组数据。 + */ + List getCandidateUsernames(FlowTaskExt flowTaskExt, String taskId); + + /** + * 获取当前任务在流程图中配置到的部门岗位Id集合和岗位Id集合。 + * + * @param flowTaskExt 流程任务扩展对象。 + * @param processInstanceId 流程实例Id。 + * @param historic 是否为历史任务。 + * @return first为部门岗位Id集合,second是岗位Id集合。 + */ + Tuple2, Set> getDeptPostIdAndPostIds( + FlowTaskExt flowTaskExt, String processInstanceId, boolean historic); + + /** + * 分析指定流程部署Id的流程图。 + * + * @param processDefinitionId 流程定义Id。 + * @return 分析结果根对象。 + */ + AnalyzedNode analyzeBpmnRoads(String processDefinitionId); + + /** + * 自由跳转。 + * + * @param task 当前的待办任务。 + * @param targetKey 目标任务的Key定义。 + * @param comment 跳转说明。 + * @param delegateAssignee 指派人。 + * @return 跳转结果,如果失败,会返回详细的错误信息。 + */ + CallResult freeJumpTo(Task task, String targetKey, String comment, String delegateAssignee); + + /** + * 流程干预。 + * + * @param task 当前的待办任务。 + * @param targetKey 目标任务的Key定义。 + * @param comment 干预说明。 + * @param delegateAssignee 指派人。 + * @return 干预结果,如果失败,会返回详细的错误信息。 + */ + CallResult interveneTo(Task task, String targetKey, String comment, String delegateAssignee); + + /** + * 获取指定任务的运行时子任务Id列表。 + * + * @param processDefinitionId 流程定义Id。 + * @param taskKey 流程定义标识。 + * @param allActivitiIds 所有活动任务的定义Id列表 + * @return allActivitiIds中属于taskKey的子任务的Id列表。 + */ + List getChildActivitiIdList(String processDefinitionId, String taskKey, List allActivitiIds); + + /** + * 获取流程图中所有用户任务的映射。 + * + * @param processDefinitionId 流程定义Id。 + * @return 流程图中所有用户任务的映射。 + */ + Map getAllUserTaskMap(String processDefinitionId); + + /** + * 获取流程图中指定的用户任务。 + * + * @param processDefinitionId 流程定义Id。 + * @param taskKey 用户任务标识。 + * @return 用户任务。 + */ + UserTask getUserTask(String processDefinitionId, String taskKey); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowCategoryService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowCategoryService.java new file mode 100644 index 0000000..62ee5b9 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowCategoryService.java @@ -0,0 +1,69 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowCategory; + +import java.util.List; + +/** + * FlowCategory数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowCategoryService extends IBaseService { + + /** + * 保存新增对象。 + * + * @param flowCategory 新增对象。 + * @return 返回新增对象。 + */ + FlowCategory saveNew(FlowCategory flowCategory); + + /** + * 更新数据对象。 + * + * @param flowCategory 更新的对象。 + * @param originalFlowCategory 原有数据对象。 + * @return 成功返回true,否则false。 + */ + boolean update(FlowCategory flowCategory, FlowCategory originalFlowCategory); + + /** + * 删除指定数据。 + * + * @param categoryId 主键Id。 + * @return 成功返回true,否则false。 + */ + boolean remove(Long categoryId); + + /** + * 获取单表查询结果。由于没有关联数据查询,因此在仅仅获取单表数据的场景下,效率更高。 + * 如果需要同时获取关联数据,请移步(getFlowCategoryListWithRelation)方法。 + * + * @param filter 过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + List getFlowCategoryList(FlowCategory filter, String orderBy); + + /** + * 获取主表的查询结果,以及主表关联的字典数据和一对一从表数据,以及一对一从表的字典数据。 + * 该查询会涉及到一对一从表的关联过滤,或一对多从表的嵌套关联过滤,因此性能不如单表过滤。 + * 如果仅仅需要获取主表数据,请移步(getFlowCategoryList),以便获取更好的查询性能。 + * + * @param filter 主表过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + List getFlowCategoryListWithRelation(FlowCategory filter, String orderBy); + + /** + * 当前流程分类编码是否存在。 + * + * @param code 流程分类编码。 + * @return true存在,否则false。 + */ + boolean existByCode(String code); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowEntryService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowEntryService.java new file mode 100644 index 0000000..a21c89f --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowEntryService.java @@ -0,0 +1,134 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowEntry; +import apelet.common.flow.model.FlowEntryPublish; + +import javax.xml.stream.XMLStreamException; +import java.util.List; +import java.util.Set; + +/** + * FlowEntry数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowEntryService extends IBaseService { + + /** + * 保存新增对象。 + * + * @param flowEntry 新增工作流对象。 + * @return 返回新增对象。 + */ + FlowEntry saveNew(FlowEntry flowEntry); + + /** + * 发布指定流程。 + * + * @param flowEntry 待发布的流程对象。 + * @param initTaskInfo 第一个非开始节点任务的附加信息。 + * @throws XMLStreamException 解析bpmn.xml的异常。 + */ + void publish(FlowEntry flowEntry, String initTaskInfo) throws XMLStreamException; + + /** + * 更新数据对象。 + * + * @param flowEntry 更新的对象。 + * @param originalFlowEntry 原有数据对象。 + * @return 成功返回true,否则false。 + */ + boolean update(FlowEntry flowEntry, FlowEntry originalFlowEntry); + + /** + * 删除指定数据。 + * + * @param entryId 主键Id。 + * @return 成功返回true,否则false。 + */ + boolean remove(Long entryId); + + /** + * 获取单表查询结果。由于没有关联数据查询,因此在仅仅获取单表数据的场景下,效率更高。 + * 如果需要同时获取关联数据,请移步(getFlowEntryListWithRelation)方法。 + * + * @param filter 过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + List getFlowEntryList(FlowEntry filter, String orderBy); + + /** + * 获取主表的查询结果,以及主表关联的字典数据和一对一从表数据,以及一对一从表的字典数据。 + * 该查询会涉及到一对一从表的关联过滤,或一对多从表的嵌套关联过滤,因此性能不如单表过滤。 + * 如果仅仅需要获取主表数据,请移步(getFlowEntryList),以便获取更好的查询性能。 + * + * @param filter 主表过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + List getFlowEntryListWithRelation(FlowEntry filter, String orderBy); + + /** + * 根据流程定义标识获取流程对象。从缓存中读取,如不存在则从数据库读取后,再同步到缓存。 + * + * @param processDefinitionKey 流程定义标识。 + * @return 流程对象。 + */ + FlowEntry getFlowEntryFromCache(String processDefinitionKey); + + /** + * 根据流程Id获取流程发布列表数据。 + * + * @param entryId 流程Id。 + * @return 流程关联的发布列表数据。 + */ + List getFlowEntryPublishList(Long entryId); + + /** + * 根据流程引擎中的流程定义Id集合,查询流程发布对象。 + * + * @param processDefinitionIdSet 流程引擎中的流程定义Id集合。 + * @return 查询结果。 + */ + List getFlowEntryPublishList(Set processDefinitionIdSet); + + /** + * 获取指定工作流发布版本对象。从缓存中读取,如缓存中不存在,从数据库读取并同步缓存。 + * + * @param entryPublishId 工作流发布对象Id。 + * @return 查询后的对象。 + */ + FlowEntryPublish getFlowEntryPublishFromCache(Long entryPublishId); + + /** + * 为指定工作流更新发布的主版本。 + * + * @param flowEntry 工作流对象。 + * @param newMainFlowEntryPublish 工作流新的发布主版本对象。 + */ + void updateFlowEntryMainVersion(FlowEntry flowEntry, FlowEntryPublish newMainFlowEntryPublish); + + /** + * 挂起指定的工作流发布对象。 + * + * @param flowEntryPublish 待挂起的工作流发布对象。 + */ + void suspendFlowEntryPublish(FlowEntryPublish flowEntryPublish); + + /** + * 激活指定的工作流发布对象。 + * + * @param flowEntryPublish 待恢复的工作流发布对象。 + */ + void activateFlowEntryPublish(FlowEntryPublish flowEntryPublish); + + /** + * 判断指定流程定义标识是否存在。 + * @param processDefinitionKey 流程定义标识。 + * @return true存在,否则false。 + */ + boolean existByProcessDefinitionKey(String processDefinitionKey); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowEntryVariableService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowEntryVariableService.java new file mode 100644 index 0000000..89aa581 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowEntryVariableService.java @@ -0,0 +1,68 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowEntryVariable; + +import java.util.List; + +/** + * 流程变量数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowEntryVariableService extends IBaseService { + + /** + * 保存新增对象。 + * + * @param flowEntryVariable 新增对象。 + * @return 返回新增对象。 + */ + FlowEntryVariable saveNew(FlowEntryVariable flowEntryVariable); + + /** + * 更新数据对象。 + * + * @param flowEntryVariable 更新的对象。 + * @param originalFlowEntryVariable 原有数据对象。 + * @return 成功返回true,否则false。 + */ + boolean update(FlowEntryVariable flowEntryVariable, FlowEntryVariable originalFlowEntryVariable); + + /** + * 删除指定数据。 + * + * @param variableId 主键Id。 + * @return 成功返回true,否则false。 + */ + boolean remove(Long variableId); + + /** + * 删除指定流程Id的所有变量。 + * + * @param entryId 流程Id。 + */ + void removeByEntryId(Long entryId); + + /** + * 获取单表查询结果。由于没有关联数据查询,因此在仅仅获取单表数据的场景下,效率更高。 + * 如果需要同时获取关联数据,请移步(getFlowEntryVariableListWithRelation)方法。 + * + * @param filter 过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + List getFlowEntryVariableList(FlowEntryVariable filter, String orderBy); + + /** + * 获取主表的查询结果,以及主表关联的字典数据和一对一从表数据,以及一对一从表的字典数据。 + * 该查询会涉及到一对一从表的关联过滤,或一对多从表的嵌套关联过滤,因此性能不如单表过滤。 + * 如果仅仅需要获取主表数据,请移步(getFlowEntryVariableList),以便获取更好的查询性能。 + * + * @param filter 主表过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + List getFlowEntryVariableListWithRelation(FlowEntryVariable filter, String orderBy); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowMessageService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowMessageService.java new file mode 100644 index 0000000..8bfccd0 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowMessageService.java @@ -0,0 +1,113 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowMessage; +import apelet.common.flow.model.FlowWorkOrder; +import com.alibaba.fastjson.JSONObject; +import org.flowable.task.api.Task; + +import java.util.List; + +/** + * 工作流消息数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowMessageService extends IBaseService { + + /** + * 保存新增对象。 + * + * @param flowMessage 新增对象。 + * @return 保存后的消息对象。 + */ + FlowMessage saveNew(FlowMessage flowMessage); + + /** + * 根据工单参数,保存催单消息对象。如果当前工单存在多个待办任务,则插入多条催办消息数据。 + * + * @param flowWorkOrder 待催办的工单。 + */ + void saveNewRemindMessage(FlowWorkOrder flowWorkOrder); + + /** + * 保存抄送消息对象。 + * + * @param task 待抄送的任务。 + * @param copyDataJson 抄送人员或者组的Id数据。 + */ + void saveNewCopyMessage(Task task, JSONObject copyDataJson); + + /** + * 更新指定运行时任务Id的消费为已完成状态。 + * + * @param taskId 运行时任务Id。 + */ + void updateFinishedStatusByTaskId(String taskId); + + /** + * 更新指定流程实例Id的消费为已完成状态。 + * + * @param processInstanceId 流程实例IdId。 + */ + void updateFinishedStatusByProcessInstanceId(String processInstanceId); + + /** + * 获取当前用户的催办消息列表。 + * + * @return 查询后的催办消息列表。 + */ + List getRemindingMessageListByUser(); + + /** + * 获取当前用户的抄送消息列表。 + * + * @param read true表示已读,false表示未读。 + * @return 查询后的抄送消息列表。 + */ + List getCopyMessageListByUser(Boolean read); + + /** + * 判断当前用户是否有权限访问指定消息Id。 + * + * @param messageId 消息Id。 + * @return true为合法访问者,否则false。 + */ + boolean isCandidateIdentityOnMessage(Long messageId); + + /** + * 读取抄送消息,同时更新当前用户对指定抄送消息的读取状态。 + * + * @param messageId 消息Id。 + */ + void readCopyTask(Long messageId); + + /** + * 计算当前用户催办消息的数量。 + * + * @return 当前用户催办消息数量。 + */ + int countRemindingMessageListByUser(); + + /** + * 计算当前用户未读抄送消息的数量。 + * + * @return 当前用户未读抄送消息数量。 + */ + int countCopyMessageByUser(); + + /** + * 计算当前用户未读系统消息的数量。 + * + * @return 当前用户未读系统消息数量。 + */ + int countSysMessageByUser(); + + /** + * 删除指定流程实例的消息。 + * + * @param processInstanceId 流程实例Id。 + */ + void removeByProcessInstanceId(String processInstanceId); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowMultiInstanceTransService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowMultiInstanceTransService.java new file mode 100644 index 0000000..68a6dd7 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowMultiInstanceTransService.java @@ -0,0 +1,38 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowMultiInstanceTrans; + +/** + * 会签任务操作流水数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowMultiInstanceTransService extends IBaseService { + + /** + * 保存新增对象。 + * + * @param flowMultiInstanceTrans 新增对象。 + * @return 返回新增对象。 + */ + FlowMultiInstanceTrans saveNew(FlowMultiInstanceTrans flowMultiInstanceTrans); + + /** + * 根据流程执行Id获取对象。 + * + * @param executionId 流程执行Id。 + * @param taskId 执行任务Id。 + * @return 数据对象。 + */ + FlowMultiInstanceTrans getByExecutionId(String executionId, String taskId); + + /** + * 根据多实例的统一执行Id,获取assgineeList字段不为空的数据。 + * + * @param multiInstanceExecId 多实例统一执行Id。 + * @return 数据对象。 + */ + FlowMultiInstanceTrans getWithAssigneeListByMultiInstanceExecId(String multiInstanceExecId); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowNodeTasksService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowNodeTasksService.java new file mode 100644 index 0000000..a0fc2a4 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowNodeTasksService.java @@ -0,0 +1,9 @@ +package apelet.common.flow.service; + + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowNodeTasks; + +public interface FlowNodeTasksService extends IBaseService { + +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskCommentService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskCommentService.java new file mode 100644 index 0000000..92b06b0 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskCommentService.java @@ -0,0 +1,84 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowTaskComment; + +import java.util.List; +import java.util.Set; + +/** + * 流程任务批注数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowTaskCommentService extends IBaseService { + + /** + * 保存新增对象。 + * + * @param flowTaskComment 新增对象。 + * @return 返回新增对象。 + */ + FlowTaskComment saveNew(FlowTaskComment flowTaskComment); + + /** + * 查询指定流程实例Id下的所有审批任务的批注。 + * + * @param processInstanceId 流程实例Id。 + * @return 查询结果集。 + */ + List getFlowTaskCommentList(String processInstanceId); + + /** + * 查询与指定流程任务Id集合关联的所有审批任务的批注。 + * + * @param taskIdSet 流程任务Id集合。 + * @return 查询结果集。 + */ + List getFlowTaskCommentListByTaskIds(Set taskIdSet); + + /** + * 获取指定流程实例的最后一条审批任务。 + * + * @param processInstanceId 流程实例Id。 + * @return 查询结果。 + */ + FlowTaskComment getLatestFlowTaskComment(String processInstanceId); + + /** + * 获取指定流程实例和任务定义标识的最后一条审批任务。 + * + * @param processInstanceId 流程实例Id。 + * @param taskDefinitionKey 任务定义标识。 + * @return 查询结果。 + */ + FlowTaskComment getLatestFlowTaskComment(String processInstanceId, String taskDefinitionKey); + + /** + * 获取指定流程实例的第一条审批任务。 + * + * @param processInstanceId 流程实例Id。 + * @return 查询结果。 + */ + FlowTaskComment getFirstFlowTaskComment(String processInstanceId); + + /** + * 获取指定任务实例和执行批次的审批数据列表。 + * + * @param processInstanceId 流程实例。 + * @param taskId 任务Id + * @param executionId 任务执行Id + * @return 审批数据列表。 + */ + List getFlowTaskCommentListByExecutionId( + String processInstanceId, String taskId, String executionId); + + /** + * 根据多实例执行Id获取任务审批对象数据列表。 + * + * @param multiInstanceExecId 多实例执行Id。 + * @return 审批数据列表。 + */ + List getFlowTaskCommentListByMultiInstanceExecId(String multiInstanceExecId); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java new file mode 100644 index 0000000..0fbe843 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java @@ -0,0 +1,121 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.object.FlowElementExtProperty; +import apelet.common.flow.vo.FlowUserInfoVo; +import com.alibaba.fastjson.JSONObject; +import org.flowable.bpmn.model.*; +import org.flowable.task.api.TaskInfo; + +import java.util.List; +import java.util.Map; + +/** + * 流程任务扩展数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowTaskExtService extends IBaseService { + + /** + * 批量插入流程任务扩展信息列表。 + * + * @param flowTaskExtList 流程任务扩展信息列表。 + */ + void saveBatch(List flowTaskExtList); + + /** + * 查询指定的流程任务扩展对象。 + * + * @param processDefinitionId 流程引擎的定义Id。 + * @param taskId 流程引擎的任务Id。 + * @return 查询结果。 + */ + FlowTaskExt getByProcessDefinitionIdAndTaskId(String processDefinitionId, String taskId); + + /** + * 查询指定的流程定义的任务扩展对象。 + * + * @param processDefinitionId 流程引擎的定义Id。 + * @return 查询结果。 + */ + List getByProcessDefinitionId(String processDefinitionId); + + /** + * 获取任务扩展信息中的候选人用户信息列表。 + * + * @param processInstanceId 流程引擎的实例Id。 + * @param flowTaskExt 任务扩展对象。 + * @param taskInfo 任务信息。 + * @param isMultiInstanceTask 是否为多实例任务。 + * @param historic 是否为历史任务。 + * @return 候选人用户信息列表。 + */ + List getCandidateUserInfoList( + String processInstanceId, + FlowTaskExt flowTaskExt, + TaskInfo taskInfo, + boolean isMultiInstanceTask, + boolean historic); + + /** + * 获取指定任务的用户列表信息。 + * + * @param processInstanceId 流程实例。 + * @param executionId 执行实例。 + * @param flowTaskExt 流程用户任务的扩展对象。 + * @return 候选人用户信息列表。 + */ + List getCandidateUserInfoList( + String processInstanceId, + String executionId, + FlowTaskExt flowTaskExt); + + /** + * 通过UserTask对象中的扩展节点信息,构建FLowTaskExt对象。 + * + * @param userTask 流程图中定义的用户任务对象。 + * @return 构建后的流程任务扩展信息对象。 + */ + FlowTaskExt buildTaskExtByUserTask(UserTask userTask); + + /** + * 获取指定流程图中所有UserTask对象的扩展节点信息,构建FLowTaskExt对象列表。 + * + * @param bpmnModel 流程图模型对象。 + * @return 当前流程图中所有用户流程任务的扩展信息对象列表。 + */ + List buildTaskExtList(BpmnModel bpmnModel); + + /** + * 根据流程定义中用户任务的扩展节点数据,构建出前端所需的操作列表数据对象。 + * @param extensionMap 用户任务的扩展节点。 + * @return 前端所需的操作列表数据对象。 + */ + List buildOperationListExtensionElement(Map> extensionMap); + + /** + * 根据流程定义中用户任务的扩展节点数据,构建出前端所需的变量列表数据对象。 + * @param extensionMap 用户任务的扩展节点。 + * @return 前端所需的变量列表数据对象。 + */ + List buildVariableListExtensionElement(Map> extensionMap); + + /** + * 读取流程定义中,流程元素的扩展属性数据。 + * + * @param element 流程图中定义的流程元素。 + * @return 流程元素的扩展属性数据。 + */ + FlowElementExtProperty buildFlowElementExt(FlowElement element); + + /** + * 读取流程定义中,流程元素的扩展属性数据。 + * + * @param element 流程图中定义的流程元素。 + * @return 流程元素的扩展属性数据,并转换为JSON对象。 + */ + JSONObject buildFlowElementExtToJson(FlowElement element); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskTimeoutJobService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskTimeoutJobService.java new file mode 100644 index 0000000..413075e --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskTimeoutJobService.java @@ -0,0 +1,52 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowTaskTimeoutJob; +import org.flowable.task.api.Task; + +import java.util.List; + +/** + * 超时任务作业操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowTaskTimeoutJobService extends IBaseService { + + /** + * 保存超时任务作业对象。 + * + * @param job 超时任务作业对象。 + */ + void saveNew(FlowTaskTimeoutJob job); + + /** + * 执行超时任务作业对应的业务逻辑。 + * + * @param job 超时任务作业对象。 + * @param task 运行时的待办任务对象。 + */ + void executeJob(FlowTaskTimeoutJob job, Task task); + + /** + * 删除当前流程运行时任务的超时作业数据。这里仅删除未执行过的数据。 + * + * @param taskId 运行时任务Id。 + */ + void deleteByTaskId(String taskId); + + /** + * 删除当前流程实例的超时作业数据。这里仅删除非执行失败的数据。 + * + * @param processInstanceId 流程实例Id。 + */ + void deleteByProcessInstanceId(String processInstanceId); + + /** + * 获取可执行的Job列表。即状态为0同时execTime小于当前时间的任务。 + * + * @return 可执行的Job列表。 + */ + List getExecutableList(); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowTransProducerService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTransProducerService.java new file mode 100644 index 0000000..e93f89c --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTransProducerService.java @@ -0,0 +1,40 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowTransProducer; + +import java.util.List; +import java.util.Set; + +/** + * 流程引擎审批操作的生产者流水服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowTransProducerService extends IBaseService { + + /** + * 根据流程实例Id集合查询,每个流程实例关联的生产者流水数据。 + * + * @param processInstanceIds 流程实例Id集合。 + * @return 每个流程实例关联的生产者流水数据。 + */ + List getListByProcessInstanceIds(Set processInstanceIds); + + /** + * 根据流程实例Id和任务Id查询关联的生产者流水数据。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @return 关联的生产者流水数据。 + */ + FlowTransProducer getByProcessInstanceIdAndTaskId(String processInstanceId, String taskId); + + /** + * 删除指定流程实例Id的生产者流水数据。 + * + * @param processInstanceId 流程实例Id。 + */ + void deleteByProcessInstanceId(String processInstanceId); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowVariableLogService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowVariableLogService.java new file mode 100644 index 0000000..5d6008d --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowVariableLogService.java @@ -0,0 +1,32 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.flow.model.FlowVariableLog; + +import java.util.List; +import java.util.Map; + +/** + * 流程请求日志操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowVariableLogService extends IBaseService { + + /** + * 保存流程任务变量日志对象。 + * + * @param flowVariableLog 流程任务变量日志对象。 + */ + void saveNew(FlowVariableLog flowVariableLog); + + /** + * 获取指定流程实例和任务标识集合的变量日志。 + * + * @param processInstanceId 流程实例Id。 + * @param taskKeys 流程任务标识集合。 + * @return 键是taskKey,值是对应的变量数据。 + */ + Map getVariableMap(String processInstanceId, List taskKeys); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowWorkOrderService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowWorkOrderService.java new file mode 100644 index 0000000..d1dfd4e --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowWorkOrderService.java @@ -0,0 +1,172 @@ +package apelet.common.flow.service; + +import apelet.common.core.base.service.IBaseService; +import apelet.common.core.object.CallResult; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.flow.model.FlowWorkOrderExt; +import apelet.common.flow.vo.FlowWorkOrderVo; +import org.flowable.engine.runtime.ProcessInstance; + +import java.util.List; +import java.util.Set; + +/** + * 工作流工单表数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowWorkOrderService extends IBaseService { + + /** + * 保存新增对象。 + * + * @param instance 流程实例对象。 + * @param dataId 流程实例的BusinessKey。 + * @param onlineTableId 在线数据表的主键Id。 + * @param tableName 面向静态表单所使用的表名。 + * @return 新增的工作流工单对象。 + */ + FlowWorkOrder saveNew(ProcessInstance instance, Object dataId, Long onlineTableId, String tableName); + + /** + * 保存工单草稿。 + * + * @param instance 流程实例对象。 + * @param onlineTableId 在线表单的主表Id。 + * @param tableName 静态表单的主表表名。 + * @param masterData 主表数据。 + * @param slaveData 从表数据。 + * @return 工单对象。 + */ + FlowWorkOrder saveNewWithDraft( + ProcessInstance instance, Long onlineTableId, String tableName, String masterData, String slaveData); + + /** + * 更新流程工单的草稿数据。 + * + * @param workOrderId 工单Id。 + * @param masterData 主表数据。 + * @param slaveData 从表数据。 + */ + void updateDraft(Long workOrderId, String masterData, String slaveData); + + /** + * 删除指定数据。 + * + * @param workOrderId 主键Id。 + * @return 成功返回true,否则false。 + */ + boolean remove(Long workOrderId); + + /** + * 删除指定流程实例Id的关联工单。 + * + * @param processInstanceId 流程实例Id。 + */ + void removeByProcessInstanceId(String processInstanceId); + + /** + * 获取工作流工单单表查询结果。 + * + * @param filter 过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + List getFlowWorkOrderList(FlowWorkOrder filter, String orderBy); + + /** + * 获取工作流工单列表及其关联字典数据。 + * + * @param filter 过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + List getFlowWorkOrderListWithRelation(FlowWorkOrder filter, String orderBy); + + /** + * 根据流程实例Id,查询关联的工单对象。 + * + * @param processInstanceId 流程实例Id。 + * @return 工作流工单对象。 + */ + FlowWorkOrder getFlowWorkOrderByProcessInstanceId(String processInstanceId); + + /** + * 根据业务主键,查询是否存在指定的工单。 + * + * @param tableName 静态表单工作流使用的数据表。 + * @param businessKey 业务数据主键Id。 + * @param unfinished 是否为没有结束工单。 + * @return 存在返回true,否则false。 + */ + boolean existByBusinessKey(String tableName, Object businessKey, boolean unfinished); + + /** + * 根据流程定义和业务主键,查询是否存在指定的未完成工单。 + * + * @param processDefinitionKey 静态表单工作流使用的数据表。 + * @param businessKey 业务数据主键Id。 + * @return 存在返回true,否则false。 + */ + boolean existUnfinished(String processDefinitionKey, Object businessKey); + + /** + * 根据流程实例Id,更新流程状态。 + * + * @param processInstanceId 流程实例Id。 + * @param flowStatus 新的流程状态值,如果该值为null,不执行任何更新。 + */ + void updateFlowStatusByProcessInstanceId(String processInstanceId, Integer flowStatus); + + /** + * 根据流程实例Id,更新流程最后审批状态。 + * + * @param processInstanceId 流程实例Id。 + * @param approvalStatus 新的流程最后审批状态,如果该值为null,不执行任何更新。 + */ + void updateLatestApprovalStatusByProcessInstanceId(String processInstanceId, Integer approvalStatus); + + /** + * 是否有查看该工单的数据权限。 + * + * @param processInstanceId 流程实例Id。 + * @return 存在返回true,否则false。 + */ + boolean hasDataPermOnFlowWorkOrder(String processInstanceId); + + /** + * 根据工单列表中的submitUserName,找到映射的userShowName,并会写到Vo中指定字段。 + * 同时这也是一个如何通过插件方法,将loginName映射到showName的示例, + * + * @param dataList 工单Vo对象列表。 + */ + void fillUserShowNameByLoginName(List dataList); + + /** + * 根据工单Id获取工单扩展对象数据。 + * + * @param workOrderId 工单Id。 + * @return 工单扩展对象。 + */ + FlowWorkOrderExt getFlowWorkOrderExtByWorkOrderId(Long workOrderId); + + /** + * 根据工单Id集合获取工单扩展对象数据列表。 + * + * @param workOrderIds 工单Id集合。 + * @return 工单扩展对象列表。 + */ + List getFlowWorkOrderExtByWorkOrderIds(Set workOrderIds); + + /** + * 移除草稿工单,同时停止已经启动的流程实例。 + * + * @param flowWorkOrder 工单对象。 + * @return 停止流程实例的结果。 + */ + CallResult removeDraft(FlowWorkOrder flowWorkOrder); + + + String generateWorkOrderCode(String processDefinitionKey); +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java new file mode 100644 index 0000000..f028fc9 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java @@ -0,0 +1,2444 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MultiDatabaseWriteMethod; +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.constant.UserFilterGroup; +import apelet.common.core.exception.MyRuntimeException; +import apelet.common.core.object.*; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.core.util.MyCommonUtil; +import apelet.common.core.util.MyDateUtil; +import apelet.common.flow.cmd.RestartProcessInstanceCmd; +import apelet.common.flow.constant.*; +import apelet.common.flow.exception.FlowOperationException; +import apelet.common.flow.model.*; +import apelet.common.flow.object.*; +import apelet.common.flow.service.*; +import apelet.common.flow.util.BaseFlowIdentityExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.vo.FlowTaskVo; +import apelet.common.flow.vo.FlowUserInfoVo; +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.BooleanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Cleanup; +import lombok.Data; +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.impl.de.odysseus.el.ExpressionFactoryImpl; +import org.flowable.common.engine.impl.de.odysseus.el.util.SimpleContext; +import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.common.engine.impl.javax.el.ExpressionFactory; +import org.flowable.common.engine.impl.javax.el.ValueExpression; +import org.flowable.engine.*; +import org.flowable.engine.delegate.ExecutionListener; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.history.HistoricProcessInstanceQuery; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.*; +import org.flowable.identitylink.api.IdentityLink; +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.variable.api.history.HistoricVariableInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +import static apelet.common.flow.object.AnalyzedNode.UserTaskInfo; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowApiService") +public class FlowApiServiceImpl implements FlowApiService { + + @Autowired + private RepositoryService repositoryService; + @Autowired + private RuntimeService runtimeService; + @Autowired + private TaskService taskService; + @Autowired + private HistoryService historyService; + @Autowired + private ManagementService managementService; + @Autowired + private FlowEntryService flowEntryService; + @Autowired + private FlowTaskCommentService flowTaskCommentService; + @Autowired + private FlowTaskExtService flowTaskExtService; + @Autowired + private FlowWorkOrderService flowWorkOrderService; + @Autowired + private FlowMessageService flowMessageService; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private FlowMultiInstanceTransService flowMultiInstanceTransService; + @Autowired + private FlowTransProducerService flowTransProducerService; + @Autowired + private FlowVariableLogService flowVariableLogService; + @Autowired + private FlowTaskTimeoutJobService flowTaskTimeoutJobService; + + @Transactional(rollbackFor = Exception.class) + @Override + public ProcessInstance start(String processDefinitionId, Object dataId) { + TokenData tokenData = TokenData.takeFromRequest(); + Map variableMap = this.initAndGetProcessInstanceVariables(processDefinitionId); + Authentication.setAuthenticatedUserId(tokenData.getLoginName()); + String businessKey = dataId == null ? null : dataId.toString(); + ProcessInstanceBuilder builder = runtimeService.createProcessInstanceBuilder() + .processDefinitionId(processDefinitionId).businessKey(businessKey).variables(variableMap); + if (tokenData.getTenantId() != null) { + builder.tenantId(tokenData.getTenantId().toString()); + } else { + if (tokenData.getAppCode() != null) { + builder.tenantId(tokenData.getAppCode()); + } + } + return builder.start(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public ProcessInstance revive(String processInstanceId, List taskKeys, String taskComment) { + FlowTaskComment comment = new FlowTaskComment(); + comment.setTargetTaskKey(StrUtil.join(",", taskKeys)); + comment.setProcessInstanceId(processInstanceId); + comment.setApprovalType(FlowApprovalType.REVIVE); + comment.setTaskComment(taskComment); + flowTaskCommentService.saveNew(comment); + Map variableMap = flowVariableLogService.getVariableMap(processInstanceId, taskKeys); + ProcessInstance instance = + managementService.executeCommand(new RestartProcessInstanceCmd(processInstanceId, taskKeys, variableMap)); + flowWorkOrderService.updateFlowStatusByProcessInstanceId(processInstanceId, FlowTaskStatus.SUBMITTED); + return instance; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Task takeFirstTask(String processInstanceId, FlowTaskComment flowTaskComment, JSONObject taskVariableData) { + String loginName = TokenData.takeFromRequest().getLoginName(); + // 获取流程启动后的第一个任务。 + Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).active().singleResult(); + if (StrUtil.equalsAny(task.getAssignee(), loginName, FlowConstant.START_USER_NAME_VAR)) { + // 按照规则,调用该方法的用户,就是第一个任务的assignee,因此默认会自动执行complete。 + flowTaskComment.fillWith(task); + this.completeTask(task, flowTaskComment, taskVariableData, null); + } + return task; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public ProcessInstance startAndTakeFirst( + String processDefinitionId, Object dataId, FlowTaskComment flowTaskComment, JSONObject taskVariableData) { + ProcessInstance instance = this.start(processDefinitionId, dataId); + this.takeFirstTask(instance.getProcessInstanceId(), flowTaskComment, taskVariableData); + return instance; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void submitConsign( + HistoricTaskInstance startTaskInstance, Task multiInstanceActiveTask, String newAssignees, boolean isAdd) { + JSONArray assigneeArray = JSON.parseArray(newAssignees); + String multiInstanceExecId = this.getExecutionVariableStringWithSafe( + multiInstanceActiveTask.getExecutionId(), FlowConstant.MULTI_SIGN_TASK_EXECUTION_ID_VAR); + FlowMultiInstanceTrans trans = + flowMultiInstanceTransService.getWithAssigneeListByMultiInstanceExecId(multiInstanceExecId); + Set assigneeSet = new HashSet<>(StrUtil.split(trans.getAssigneeList(), ",")); + Task runtimeTask = null; + for (int i = 0; i < assigneeArray.size(); i++) { + String assignee = assigneeArray.getString(i); + if (isAdd) { + assigneeSet.add(assignee); + } else { + assigneeSet.remove(assignee); + } + if (isAdd) { + Map variables = new HashMap<>(2); + variables.put("assignee", assigneeArray.getString(i)); + variables.put(FlowConstant.MULTI_SIGN_START_TASK_VAR, startTaskInstance.getId()); + runtimeService.addMultiInstanceExecution( + multiInstanceActiveTask.getTaskDefinitionKey(), multiInstanceActiveTask.getProcessInstanceId(), variables); + } else { + TaskQuery query = taskService.createTaskQuery().active(); + query.processInstanceId(multiInstanceActiveTask.getProcessInstanceId()); + query.taskDefinitionKey(multiInstanceActiveTask.getTaskDefinitionKey()); + query.taskAssignee(assignee); + runtimeTask = query.singleResult(); + if (runtimeTask == null) { + throw new FlowOperationException("审批人 [" + assignee + "] 已经提交审批,不能执行减签操作!"); + } + runtimeService.deleteMultiInstanceExecution(runtimeTask.getExecutionId(), false); + } + } + if (!isAdd && runtimeTask != null) { + this.doChangeTask(runtimeTask); + } + trans.setAssigneeList(StrUtil.join(",", assigneeSet)); + flowMultiInstanceTransService.updateById(trans); + FlowTaskComment flowTaskComment = new FlowTaskComment(); + flowTaskComment.fillWith(startTaskInstance); + flowTaskComment.setApprovalType(isAdd ? FlowApprovalType.MULTI_CONSIGN : FlowApprovalType.MULTI_MINUS_SIGN); + String showName = TokenData.takeFromRequest().getLoginName(); + String comment = String.format("用户 [%s] [%s] [%s]。", isAdd ? "加签" : "减签", showName, newAssignees); + flowTaskComment.setTaskComment(comment); + flowTaskCommentService.saveNew(flowTaskComment); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void completeTask(Task task, FlowTaskComment flowTaskComment, JSONObject taskVariableData, FlowApiOption option) { + String value = this.getTaskVariableStringWithSafe(task.getId(), FlowConstant.REJECT_TO_SOURCE_DATA_VAR); + if (StrUtil.isNotBlank(value) && flowTaskComment.getApprovalType().equals(FlowApprovalType.AGREE)) { + flowTaskComment.fillWith(task); + flowTaskCommentService.saveNew(flowTaskComment); + Integer approvalStatus = MapUtil.getInt(taskVariableData, FlowConstant.LATEST_APPROVAL_STATUS_KEY); + flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(task.getProcessInstanceId(), approvalStatus); + FlowRejectData rejectData = JSON.parseObject(value, FlowRejectData.class); + Map variables = MapUtil.newHashMap(); + variables.put(FlowConstant.REJECT_BACK_TO_SOURCE_DATA_VAR, rejectData.getSourceUser()); + this.doChanageState(task.getProcessInstanceId(), + CollUtil.newArrayList(task.getTaskDefinitionKey()), + CollUtil.newArrayList(rejectData.getSourceTaskKey()), null, variables); + flowMessageService.updateFinishedStatusByTaskId(task.getId()); + return; + } + JSONObject passCopyData = null; + if (taskVariableData != null) { + passCopyData = (JSONObject) taskVariableData.remove(FlowConstant.COPY_DATA_KEY); + } + // 判断当前完成执行的任务,是否存在抄送设置。 + Object copyData = runtimeService.getVariable( + task.getProcessInstanceId(), FlowConstant.COPY_DATA_MAP_PREFIX + task.getTaskDefinitionKey()); + if (copyData != null || passCopyData != null) { + JSONObject copyDataJson = this.mergeCopyData(copyData, passCopyData); + flowMessageService.saveNewCopyMessage(task, copyDataJson); + } + if (flowTaskComment != null) { + if (taskVariableData == null) { + taskVariableData = new JSONObject(); + } + // 这里处理多实例会签逻辑。 + if (flowTaskComment.getApprovalType().equals(FlowApprovalType.MULTI_SIGN)) { + String loginName = TokenData.takeFromRequest().getLoginName(); + String assigneeList = this.getMultiInstanceAssigneeList(task, taskVariableData); + Assert.isTrue(StrUtil.isNotBlank(assigneeList)); + taskVariableData.put(FlowConstant.MULTI_AGREE_COUNT_VAR, 0); + taskVariableData.put(FlowConstant.MULTI_REFUSE_COUNT_VAR, 0); + taskVariableData.put(FlowConstant.MULTI_ABSTAIN_COUNT_VAR, 0); + taskVariableData.put(FlowConstant.MULTI_SIGN_NUM_OF_INSTANCES_VAR, 0); + taskVariableData.put(FlowConstant.MULTI_SIGN_START_TASK_VAR, task.getId()); + String multiInstanceExecId = MyCommonUtil.generateUuid(); + taskVariableData.put(FlowConstant.MULTI_SIGN_TASK_EXECUTION_ID_VAR, multiInstanceExecId); + String comment = String.format("用户 [%s] 会签 [%s]。", loginName, assigneeList); + FlowMultiInstanceTrans multiInstanceTrans = new FlowMultiInstanceTrans(task); + multiInstanceTrans.setMultiInstanceExecId(multiInstanceExecId); + multiInstanceTrans.setAssigneeList(assigneeList); + flowMultiInstanceTransService.saveNew(multiInstanceTrans); + flowTaskComment.setTaskComment(comment); + } + // 处理转办。 + if (FlowApprovalType.TRANSFER.equals(flowTaskComment.getApprovalType())) { + this.transferTo(task, flowTaskComment); + return; + } + this.handleMultiInstanceApprovalType( + task.getExecutionId(), flowTaskComment.getApprovalType(), taskVariableData); + taskVariableData.put(FlowConstant.OPERATION_TYPE_VAR, flowTaskComment.getApprovalType()); + this.setSubmitUserVar(taskVariableData, flowTaskComment); + flowTaskComment.fillWith(task); + if (this.isMultiInstanceTask(task.getProcessDefinitionId(), task.getTaskDefinitionKey())) { + String multiInstanceExecId = getExecutionVariableStringWithSafe( + task.getExecutionId(), FlowConstant.MULTI_SIGN_TASK_EXECUTION_ID_VAR); + FlowMultiInstanceTrans multiInstanceTrans = new FlowMultiInstanceTrans(task); + multiInstanceTrans.setMultiInstanceExecId(multiInstanceExecId); + flowMultiInstanceTransService.saveNew(multiInstanceTrans); + flowTaskComment.setMultiInstanceExecId(multiInstanceExecId); + } + flowTaskCommentService.saveNew(flowTaskComment); + } + Integer approvalStatus = MapUtil.getInt(taskVariableData, FlowConstant.LATEST_APPROVAL_STATUS_KEY); + flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(task.getProcessInstanceId(), approvalStatus); + taskService.complete(task.getId(), taskVariableData, this.makeTransientVariableMap(taskVariableData)); + flowMessageService.updateFinishedStatusByTaskId(task.getId()); + if (this.canDeleteTimeoutTaskJob(option)) { + flowTaskTimeoutJobService.deleteByTaskId(task.getId()); + } + } + + private void setSubmitUserVar(JSONObject taskVariableData, FlowTaskComment comment) { + TokenData tokenData = TokenData.takeFromRequest(); + if (tokenData != null) { + taskVariableData.put(FlowConstant.SUBMIT_USER_VAR, tokenData.getLoginName()); + } else { + if (StrUtil.isNotBlank(comment.getCreateLoginName())) { + taskVariableData.put(FlowConstant.SUBMIT_USER_VAR, comment.getCreateLoginName()); + } + } + } + + private boolean canDeleteTimeoutTaskJob(FlowApiOption option) { + return option == null || option.isDeleteTimeoutTaskJob(); + } + + private JSONObject makeTransientVariableMap(JSONObject taskVariableData) { + JSONObject result = new JSONObject(); + if (taskVariableData == null) { + return result; + } + Object masterData = taskVariableData.get(FlowConstant.MASTER_DATA_KEY); + if (masterData != null) { + result.put(FlowConstant.MASTER_DATA_KEY, masterData); + } + Object slaveData = taskVariableData.get(FlowConstant.SLAVE_DATA_KEY); + if (slaveData != null) { + result.put(FlowConstant.SLAVE_DATA_KEY, slaveData); + } + Object masterTable = taskVariableData.get(FlowConstant.MASTER_TABLE_KEY); + if (masterTable != null) { + result.put(FlowConstant.MASTER_TABLE_KEY, masterTable); + } + taskVariableData.remove(FlowConstant.MASTER_DATA_KEY); + taskVariableData.remove(FlowConstant.SLAVE_DATA_KEY); + taskVariableData.remove(FlowConstant.MASTER_TABLE_KEY); + return result; + } + + private String getMultiInstanceAssigneeList(Task task, JSONObject taskVariableData) { + JSONArray assigneeArray = taskVariableData.getJSONArray(FlowConstant.MULTI_ASSIGNEE_LIST_VAR); + String assigneeList; + if (CollUtil.isEmpty(assigneeArray)) { + FlowTaskExt flowTaskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId( + task.getProcessDefinitionId(), task.getTaskDefinitionKey()); + assigneeList = this.buildMutiSignAssigneeList(flowTaskExt.getOperationListJson()); + if (assigneeList != null) { + taskVariableData.put(FlowConstant.MULTI_ASSIGNEE_LIST_VAR, StrUtil.split(assigneeList, ',')); + } + } else { + assigneeList = CollUtil.join(assigneeArray, ","); + } + return assigneeList; + } + + private JSONObject mergeCopyData(Object copyData, JSONObject passCopyData) { + // passCopyData是传阅数据,copyData是抄送数据。 + JSONObject resultCopyDataJson = passCopyData; + if (resultCopyDataJson == null) { + resultCopyDataJson = JSON.parseObject(copyData.toString()); + } else if (copyData != null) { + JSONObject copyDataJson = JSON.parseObject(copyData.toString()); + for (Map.Entry entry : copyDataJson.entrySet()) { + String value = resultCopyDataJson.getString(entry.getKey()); + if (value == null) { + resultCopyDataJson.put(entry.getKey(), entry.getValue()); + } else { + List list1 = StrUtil.split(value, ","); + List list2 = StrUtil.split(entry.getValue().toString(), ","); + Set valueSet = new HashSet<>(list1); + valueSet.addAll(list2); + resultCopyDataJson.put(entry.getKey(), StrUtil.join(",", valueSet)); + } + } + } + this.processMergeCopyData(resultCopyDataJson); + return resultCopyDataJson; + } + + private void processMergeCopyData(JSONObject resultCopyDataJson) { + TokenData tokenData = TokenData.takeFromRequest(); + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + for (Map.Entry entry : resultCopyDataJson.entrySet()) { + String type = entry.getKey(); + switch (type) { + case FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR: + Object upLeaderDeptPostId = + flowIdentityExtHelper.getUpLeaderDeptPostId(tokenData.getDeptId()); + entry.setValue(upLeaderDeptPostId); + break; + case FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR: + Object leaderDeptPostId = + flowIdentityExtHelper.getLeaderDeptPostId(tokenData.getDeptId()); + entry.setValue(leaderDeptPostId); + break; + case FlowConstant.GROUP_TYPE_SELF_DEPT_POST_VAR: + Set selfPostIdSet = new HashSet<>(StrUtil.split(entry.getValue().toString(), ",")); + Map deptPostIdMap = + flowIdentityExtHelper.getDeptPostIdMap(tokenData.getDeptId(), selfPostIdSet); + String deptPostIdValues = ""; + if (deptPostIdMap != null) { + deptPostIdValues = StrUtil.join(",", deptPostIdMap.values()); + } + entry.setValue(deptPostIdValues); + break; + case FlowConstant.GROUP_TYPE_SIBLING_DEPT_POST_VAR: + Set siblingPostIdSet = new HashSet<>(StrUtil.split(entry.getValue().toString(), ",")); + Map siblingDeptPostIdMap = + flowIdentityExtHelper.getSiblingDeptPostIdMap(tokenData.getDeptId(), siblingPostIdSet); + String siblingDeptPostIdValues = ""; + if (siblingDeptPostIdMap != null) { + siblingDeptPostIdValues = StrUtil.join(",", siblingDeptPostIdMap.values()); + } + entry.setValue(siblingDeptPostIdValues); + break; + case FlowConstant.GROUP_TYPE_UP_DEPT_POST_VAR: + Set upPostIdSet = new HashSet<>(StrUtil.split(entry.getValue().toString(), ",")); + Map upDeptPostIdMap = + flowIdentityExtHelper.getUpDeptPostIdMap(tokenData.getDeptId(), upPostIdSet); + String upDeptPostIdValues = ""; + if (upDeptPostIdMap != null) { + upDeptPostIdValues = StrUtil.join(",", upDeptPostIdMap.values()); + } + entry.setValue(upDeptPostIdValues); + break; + default: + break; + } + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public CallResult verifyAssigneeOrCandidateAndClaim(Task task) { + String errorMessage; + String loginName = TokenData.takeFromRequest().getLoginName(); + // 这里必须先执行拾取操作,如果当前用户是候选人,特别是对于分布式场景,更是要先完成候选人的拾取。 + if (task.getAssignee() == null) { + // 没有指派人 + if (!this.isAssigneeOrCandidate(task)) { + errorMessage = "数据验证失败,当前用户不是该待办任务的候选人,请刷新后重试!"; + return CallResult.error(errorMessage); + } + // 作为候选人主动拾取任务。 + taskService.claim(task.getId(), loginName); + } else { + if (!task.getAssignee().equals(loginName)) { + errorMessage = "数据验证失败,当前用户不是该待办任务的指派人,请刷新后重试!"; + return CallResult.error(errorMessage); + } + } + return CallResult.ok(); + } + + @Override + public Map initAndGetProcessInstanceVariables(String processDefinitionId) { + TokenData tokenData = TokenData.takeFromRequest(); + String loginName = tokenData.getLoginName(); + // 设置流程变量。 + Map variableMap = new HashMap<>(4); + variableMap.put(FlowConstant.PROC_INSTANCE_INITIATOR_VAR, loginName); + variableMap.put(FlowConstant.PROC_INSTANCE_START_USER_NAME_VAR, loginName); + List flowTaskExtList = flowTaskExtService.getByProcessDefinitionId(processDefinitionId); + boolean hasDeptPostLeader = false; + boolean hasUpDeptPostLeader = false; + boolean hasPostCandidateGroup = false; + for (FlowTaskExt flowTaskExt : flowTaskExtList) { + if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER)) { + hasUpDeptPostLeader = true; + } else if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_DEPT_POST_LEADER)) { + hasDeptPostLeader = true; + } else if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_POST)) { + hasPostCandidateGroup = true; + } + } + // 如果流程图的配置中包含用户身份相关的变量(如:部门领导和上级领导审批),flowIdentityExtHelper就不能为null。 + // 这个需要子类去实现 BaseFlowIdentityExtHelper 接口,并注册到FlowCustomExtFactory的工厂中。 + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + if (hasUpDeptPostLeader) { + Assert.notNull(flowIdentityExtHelper); + Object upLeaderDeptPostId = flowIdentityExtHelper.getUpLeaderDeptPostId(tokenData.getDeptId()); + if (upLeaderDeptPostId == null) { + variableMap.put(FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR, null); + } else { + variableMap.put(FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR, upLeaderDeptPostId.toString()); + } + } + if (hasDeptPostLeader) { + Assert.notNull(flowIdentityExtHelper); + Object leaderDeptPostId = flowIdentityExtHelper.getLeaderDeptPostId(tokenData.getDeptId()); + if (leaderDeptPostId == null) { + variableMap.put(FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR, null); + } else { + variableMap.put(FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR, leaderDeptPostId.toString()); + } + } + if (hasPostCandidateGroup) { + Assert.notNull(flowIdentityExtHelper); + Map postGroupDataMap = + this.buildPostCandidateGroupData(flowIdentityExtHelper, flowTaskExtList); + variableMap.putAll(postGroupDataMap); + } + this.buildCopyData(flowTaskExtList, variableMap); + return variableMap; + } + + private void buildCopyData(List flowTaskExtList, Map variableMap) { + for (FlowTaskExt flowTaskExt : flowTaskExtList) { + if (StrUtil.isBlank(flowTaskExt.getCopyListJson())) { + continue; + } + List copyDataList = JSON.parseArray(flowTaskExt.getCopyListJson(), JSONObject.class); + Map copyDataMap = new HashMap<>(copyDataList.size()); + for (JSONObject copyData : copyDataList) { + String type = copyData.getString("type"); + String id = copyData.getString("id"); + copyDataMap.put(type, id == null ? "" : id); + } + variableMap.put(FlowConstant.COPY_DATA_MAP_PREFIX + flowTaskExt.getTaskId(), JSON.toJSONString(copyDataMap)); + } + } + + private Map buildPostCandidateGroupData( + BaseFlowIdentityExtHelper flowIdentityExtHelper, List flowTaskExtList) { + Map postVariableMap = MapUtil.newHashMap(); + Set selfPostIdSet = new HashSet<>(); + Set siblingPostIdSet = new HashSet<>(); + Set upPostIdSet = new HashSet<>(); + for (FlowTaskExt flowTaskExt : flowTaskExtList) { + if (flowTaskExt.getGroupType().equals(FlowConstant.GROUP_TYPE_POST)) { + Assert.notNull(flowTaskExt.getDeptPostListJson()); + List groupDataList = + JSONArray.parseArray(flowTaskExt.getDeptPostListJson(), FlowTaskPostCandidateGroup.class); + for (FlowTaskPostCandidateGroup groupData : groupDataList) { + switch (groupData.getType()) { + case FlowConstant.GROUP_TYPE_SELF_DEPT_POST_VAR: + selfPostIdSet.add(groupData.getPostId()); + break; + case FlowConstant.GROUP_TYPE_SIBLING_DEPT_POST_VAR: + siblingPostIdSet.add(groupData.getPostId()); + break; + case FlowConstant.GROUP_TYPE_UP_DEPT_POST_VAR: + upPostIdSet.add(groupData.getPostId()); + break; + default: + break; + } + } + } + } + postVariableMap.putAll(this.buildSelfPostCandidateGroupData(flowIdentityExtHelper, selfPostIdSet)); + postVariableMap.putAll(this.buildSiblingPostCandidateGroupData(flowIdentityExtHelper, siblingPostIdSet)); + postVariableMap.putAll(this.buildUpPostCandidateGroupData(flowIdentityExtHelper, upPostIdSet)); + return postVariableMap; + } + + private Map buildSelfPostCandidateGroupData( + BaseFlowIdentityExtHelper flowIdentityExtHelper, Set selfPostIdSet) { + Map postVariableMap = MapUtil.newHashMap(); + if (CollUtil.isNotEmpty(selfPostIdSet)) { + Map deptPostIdMap = + flowIdentityExtHelper.getDeptPostIdMap(TokenData.takeFromRequest().getDeptId(), selfPostIdSet); + for (String postId : selfPostIdSet) { + if (MapUtil.isNotEmpty(deptPostIdMap) && deptPostIdMap.containsKey(postId)) { + String deptPostId = deptPostIdMap.get(postId); + postVariableMap.put(FlowConstant.SELF_DEPT_POST_PREFIX + postId, deptPostId); + } else { + postVariableMap.put(FlowConstant.SELF_DEPT_POST_PREFIX + postId, ""); + } + } + } + return postVariableMap; + } + + private Map buildSiblingPostCandidateGroupData( + BaseFlowIdentityExtHelper flowIdentityExtHelper, Set siblingPostIdSet) { + Map postVariableMap = MapUtil.newHashMap(); + if (CollUtil.isNotEmpty(siblingPostIdSet)) { + Map siblingDeptPostIdMap = + flowIdentityExtHelper.getSiblingDeptPostIdMap(TokenData.takeFromRequest().getDeptId(), siblingPostIdSet); + for (String postId : siblingPostIdSet) { + if (MapUtil.isNotEmpty(siblingDeptPostIdMap) && siblingDeptPostIdMap.containsKey(postId)) { + String siblingDeptPostId = siblingDeptPostIdMap.get(postId); + postVariableMap.put(FlowConstant.SIBLING_DEPT_POST_PREFIX + postId, siblingDeptPostId); + } else { + postVariableMap.put(FlowConstant.SIBLING_DEPT_POST_PREFIX + postId, ""); + } + } + } + return postVariableMap; + } + + private Map buildUpPostCandidateGroupData( + BaseFlowIdentityExtHelper flowIdentityExtHelper, Set upPostIdSet) { + Map postVariableMap = MapUtil.newHashMap(); + if (CollUtil.isNotEmpty(upPostIdSet)) { + Map upDeptPostIdMap = + flowIdentityExtHelper.getUpDeptPostIdMap(TokenData.takeFromRequest().getDeptId(), upPostIdSet); + for (String postId : upPostIdSet) { + if (MapUtil.isNotEmpty(upDeptPostIdMap) && upDeptPostIdMap.containsKey(postId)) { + String upDeptPostId = upDeptPostIdMap.get(postId); + postVariableMap.put(FlowConstant.UP_DEPT_POST_PREFIX + postId, upDeptPostId); + } else { + postVariableMap.put(FlowConstant.UP_DEPT_POST_PREFIX + postId, ""); + } + } + } + return postVariableMap; + } + + @Override + public boolean isAssigneeOrCandidate(TaskInfo task) { + String loginName = TokenData.takeFromRequest().getLoginName(); + if (StrUtil.isNotBlank(task.getAssignee())) { + return StrUtil.equals(loginName, task.getAssignee()); + } + TaskQuery query = taskService.createTaskQuery(); + this.buildCandidateCondition(query, loginName); + query.taskId(task.getId()); + return query.active().count() != 0; + } + + @Override + public Collection getProcessAllElements(String processDefinitionId) { + Process process = repositoryService.getBpmnModel(processDefinitionId).getProcesses().get(0); + return this.getAllElements(process.getFlowElements(), null); + } + + @Override + public boolean isProcessInstanceStarter(String processInstanceId) { + String loginName = TokenData.takeFromRequest().getLoginName(); + return historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId).startedBy(loginName).count() != 0; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void setBusinessKeyForProcessInstance(String processInstanceId, Object dataId) { + runtimeService.updateBusinessKey(processInstanceId, dataId.toString()); + } + + @Override + public boolean existActiveProcessInstance(String processInstanceId) { + return runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId).active().count() != 0; + } + + @Override + public ProcessInstance getProcessInstance(String processInstanceId) { + return runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + } + + @Override + public ProcessInstance getProcessInstanceByBusinessKey(String processDefinitionId, String businessKey) { + return runtimeService.createProcessInstanceQuery() + .processDefinitionId(processDefinitionId).processInstanceBusinessKey(businessKey).singleResult(); + } + + @Override + public Task getProcessInstanceActiveTask(String processInstanceId, String taskId) { + TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId); + if (StrUtil.isNotBlank(taskId)) { + query.taskId(taskId); + } + return query.active().singleResult(); + } + + @Override + public List getProcessInstanceActiveTaskList(String processInstanceId) { + return taskService.createTaskQuery().processInstanceId(processInstanceId).list(); + } + + @Override + public Task getTaskById(String taskId) { + return taskService.createTaskQuery().taskId(taskId).singleResult(); + } + + @Override + public MyPageData getTaskListByUserName( + String username, String definitionKey, String definitionName, String taskName, MyPageParam pageParam) { + TaskQuery query = this.createQuery(); + if (StrUtil.isNotBlank(definitionKey)) { + query.processDefinitionKey(definitionKey); + } + if (StrUtil.isNotBlank(definitionName)) { + query.processDefinitionNameLike("%" + definitionName + "%"); + } + if (StrUtil.isNotBlank(taskName)) { + query.taskNameLike("%" + taskName + "%"); + } + this.buildCandidateCondition(query, username); + long totalCount = query.count(); + query.orderByTaskCreateTime().desc(); + int firstResult = (pageParam.getPageNum() - 1) * pageParam.getPageSize(); + List taskList = query.listPage(firstResult, pageParam.getPageSize()); + return new MyPageData<>(taskList, totalCount); + } + + @Override + public long getTaskCountByUserName(String username) { + TaskQuery query = this.createQuery(); + this.buildCandidateCondition(query, username); + return query.count(); + } + + @Override + public List getTaskListByProcessInstanceIds(List processInstanceIdSet) { + return taskService.createTaskQuery().processInstanceIdIn(processInstanceIdSet).active().list(); + } + + @Override + public List getProcessInstanceList(Set processInstanceIdSet) { + return runtimeService.createProcessInstanceQuery().processInstanceIds(processInstanceIdSet).list(); + } + + @Override + public ProcessDefinition getProcessDefinitionById(String processDefinitionId) { + return repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult(); + } + + @Override + public List getProcessDefinitionList(Set processDefinitionIdSet) { + return repositoryService.createProcessDefinitionQuery().processDefinitionIds(processDefinitionIdSet).list(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void suspendProcessDefinition(String processDefinitionId) { + repositoryService.suspendProcessDefinitionById(processDefinitionId); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void activateProcessDefinition(String processDefinitionId) { + repositoryService.activateProcessDefinitionById(processDefinitionId); + } + + @Override + public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) { + return repositoryService.getBpmnModel(processDefinitionId); + } + + @Override + public boolean isMultiInstanceTask(String processDefinitionId, String taskKey) { + BpmnModel model = this.getBpmnModelByDefinitionId(processDefinitionId); + FlowElement flowElement = model.getFlowElement(taskKey); + if (!(flowElement instanceof UserTask)) { + return false; + } + UserTask userTask = (UserTask) flowElement; + return userTask.hasMultiInstanceLoopCharacteristics(); + } + + @Override + public ProcessDefinition getProcessDefinitionByDeployId(String deployId) { + return repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult(); + } + + @Override + public void setProcessInstanceVariables(String processInstanceId, Map variableMap) { + runtimeService.setVariables(processInstanceId, variableMap); + } + + @Override + public Object getProcessInstanceVariable(String processInstanceId, String variableName) { + return runtimeService.getVariable(processInstanceId, variableName); + } + + @Override + public List convertToFlowTaskList(List taskList) { + List flowTaskVoList = new LinkedList<>(); + if (CollUtil.isEmpty(taskList)) { + return flowTaskVoList; + } + Set processDefinitionIdSet = taskList.stream() + .map(Task::getProcessDefinitionId).collect(Collectors.toSet()); + Set procInstanceIdSet = taskList.stream() + .map(Task::getProcessInstanceId).collect(Collectors.toSet()); + List flowEntryPublishList = + flowEntryService.getFlowEntryPublishList(processDefinitionIdSet); + Map flowEntryPublishMap = + flowEntryPublishList.stream().collect(Collectors.toMap(FlowEntryPublish::getProcessDefinitionId, c -> c)); + List instanceList = this.getProcessInstanceList(procInstanceIdSet); + Map instanceMap = + instanceList.stream().collect(Collectors.toMap(ProcessInstance::getId, c -> c)); + List definitionList = this.getProcessDefinitionList(processDefinitionIdSet); + Map definitionMap = + definitionList.stream().collect(Collectors.toMap(ProcessDefinition::getId, c -> c)); + List workOrderList = + flowWorkOrderService.getInList("processInstanceId", procInstanceIdSet); + Map workOrderMap = + workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c)); + for (Task task : taskList) { + FlowTaskVo flowTaskVo = new FlowTaskVo(); + flowTaskVo.setTaskId(task.getId()); + flowTaskVo.setTaskName(task.getName()); + flowTaskVo.setTaskKey(task.getTaskDefinitionKey()); + flowTaskVo.setTaskFormKey(task.getFormKey()); + flowTaskVo.setTaskStartTime(task.getCreateTime()); + flowTaskVo.setEntryId(flowEntryPublishMap.get(task.getProcessDefinitionId()).getEntryId()); + ProcessDefinition processDefinition = definitionMap.get(task.getProcessDefinitionId()); + flowTaskVo.setProcessDefinitionId(processDefinition.getId()); + flowTaskVo.setProcessDefinitionName(processDefinition.getName()); + flowTaskVo.setProcessDefinitionKey(processDefinition.getKey()); + flowTaskVo.setProcessDefinitionVersion(processDefinition.getVersion()); + ProcessInstance processInstance = instanceMap.get(task.getProcessInstanceId()); + flowTaskVo.setProcessInstanceId(processInstance.getId()); + Object initiator = this.getProcessInstanceVariable( + processInstance.getId(), FlowConstant.PROC_INSTANCE_INITIATOR_VAR); + flowTaskVo.setProcessInstanceInitiator(initiator.toString()); + flowTaskVo.setProcessInstanceStartTime(processInstance.getStartTime()); + flowTaskVo.setBusinessKey(processInstance.getBusinessKey()); + FlowWorkOrder flowWorkOrder = workOrderMap.get(task.getProcessInstanceId()); + if (flowWorkOrder != null) { + flowTaskVo.setIsDraft(flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT)); + flowTaskVo.setWorkOrderCode(flowWorkOrder.getWorkOrderCode()); + } + flowTaskVoList.add(flowTaskVo); + } + Set loginNameSet = flowTaskVoList.stream() + .map(FlowTaskVo::getProcessInstanceInitiator).collect(Collectors.toSet()); + List flowUserInfos = flowCustomExtFactory + .getFlowIdentityExtHelper().getUserInfoListByUsernameSet(loginNameSet); + Map userInfoMap = + flowUserInfos.stream().collect(Collectors.toMap(FlowUserInfoVo::getLoginName, c -> c)); + for (FlowTaskVo flowTaskVo : flowTaskVoList) { + FlowUserInfoVo userInfo = userInfoMap.get(flowTaskVo.getProcessInstanceInitiator()); + flowTaskVo.setShowName(userInfo.getShowName()); + flowTaskVo.setHeadImageUrl(userInfo.getHeadImageUrl()); + } + return flowTaskVoList; + } + + @Override + public void addProcessInstanceEndListener(BpmnModel bpmnModel, Class listenerClazz) { + Assert.notNull(listenerClazz); + Process process = bpmnModel.getMainProcess(); + FlowableListener listener = this.createListener("end", listenerClazz.getName()); + process.getExecutionListeners().add(listener); + } + + @Override + public void addExecutionListener( + FlowElement flowElement, + Class listenerClazz, + String event, + List fieldExtensions) { + Assert.notNull(listenerClazz); + FlowableListener listener = this.createListener(event, listenerClazz.getName()); + if (fieldExtensions != null) { + listener.setFieldExtensions(fieldExtensions); + } + flowElement.getExecutionListeners().add(listener); + } + + @Override + public void addTaskCreateListener(UserTask userTask, Class listenerClazz) { + + this.addTaskListenerByEvent(userTask, listenerClazz, "create"); + } + @Override + public void addTaskListenerByEvent(UserTask userTask, Class listenerClazz, String event) { + Assert.notNull(listenerClazz); + FlowableListener listener = this.createListener(event, listenerClazz.getName()); + userTask.getTaskListeners().add(listener); + } + + + + @Override + public HistoricProcessInstance getHistoricProcessInstance(String processInstanceId) { + return historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + } + + @Override + public List getHistoricProcessInstanceList(Set processInstanceIdSet) { + return historyService.createHistoricProcessInstanceQuery().processInstanceIds(processInstanceIdSet).list(); + } + + @Override + public MyPageData getHistoricProcessInstanceList( + String processDefinitionKey, + String processDefinitionName, + String startUser, + String beginDate, + String endDate, + MyPageParam pageParam, + boolean finishedOnly) throws ParseException { + HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery(); + if (StrUtil.isNotBlank(processDefinitionKey)) { + query.processDefinitionKey(processDefinitionKey); + } + if (StrUtil.isNotBlank(processDefinitionName)) { + query.processDefinitionName(processDefinitionName); + } + if (StrUtil.isNotBlank(startUser)) { + query.startedBy(startUser); + } + if (StrUtil.isNotBlank(beginDate)) { + SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); + query.startedAfter(sdf.parse(beginDate)); + } + if (StrUtil.isNotBlank(endDate)) { + SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); + query.startedBefore(sdf.parse(endDate)); + } + TokenData tokenData = TokenData.takeFromRequest(); + if (tokenData.getTenantId() != null) { + query.processInstanceTenantId(tokenData.getTenantId().toString()); + } else { + if (tokenData.getAppCode() == null) { + query.processInstanceWithoutTenantId(); + } else { + query.processInstanceTenantId(tokenData.getAppCode()); + } + } + if (finishedOnly) { + query.finished(); + } + query.orderByProcessInstanceStartTime().desc(); + long totalCount = query.count(); + int firstResult = (pageParam.getPageNum() - 1) * pageParam.getPageSize(); + List instanceList = query.listPage(firstResult, pageParam.getPageSize()); + return new MyPageData<>(instanceList, totalCount); + } + + @Override + public MyPageData getHistoricTaskInstanceFinishedList( + String processDefinitionName, + String beginDate, + String endDate, + MyPageParam pageParam) throws ParseException { + String loginName = TokenData.takeFromRequest().getLoginName(); + HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery() + .taskAssignee(loginName) + .finished(); + if (StrUtil.isNotBlank(processDefinitionName)) { + query.processDefinitionName(processDefinitionName); + } + if (StrUtil.isNotBlank(beginDate)) { + SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); + query.taskCompletedAfter(sdf.parse(beginDate)); + } + if (StrUtil.isNotBlank(endDate)) { + SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); + query.taskCompletedBefore(sdf.parse(endDate)); + } + TokenData tokenData = TokenData.takeFromRequest(); + if (tokenData.getTenantId() != null) { + query.taskTenantId(tokenData.getTenantId().toString()); + } else { + if (StrUtil.isBlank(tokenData.getAppCode())) { + query.taskWithoutTenantId(); + } else { + query.taskTenantId(tokenData.getAppCode()); + } + } + query.orderByHistoricTaskInstanceEndTime().desc(); + long totalCount = query.count(); + int firstResult = (pageParam.getPageNum() - 1) * pageParam.getPageSize(); + List instanceList = query.listPage(firstResult, pageParam.getPageSize()); + return new MyPageData<>(instanceList, totalCount); + } + + @Override + public List getHistoricActivityInstanceList(String processInstanceId) { + return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list(); + } + + @Override + public List getHistoricActivityInstanceListOrderByStartTime(String processInstanceId) { + return historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list(); + } + + @Override + public HistoricTaskInstance getHistoricTaskInstance(String processInstanceId, String taskId) { + return historyService.createHistoricTaskInstanceQuery() + .processInstanceId(processInstanceId).taskId(taskId).singleResult(); + } + + @Override + public List getHistoricUnfinishedInstanceList(String processInstanceId) { + return historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processInstanceId).unfinished().list(); + } + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public CallResult stopProcessInstance(String processInstanceId, String stopReason, boolean forCancel) { + //需要先更新状态,以便FlowFinishedListener监听器可以正常的判断流程结束的状态。 + int status = FlowTaskStatus.STOPPED; + if (forCancel) { + status = FlowTaskStatus.CANCELLED; + } + return this.stopProcessInstance(processInstanceId, stopReason, status); + } + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public CallResult stopProcessInstance(String processInstanceId, String stopReason, int status) { + List taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).active().list(); + if (CollUtil.isEmpty(taskList)) { + return CallResult.error("数据验证失败,当前流程尚未开始或已经结束!"); + } + BpmnModel bpmnModel = repositoryService.getBpmnModel(taskList.get(0).getProcessDefinitionId()); + EndEvent endEvent = bpmnModel.getMainProcess() + .findFlowElementsOfType(EndEvent.class, false).get(0); + List currentActivitiIds = new LinkedList<>(); + flowWorkOrderService.updateFlowStatusByProcessInstanceId(processInstanceId, status); + for (Task task : taskList) { + String currActivityId = task.getTaskDefinitionKey(); + currentActivitiIds.add(currActivityId); + FlowNode currFlow = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currActivityId); + if (currFlow == null) { + List subProcessList = + bpmnModel.getMainProcess().findFlowElementsOfType(SubProcess.class); + for (SubProcess subProcess : subProcessList) { + FlowElement flowElement = subProcess.getFlowElement(currActivityId); + if (flowElement != null) { + currFlow = (FlowNode) flowElement; + break; + } + } + } + org.springframework.util.Assert.notNull(currFlow, "currFlow can't be NULL"); + if (!(currFlow.getParentContainer().equals(endEvent.getParentContainer()))) { + throw new FlowOperationException("数据验证失败,不能从子流程直接中止!"); + } + FlowTaskComment taskComment = new FlowTaskComment(task); + taskComment.setApprovalType(FlowApprovalType.STOP); + taskComment.setTaskComment(stopReason); + flowTaskCommentService.saveNew(taskComment); + } + this.doChanageState(processInstanceId, currentActivitiIds, + CollUtil.newArrayList(endEvent.getId()), null, null); + flowMessageService.updateFinishedStatusByProcessInstanceId(processInstanceId); + return CallResult.ok(); + } + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public void deleteProcessInstance(String processInstanceId) { + historyService.deleteHistoricProcessInstance(processInstanceId); + flowMessageService.removeByProcessInstanceId(processInstanceId); + FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId); + if (workOrder == null) { + return; + } + FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey()); + if (StrUtil.isNotBlank(flowEntry.getExtensionData())) { + FlowEntryExtensionData extData = JSON.parseObject(flowEntry.getExtensionData(), FlowEntryExtensionData.class); + if (BooleanUtil.isTrue(extData.getCascadeDeleteBusinessData())) { + if (workOrder.getOnlineTableId() != null) { + // 级联删除在线表单工作流的业务数据。 + flowCustomExtFactory.getOnlineBusinessDataExtHelper().deleteBusinessData(workOrder); + } else { + // 级联删除路由表单工作流的业务数据。 + flowCustomExtFactory.getBusinessDataExtHelper().deleteBusinessData(workOrder); + } + } + } + flowWorkOrderService.removeByProcessInstanceId(processInstanceId); + } + + @Override + public Object getTaskVariable(String taskId, String variableName) { + return taskService.getVariable(taskId, variableName); + } + + @Override + public String getTaskVariableStringWithSafe(String taskId, String variableName) { + try { + Object v = taskService.getVariable(taskId, variableName); + if (v == null) { + return null; + } + return v.toString(); + } catch (Exception e) { + String errorMessage = + String.format("Failed to getTaskVariable taskId [%s], variableName [%s]", taskId, variableName); + log.error(errorMessage, e); + return null; + } + } + + @Override + public Object getExecutionVariable(String executionId, String variableName) { + return runtimeService.getVariable(executionId, variableName); + } + + @Override + public String getExecutionVariableStringWithSafe(String executionId, String variableName) { + try { + Object v = runtimeService.getVariable(executionId, variableName); + if (v == null) { + return null; + } + return v.toString(); + } catch (Exception e) { + String errorMessage = String.format( + "Failed to getExecutionVariableStringWithSafe executionId [%s], variableName [%s]", executionId, variableName); + log.error(errorMessage, e); + return null; + } + } + + @Override + public Object getHistoricProcessInstanceVariable(String processInstanceId, String variableName) { + HistoricVariableInstance hv = historyService.createHistoricVariableInstanceQuery() + .processInstanceId(processInstanceId).variableName(variableName).singleResult(); + return hv == null ? null : hv.getValue(); + } + + @Override + public BpmnModel convertToBpmnModel(String bpmnXml) throws XMLStreamException { + BpmnXMLConverter converter = new BpmnXMLConverter(); + InputStream in = new ByteArrayInputStream(bpmnXml.getBytes(StandardCharsets.UTF_8)); + @Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(in); + return converter.convertToBpmnModel(reader); + } + + @MultiDatabaseWriteMethod + @Transactional + @Override + public CallResult backToRuntimeTask( + Task task, String targetKey, Integer backType, String reason, JSONObject taskVariableData) { + try { + Tuple3, List, Boolean> t = this.calculateCurrentAndTargetIds(task, targetKey); + Map variables = null; + if (BooleanUtil.isTrue(t.getThird())) { + variables = new HashMap<>(1); + FlowRejectData rejectData = new FlowRejectData(); + rejectData.setSourceUser(TokenData.takeFromRequest().getLoginName()); + rejectData.setSourceTaskKey(task.getTaskDefinitionKey()); + rejectData.setTargetTaskKeys(t.getSecond()); + variables.put(FlowConstant.REJECT_TO_SOURCE_DATA_VAR, JSON.toJSONString(rejectData)); + } + BpmnModel model = getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + List allTaskIds = new ArrayList<>(t.getFirst()); + allTaskIds.addAll(t.getSecond()); + Set containerIdSet = allTaskIds.stream().map(taskId -> + model.getFlowElement(taskId).getParentContainer().getId()).collect(Collectors.toSet()); + if (containerIdSet.size() > 1) { + String processInstanceId = task.getProcessInstanceId(); + if (targetKey == null) { + this.doChanageState(processInstanceId, t.getFirst(), t.getSecond(), null, variables); + } else { + // 判断targetKey是否为流程录入发起节点 + FlowTaskComment firstTaskComment = + flowTaskCommentService.getFirstFlowTaskComment(task.getProcessInstanceId()); + if (targetKey.equals(firstTaskComment.getTaskKey())) { + this.doChanageState(processInstanceId, + t.getFirst(), CollUtil.newArrayList(targetKey), null, variables); + } else { + return CallResult.error("不支持子流程驳回到主流程的其他节点!"); + } + } + } else { + CallResult verifyResult = canRejectTo(model, task, t.getFirst(), t.getSecond()); + if (!verifyResult.isSuccess()) { + return verifyResult; + } + this.doChanageState(task.getProcessInstanceId(), t.getFirst(), t.getSecond(), null, variables); + } + FlowTaskComment comment = new FlowTaskComment(); + comment.setTaskId(task.getId()); + comment.setTaskKey(task.getTaskDefinitionKey()); + comment.setTaskName(task.getName()); + switch (backType) { + case FlowBackType.REJECT: + comment.setApprovalType(FlowApprovalType.REJECT); + break; + case FlowBackType.REVOKE: + comment.setApprovalType(FlowApprovalType.REVOKE); + break; + default: + break; + } + comment.setProcessInstanceId(task.getProcessInstanceId()); + comment.setTaskComment(reason); + comment.setTargetTaskKey(JSON.toJSONString(t.getSecond())); + flowTaskCommentService.saveNew(comment); + Integer approvalStatus = MapUtil.getInt(taskVariableData, FlowConstant.LATEST_APPROVAL_STATUS_KEY); + flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(task.getProcessInstanceId(), approvalStatus); + return CallResult.ok(); + } catch (MyRuntimeException e) { + return CallResult.error(e.getMessage()); + } + } + + private CallResult canRejectTo(BpmnModel model, Task task, List currentIds, List targetIds) { + // 先判断是否是点对点,如果是就忽略后面的判断了。 + if (this.isRejectBackToSourceUserTask(task)) { + return CallResult.ok(); + } + List flowEntryPublishList = + flowEntryService.getFlowEntryPublishList(CollUtil.newHashSet(task.getProcessDefinitionId())); + String parentContainerId = model.getFlowElement(task.getTaskDefinitionKey()).getParentContainer().getId(); + AnalyzedNode rootNode = + this.calculateAnalyzeNode(flowEntryPublishList.get(0).getAnalyzedNodeJson(), model, parentContainerId); + Map fullNodeMap = this.expandToAnalyzedNodeMap(rootNode); + UserTaskInfo sourceUserTaskInfo = this.deduceUserTaskInfo(fullNodeMap.values(), task.getTaskDefinitionKey()); + org.springframework.util.Assert.notNull(sourceUserTaskInfo, "sourceUserTaskInfo can't be NULL."); + List targetUserTaskInfos = new LinkedList<>(); + for (String targetId : targetIds) { + targetUserTaskInfos.add(this.deduceUserTaskInfo(fullNodeMap.values(), targetId)); + } + AnalyzedNode sourceNode = fullNodeMap.get(sourceUserTaskInfo.getNodeId()); + List targetNodes = new LinkedList<>(); + for (UserTaskInfo targetUserTaskInfo : targetUserTaskInfos) { + targetNodes.add(fullNodeMap.get(targetUserTaskInfo.getNodeId())); + } + // 源节点和目标节点位于相同的执行分支,如果是并行网关也是同一个分支。 + if (targetIds.size() == 1 && sourceNode == targetNodes.get(0)) { + return CallResult.ok(); + } + // 如果源节点位于并行网关之内,去找其他分支是否存在审批任务。 + if (this.placedWithinParallelGateway(fullNodeMap, sourceNode)) { + List> allRoads = sourceNode.getUserTaskRoads(); + for (List road : allRoads) { + if (road.stream().filter(d -> currentIds.contains(d.getTaskId())).findFirst().orElse(null) == null) { + return CallResult.error("不能执行驳回操作,其他并行网关分支已经全部审批完成!"); + } + } + } + return CallResult.ok(); + } + + private Tuple3, List, Boolean> calculateCurrentAndTargetIds(Task task, String targetKey) { + List taskList = this.getProcessInstanceActiveTaskList(task.getProcessInstanceId()); + List targetIds; + if (StrUtil.isNotBlank(targetKey)) { + if (taskList.stream().anyMatch(t -> t.getTaskDefinitionKey().equals(targetKey))) { + throw new MyRuntimeException("数据验证失败,不能驳回到其他待办任务!"); + } + List histTaskList = + this.getHistoricActivityInstanceList(task.getProcessInstanceId()); + if (histTaskList.stream().noneMatch(t -> t.getActivityId().equals(targetKey))) { + throw new MyRuntimeException("数据验证失败,不能驳回到没有执行过的流程任务节点!"); + } + targetIds = CollUtil.newArrayList(targetKey); + } else { + TypedCallResult> callResult = this.deduceBackTargetIds(task); + if (!callResult.isSuccess()) { + throw new MyRuntimeException(callResult.getErrorMessage()); + } + targetIds = callResult.getData(); + } + if (this.isRejectBackToSourceUserTask(task) && (targetKey != null || targetIds.size() == 1)) { + return new Tuple3<>(CollUtil.newArrayList(task.getTaskDefinitionKey()), targetIds, true); + } + List currentIds = taskList.stream().map(Task::getTaskDefinitionKey).collect(Collectors.toList()); + if (targetKey == null) { + currentIds = this.getChildActivitiIdList(task.getProcessDefinitionId(), targetIds.get(0), currentIds); + } + return new Tuple3<>(currentIds, targetIds, false); + } + + private boolean isRejectBackToSourceUserTask(Task task) { + FlowTaskExt taskExt = flowTaskExtService + .getByProcessDefinitionIdAndTaskId(task.getProcessDefinitionId(), task.getTaskDefinitionKey()); + FlowUserTaskExtData extData = JSON.parseObject(taskExt.getExtraDataJson(), FlowUserTaskExtData.class); + return extData != null && StrUtil.equals(extData.getRejectType(), FlowUserTaskExtData.REJECT_TYPE_BACK_TO_SOURCE); + } + + private UserTask findUserTaskByTask(String processDefinitionId, String taskKey) { + ProcessDefinition processDefinition = this.getProcessDefinitionById(processDefinitionId); + Collection allElements = this.getProcessAllElements(processDefinition.getId()); + return (UserTask) this.findFlement(allElements, taskKey); + } + + private TypedCallResult> deduceBackTargetIds(Task task) { + UserTask source = this.findUserTaskByTask(task.getProcessDefinitionId(), task.getTaskDefinitionKey()); + List parentUserTaskList = + this.getParentUserTaskList(source, new HashSet<>(), new ArrayList<>(), false); + if (CollUtil.isEmpty(parentUserTaskList)) { + return TypedCallResult.error("数据验证失败,当前节点为初始任务节点,不能驳回!"); + } + // 获取上一步途径过的用户任务Ids + UserTask targetUserTask = this.getBackParentUserTask(parentUserTaskList, task); + if (targetUserTask == null) { + return TypedCallResult.error("数据验证失败,不能获取驳回节点!"); + } + String parallelGatewayId = null; + List outgoingFlows = targetUserTask.getOutgoingFlows(); + for (SequenceFlow outgoingFlow : outgoingFlows) { + if (outgoingFlow.getTargetFlowElement() instanceof ParallelGateway) { + parallelGatewayId = outgoingFlow.getTargetFlowElement().getId(); + break; + } + } + if (parallelGatewayId == null) { + return TypedCallResult.ok(CollUtil.newArrayList(targetUserTask.getId())); + } + List targetUserTasks = CollUtil.newArrayList(targetUserTask); + for (UserTask parentTask : parentUserTaskList) { + if (parentTask == targetUserTask) { + continue; + } + for (SequenceFlow outgoingFlow : parentTask.getOutgoingFlows()) { + if (StrUtil.equals(outgoingFlow.getTargetFlowElement().getId(), parallelGatewayId)) { + targetUserTasks.add(parentTask); + } + } + } + List targetIds = targetUserTasks.stream().map(UserTask::getId).collect(Collectors.toList()); + return TypedCallResult.ok(targetIds); + } + + @Transactional + @Override + public CallResult backToRuntimeTaskAndTransfer( + Task task, String targetKey, Integer backType, String reason, String delegateAssignee) { + CallResult result = this.backToRuntimeTask(task, targetKey, backType, reason, null); + if (!result.isSuccess()) { + return result; + } + FlowTaskComment flowTaskComment = new FlowTaskComment(); + flowTaskComment.setDelegateAssignee(delegateAssignee); + flowTaskComment.setApprovalType(FlowApprovalType.INTERVENE); + Task targetTask = this.getProcessInstanceActiveTaskList(task.getProcessInstanceId()).get(0); + this.transferTo(targetTask, flowTaskComment); + return result; + } + + @Override + public List getRejectCandidateUserTaskList(Task task) { + List userTaskList = null; + ProcessDefinition processDefinition = this.getProcessDefinitionById(task.getProcessDefinitionId()); + Collection allElements = this.getProcessAllElements(processDefinition.getId()); + if (CollUtil.isEmpty(allElements)) { + return userTaskList; + } + // 获取当前任务节点元素 + UserTask source = null; + for (FlowElement flowElement : allElements) { + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + source = (UserTask) flowElement; + break; + } + } + // 获取节点的所有路线 + List> roads = this.findRoad(source, null, null, null); + // 可回退的节点列表 + userTaskList = new ArrayList<>(); + for (List road : roads) { + if (CollUtil.isEmpty(userTaskList)) { + // 还没有可回退节点直接添加 + userTaskList = road; + } else { + // 如果已有回退节点,则比对取交集部分 + userTaskList.retainAll(road); + } + } + return userTaskList; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void transferTo(Task task, FlowTaskComment flowTaskComment) { + List transferUserList = StrUtil.split(flowTaskComment.getDelegateAssignee(), ","); + for (String transferUser : transferUserList) { + if (transferUser.equals(FlowConstant.START_USER_NAME_VAR)) { + String startUser = this.getProcessInstanceVariable( + task.getProcessInstanceId(), FlowConstant.PROC_INSTANCE_START_USER_NAME_VAR).toString(); + String newDelegateAssignee = StrUtil.replace( + flowTaskComment.getDelegateAssignee(), FlowConstant.START_USER_NAME_VAR, startUser); + flowTaskComment.setDelegateAssignee(newDelegateAssignee); + transferUserList = StrUtil.split(flowTaskComment.getDelegateAssignee(), ","); + break; + } + } + taskService.unclaim(task.getId()); + FlowTaskExt taskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId( + task.getProcessDefinitionId(), task.getTaskDefinitionKey()); + if (StrUtil.isNotBlank(taskExt.getCandidateUsernames())) { + List candidateUsernames = this.getCandidateUsernames(taskExt, task.getId()); + if (CollUtil.isNotEmpty(candidateUsernames)) { + for (String username : candidateUsernames) { + taskService.deleteCandidateUser(task.getId(), username); + } + } + } else if (StrUtil.equals(taskExt.getGroupType(), FlowConstant.GROUP_TYPE_ASSIGNEE)) { + List links = taskService.getIdentityLinksForTask(task.getId()); + for (IdentityLink link : links) { + taskService.deleteUserIdentityLink(task.getId(), link.getUserId(), link.getType()); + } + } else { + this.removeCandidateGroup(taskExt, task); + } + transferUserList.forEach(u -> taskService.addCandidateUser(task.getId(), u)); + flowTaskComment.fillWith(task); + flowTaskCommentService.saveNew(flowTaskComment); + } + + @Override + public List getCandidateUsernames(FlowTaskExt flowTaskExt, String taskId) { + if (StrUtil.isBlank(flowTaskExt.getCandidateUsernames())) { + return Collections.emptyList(); + } + if (!StrUtil.equals(flowTaskExt.getCandidateUsernames(), "${" + FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR + "}")) { + return StrUtil.split(flowTaskExt.getCandidateUsernames(), ","); + } + Object candidateUsernames = getTaskVariableStringWithSafe(taskId, FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR); + return candidateUsernames == null ? null : StrUtil.split(candidateUsernames.toString(), ","); + } + + @Override + public Tuple2, Set> getDeptPostIdAndPostIds( + FlowTaskExt flowTaskExt, String processInstanceId, boolean historic) { + Set postIdSet = new LinkedHashSet<>(); + Set deptPostIdSet = new LinkedHashSet<>(); + if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER)) { + Object v = this.getProcessInstanceVariable( + processInstanceId, FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR, historic); + if (ObjectUtil.isNotEmpty(v)) { + deptPostIdSet.add(v.toString()); + } + } else if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_DEPT_POST_LEADER)) { + Object v = this.getProcessInstanceVariable( + processInstanceId, FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR, historic); + if (ObjectUtil.isNotEmpty(v)) { + deptPostIdSet.add(v.toString()); + } + } else if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_POST) + && StrUtil.isNotBlank(flowTaskExt.getDeptPostListJson())) { + this.buildDeptPostIdAndPostIdsForPost(flowTaskExt, processInstanceId, historic, postIdSet, deptPostIdSet); + } + return new Tuple2<>(deptPostIdSet, postIdSet); + } + + @Override + public AnalyzedNode analyzeBpmnRoads(String processDefinitionId) { + Process mainProcess = repositoryService.getBpmnModel(processDefinitionId).getMainProcess(); + Collection mainElements = mainProcess.getFlowElements(); + AnalyzedNode rootNode = this.doAnalyzeBpmnRoads(mainElements); + rootNode.setContainerId(mainProcess.getId()); + List subProcesses = new LinkedList<>(); + this.calculateAllSubProcessList(mainElements, subProcesses); + subProcesses.forEach(subProcess -> { + AnalyzedNode subProcessNode = this.doAnalyzeBpmnRoads(subProcess.getFlowElements()); + subProcessNode.setContainerId(subProcess.getId()); + rootNode.getSubProcessNodes().add(subProcessNode); + }); + return rootNode; + } + + @Transactional + @Override + public CallResult freeJumpTo(Task task, String targetKey, String comment, String delegateAssignee) { + BpmnModel model = repositoryService.getBpmnModel(task.getProcessDefinitionId()); + if (!this.withinSameContainer(model, task.getTaskDefinitionKey(), targetKey)) { + return CallResult.error("不支持从子流程内的任务节点跳入到主流程或其他子流程内的任务节点!"); + } + CallResult jumpResult = this.doJumpTo(model, task, targetKey, delegateAssignee); + if (!jumpResult.isSuccess()) { + return jumpResult; + } + FlowTaskComment taskComment = new FlowTaskComment(); + taskComment.fillWith(task); + taskComment.setApprovalType(FlowApprovalType.FREE_JUMP); + taskComment.setTaskComment(comment); + taskComment.setTargetTaskKey(targetKey); + flowTaskCommentService.saveNew(taskComment); + return jumpResult; + } + + @Override + public CallResult interveneTo(Task task, String targetKey, String comment, String delegateAssignee) { + BpmnModel model = repositoryService.getBpmnModel(task.getProcessDefinitionId()); + if (!this.withinSameContainer(model, task.getTaskDefinitionKey(), targetKey)) { + return CallResult.error("不支持从子流程内的任务节点跳入到主流程或其他子流程内的任务节点!"); + } + CallResult jumpResult = this.doJumpTo(model, task, targetKey, delegateAssignee); + if (!jumpResult.isSuccess()) { + return jumpResult; + } + FlowTaskComment taskComment = new FlowTaskComment(); + taskComment.fillWith(task); + taskComment.setTargetTaskKey(targetKey); + taskComment.setTaskComment(comment); + taskComment.setApprovalType(FlowApprovalType.INTERVENE); + taskComment.setDelegateAssignee(delegateAssignee); + flowTaskCommentService.saveNew(taskComment); + return jumpResult; + } + + @Override + public List getChildActivitiIdList(String processDefinitionId, String taskKey, List allActivitiIds) { + if (StrUtil.isBlank(taskKey) || CollUtil.isEmpty(allActivitiIds)) { + return new LinkedList<>(); + } + FlowElement source = this.findUserTaskByTask(processDefinitionId, taskKey); + List childList = this.getChildUserTaskList(source, new HashSet<>(), new ArrayList<>()); + Set childIds = childList.stream().map(UserTask::getId).collect(Collectors.toSet()); + return allActivitiIds.stream().filter(childIds::contains).collect(Collectors.toList()); + } + + @Override + public Map getAllUserTaskMap(String processDefinitionId) { + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); + Process process = bpmnModel.getProcesses().get(0); + return process.findFlowElementsOfType(UserTask.class) + .stream().collect(Collectors.toMap(UserTask::getId, a -> a, (k1, k2) -> k1)); + } + + @Override + public UserTask getUserTask(String processDefinitionId, String taskKey) { + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); + for (Process process : bpmnModel.getProcesses()) { + UserTask userTask = process.findFlowElementsOfType(UserTask.class) + .stream().filter(t -> t.getId().equals(taskKey)).findFirst().orElse(null); + if (userTask != null) { + return userTask; + } + } + return null; + } + + private AnalyzedNode doAnalyzeBpmnRoads(Collection allElements) { + StartEvent startEvent = (StartEvent) allElements.stream() + .filter(StartEvent.class::isInstance).findFirst().orElse(null); + org.springframework.util.Assert.notNull(startEvent, "StartEvent can't be NULL."); + List> roads = + this.findForwardRoad(startEvent, new LinkedList<>(), new HashSet<>(), new LinkedList<>()); + List> normalizedRoads = new LinkedList<>(); + for (List road : roads) { + List normalizedRoad = new LinkedList<>(); + for (FlowElement el : road) { + if (!normalizedRoad.contains(el)) { + normalizedRoad.add(el); + } + } + normalizedRoads.add(normalizedRoad); + } + roads = normalizedRoads; + List gatewayExts = this.calculateGatewayPassCount(roads); + Map gatewayNodeMap = this.analyzeGatewayPair(gatewayExts, new HashSet<>()); + this.normalizeGatewayParentRelation(gatewayNodeMap, roads); + AnalyzedNode rootNode = new AnalyzedNode(); + rootNode.setStartId(startEvent.getId()); + rootNode.setEndId("end"); + rootNode.setStartEvent(true); + rootNode.setParallelGateway(false); + for (AnalyzedNode node : gatewayNodeMap.values()) { + if (node.getParentId() == null) { + node.setParentId(startEvent.getId()); + rootNode.getChildList().add(node); + } + } + Map fullNodeMap = new HashMap<>(gatewayNodeMap); + fullNodeMap.put(rootNode.getStartId(), rootNode); + List> allRoads = new LinkedList<>(); + roads.forEach(road -> allRoads.add(road.stream().map(FlowElement::getId).collect(Collectors.toList()))); + rootNode.setAllRoads(allRoads); + this.analyzeRoadPath(fullNodeMap, roads, startEvent); + return rootNode; + } + + private void calculateAllSubProcessList(Collection elements, List resultList) { + for (FlowElement element : elements) { + if (element instanceof SubProcess) { + SubProcess subProcess = (SubProcess) element; + resultList.add(subProcess); + this.calculateAllSubProcessList(subProcess.getFlowElements(), resultList); + } + } + } + + private AnalyzedNode calculateAnalyzeNode(String analyzeNodeJson, BpmnModel model, String containerId) { + AnalyzedNode rootNode = JSON.parseObject(analyzeNodeJson, AnalyzedNode.class); + if (!model.getMainProcess().getId().equals(containerId)) { + for (AnalyzedNode subProcessNode : rootNode.getSubProcessNodes()) { + if (containerId.equals(subProcessNode.getContainerId())) { + rootNode = subProcessNode; + break; + } + } + } + return rootNode; + } + + private boolean withinSameContainer(BpmnModel model, String sourceKey, String targetKey) { + String sourceContainerId = model.getFlowElement(sourceKey).getParentContainer().getId(); + String targetContainerId = model.getFlowElement(targetKey).getParentContainer().getId(); + return sourceContainerId.equals(targetContainerId); + } + + private CallResult doJumpTo(BpmnModel model, Task task, String targetKey, String delegateAssignee) { + String sourceContainerId = model.getFlowElement(task.getTaskDefinitionKey()).getParentContainer().getId(); + List flowEntryPublishList = + flowEntryService.getFlowEntryPublishList(CollUtil.newHashSet(task.getProcessDefinitionId())); + AnalyzedNode rootNode = + this.calculateAnalyzeNode(flowEntryPublishList.get(0).getAnalyzedNodeJson(), model, sourceContainerId); + Map fullNodeMap = this.expandToAnalyzedNodeMap(rootNode); + UserTaskInfo sourceUserTaskInfo = this.deduceUserTaskInfo(fullNodeMap.values(), task.getTaskDefinitionKey()); + org.springframework.util.Assert.notNull(sourceUserTaskInfo, "sourceUserTaskInfo can't be NULL."); + if (sourceUserTaskInfo.isMultiInstance()) { + return CallResult.error("数据验证失败,跳转的源节点不能是多实例会签节点!"); + } + UserTaskInfo targetUserTaskInfo = this.deduceUserTaskInfo(fullNodeMap.values(), targetKey); + if (targetUserTaskInfo == null) { + return CallResult.error("数据验证失败,跳转的目标节点不存在!"); + } + if (targetUserTaskInfo.isMultiInstance()) { + return CallResult.error("数据验证失败,跳转的目标节点不能是多实例会签节点!"); + } + AnalyzedNode sourceNode = fullNodeMap.get(sourceUserTaskInfo.getNodeId()); + AnalyzedNode targetNode = fullNodeMap.get(targetUserTaskInfo.getNodeId()); + if (sourceNode == targetNode) { + if (sourceNode.isParallelGateway() && + sourceUserTaskInfo.getRoadIndex() != targetUserTaskInfo.getRoadIndex()) { + return CallResult.error("数据验证失败,相同并行网关内不同线路的任务不能直接跳转!"); + } + this.doChanageState(task.getProcessInstanceId(), + CollUtil.newArrayList(task.getTaskDefinitionKey()), CollUtil.newArrayList(targetKey), delegateAssignee, null); + return CallResult.ok(); + } + if (!this.placedWithinParallelGateway(fullNodeMap, targetNode)) { + if (!this.placedWithinParallelGateway(fullNodeMap, sourceNode)) { + this.doChanageState(task.getProcessInstanceId(), + CollUtil.newArrayList(task.getTaskDefinitionKey()), CollUtil.newArrayList(targetKey), delegateAssignee, null); + } else { + List runActivityIds = this.getCurrentActivityIds(task.getProcessInstanceId()); + this.doChanageState(task.getProcessInstanceId(), + runActivityIds, CollUtil.newArrayList(targetKey), delegateAssignee, null); + } + return CallResult.ok(); + } + AnalyzedNode targetParanetNode = this.deduceParentParallelGatewayNode(fullNodeMap, targetNode); + AnalyzedNode sourceParentNode = this.deduceParentParallelGatewayNode(fullNodeMap, sourceNode); + if (this.isParentOrEqualNode(targetParanetNode, sourceParentNode, fullNodeMap)) { + List runActivityIds = this.getCurrentActivityIds(task.getProcessInstanceId()); + List canJumpedActivityIds = runActivityIds.stream() + .filter(id -> this.canFlowTo(id, targetKey, rootNode.getAllRoads())).collect(Collectors.toList()); + if (!CollUtil.contains(canJumpedActivityIds, task.getTaskDefinitionKey())) { + return CallResult.error("数据验证失败,原任务节点和目标任务节点位于不同的执行路径,不能直接跳转!"); + } + this.doChanageState(task.getProcessInstanceId(), + canJumpedActivityIds, CollUtil.newArrayList(targetKey), delegateAssignee, null); + return CallResult.ok(); + } + if (!StrUtil.equals(targetNode.getParentId(), sourceNode.getStartId())) { + return CallResult.error("数据验证失败,不能跳转到没有直接上下级关系的并行网关内的目标任务!"); + } + List targetIds = this.getTargetActivityIds(targetNode, targetKey); + doChanageState(task.getProcessInstanceId(), + CollUtil.newArrayList(task.getTaskDefinitionKey()), targetIds, delegateAssignee, null); + return CallResult.ok(); + } + + private void doChanageState( + String processInstanceId, + List currentIds, + List targetIds, + String delegateAssignee, + Map variables) { + if (ObjectUtil.hasEmpty(currentIds, targetIds)) { + throw new MyRuntimeException("跳转的源节点和任务节点数量均不能为空!"); + } + if (currentIds.size() > 1 && targetIds.size() > 1) { + throw new MyRuntimeException("目前暂不支持源节点和目标节点同时为多个的场景!"); + } + ChangeActivityStateBuilder builder = + runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId); + if (targetIds.size() == 1) { + if (currentIds.size() == 1) { + builder.moveActivityIdTo(currentIds.get(0), targetIds.get(0)); + } else { + builder.moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)); + } + } else { + builder.moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds); + } + if (StrUtil.isNotBlank(delegateAssignee)) { + targetIds.forEach(targetId -> { + builder.localVariable(targetId, FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR, delegateAssignee); + builder.localVariable(targetId, FlowConstant.DELEGATE_ASSIGNEE_VAR, delegateAssignee); + }); + } else { + targetIds.forEach(targetId -> { + FlowTaskComment comment = flowTaskCommentService.getLatestFlowTaskComment(processInstanceId, targetId); + if (comment != null && StrUtil.isNotBlank(comment.getCreateLoginName())) { + builder.localVariable(targetId, FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR, comment.getCreateLoginName()); + } + }); + } + if (MapUtil.isNotEmpty(variables)) { + targetIds.forEach(targetId -> builder.localVariables(targetId, variables)); + } + builder.changeState(); + } + + private List getTargetActivityIds(AnalyzedNode targetNode, String targetKey) { + List targetIds = CollUtil.newLinkedList(targetKey); + for (List road : targetNode.getUserTaskRoads()) { + boolean found = false; + for (UserTaskInfo userTaskInfo : road) { + if (userTaskInfo.getTaskId().equals(targetKey)) { + found = true; + break; + } + } + if (!found && CollUtil.isNotEmpty(road)) { + targetIds.add(road.get(0).getTaskId()); + } + } + return targetIds; + } + + private List getCurrentActivityIds(String processInstanceId) { + List runExecutionList = + runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list(); + return runExecutionList.stream() + .filter(c -> StrUtil.isNotBlank(c.getActivityId())) + .map(Execution::getActivityId).collect(Collectors.toList()); + } + + private boolean isParentOrEqualNode( + AnalyzedNode parentNode, AnalyzedNode childNode, Map fullNodeMap) { + if (parentNode == childNode) { + return true; + } + if (parentNode == null || childNode == null || childNode.getParentId() == null) { + return false; + } + if (childNode.getParentId().equals(parentNode.getStartId())) { + return true; + } + return this.isParentOrEqualNode(parentNode, fullNodeMap.get(childNode.getParentId()), fullNodeMap); + } + + private boolean canFlowTo(String sourceId, String targetId, List> allRoads) { + if (CollUtil.isEmpty(allRoads)) { + return false; + } + for (List road : allRoads) { + if (road.contains(sourceId) && road.contains(targetId)) { + return true; + } + } + return false; + } + + private void calculateParentParallelNodes( + Map fullNodeMap, AnalyzedNode node, List resultList) { + if (node.isParallelGateway()) { + resultList.add(node); + } + if (StrUtil.isBlank(node.getParentId())) { + return; + } + this.calculateParentParallelNodes(fullNodeMap, fullNodeMap.get(node.getParentId()), resultList); + } + + private AnalyzedNode deduceParentParallelGatewayNode(Map fullNodeMap, AnalyzedNode node) { + if (node.isParallelGateway()) { + return node; + } + if (StrUtil.isBlank(node.getParentId())) { + return null; + } + return this.deduceParentParallelGatewayNode(fullNodeMap, fullNodeMap.get(node.getParentId())); + } + + private boolean placedWithinParallelGateway(Map fullNodeMap, AnalyzedNode node) { + if (node.isParallelGateway()) { + return true; + } + if (StrUtil.isBlank(node.getParentId())) { + return false; + } + return this.placedWithinParallelGateway(fullNodeMap, fullNodeMap.get(node.getParentId())); + } + + private UserTaskInfo deduceUserTaskInfo(Collection nodeList, String taskId) { + for (AnalyzedNode node : nodeList) { + if (CollUtil.isNotEmpty(node.getUserTaskRoads())) { + for (List road : node.getUserTaskRoads()) { + UserTaskInfo foundInfo = + road.stream().filter(info -> info.getTaskId().equals(taskId)).findFirst().orElse(null); + if (foundInfo != null) { + return foundInfo; + } + } + } + } + return null; + } + + private Map expandToAnalyzedNodeMap(AnalyzedNode node) { + Map fullNodeMap = MapUtil.newHashMap(); + fullNodeMap.put(node.getStartId(), node); + if (CollUtil.isNotEmpty(node.getChildList())) { + for (AnalyzedNode childNode : node.getChildList()) { + fullNodeMap.putAll(this.expandToAnalyzedNodeMap(childNode)); + } + } + return fullNodeMap; + } + + private void removeCandidateGroup(FlowTaskExt taskExt, Task task) { + if (StrUtil.isNotBlank(taskExt.getDeptIds())) { + for (String deptId : StrUtil.split(taskExt.getDeptIds(), ",")) { + taskService.deleteCandidateGroup(task.getId(), deptId); + } + } + if (StrUtil.isNotBlank(taskExt.getRoleIds())) { + for (String roleId : StrUtil.split(taskExt.getRoleIds(), ",")) { + taskService.deleteCandidateGroup(task.getId(), roleId); + } + } + Tuple2, Set> tuple2 = + getDeptPostIdAndPostIds(taskExt, task.getProcessInstanceId(), false); + if (CollUtil.isNotEmpty(tuple2.getFirst())) { + for (String deptPostId : tuple2.getFirst()) { + taskService.deleteCandidateGroup(task.getId(), deptPostId); + } + } + if (CollUtil.isNotEmpty(tuple2.getSecond())) { + for (String postId : tuple2.getSecond()) { + taskService.deleteCandidateGroup(task.getId(), postId); + } + } + } + + private void buildDeptPostIdAndPostIdsForPost( + FlowTaskExt flowTaskExt, + String processInstanceId, + boolean historic, + Set postIdSet, + Set deptPostIdSet) { + List groupDataList = + JSON.parseArray(flowTaskExt.getDeptPostListJson(), FlowTaskPostCandidateGroup.class); + for (FlowTaskPostCandidateGroup groupData : groupDataList) { + switch (groupData.getType()) { + case FlowConstant.GROUP_TYPE_ALL_DEPT_POST_VAR: + postIdSet.add(groupData.getPostId()); + break; + case FlowConstant.GROUP_TYPE_DEPT_POST_VAR: + deptPostIdSet.add(groupData.getDeptPostId()); + break; + case FlowConstant.GROUP_TYPE_SELF_DEPT_POST_VAR: + Object v = this.getProcessInstanceVariable( + processInstanceId, FlowConstant.SELF_DEPT_POST_PREFIX + groupData.getPostId(), historic); + if (ObjectUtil.isNotEmpty(v)) { + deptPostIdSet.add(v.toString()); + } + break; + case FlowConstant.GROUP_TYPE_UP_DEPT_POST_VAR: + Object v2 = this.getProcessInstanceVariable( + processInstanceId, FlowConstant.UP_DEPT_POST_PREFIX + groupData.getPostId(), historic); + if (ObjectUtil.isNotEmpty(v2)) { + deptPostIdSet.add(v2.toString()); + } + break; + case FlowConstant.GROUP_TYPE_SIBLING_DEPT_POST_VAR: + Object v3 = this.getProcessInstanceVariable( + processInstanceId, FlowConstant.SIBLING_DEPT_POST_PREFIX + groupData.getPostId(), historic); + if (ObjectUtil.isNotEmpty(v3)) { + deptPostIdSet.addAll(StrUtil.split(v3.toString(), ",") + .stream().filter(StrUtil::isNotBlank).collect(Collectors.toList())); + } + break; + default: + break; + } + } + } + + private Object getProcessInstanceVariable(String processInstanceId, String variableName, boolean historic) { + if (historic) { + return getHistoricProcessInstanceVariable(processInstanceId, variableName); + } + return getProcessInstanceVariable(processInstanceId, variableName); + } + + private void analyzeRoadPath( + Map fullNodeMap, List> roads, StartEvent startEvent) { + Deque stack = new LinkedList<>(); + stack.push(startEvent.getId()); + for (List road : roads) { + int index = roads.indexOf(road); + road.forEach(el -> { + if (el instanceof Gateway) { + if (fullNodeMap.containsKey(el.getId())) { + stack.push(el.getId()); + } else { + this.safePollParallelGateway(fullNodeMap, stack); + } + } else if (el instanceof UserTask) { + String lastElementId = stack.peek(); + if (lastElementId != null) { + AnalyzedNode node = fullNodeMap.get(lastElementId); + node.getAllUserTasks().add("road" + index + "---" + el.getId()); + } + } + }); + } + this.analyzeRoadAndSplitToPath(fullNodeMap, roads); + } + + private void analyzeRoadAndSplitToPath(Map fullGatewayMap, List> roads) { + Map allFlowElementMap = new HashMap<>(20); + roads.forEach(road -> allFlowElementMap.putAll(road.stream() + .filter(UserTask.class::isInstance).collect(Collectors.toMap(FlowElement::getId, el -> el)))); + fullGatewayMap.values().forEach(node -> { + Set set = new HashSet<>(); + for (int i = 0; i < roads.size(); i++) { + StringBuilder sb = new StringBuilder(128); + for (String el : node.getAllUserTasks()) { + String prefix = "road" + i + "---"; + if (StrUtil.startWith(el, prefix)) { + sb.append(StrUtil.removePrefix(el, prefix)).append(","); + } + } + set.add(sb.toString()); + } + set = set.stream().filter(StrUtil::isNotBlank).collect(Collectors.toSet()); + int index = 0; + for (String s : set) { + List idList = StrUtil.split(s, ",") + .stream().filter(StrUtil::isNotBlank).collect(Collectors.toList()); + List userTaskRoad = new LinkedList<>(); + for (String id : idList) { + FlowElement el = allFlowElementMap.get(id); + UserTask userTask = (UserTask) el; + userTaskRoad.add(new UserTaskInfo( + userTask.getId(), userTask.hasMultiInstanceLoopCharacteristics(), node.getStartId(), index)); + } + index++; + node.getUserTaskRoads().add(userTaskRoad); + } + }); + } + + private void normalizeGatewayParentRelation(Map fullGatewayMap, List> roads) { + for (List road : roads) { + Deque stack = new LinkedList<>(); + road.stream().filter(Gateway.class::isInstance).forEach(el -> { + if (fullGatewayMap.containsKey(el.getId())) { + String lastElementId = stack.peek(); + if (lastElementId != null) { + AnalyzedNode parentNode = fullGatewayMap.get(lastElementId); + if (!lastElementId.equals(el.getId())) { + fullGatewayMap.get(el.getId()).setParentId(parentNode.getStartId()); + } + } + stack.push(el.getId()); + } else { + this.safePollParallelGateway(fullGatewayMap, stack); + } + }); + } + fullGatewayMap.values().stream().filter(node -> node.getParentId() != null).forEach(node -> { + AnalyzedNode parentNode = fullGatewayMap.get(node.getParentId()); + parentNode.getChildList().add(node); + }); + } + + private void safePollParallelGateway(Map fullGatewayMap, Deque stack) { + AnalyzedNode lastNode; + do { + String lastElementId = stack.poll(); + if (lastElementId == null) { + break; + } + lastNode = fullGatewayMap.get(lastElementId); + } while (!lastNode.isParallelGateway()); + } + + private Map analyzeGatewayPair(List gatewayExts, Set scannedSet) { + Map fullGatewayMap = MapUtil.newHashMap(); + int index = 0; + for (GatewayExt ext : gatewayExts) { + index++; + if (scannedSet.contains(ext.gateway.getId())) { + continue; + } + AnalyzedNode node = new AnalyzedNode(); + node.setStartId(ext.gateway.getId()); + node.setParallelGateway(ext.getGateway() instanceof ParallelGateway); + fullGatewayMap.put(node.getStartId(), node); + if (node.isParallelGateway()) { + List leftList = gatewayExts.subList(index, gatewayExts.size()); + this.findParallelGatewayEnd(leftList, ext.getPassCount(), node, fullGatewayMap, scannedSet); + } + } + return fullGatewayMap; + } + + private void findParallelGatewayEnd( + List leftList, + int passCount, + AnalyzedNode node, + Map fullGatewayMap, + Set scannedSet) { + List subList = new LinkedList<>(); + for (GatewayExt ext2 : leftList) { + if (ext2.getPassCount() == passCount) { + node.setEndId(ext2.gateway.getId()); + scannedSet.add(ext2.gateway.getId()); + if (CollUtil.isNotEmpty(subList)) { + fullGatewayMap.putAll(this.analyzeGatewayPair(subList, scannedSet)); + scannedSet.addAll(subList.stream() + .map(subExt -> subExt.getGateway().getId()).collect(Collectors.toList())); + } + break; + } else { + subList.add(ext2); + } + } + } + + private List calculateGatewayPassCount(List> roads) { + List gatewayExts = new LinkedList<>(); + for (List road : roads) { + road.stream().filter(Gateway.class::isInstance).forEach(el -> { + GatewayExt gatewayExt = gatewayExts.stream() + .filter(ext -> ext.gateway.getId().equals(el.getId())).findFirst().orElse(null); + if (gatewayExt != null) { + gatewayExt.setPassCount(gatewayExt.getPassCount() + 1); + } else { + gatewayExts.add(new GatewayExt((Gateway) el)); + } + }); + } + return gatewayExts; + } + + @Data + @AllArgsConstructor + static class GatewayExt { + private Gateway gateway; + private int passCount = 1; + + public GatewayExt(Gateway gateway) { + this.gateway = gateway; + } + } + + private List> findForwardRoad( + FlowElement source, + List passRoads, + Set hasSequenceFlow, + List> roads) { + List sequenceFlows = getElementOutgoingFlows(source); + if (CollUtil.isEmpty(sequenceFlows)) { + roads.add(passRoads); + return roads; + } + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + hasSequenceFlow.add(sequenceFlow.getId()); + FlowElement element = sequenceFlow.getTargetFlowElement(); + boolean pushed = false; + if (element instanceof UserTask + || element instanceof ParallelGateway + || element instanceof ExclusiveGateway) { + passRoads.add(element); + pushed = true; + } + roads = findForwardRoad(sequenceFlow.getTargetFlowElement(), + new ArrayList<>(passRoads), new HashSet<>(hasSequenceFlow), roads); + if (pushed) { + passRoads.remove(element); + } + } + return roads; + } + + private List> findRoad( + FlowElement source, List passRoads, Set hasSequenceFlow, List> roads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + roads = roads == null ? new ArrayList<>() : roads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + if (source instanceof StartEvent && source.getSubProcess() != null) { + roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads); + } + List sequenceFlows = getElementIncomingFlows(source); + if (CollUtil.isEmpty(sequenceFlows)) { + roads.add(passRoads); + return roads; + } + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + hasSequenceFlow.add(sequenceFlow.getId()); + UserTask nowUserTask = null; + if (sequenceFlow.getSourceFlowElement() instanceof UserTask + && !((UserTask) sequenceFlow.getSourceFlowElement()).hasMultiInstanceLoopCharacteristics()) { + nowUserTask = (UserTask) sequenceFlow.getSourceFlowElement(); + passRoads.add(nowUserTask); + } + roads = findRoad(sequenceFlow.getSourceFlowElement(), + new ArrayList<>(passRoads), new HashSet<>(hasSequenceFlow), roads); + if (nowUserTask != null) { + passRoads.remove(nowUserTask); + } + } + return roads; + } + + private List getParentUserTaskList( + FlowElement source, Set hasSequenceFlow, List userTaskList, boolean subProcessStart) { + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = getParentUserTaskList( + source.getSubProcess(), hasSequenceFlow, userTaskList, true); + } + // 如果是子流程,需要临时定位到子流程内部的结束事件节点。 + if (!subProcessStart && source instanceof SubProcess) { + for (FlowElement element : ((SubProcess) source).getFlowElements()) { + if (element instanceof EndEvent) { + source = element; + break; + } + } + } + List sequenceFlows = getElementIncomingFlows(source); + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (!hasSequenceFlow.contains(sequenceFlow.getId())) { + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + this.findParentUserTaskListByFlow(sequenceFlow, hasSequenceFlow, userTaskList); + } + } + } + return userTaskList; + } + + private void findParentUserTaskListByFlow( + SequenceFlow sequenceFlow, Set hasSequenceFlow, List userTaskList) { + FlowElement sourceElement = sequenceFlow.getSourceFlowElement(); + // 类型为用户节点,则新增父级节点 + if (sourceElement instanceof UserTask) { + userTaskList.add((UserTask) sourceElement); + return; + } + getParentUserTaskList(sourceElement, new HashSet<>(hasSequenceFlow), userTaskList, false); + } + + private List getChildUserTaskListFromSubProcess(SubProcess subProcess, List userTaskList) { + for (FlowElement e : subProcess.getFlowElements()) { + if (e instanceof StartEvent) { + for (SequenceFlow sequenceFlow : ((StartEvent) e).getOutgoingFlows()) { + FlowElement targetElement = sequenceFlow.getTargetFlowElement(); + if (targetElement instanceof UserTask) { + userTaskList.add((UserTask) targetElement); + } + } + return userTaskList; + } + } + return userTaskList; + } + + private List getChildUserTaskList( + FlowElement source, Set hasSequenceFlow, List userTaskList) { + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + if (source instanceof SubProcess) { + return this.getChildUserTaskListFromSubProcess((SubProcess) source, userTaskList); + } + if (source instanceof EndEvent && source.getSubProcess() != null) { + sequenceFlows = getElementOutgoingFlows(source.getSubProcess()); + } + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (!hasSequenceFlow.contains(sequenceFlow.getId())) { + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + this.findChildUserTaskListByFlow(sequenceFlow, hasSequenceFlow, userTaskList); + } + } + } + return userTaskList; + } + + private void findChildUserTaskListByFlow( + SequenceFlow sequenceFlow, Set hasSequenceFlow, List userTaskList) { + FlowElement targetElement = sequenceFlow.getTargetFlowElement(); + if (targetElement instanceof UserTask) { + userTaskList.add((UserTask) targetElement); + return; + } + getChildUserTaskList(targetElement, new HashSet<>(hasSequenceFlow), userTaskList); + } + + private void handleMultiInstanceApprovalType(String executionId, String approvalType, JSONObject taskVariableData) { + if (StrUtil.isBlank(approvalType)) { + return; + } + if (StrUtil.equalsAny(approvalType, + FlowApprovalType.MULTI_AGREE, + FlowApprovalType.MULTI_REFUSE, + FlowApprovalType.MULTI_ABSTAIN)) { + Map variables = runtimeService.getVariables(executionId); + Integer agreeCount = (Integer) variables.get(FlowConstant.MULTI_AGREE_COUNT_VAR); + Integer refuseCount = (Integer) variables.get(FlowConstant.MULTI_REFUSE_COUNT_VAR); + Integer abstainCount = (Integer) variables.get(FlowConstant.MULTI_ABSTAIN_COUNT_VAR); + Integer nrOfInstances = (Integer) variables.get(FlowConstant.NUMBER_OF_INSTANCES_VAR); + taskVariableData.put(FlowConstant.MULTI_AGREE_COUNT_VAR, agreeCount); + taskVariableData.put(FlowConstant.MULTI_REFUSE_COUNT_VAR, refuseCount); + taskVariableData.put(FlowConstant.MULTI_ABSTAIN_COUNT_VAR, abstainCount); + taskVariableData.put(FlowConstant.MULTI_SIGN_NUM_OF_INSTANCES_VAR, nrOfInstances); + switch (approvalType) { + case FlowApprovalType.MULTI_AGREE: + if (agreeCount == null) { + agreeCount = 0; + } + taskVariableData.put(FlowConstant.MULTI_AGREE_COUNT_VAR, agreeCount + 1); + break; + case FlowApprovalType.MULTI_REFUSE: + if (refuseCount == null) { + refuseCount = 0; + } + taskVariableData.put(FlowConstant.MULTI_REFUSE_COUNT_VAR, refuseCount + 1); + break; + case FlowApprovalType.MULTI_ABSTAIN: + if (abstainCount == null) { + abstainCount = 0; + } + taskVariableData.put(FlowConstant.MULTI_ABSTAIN_COUNT_VAR, abstainCount + 1); + break; + default: + break; + } + } + } + + private TaskQuery createQuery() { + TaskQuery query = taskService.createTaskQuery().active(); + TokenData tokenData = TokenData.takeFromRequest(); + if (tokenData.getTenantId() != null) { + query.taskTenantId(tokenData.getTenantId().toString()); + } else { + if (StrUtil.isBlank(tokenData.getAppCode())) { + query.taskWithoutTenantId(); + } else { + query.taskTenantId(tokenData.getAppCode()); + } + } + return query; + } + + private void buildCandidateCondition(TaskQuery query, String loginName) { + Set groupIdSet = new HashSet<>(); + // NOTE: 需要注意的是,部门Id、部门岗位Id,或者其他类型的分组Id,他们之间一定不能重复。 + TokenData tokenData = TokenData.takeFromRequest(); + Object deptId = tokenData.getDeptId(); + if (deptId != null) { + groupIdSet.add(deptId.toString()); + } + String roleIds = tokenData.getRoleIds(); + if (StrUtil.isNotBlank(tokenData.getRoleIds())) { + groupIdSet.addAll(StrUtil.split(roleIds, ",")); + } + String postIds = tokenData.getPostIds(); + if (StrUtil.isNotBlank(tokenData.getPostIds())) { + groupIdSet.addAll(StrUtil.split(postIds, ",")); + } + String deptPostIds = tokenData.getDeptPostIds(); + if (StrUtil.isNotBlank(deptPostIds)) { + groupIdSet.addAll(StrUtil.split(deptPostIds, ",")); + } + if (CollUtil.isNotEmpty(groupIdSet)) { + query.or().taskCandidateGroupIn(groupIdSet).taskCandidateOrAssigned(loginName).endOr(); + } else { + query.taskCandidateOrAssigned(loginName); + } + } + + private String buildMutiSignAssigneeList(String operationListJson) { + FlowTaskMultiSignAssign multiSignAssignee = null; + List taskOperationList = JSONArray.parseArray(operationListJson, FlowTaskOperation.class); + for (FlowTaskOperation taskOperation : taskOperationList) { + if (FlowApprovalType.MULTI_SIGN.equals(taskOperation.getType())) { + multiSignAssignee = taskOperation.getMultiSignAssignee(); + break; + } + } + org.springframework.util.Assert.notNull(multiSignAssignee, "multiSignAssignee can't be NULL"); + if (UserFilterGroup.USER.equals(multiSignAssignee.getAssigneeType())) { + return multiSignAssignee.getAssigneeList(); + } + Set usernameSet = null; + BaseFlowIdentityExtHelper extHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + Set idSet = CollUtil.newHashSet(StrUtil.split(multiSignAssignee.getAssigneeList(), ",")); + switch (multiSignAssignee.getAssigneeType()) { + case UserFilterGroup.ROLE: + usernameSet = extHelper.getUsernameListByRoleIds(idSet); + break; + case UserFilterGroup.DEPT: + usernameSet = extHelper.getUsernameListByDeptIds(idSet); + break; + case UserFilterGroup.POST: + usernameSet = extHelper.getUsernameListByPostIds(idSet); + break; + case UserFilterGroup.DEPT_POST: + usernameSet = extHelper.getUsernameListByDeptPostIds(idSet); + break; + default: + break; + } + return CollUtil.isEmpty(usernameSet) ? null : CollUtil.join(usernameSet, ","); + } + + private Collection getAllElements(Collection flowElements, Collection allElements) { + allElements = allElements == null ? new ArrayList<>() : allElements; + for (FlowElement flowElement : flowElements) { + allElements.add(flowElement); + if (flowElement instanceof SubProcess) { + allElements = getAllElements(((SubProcess) flowElement).getFlowElements(), allElements); + } + } + return allElements; + } + + private void doChangeTask(Task runtimeTask) { + Map allUserTaskMap = + this.getAllUserTaskMap(runtimeTask.getProcessDefinitionId()); + UserTask userTaskModel = allUserTaskMap.get(runtimeTask.getTaskDefinitionKey()); + String completeCondition = userTaskModel.getLoopCharacteristics().getCompletionCondition(); + Execution parentExecution = this.getMultiInstanceRootExecution(runtimeTask); + Object nrOfCompletedInstances = runtimeService.getVariable( + parentExecution.getId(), FlowConstant.NUMBER_OF_COMPLETED_INSTANCES_VAR); + Object nrOfInstances = runtimeService.getVariable( + parentExecution.getId(), FlowConstant.NUMBER_OF_INSTANCES_VAR); + ExpressionFactory factory = new ExpressionFactoryImpl(); + SimpleContext context = new SimpleContext(); + context.setVariable("nrOfCompletedInstances", + factory.createValueExpression(nrOfCompletedInstances, Integer.class)); + context.setVariable("nrOfInstances", + factory.createValueExpression(nrOfInstances, Integer.class)); + ValueExpression e = factory.createValueExpression(context, completeCondition, Boolean.class); + Boolean ok = Convert.convert(Boolean.class, e.getValue(context)); + if (BooleanUtil.isTrue(ok)) { + FlowElement targetKey = userTaskModel.getOutgoingFlows().get(0).getTargetFlowElement(); + ChangeActivityStateBuilder builder = runtimeService.createChangeActivityStateBuilder() + .processInstanceId(runtimeTask.getProcessInstanceId()) + .moveActivityIdTo(userTaskModel.getId(), targetKey.getId()); + builder.localVariable(targetKey.getId(), FlowConstant.MULTI_SIGN_NUM_OF_INSTANCES_VAR, nrOfInstances); + builder.changeState(); + } + } + + private Execution getMultiInstanceRootExecution(Task runtimeTask) { + List executionList = runtimeService.createExecutionQuery() + .processInstanceId(runtimeTask.getProcessInstanceId()) + .activityId(runtimeTask.getTaskDefinitionKey()).list(); + for (Execution e : executionList) { + ExecutionEntityImpl ee = (ExecutionEntityImpl) e; + if (ee.isMultiInstanceRoot()) { + return e; + } + } + Execution execution = executionList.get(0); + return runtimeService.createExecutionQuery() + .processInstanceId(runtimeTask.getProcessInstanceId()) + .executionId(execution.getParentId()).singleResult(); + } + + private List getElementIncomingFlows(FlowElement source) { + List sequenceFlows = null; + if (source instanceof org.flowable.bpmn.model.Task) { + sequenceFlows = ((org.flowable.bpmn.model.Task) source).getIncomingFlows(); + } else if (source instanceof Gateway) { + sequenceFlows = ((Gateway) source).getIncomingFlows(); + } else if (source instanceof SubProcess) { + sequenceFlows = ((SubProcess) source).getIncomingFlows(); + } else if (source instanceof StartEvent) { + sequenceFlows = ((StartEvent) source).getIncomingFlows(); + } else if (source instanceof EndEvent) { + sequenceFlows = ((EndEvent) source).getIncomingFlows(); + } + return sequenceFlows; + } + + private List getElementOutgoingFlows(FlowElement source) { + List sequenceFlows = null; + if (source instanceof org.flowable.bpmn.model.Task) { + sequenceFlows = ((org.flowable.bpmn.model.Task) source).getOutgoingFlows(); + } else if (source instanceof Gateway) { + sequenceFlows = ((Gateway) source).getOutgoingFlows(); + } else if (source instanceof SubProcess) { + sequenceFlows = ((SubProcess) source).getOutgoingFlows(); + } else if (source instanceof StartEvent) { + sequenceFlows = ((StartEvent) source).getOutgoingFlows(); + } else if (source instanceof EndEvent) { + sequenceFlows = ((EndEvent) source).getOutgoingFlows(); + } + return sequenceFlows; + } + + private FlowableListener createListener(String eventName, String listenerClassName) { + FlowableListener listener = new FlowableListener(); + listener.setEvent(eventName); + listener.setImplementationType("class"); + listener.setImplementation(listenerClassName); + return listener; + } + + private FlowElement findFlement(Collection allElements, String taskKey) { + for (FlowElement flowElement : allElements) { + if (flowElement.getId().equals(taskKey)) { + return flowElement; + } + } + return null; + } + + private UserTask getBackParentUserTask(List parentUserTaskList, Task task) { + List comments = flowTaskCommentService.getFlowTaskCommentList(task.getProcessInstanceId()); + if (CollUtil.isEmpty(comments)) { + return null; + } + List reversedComments = CollUtil.reverse(comments); + for (FlowTaskComment comment : reversedComments) { + for (UserTask parentTask : parentUserTaskList) { + if (parentTask.getId().equals(comment.getTaskKey())) { + return parentTask; + } + } + } + return null; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowCategoryServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowCategoryServiceImpl.java new file mode 100644 index 0000000..a4acb1c --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowCategoryServiceImpl.java @@ -0,0 +1,132 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.object.MyRelationParam; +import apelet.common.core.object.TokenData; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.dao.FlowCategoryMapper; +import apelet.common.flow.model.FlowCategory; +import apelet.common.flow.service.FlowCategoryService; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.github.pagehelper.Page; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowCategoryService") +public class FlowCategoryServiceImpl extends BaseService implements FlowCategoryService { + + @Autowired + private FlowCategoryMapper flowCategoryMapper; + @Autowired + private IdGeneratorWrapper idGenerator; + + /** + * 返回当前Service的主表Mapper对象。 + * + * @return 主表Mapper对象。 + */ + @Override + protected BaseDaoMapper mapper() { + return flowCategoryMapper; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public FlowCategory saveNew(FlowCategory flowCategory) { + flowCategory.setCategoryId(idGenerator.nextLongId()); + TokenData tokenData = TokenData.takeFromRequest(); + flowCategory.setAppCode(tokenData.getAppCode()); + flowCategory.setTenantId(tokenData.getTenantId()); + flowCategory.setUpdateUserId(tokenData.getUserId()); + flowCategory.setCreateUserId(tokenData.getUserId()); + Date now = new Date(); + flowCategory.setUpdateTime(now); + flowCategory.setCreateTime(now); + flowCategoryMapper.insert(flowCategory); + return flowCategory; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean update(FlowCategory flowCategory, FlowCategory originalFlowCategory) { + TokenData tokenData = TokenData.takeFromRequest(); + flowCategory.setAppCode(tokenData.getAppCode()); + flowCategory.setTenantId(tokenData.getTenantId()); + flowCategory.setUpdateUserId(tokenData.getUserId()); + flowCategory.setCreateUserId(originalFlowCategory.getCreateUserId()); + flowCategory.setUpdateTime(new Date()); + flowCategory.setCreateTime(originalFlowCategory.getCreateTime()); + // 这里重点提示,在执行主表数据更新之前,如果有哪些字段不支持修改操作,请用原有数据对象字段替换当前数据字段。 + UpdateWrapper uw = + this.createUpdateQueryForNullValue(flowCategory, flowCategory.getCategoryId()); + return flowCategoryMapper.update(flowCategory, uw) == 1; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean remove(Long categoryId) { + return flowCategoryMapper.deleteById(categoryId) == 1; + } + + @Override + public List getFlowCategoryList(FlowCategory filter, String orderBy) { + if (filter == null) { + filter = new FlowCategory(); + } + TokenData tokenData = TokenData.takeFromRequest(); + filter.setTenantId(tokenData.getTenantId()); + filter.setAppCode(tokenData.getAppCode()); + return flowCategoryMapper.getFlowCategoryList(filter, orderBy); + } + + @Override + public List getFlowCategoryListWithRelation(FlowCategory filter, String orderBy) { + List resultList = this.getFlowCategoryList(filter, orderBy); + // 在缺省生成的代码中,如果查询结果resultList不是Page对象,说明没有分页,那么就很可能是数据导出接口调用了当前方法。 + // 为了避免一次性的大量数据关联,规避因此而造成的系统运行性能冲击,这里手动进行了分批次读取,开发者可按需修改该值。 + int batchSize = resultList instanceof Page ? 0 : 1000; + this.buildRelationForDataList(resultList, MyRelationParam.normal(), batchSize); + return resultList; + } + + @Override + public boolean existByCode(String code) { + FlowCategory filter = new FlowCategory(); + filter.setCode(code); + return CollUtil.isNotEmpty(this.getFlowCategoryList(filter, null)); + } + + @Override + public List getInList(Set categoryIds) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.in(FlowCategory::getCategoryId, categoryIds); + TokenData tokenData = TokenData.takeFromRequest(); + if (tokenData.getAppCode() == null) { + qw.isNull(FlowCategory::getAppCode); + } else { + qw.eq(FlowCategory::getAppCode, tokenData.getAppCode()); + } + if (tokenData.getTenantId() == null) { + qw.isNull(FlowCategory::getTenantId); + } else { + qw.eq(FlowCategory::getTenantId, tokenData.getTenantId()); + } + return flowCategoryMapper.selectList(qw); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java new file mode 100644 index 0000000..26e31e6 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java @@ -0,0 +1,508 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.object.CallResult; +import apelet.common.core.object.MyRelationParam; +import apelet.common.core.object.TokenData; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.core.util.MyModelUtil; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.dao.FlowEntryMapper; +import apelet.common.flow.dao.FlowEntryPublishMapper; +import apelet.common.flow.dao.FlowEntryPublishVariableMapper; +import apelet.common.flow.listener.*; +import apelet.common.flow.model.*; +import apelet.common.flow.model.constant.FlowEntryStatus; +import apelet.common.flow.model.constant.FlowVariableType; +import apelet.common.flow.object.*; +import apelet.common.flow.service.*; +import apelet.common.flow.util.BaseFlowIdentityExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.util.FlowRedisKeyUtil; +import apelet.common.redis.util.CommonRedisUtil; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.github.pagehelper.Page; +import lombok.Cleanup; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.*; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowEntryService") +public class FlowEntryServiceImpl extends BaseService implements FlowEntryService { + + @Autowired + private FlowEntryMapper flowEntryMapper; + @Autowired + private FlowEntryPublishMapper flowEntryPublishMapper; + @Autowired + private FlowEntryPublishVariableMapper flowEntryPublishVariableMapper; + @Autowired + private FlowEntryVariableService flowEntryVariableService; + @Autowired + private FlowCategoryService flowCategoryService; + @Autowired + private FlowTaskExtService flowTaskExtService; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private RepositoryService repositoryService; + @Autowired + private IdGeneratorWrapper idGenerator; + @Autowired + private CommonRedisUtil commonRedisUtil; + + private static final Integer FLOW_ENTRY_PUBLISH_TTL = 60 * 60 * 24; + + /** + * 返回当前Service的主表Mapper对象。 + * + * @return 主表Mapper对象。 + */ + @Override + protected BaseDaoMapper mapper() { + return flowEntryMapper; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public FlowEntry saveNew(FlowEntry flowEntry) { + flowEntry.setEntryId(idGenerator.nextLongId()); + flowEntry.setStatus(FlowEntryStatus.UNPUBLISHED); + TokenData tokenData = TokenData.takeFromRequest(); + flowEntry.setAppCode(tokenData.getAppCode()); + flowEntry.setTenantId(tokenData.getTenantId()); + flowEntry.setUpdateUserId(tokenData.getUserId()); + flowEntry.setCreateUserId(tokenData.getUserId()); + Date now = new Date(); + flowEntry.setUpdateTime(now); + flowEntry.setCreateTime(now); + flowEntryMapper.insert(flowEntry); + this.insertBuiltinEntryVariables(flowEntry.getEntryId()); + return flowEntry; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void publish(FlowEntry flowEntry, String initTaskInfo) throws XMLStreamException { + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey())); + FlowCategory flowCategory = flowCategoryService.getById(flowEntry.getCategoryId()); + InputStream xmlStream = new ByteArrayInputStream( + flowEntry.getBpmnXml().getBytes(StandardCharsets.UTF_8)); + @Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(xmlStream); + BpmnXMLConverter converter = new BpmnXMLConverter(); + BpmnModel bpmnModel = converter.convertToBpmnModel(reader); + bpmnModel.getMainProcess().setName(flowEntry.getProcessDefinitionName()); + bpmnModel.getMainProcess().setId(flowEntry.getProcessDefinitionKey()); + flowApiService.addProcessInstanceEndListener(bpmnModel, FlowFinishedListener.class); + List flowTaskExtList = flowTaskExtService.buildTaskExtList(bpmnModel); + if (StrUtil.isNotBlank(flowEntry.getExtensionData())) { + FlowEntryExtensionData flowEntryExtensionData = + JSON.parseObject(flowEntry.getExtensionData(), FlowEntryExtensionData.class); + this.mergeTaskNotifyData(flowEntryExtensionData, flowTaskExtList); + this.processFlowTaskRevive(flowEntryExtensionData, bpmnModel); + } + this.processFlowTaskExtList(flowTaskExtList, bpmnModel); + TokenData tokenData = TokenData.takeFromRequest(); + Deployment deploy = repositoryService.createDeployment() + .addBpmnModel(flowEntry.getProcessDefinitionKey() + ".bpmn", bpmnModel) + .tenantId(tokenData.getTenantId() != null ? tokenData.getTenantId().toString() : tokenData.getAppCode()) + .name(flowEntry.getProcessDefinitionName()) + .key(flowEntry.getProcessDefinitionKey()) + .category(flowCategory.getCode()) + .deploy(); + ProcessDefinition processDefinition = flowApiService.getProcessDefinitionByDeployId(deploy.getId()); + FlowEntryPublish flowEntryPublish = new FlowEntryPublish(); + flowEntryPublish.setEntryPublishId(idGenerator.nextLongId()); + flowEntryPublish.setEntryId(flowEntry.getEntryId()); + flowEntryPublish.setProcessDefinitionId(processDefinition.getId()); + flowEntryPublish.setDeployId(processDefinition.getDeploymentId()); + flowEntryPublish.setPublishVersion(processDefinition.getVersion()); + flowEntryPublish.setActiveStatus(true); + flowEntryPublish.setMainVersion(flowEntry.getStatus().equals(FlowEntryStatus.UNPUBLISHED)); + flowEntryPublish.setCreateUserId(TokenData.takeFromRequest().getUserId()); + flowEntryPublish.setPublishTime(new Date()); + flowEntryPublish.setInitTaskInfo(initTaskInfo); + flowEntryPublish.setExtensionData(flowEntry.getExtensionData()); + AnalyzedNode rootNode = flowApiService.analyzeBpmnRoads(processDefinition.getId()); + flowEntryPublish.setAnalyzedNodeJson(JSON.toJSONString(rootNode)); + flowEntryPublishMapper.insert(flowEntryPublish); + FlowEntry updatedFlowEntry = new FlowEntry(); + updatedFlowEntry.setEntryId(flowEntry.getEntryId()); + updatedFlowEntry.setStatus(FlowEntryStatus.PUBLISHED); + updatedFlowEntry.setLatestPublishTime(new Date()); + // 对于从未发布过的工作,第一次发布的时候会将本地发布置位主版本。 + if (flowEntry.getStatus().equals(FlowEntryStatus.UNPUBLISHED)) { + updatedFlowEntry.setMainEntryPublishId(flowEntryPublish.getEntryPublishId()); + } + flowEntryMapper.updateById(updatedFlowEntry); + FlowEntryVariable flowEntryVariableFilter = new FlowEntryVariable(); + flowEntryVariableFilter.setEntryId(flowEntry.getEntryId()); + List flowEntryVariableList = + flowEntryVariableService.getFlowEntryVariableList(flowEntryVariableFilter, null); + if (CollUtil.isNotEmpty(flowTaskExtList)) { + flowTaskExtList.forEach(t -> t.setProcessDefinitionId(processDefinition.getId())); + flowTaskExtService.saveBatch(flowTaskExtList); + } + this.insertEntryPublishVariables(flowEntryVariableList, flowEntryPublish.getEntryPublishId()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean update(FlowEntry flowEntry, FlowEntry originalFlowEntry) { + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey())); + TokenData tokenData = TokenData.takeFromRequest(); + flowEntry.setAppCode(tokenData.getAppCode()); + flowEntry.setTenantId(tokenData.getTenantId()); + flowEntry.setUpdateUserId(tokenData.getUserId()); + flowEntry.setCreateUserId(originalFlowEntry.getCreateUserId()); + flowEntry.setUpdateTime(new Date()); + flowEntry.setCreateTime(originalFlowEntry.getCreateTime()); + flowEntry.setPageId(originalFlowEntry.getPageId()); + return flowEntryMapper.updateById(flowEntry) == 1; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean remove(Long entryId) { + FlowEntry flowEntry = this.getById(entryId); + if (flowEntry != null) { + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey())); + } + if (flowEntryMapper.deleteById(entryId) != 1) { + return false; + } + flowEntryVariableService.removeByEntryId(entryId); + return true; + } + + @Override + public List getFlowEntryList(FlowEntry filter, String orderBy) { + if (filter == null) { + filter = new FlowEntry(); + } + TokenData tokenData = TokenData.takeFromRequest(); + filter.setTenantId(tokenData.getTenantId()); + filter.setAppCode(tokenData.getAppCode()); + return flowEntryMapper.getFlowEntryList(filter, orderBy); + } + + @Override + public List getFlowEntryListWithRelation(FlowEntry filter, String orderBy) { + List resultList = this.getFlowEntryList(filter, orderBy); + // 在缺省生成的代码中,如果查询结果resultList不是Page对象,说明没有分页,那么就很可能是数据导出接口调用了当前方法。 + // 为了避免一次性的大量数据关联,规避因此而造成的系统运行性能冲击,这里手动进行了分批次读取,开发者可按需修改该值。 + int batchSize = resultList instanceof Page ? 0 : 1000; + this.buildRelationForDataList(resultList, MyRelationParam.normal(), batchSize); + Set mainEntryPublishIdSet = resultList.stream() + .map(FlowEntry::getMainEntryPublishId).filter(Objects::nonNull).collect(Collectors.toSet()); + if (CollUtil.isNotEmpty(mainEntryPublishIdSet)) { + List mainEntryPublishList = + flowEntryPublishMapper.selectBatchIds(mainEntryPublishIdSet); + MyModelUtil.makeOneToOneRelation(FlowEntry.class, resultList, FlowEntry::getMainEntryPublishId, + mainEntryPublishList, FlowEntryPublish::getEntryPublishId, "mainFlowEntryPublish"); + } + return resultList; + } + + @Override + public FlowEntry getFlowEntryFromCache(String processDefinitionKey) { + String key = FlowRedisKeyUtil.makeFlowEntryKey(processDefinitionKey); + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(FlowEntry::getProcessDefinitionKey, processDefinitionKey); + TokenData tokenData = TokenData.takeFromRequest(); + if (StrUtil.isNotBlank(tokenData.getAppCode())) { + qw.eq(FlowEntry::getAppCode, tokenData.getAppCode()); + } else { + qw.isNull(FlowEntry::getAppCode); + } + if (tokenData.getTenantId() != null) { + qw.eq(FlowEntry::getTenantId, tokenData.getTenantId()); + } else { + qw.isNull(FlowEntry::getTenantId); + } + return commonRedisUtil.getFromCacheWithQueryWrapper(key, qw, flowEntryMapper::selectOne, FlowEntry.class); + } + + @Override + public List getFlowEntryPublishList(Long entryId) { + FlowEntryPublish filter = new FlowEntryPublish(); + filter.setEntryId(entryId); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(filter); + queryWrapper.orderByDesc(FlowEntryPublish::getEntryPublishId); + return flowEntryPublishMapper.selectList(queryWrapper); + } + + @Override + public List getFlowEntryPublishList(Set processDefinitionIdSet) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(FlowEntryPublish::getProcessDefinitionId, processDefinitionIdSet); + return flowEntryPublishMapper.selectList(queryWrapper); + } + + @Override + public FlowEntryPublish getFlowEntryPublishFromCache(Long entryPublishId) { + String key = FlowRedisKeyUtil.makeFlowEntryPublishKey(entryPublishId); + return commonRedisUtil.getFromCache( + key, entryPublishId, flowEntryPublishMapper::selectById, FlowEntryPublish.class, FLOW_ENTRY_PUBLISH_TTL); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void updateFlowEntryMainVersion(FlowEntry flowEntry, FlowEntryPublish newMainFlowEntryPublish) { + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey())); + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryPublishKey(newMainFlowEntryPublish.getEntryPublishId())); + FlowEntryPublish oldMainFlowEntryPublish = + flowEntryPublishMapper.selectById(flowEntry.getMainEntryPublishId()); + if (oldMainFlowEntryPublish != null) { + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryPublishKey(oldMainFlowEntryPublish.getEntryPublishId())); + oldMainFlowEntryPublish.setMainVersion(false); + flowEntryPublishMapper.updateById(oldMainFlowEntryPublish); + } + newMainFlowEntryPublish.setMainVersion(true); + flowEntryPublishMapper.updateById(newMainFlowEntryPublish); + FlowEntry updatedEntry = new FlowEntry(); + updatedEntry.setEntryId(flowEntry.getEntryId()); + updatedEntry.setMainEntryPublishId(newMainFlowEntryPublish.getEntryPublishId()); + flowEntryMapper.updateById(updatedEntry); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void suspendFlowEntryPublish(FlowEntryPublish flowEntryPublish) { + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryPublishKey(flowEntryPublish.getEntryPublishId())); + FlowEntryPublish updatedEntryPublish = new FlowEntryPublish(); + updatedEntryPublish.setEntryPublishId(flowEntryPublish.getEntryPublishId()); + updatedEntryPublish.setActiveStatus(false); + flowEntryPublishMapper.updateById(updatedEntryPublish); + flowApiService.suspendProcessDefinition(flowEntryPublish.getProcessDefinitionId()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void activateFlowEntryPublish(FlowEntryPublish flowEntryPublish) { + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryPublishKey(flowEntryPublish.getEntryPublishId())); + FlowEntryPublish updatedEntryPublish = new FlowEntryPublish(); + updatedEntryPublish.setEntryPublishId(flowEntryPublish.getEntryPublishId()); + updatedEntryPublish.setActiveStatus(true); + flowEntryPublishMapper.updateById(updatedEntryPublish); + flowApiService.activateProcessDefinition(flowEntryPublish.getProcessDefinitionId()); + } + + @Override + public boolean existByProcessDefinitionKey(String processDefinitionKey) { + FlowEntry filter = new FlowEntry(); + filter.setProcessDefinitionKey(processDefinitionKey); + return CollUtil.isNotEmpty(this.getFlowEntryList(filter, null)); + } + + @Override + public CallResult verifyRelatedData(FlowEntry flowEntry, FlowEntry originalFlowEntry) { + String errorMessageFormat = "数据验证失败,关联的%s并不存在,请刷新后重试!"; + if (this.needToVerify(flowEntry, originalFlowEntry, FlowEntry::getCategoryId) + && !flowCategoryService.existId(flowEntry.getCategoryId())) { + return CallResult.error(String.format(errorMessageFormat, "流程类别Id")); + } + return CallResult.ok(); + } + + private void insertBuiltinEntryVariables(Long entryId) { + Date now = new Date(); + FlowEntryVariable operationTypeVariable = new FlowEntryVariable(); + operationTypeVariable.setVariableId(idGenerator.nextLongId()); + operationTypeVariable.setEntryId(entryId); + operationTypeVariable.setVariableName(FlowConstant.OPERATION_TYPE_VAR); + operationTypeVariable.setShowName("审批类型"); + operationTypeVariable.setVariableType(FlowVariableType.TASK); + operationTypeVariable.setBuiltin(true); + operationTypeVariable.setCreateTime(now); + flowEntryVariableService.saveNew(operationTypeVariable); + FlowEntryVariable startUserNameVariable = new FlowEntryVariable(); + startUserNameVariable.setVariableId(idGenerator.nextLongId()); + startUserNameVariable.setEntryId(entryId); + startUserNameVariable.setVariableName("startUserName"); + startUserNameVariable.setShowName("流程启动用户"); + startUserNameVariable.setVariableType(FlowVariableType.INSTANCE); + startUserNameVariable.setBuiltin(true); + startUserNameVariable.setCreateTime(now); + flowEntryVariableService.saveNew(startUserNameVariable); + } + + private void insertEntryPublishVariables(List entryVariableList, Long entryPublishId) { + if (CollUtil.isEmpty(entryVariableList)) { + return; + } + List entryPublishVariableList = + MyModelUtil.copyCollectionTo(entryVariableList, FlowEntryPublishVariable.class); + for (FlowEntryPublishVariable variable : entryPublishVariableList) { + variable.setVariableId(idGenerator.nextLongId()); + variable.setEntryPublishId(entryPublishId); + } + flowEntryPublishVariableMapper.insertList(entryPublishVariableList); + } + + private void mergeTaskNotifyData(FlowEntryExtensionData flowEntryExtensionData, List flowTaskExtList) { + if (CollUtil.isEmpty(flowEntryExtensionData.getNotifyTypes())) { + return; + } + List flowTaskNotifyTypes = + flowEntryExtensionData.getNotifyTypes().stream().filter(StrUtil::isNotBlank).collect(Collectors.toList()); + if (CollUtil.isEmpty(flowTaskNotifyTypes)) { + return; + } + for (FlowTaskExt flowTaskExt : flowTaskExtList) { + if (flowTaskExt.getExtraDataJson() == null) { + JSONObject o = new JSONObject(); + o.put(FlowConstant.USER_TASK_NOTIFY_TYPES_KEY, flowTaskNotifyTypes); + flowTaskExt.setExtraDataJson(o.toJSONString()); + } else { + FlowUserTaskExtData taskExtData = + JSON.parseObject(flowTaskExt.getExtraDataJson(), FlowUserTaskExtData.class); + if (CollUtil.isEmpty(taskExtData.getFlowNotifyTypeList())) { + taskExtData.setFlowNotifyTypeList(flowTaskNotifyTypes); + } else { + Set notifyTypesSet = taskExtData.getFlowNotifyTypeList() + .stream().filter(StrUtil::isNotBlank).collect(Collectors.toSet()); + notifyTypesSet.addAll(flowTaskNotifyTypes); + taskExtData.setFlowNotifyTypeList(new LinkedList<>(notifyTypesSet)); + } + flowTaskExt.setExtraDataJson(JSON.toJSONString(taskExtData)); + } + } + } + + private void doAddLatestApprovalStatusListener(Collection elementList) { + List sequenceFlowList = + elementList.stream().filter(SequenceFlow.class::isInstance).collect(Collectors.toList()); + for (FlowElement sequenceFlow : sequenceFlowList) { + FlowElementExtProperty extProperty = flowTaskExtService.buildFlowElementExt(sequenceFlow); + if (extProperty != null && extProperty.getLatestApprovalStatus() != null) { + List fieldExtensions = new LinkedList<>(); + FieldExtension fieldExtension = new FieldExtension(); + fieldExtension.setFieldName(FlowConstant.LATEST_APPROVAL_STATUS_KEY); + fieldExtension.setStringValue(extProperty.getLatestApprovalStatus().toString()); + fieldExtensions.add(fieldExtension); + flowApiService.addExecutionListener( + sequenceFlow, UpdateLatestApprovalStatusListener.class, "start", fieldExtensions); + } + } + List subProcesseList = elementList.stream() + .filter(SubProcess.class::isInstance).map(SubProcess.class::cast).collect(Collectors.toList()); + for (SubProcess subProcess : subProcesseList) { + this.doAddLatestApprovalStatusListener(subProcess.getFlowElements()); + } + } + + private void calculateAllElementList(Collection elements, List resultList) { + resultList.addAll(elements); + for (FlowElement element : elements) { + if (element instanceof SubProcess) { + this.calculateAllElementList(((SubProcess) element).getFlowElements(), resultList); + } + } + } + + private void processFlowTaskRevive(FlowEntryExtensionData flowEntryExtensionData, BpmnModel bpmnModel) { + if (BooleanUtil.isTrue(flowEntryExtensionData.getSupportRevive())) { + List mainProcessUserTasks = bpmnModel.getMainProcess().getFlowElements() + .stream().filter(UserTask.class::isInstance).map(t -> (UserTask) t).collect(Collectors.toList()); + mainProcessUserTasks.forEach(t -> flowApiService.addTaskCreateListener(t, FlowTaskReviveListener.class)); + } + } + + private void processFlowTaskExtList(List flowTaskExtList, BpmnModel bpmnModel) { + List elementList = new LinkedList<>(); + this.calculateAllElementList(bpmnModel.getMainProcess().getFlowElements(), elementList); + this.doAddLatestApprovalStatusListener(elementList); + Map elementMap = elementList.stream() + .filter(UserTask.class::isInstance).collect(Collectors.toMap(FlowElement::getId, c -> c)); + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + for (FlowTaskExt t : flowTaskExtList) { + UserTask userTask = (UserTask) elementMap.get(t.getTaskId()); + flowApiService.addTaskCreateListener(userTask, FlowUserTaskListener.class); + Map> attributes = userTask.getAttributes(); + if (CollUtil.isNotEmpty(attributes.get(FlowConstant.USER_TASK_AUTO_SKIP_KEY))) { + flowApiService.addTaskCreateListener(userTask, AutoSkipTaskListener.class); + } + // 如果流程图中包含部门领导审批和上级部门领导审批的选项,就需要注册 FlowCustomExtFactory 工厂中的 + // BaseFlowIdentityExtHelper 对象,该注册操作需要业务模块中实现。 + if (StrUtil.equals(t.getGroupType(), FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER)) { + userTask.setCandidateGroups( + CollUtil.newArrayList("${" + FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR + "}")); + Assert.notNull(flowIdentityExtHelper); + flowApiService.addTaskCreateListener(userTask, flowIdentityExtHelper.getUpDeptPostLeaderListener()); + } else if (StrUtil.equals(t.getGroupType(), FlowConstant.GROUP_TYPE_DEPT_POST_LEADER)) { + userTask.setCandidateGroups( + CollUtil.newArrayList("${" + FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR + "}")); + Assert.notNull(flowIdentityExtHelper); + flowApiService.addTaskCreateListener(userTask, flowIdentityExtHelper.getDeptPostLeaderListener()); + } else if (StrUtil.equals(t.getGroupType(), FlowConstant.GROUP_TYPE_POST)) { + Assert.notNull(t.getDeptPostListJson()); + List groupDataList = + JSON.parseArray(t.getDeptPostListJson(), FlowTaskPostCandidateGroup.class); + List candidateGroupList = + FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList); + userTask.setCandidateGroups(candidateGroupList); + } + this.processFlowTaskExtListener(userTask, t); + } + } + + private void processFlowTaskExtListener(UserTask userTask, FlowTaskExt taskExt) { + if (StrUtil.isBlank(taskExt.getExtraDataJson())) { + return; + } + FlowUserTaskExtData userTaskExtData = + JSON.parseObject(taskExt.getExtraDataJson(), FlowUserTaskExtData.class); + if (CollUtil.isNotEmpty(userTaskExtData.getFlowNotifyTypeList())) { + flowApiService.addTaskCreateListener(userTask, FlowTaskNotifyListener.class); + } + if (userTaskExtData.getRejectType().equals(FlowUserTaskExtData.REJECT_TYPE_BACK_TO_SOURCE)) { + flowApiService.addTaskCreateListener(userTask, FlowTaskRejectBackListener.class); + } + if (StrUtil.isNotBlank(userTaskExtData.getTimeoutHandleWay())) { + flowApiService.addTaskCreateListener(userTask, FlowTaskTimeoutListener.class); + } + if (StrUtil.isNotBlank(userTaskExtData.getEmptyUserHandleWay())) { + flowApiService.addTaskCreateListener(userTask, FlowEmptyUserHandleListener.class); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryVariableServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryVariableServiceImpl.java new file mode 100644 index 0000000..eeaa4de --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryVariableServiceImpl.java @@ -0,0 +1,138 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.object.MyRelationParam; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.dao.FlowEntryVariableMapper; +import apelet.common.flow.model.FlowEntryVariable; +import apelet.common.flow.service.FlowEntryVariableService; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.github.pagehelper.Page; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; + +/** + * 流程变量数据操作服务类。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowEntryVariableService") +public class FlowEntryVariableServiceImpl extends BaseService implements FlowEntryVariableService { + + @Autowired + private FlowEntryVariableMapper flowEntryVariableMapper; + @Autowired + private IdGeneratorWrapper idGenerator; + + /** + * 返回当前Service的主表Mapper对象。 + * + * @return 主表Mapper对象。 + */ + @Override + protected BaseDaoMapper mapper() { + return flowEntryVariableMapper; + } + + /** + * 保存新增对象。 + * + * @param flowEntryVariable 新增对象。 + * @return 返回新增对象。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public FlowEntryVariable saveNew(FlowEntryVariable flowEntryVariable) { + flowEntryVariable.setVariableId(idGenerator.nextLongId()); + flowEntryVariable.setCreateTime(new Date()); + flowEntryVariableMapper.insert(flowEntryVariable); + return flowEntryVariable; + } + + /** + * 更新数据对象。 + * + * @param flowEntryVariable 更新的对象。 + * @param originalFlowEntryVariable 原有数据对象。 + * @return 成功返回true,否则false。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public boolean update(FlowEntryVariable flowEntryVariable, FlowEntryVariable originalFlowEntryVariable) { + flowEntryVariable.setCreateTime(originalFlowEntryVariable.getCreateTime()); + // 这里重点提示,在执行主表数据更新之前,如果有哪些字段不支持修改操作,请用原有数据对象字段替换当前数据字段。 + UpdateWrapper uw = this.createUpdateQueryForNullValue(flowEntryVariable, flowEntryVariable.getVariableId()); + return flowEntryVariableMapper.update(flowEntryVariable, uw) == 1; + } + + /** + * 删除指定数据。 + * + * @param variableId 主键Id。 + * @return 成功返回true,否则false。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public boolean remove(Long variableId) { + return flowEntryVariableMapper.deleteById(variableId) == 1; + } + + /** + * 删除指定流程Id的所有变量。 + * + * @param entryId 流程Id。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void removeByEntryId(Long entryId) { + flowEntryVariableMapper.delete( + new LambdaQueryWrapper().eq(FlowEntryVariable::getEntryId, entryId)); + } + + /** + * 获取单表查询结果。由于没有关联数据查询,因此在仅仅获取单表数据的场景下,效率更高。 + * 如果需要同时获取关联数据,请移步(getFlowEntryVariableListWithRelation)方法。 + * + * @param filter 过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + @Override + public List getFlowEntryVariableList(FlowEntryVariable filter, String orderBy) { + return flowEntryVariableMapper.getFlowEntryVariableList(filter, orderBy); + } + + /** + * 获取主表的查询结果,以及主表关联的字典数据和一对一从表数据,以及一对一从表的字典数据。 + * 该查询会涉及到一对一从表的关联过滤,或一对多从表的嵌套关联过滤,因此性能不如单表过滤。 + * 如果仅仅需要获取主表数据,请移步(getFlowEntryVariableList),以便获取更好的查询性能。 + * + * @param filter 主表过滤对象。 + * @param orderBy 排序参数。 + * @return 查询结果集。 + */ + @Override + public List getFlowEntryVariableListWithRelation(FlowEntryVariable filter, String orderBy) { + List resultList = flowEntryVariableMapper.getFlowEntryVariableList(filter, orderBy); + // 在缺省生成的代码中,如果查询结果resultList不是Page对象,说明没有分页,那么就很可能是数据导出接口调用了当前方法。 + // 为了避免一次性的大量数据关联,规避因此而造成的系统运行性能冲击,这里手动进行了分批次读取,开发者可按需修改该值。 + int batchSize = resultList instanceof Page ? 0 : 1000; + this.buildRelationForDataList(resultList, MyRelationParam.normal(), batchSize); + return resultList; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowMessageServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowMessageServiceImpl.java new file mode 100644 index 0000000..5fe30fb --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowMessageServiceImpl.java @@ -0,0 +1,395 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.object.TokenData; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.dao.FlowMessageCandidateIdentityMapper; +import apelet.common.flow.dao.FlowMessageIdentityOperationMapper; +import apelet.common.flow.dao.FlowMessageMapper; +import apelet.common.flow.model.*; +import apelet.common.flow.model.constant.FlowMessageOperationType; +import apelet.common.flow.model.constant.FlowMessageType; +import apelet.common.flow.object.FlowTaskPostCandidateGroup; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowMessageService; +import apelet.common.flow.service.FlowTaskExtService; +import apelet.common.flow.util.BaseBusinessDataExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.vo.TaskInfoVo; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +/** + * 工作流消息数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowMessageService") +public class FlowMessageServiceImpl extends BaseService implements FlowMessageService { + + @Autowired + private FlowMessageMapper flowMessageMapper; + @Autowired + private FlowMessageCandidateIdentityMapper flowMessageCandidateIdentityMapper; + @Autowired + private FlowMessageIdentityOperationMapper flowMessageIdentityOperationMapper; + @Autowired + private FlowTaskExtService flowTaskExtService; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private IdGeneratorWrapper idGenerator; + + /** + * 返回当前Service的主表Mapper对象。 + * + * @return 主表Mapper对象。 + */ + @Override + protected BaseDaoMapper mapper() { + return flowMessageMapper; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public FlowMessage saveNew(FlowMessage flowMessage) { + flowMessage.setMessageId(idGenerator.nextLongId()); + TokenData tokenData = TokenData.takeFromRequest(); + if (tokenData != null) { + flowMessage.setTenantId(tokenData.getTenantId()); + flowMessage.setAppCode(tokenData.getAppCode()); + flowMessage.setCreateUserId(tokenData.getUserId()); + flowMessage.setCreateUsername(tokenData.getShowName()); + flowMessage.setUpdateUserId(tokenData.getUserId()); + } + flowMessage.setCreateTime(new Date()); + flowMessage.setUpdateTime(flowMessage.getCreateTime()); + flowMessageMapper.insert(flowMessage); + return flowMessage; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void saveNewRemindMessage(FlowWorkOrder flowWorkOrder) { + List taskList = + flowApiService.getProcessInstanceActiveTaskList(flowWorkOrder.getProcessInstanceId()); + for (Task task : taskList) { + FlowMessage filter = new FlowMessage(); + filter.setTaskId(task.getId()); + List messageList = flowMessageMapper.selectList(new QueryWrapper<>(filter)); + // 同一个任务只能催办一次,多次催办则累加催办次数。 + if (CollUtil.isNotEmpty(messageList)) { + for (FlowMessage flowMessage : messageList) { + flowMessage.setRemindCount(flowMessage.getRemindCount() + 1); + flowMessageMapper.updateById(flowMessage); + } + continue; + } + FlowMessage flowMessage = BeanUtil.copyProperties(flowWorkOrder, FlowMessage.class); + flowMessage.setMessageType(FlowMessageType.REMIND_TYPE); + flowMessage.setRemindCount(1); + flowMessage.setProcessInstanceInitiator(flowWorkOrder.getSubmitUsername()); + flowMessage.setTaskId(task.getId()); + flowMessage.setTaskName(task.getName()); + flowMessage.setTaskStartTime(task.getCreateTime()); + flowMessage.setTaskAssignee(task.getAssignee()); + flowMessage.setTaskFinished(false); + if (TokenData.takeFromRequest() == null) { + Set usernameSet = CollUtil.newHashSet(flowWorkOrder.getSubmitUsername()); + Map m = flowCustomExtFactory.getFlowIdentityExtHelper().mapUserShowNameByLoginName(usernameSet); + flowMessage.setCreateUsername(m.containsKey(flowWorkOrder.getSubmitUsername()) + ? m.get(flowWorkOrder.getSubmitUsername()) : flowWorkOrder.getSubmitUsername()); + } + this.saveNew(flowMessage); + FlowTaskExt flowTaskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId( + flowWorkOrder.getProcessDefinitionId(), task.getTaskDefinitionKey()); + if (flowTaskExt != null) { + // 插入与当前消息关联任务的候选人 + this.saveMessageCandidateIdentityWithMessage( + flowWorkOrder.getProcessInstanceId(), flowTaskExt, task, flowMessage.getMessageId()); + } + // 插入与当前消息关联任务的指派人。 + if (StrUtil.isNotBlank(task.getAssignee())) { + this.saveMessageCandidateIdentity( + flowMessage.getMessageId(), FlowConstant.GROUP_TYPE_USER_VAR, task.getAssignee()); + } + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void saveNewCopyMessage(Task task, JSONObject copyDataJson) { + if (copyDataJson.size() == 0) { + return; + } + ProcessInstance instance = flowApiService.getProcessInstance(task.getProcessInstanceId()); + BaseBusinessDataExtHelper helper = flowCustomExtFactory.getBusinessDataExtHelper(); + // 在线表单中,这个值为空。 + String businessShotData = helper.getBusinessData( + instance.getProcessDefinitionKey(), instance.getProcessInstanceId(), instance.getBusinessKey()); + FlowMessage flowMessage = new FlowMessage(); + flowMessage.setMessageType(FlowMessageType.COPY_TYPE); + flowMessage.setRemindCount(0); + flowMessage.setProcessDefinitionId(instance.getProcessDefinitionId()); + flowMessage.setProcessDefinitionKey(instance.getProcessDefinitionKey()); + flowMessage.setProcessDefinitionName(instance.getProcessDefinitionName()); + flowMessage.setProcessInstanceId(instance.getProcessInstanceId()); + flowMessage.setProcessInstanceInitiator(instance.getStartUserId()); + flowMessage.setTaskId(task.getId()); + flowMessage.setTaskDefinitionKey(task.getTaskDefinitionKey()); + flowMessage.setTaskName(task.getName()); + flowMessage.setTaskStartTime(task.getCreateTime()); + flowMessage.setTaskAssignee(task.getAssignee()); + flowMessage.setTaskFinished(false); + flowMessage.setBusinessDataShot(businessShotData); + flowMessage.setOnlineFormData(businessShotData == null); + // 如果是在线表单,这里就保存关联的在线表单Id,便于在线表单业务数据的查找。 + if (BooleanUtil.isTrue(flowMessage.getOnlineFormData())) { + TaskInfoVo taskInfo = JSON.parseObject(task.getFormKey(), TaskInfoVo.class); + flowMessage.setBusinessDataShot(taskInfo.getFormId().toString()); + } + this.saveNew(flowMessage); + for (Map.Entry entries : copyDataJson.entrySet()) { + this.saveMessageCandidateIdentityList( + flowMessage.getMessageId(), entries.getKey(), entries.getValue().toString()); + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void updateFinishedStatusByTaskId(String taskId) { + FlowMessage flowMessage = new FlowMessage(); + flowMessage.setTaskFinished(true); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowMessage::getTaskId, taskId); + flowMessageMapper.update(flowMessage, queryWrapper); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void updateFinishedStatusByProcessInstanceId(String processInstanceId) { + FlowMessage flowMessage = new FlowMessage(); + flowMessage.setTaskFinished(true); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowMessage::getProcessInstanceId, processInstanceId); + flowMessageMapper.update(flowMessage, queryWrapper); + } + + @Override + public List getRemindingMessageListByUser() { + TokenData tokenData = TokenData.takeFromRequest(); + return flowMessageMapper.getRemindingMessageListByUser( + tokenData.getTenantId(), tokenData.getAppCode(), tokenData.getLoginName(), buildGroupIdSet()); + } + + @Override + public List getCopyMessageListByUser(Boolean read) { + TokenData tokenData = TokenData.takeFromRequest(); + return flowMessageMapper.getCopyMessageListByUser( + tokenData.getTenantId(), tokenData.getAppCode(), tokenData.getLoginName(), buildGroupIdSet(), read); + } + + @Override + public boolean isCandidateIdentityOnMessage(Long messageId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowMessageCandidateIdentity::getMessageId, messageId); + queryWrapper.in(FlowMessageCandidateIdentity::getCandidateId, buildGroupIdSet()); + return flowMessageCandidateIdentityMapper.selectCount(queryWrapper) > 0; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void readCopyTask(Long messageId) { + FlowMessageIdentityOperation operation = new FlowMessageIdentityOperation(); + operation.setId(idGenerator.nextLongId()); + operation.setMessageId(messageId); + operation.setLoginName(TokenData.takeFromRequest().getLoginName()); + operation.setOperationType(FlowMessageOperationType.READ_FINISHED); + operation.setOperationTime(new Date()); + flowMessageIdentityOperationMapper.insert(operation); + } + + @Override + public int countRemindingMessageListByUser() { + TokenData tokenData = TokenData.takeFromRequest(); + return flowMessageMapper.countRemindingMessageListByUser( + tokenData.getTenantId(), tokenData.getAppCode(), tokenData.getLoginName(), buildGroupIdSet()); + } + + @Override + public int countCopyMessageByUser() { + TokenData tokenData = TokenData.takeFromRequest(); + return flowMessageMapper.countCopyMessageListByUser( + tokenData.getTenantId(), tokenData.getAppCode(), tokenData.getLoginName(), buildGroupIdSet()); + } + + @Override + public int countSysMessageByUser() { + TokenData tokenData = TokenData.takeFromRequest(); + return flowMessageMapper.countSysMessageListByUser(tokenData.getUserId()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void removeByProcessInstanceId(String processInstanceId) { + flowMessageCandidateIdentityMapper.deleteByProcessInstanceId(processInstanceId); + flowMessageIdentityOperationMapper.deleteByProcessInstanceId(processInstanceId); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowMessage::getProcessInstanceId, processInstanceId); + flowMessageMapper.delete(queryWrapper); + } + + private Set buildGroupIdSet() { + TokenData tokenData = TokenData.takeFromRequest(); + Set groupIdSet = new HashSet<>(1); + groupIdSet.add(tokenData.getLoginName()); + this.parseAndAddIdArray(groupIdSet, tokenData.getRoleIds()); + this.parseAndAddIdArray(groupIdSet, tokenData.getDeptPostIds()); + this.parseAndAddIdArray(groupIdSet, tokenData.getPostIds()); + if (tokenData.getDeptId() != null) { + groupIdSet.add(tokenData.getDeptId().toString()); + } + return groupIdSet; + } + + private void parseAndAddIdArray(Set groupIdSet, String idArray) { + if (StrUtil.isNotBlank(idArray)) { + if (groupIdSet == null) { + groupIdSet = new HashSet<>(); + } + groupIdSet.addAll(StrUtil.split(idArray, ',')); + } + } + + private void saveMessageCandidateIdentityWithMessage( + String processInstanceId, FlowTaskExt flowTaskExt, Task task, Long messageId) { + List candidates = flowApiService.getCandidateUsernames(flowTaskExt, task.getId()); + if (CollUtil.isNotEmpty(candidates)) { + this.saveMessageCandidateIdentityList( + messageId, FlowConstant.GROUP_TYPE_USER_VAR, CollUtil.join(candidates, ",")); + } + this.saveMessageCandidateIdentityList( + messageId, FlowConstant.GROUP_TYPE_ROLE_VAR, flowTaskExt.getRoleIds()); + this.saveMessageCandidateIdentityList( + messageId, FlowConstant.GROUP_TYPE_DEPT_VAR, flowTaskExt.getDeptIds()); + if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER)) { + Object v = flowApiService.getProcessInstanceVariable( + processInstanceId, FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR); + if (v != null) { + this.saveMessageCandidateIdentity( + messageId, FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR, v.toString()); + } + } else if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_DEPT_POST_LEADER)) { + Object v = flowApiService.getProcessInstanceVariable( + processInstanceId, FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR); + if (v != null) { + this.saveMessageCandidateIdentity( + messageId, FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR, v.toString()); + } + } else if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_POST)) { + Assert.notBlank(flowTaskExt.getDeptPostListJson()); + List groupDataList = + JSONArray.parseArray(flowTaskExt.getDeptPostListJson(), FlowTaskPostCandidateGroup.class); + for (FlowTaskPostCandidateGroup groupData : groupDataList) { + this.saveMessageCandidateIdentity(messageId, processInstanceId, groupData); + } + } + } + + private void saveMessageCandidateIdentity( + Long messageId, String processInstanceId, FlowTaskPostCandidateGroup groupData) { + FlowMessageCandidateIdentity candidateIdentity = new FlowMessageCandidateIdentity(); + candidateIdentity.setId(idGenerator.nextLongId()); + candidateIdentity.setMessageId(messageId); + candidateIdentity.setCandidateType(groupData.getType()); + switch (groupData.getType()) { + case FlowConstant.GROUP_TYPE_ALL_DEPT_POST_VAR: + candidateIdentity.setCandidateId(groupData.getPostId()); + flowMessageCandidateIdentityMapper.insert(candidateIdentity); + break; + case FlowConstant.GROUP_TYPE_DEPT_POST_VAR: + candidateIdentity.setCandidateId(groupData.getDeptPostId()); + flowMessageCandidateIdentityMapper.insert(candidateIdentity); + break; + case FlowConstant.GROUP_TYPE_SELF_DEPT_POST_VAR: + Object v = flowApiService.getProcessInstanceVariable( + processInstanceId, FlowConstant.SELF_DEPT_POST_PREFIX + groupData.getPostId()); + if (v != null) { + candidateIdentity.setCandidateId(v.toString()); + flowMessageCandidateIdentityMapper.insert(candidateIdentity); + } + break; + case FlowConstant.GROUP_TYPE_UP_DEPT_POST_VAR: + Object v2 = flowApiService.getProcessInstanceVariable( + processInstanceId, FlowConstant.UP_DEPT_POST_PREFIX + groupData.getPostId()); + if (v2 != null) { + candidateIdentity.setCandidateId(v2.toString()); + flowMessageCandidateIdentityMapper.insert(candidateIdentity); + } + break; + case FlowConstant.GROUP_TYPE_SIBLING_DEPT_POST_VAR: + Object v3 = flowApiService.getProcessInstanceVariable( + processInstanceId, FlowConstant.SIBLING_DEPT_POST_PREFIX + groupData.getPostId()); + if (v3 != null) { + List candidateIds = StrUtil.split(v3.toString(), ","); + for (String candidateId : candidateIds) { + candidateIdentity.setId(idGenerator.nextLongId()); + candidateIdentity.setCandidateId(candidateId); + flowMessageCandidateIdentityMapper.insert(candidateIdentity); + } + } + break; + default: + break; + } + } + private void saveMessageCandidateIdentity(Long messageId, String candidateType, String candidateId) { + FlowMessageCandidateIdentity candidateIdentity = new FlowMessageCandidateIdentity(); + candidateIdentity.setId(idGenerator.nextLongId()); + candidateIdentity.setMessageId(messageId); + candidateIdentity.setCandidateType(candidateType); + candidateIdentity.setCandidateId(candidateId); + flowMessageCandidateIdentityMapper.insert(candidateIdentity); + } + + private void saveMessageCandidateIdentityList(Long messageId, String candidateType, String identityIds) { + if (StrUtil.isNotBlank(identityIds)) { + for (String identityId : StrUtil.split(identityIds, ',')) { + FlowMessageCandidateIdentity candidateIdentity = new FlowMessageCandidateIdentity(); + candidateIdentity.setId(idGenerator.nextLongId()); + candidateIdentity.setMessageId(messageId); + candidateIdentity.setCandidateType(candidateType); + candidateIdentity.setCandidateId(identityId); + flowMessageCandidateIdentityMapper.insert(candidateIdentity); + } + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowMultiInstanceTransServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowMultiInstanceTransServiceImpl.java new file mode 100644 index 0000000..925c214 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowMultiInstanceTransServiceImpl.java @@ -0,0 +1,87 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.object.TokenData; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.dao.FlowMultiInstanceTransMapper; +import apelet.common.flow.model.FlowMultiInstanceTrans; +import apelet.common.flow.service.FlowMultiInstanceTransService; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; + +/** + * 会签任务操作流水数据操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowMultiInstanceTransService") +public class FlowMultiInstanceTransServiceImpl + extends BaseService implements FlowMultiInstanceTransService { + + @Autowired + private FlowMultiInstanceTransMapper flowMultiInstanceTransMapper; + @Autowired + private IdGeneratorWrapper idGenerator; + + /** + * 返回当前Service的主表Mapper对象。 + * + * @return 主表Mapper对象。 + */ + @Override + protected BaseDaoMapper mapper() { + return flowMultiInstanceTransMapper; + } + + /** + * 保存新增对象。 + * + * @param flowMultiInstanceTrans 新增对象。 + * @return 返回新增对象。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public FlowMultiInstanceTrans saveNew(FlowMultiInstanceTrans flowMultiInstanceTrans) { + flowMultiInstanceTrans.setId(idGenerator.nextLongId()); + TokenData tokenData = TokenData.takeFromRequest(); + flowMultiInstanceTrans.setCreateUserId(tokenData.getUserId()); + flowMultiInstanceTrans.setCreateLoginName(tokenData.getLoginName()); + flowMultiInstanceTrans.setCreateUsername(tokenData.getShowName()); + flowMultiInstanceTrans.setCreateTime(new Date()); + flowMultiInstanceTransMapper.insert(flowMultiInstanceTrans); + return flowMultiInstanceTrans; + } + + @Override + public FlowMultiInstanceTrans getByExecutionId(String executionId, String taskId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowMultiInstanceTrans::getExecutionId, executionId); + queryWrapper.eq(FlowMultiInstanceTrans::getTaskId, taskId); + return flowMultiInstanceTransMapper.selectOne(queryWrapper); + } + + @Override + public FlowMultiInstanceTrans getWithAssigneeListByMultiInstanceExecId(String multiInstanceExecId) { + if (multiInstanceExecId == null) { + return null; + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowMultiInstanceTrans::getMultiInstanceExecId, multiInstanceExecId); + queryWrapper.isNotNull(FlowMultiInstanceTrans::getAssigneeList); + return flowMultiInstanceTransMapper.selectOne(queryWrapper); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowNodeTasksServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowNodeTasksServiceImpl.java new file mode 100644 index 0000000..b9f935d --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowNodeTasksServiceImpl.java @@ -0,0 +1,30 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.dao.FlowNodeTasksMapper; +import apelet.common.flow.model.FlowNodeTasks; +import apelet.common.flow.service.FlowNodeTasksService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowNodeTasksServiceImpl") +public class FlowNodeTasksServiceImpl extends BaseService implements FlowNodeTasksService { + + @Autowired + FlowNodeTasksMapper flowNodeTasksMapper; + + + @Override + protected BaseDaoMapper mapper() { + return flowNodeTasksMapper; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskCommentServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskCommentServiceImpl.java new file mode 100644 index 0000000..84d7e93 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskCommentServiceImpl.java @@ -0,0 +1,144 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.object.TokenData; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.dao.FlowTaskCommentMapper; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.service.FlowTaskCommentService; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * 流程任务批注数据操作服务类。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowTaskCommentService") +public class FlowTaskCommentServiceImpl extends BaseService implements FlowTaskCommentService { + + @Autowired + private FlowTaskCommentMapper flowTaskCommentMapper; + @Autowired + private IdGeneratorWrapper idGenerator; + + /** + * 返回当前Service的主表Mapper对象。 + * + * @return 主表Mapper对象。 + */ + @Override + protected BaseDaoMapper mapper() { + return flowTaskCommentMapper; + } + + /** + * 保存新增对象。 + * + * @param flowTaskComment 新增对象。 + * @return 返回新增对象。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public FlowTaskComment saveNew(FlowTaskComment flowTaskComment) { + flowTaskComment.setId(idGenerator.nextLongId()); + TokenData tokenData = TokenData.takeFromRequest(); + if (tokenData != null) { + flowTaskComment.setHeadImageUrl(tokenData.getHeadImageUrl()); + flowTaskComment.setCreateUserId(tokenData.getUserId()); + flowTaskComment.setCreateLoginName(tokenData.getLoginName()); + flowTaskComment.setCreateUsername(tokenData.getShowName()); + } + flowTaskComment.setCreateTime(new Date()); + flowTaskCommentMapper.insert(flowTaskComment); + FlowTaskComment.setToRequest(flowTaskComment); + return flowTaskComment; + } + + /** + * 查询指定流程实例Id下的所有审批任务的批注。 + * + * @param processInstanceId 流程实例Id。 + * @return 查询结果集。 + */ + @Override + public List getFlowTaskCommentList(String processInstanceId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId); + queryWrapper.orderByAsc(FlowTaskComment::getId); + return flowTaskCommentMapper.selectList(queryWrapper); + } + + @Override + public List getFlowTaskCommentListByTaskIds(Set taskIdSet) { + LambdaQueryWrapper queryWrapper = + new LambdaQueryWrapper().in(FlowTaskComment::getTaskId, taskIdSet); + queryWrapper.orderByDesc(FlowTaskComment::getId); + return flowTaskCommentMapper.selectList(queryWrapper); + } + + @Override + public FlowTaskComment getLatestFlowTaskComment(String processInstanceId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId); + queryWrapper.orderByDesc(FlowTaskComment::getId); + IPage pageData = flowTaskCommentMapper.selectPage(new Page<>(1, 1), queryWrapper); + return CollUtil.isEmpty(pageData.getRecords()) ? null : pageData.getRecords().get(0); + } + + @Override + public FlowTaskComment getLatestFlowTaskComment(String processInstanceId, String taskDefinitionKey) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId); + queryWrapper.eq(FlowTaskComment::getTaskKey, taskDefinitionKey); + queryWrapper.orderByDesc(FlowTaskComment::getId); + IPage pageData = flowTaskCommentMapper.selectPage(new Page<>(1, 1), queryWrapper); + return CollUtil.isEmpty(pageData.getRecords()) ? null : pageData.getRecords().get(0); + } + + @Override + public FlowTaskComment getFirstFlowTaskComment(String processInstanceId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId); + queryWrapper.orderByAsc(FlowTaskComment::getId); + IPage pageData = flowTaskCommentMapper.selectPage(new Page<>(1, 1), queryWrapper); + return CollUtil.isEmpty(pageData.getRecords()) ? null : pageData.getRecords().get(0); + } + + @Override + public List getFlowTaskCommentListByExecutionId( + String processInstanceId, String taskId, String executionId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId); + queryWrapper.eq(FlowTaskComment::getTaskId, taskId); + queryWrapper.eq(FlowTaskComment::getExecutionId, executionId); + queryWrapper.orderByAsc(FlowTaskComment::getCreateTime); + return flowTaskCommentMapper.selectList(queryWrapper); + } + + @Override + public List getFlowTaskCommentListByMultiInstanceExecId(String multiInstanceExecId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTaskComment::getMultiInstanceExecId, multiInstanceExecId); + return flowTaskCommentMapper.selectList(queryWrapper); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java new file mode 100644 index 0000000..132cd16 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java @@ -0,0 +1,616 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.exception.MyRuntimeException; +import apelet.common.core.object.Tuple2; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.dao.FlowTaskExtMapper; +import apelet.common.flow.model.*; +import apelet.common.flow.object.FlowElementExtProperty; +import apelet.common.flow.object.FlowTaskMultiSignAssign; +import apelet.common.flow.object.FlowUserTaskExtData; +import apelet.common.flow.service.*; +import apelet.common.flow.util.BaseFlowIdentityExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.vo.FlowUserInfoVo; +import apelet.common.flow.vo.TaskInfoVo; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.*; +import org.flowable.task.api.TaskInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowTaskExtService") +public class FlowTaskExtServiceImpl extends BaseService implements FlowTaskExtService { + + @Autowired + private FlowTaskExtMapper flowTaskExtMapper; + @Autowired + private FlowEntryVariableService flowEntryVariableService; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowMultiInstanceTransService flowMultiInstanceTransService; + @Autowired + private FlowTaskCommentService flowTaskCommentService; + + private static final String ID = "id"; + private static final String TYPE = "type"; + private static final String LABEL = "label"; + private static final String NAME = "name"; + private static final String VALUE = "value"; + + /** + * 返回当前Service的主表Mapper对象。 + * + * @return 主表Mapper对象。 + */ + @Override + protected BaseDaoMapper mapper() { + return flowTaskExtMapper; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void saveBatch(List flowTaskExtList) { + if (CollUtil.isNotEmpty(flowTaskExtList)) { + flowTaskExtMapper.insertList(flowTaskExtList); + } + } + + @Override + public FlowTaskExt getByProcessDefinitionIdAndTaskId(String processDefinitionId, String taskId) { + FlowTaskExt filter = new FlowTaskExt(); + filter.setProcessDefinitionId(processDefinitionId); + filter.setTaskId(taskId); + return flowTaskExtMapper.selectOne(new QueryWrapper<>(filter)); + } + + @Override + public List getByProcessDefinitionId(String processDefinitionId) { + FlowTaskExt filter = new FlowTaskExt(); + filter.setProcessDefinitionId(processDefinitionId); + return flowTaskExtMapper.selectList(new QueryWrapper<>(filter)); + } + + @Override + public List getCandidateUserInfoList( + String processInstanceId, + FlowTaskExt flowTaskExt, + TaskInfo taskInfo, + boolean isMultiInstanceTask, + boolean historic) { + List resultUserMapList = new LinkedList<>(); + if (!isMultiInstanceTask && this.buildTransferUserList(taskInfo, resultUserMapList)) { + return resultUserMapList; + } + Set loginNameSet = new HashSet<>(); + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + if (StrUtil.isNotBlank(flowTaskExt.getDeptIds())) { + Set deptIdSet = CollUtil.newHashSet(StrUtil.split(flowTaskExt.getDeptIds(), ',')); + List userInfoList = flowIdentityExtHelper.getUserInfoListByDeptIds(deptIdSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + if (StrUtil.isNotBlank(flowTaskExt.getRoleIds())) { + Set roleIdSet = CollUtil.newHashSet(StrUtil.split(flowTaskExt.getRoleIds(), ',')); + List userInfoList = flowIdentityExtHelper.getUserInfoListByRoleIds(roleIdSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + Set usernameSet = new HashSet<>(); + switch (flowTaskExt.getGroupType()) { + case FlowConstant.GROUP_TYPE_ASSIGNEE: + usernameSet.add(taskInfo.getAssignee()); + break; + case FlowConstant.GROUP_TYPE_DEPT_POST_LEADER: + String deptPostLeaderId = flowApiService.getExecutionVariableStringWithSafe( + taskInfo.getExecutionId(), FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR); + List userInfoList = + flowIdentityExtHelper.getUserInfoListByDeptPostIds(CollUtil.newHashSet(deptPostLeaderId)); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + break; + case FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER: + String upDeptPostLeaderId = flowApiService.getExecutionVariableStringWithSafe( + taskInfo.getExecutionId(), FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR); + List upUserInfoList = + flowIdentityExtHelper.getUserInfoListByDeptPostIds(CollUtil.newHashSet(upDeptPostLeaderId)); + this.buildUserMapList(upUserInfoList, loginNameSet, resultUserMapList); + break; + default: + break; + } + List candidateUsernames = flowApiService.getCandidateUsernames(flowTaskExt, taskInfo.getId()); + if (CollUtil.isNotEmpty(candidateUsernames)) { + usernameSet.addAll(candidateUsernames); + } + if (isMultiInstanceTask) { + List assigneeList = this.getAssigneeList(taskInfo.getExecutionId(), taskInfo.getId()); + if (CollUtil.isNotEmpty(assigneeList)) { + usernameSet.addAll(assigneeList); + } + } + if (CollUtil.isNotEmpty(usernameSet)) { + List userInfoList = flowIdentityExtHelper.getUserInfoListByUsernameSet(usernameSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + Tuple2, Set> tuple2 = + flowApiService.getDeptPostIdAndPostIds(flowTaskExt, processInstanceId, historic); + Set postIdSet = tuple2.getSecond(); + Set deptPostIdSet = tuple2.getFirst(); + if (CollUtil.isNotEmpty(postIdSet)) { + List userInfoList = flowIdentityExtHelper.getUserInfoListByPostIds(postIdSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + if (CollUtil.isNotEmpty(deptPostIdSet)) { + List userInfoList = flowIdentityExtHelper.getUserInfoListByDeptPostIds(deptPostIdSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + return resultUserMapList; + } + + @Override + public List getCandidateUserInfoList( + String processInstanceId, + String executionId, + FlowTaskExt flowTaskExt) { + List resultUserMapList = new LinkedList<>(); + Set loginNameSet = new HashSet<>(); + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + if (StrUtil.isNotBlank(flowTaskExt.getDeptIds())) { + Set deptIdSet = CollUtil.newHashSet(StrUtil.split(flowTaskExt.getDeptIds(), ',')); + List userInfoList = flowIdentityExtHelper.getUserInfoListByDeptIds(deptIdSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + if (StrUtil.isNotBlank(flowTaskExt.getRoleIds())) { + Set roleIdSet = CollUtil.newHashSet(StrUtil.split(flowTaskExt.getRoleIds(), ',')); + List userInfoList = flowIdentityExtHelper.getUserInfoListByRoleIds(roleIdSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + Set usernameSet = new HashSet<>(); + switch (flowTaskExt.getGroupType()) { + case FlowConstant.GROUP_TYPE_DEPT_POST_LEADER: + String deptPostLeaderId = flowApiService.getExecutionVariableStringWithSafe( + executionId, FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR); + List userInfoList = + flowIdentityExtHelper.getUserInfoListByDeptPostIds(CollUtil.newHashSet(deptPostLeaderId)); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + break; + case FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER: + String upDeptPostLeaderId = flowApiService.getExecutionVariableStringWithSafe( + executionId, FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR); + List upUserInfoList = + flowIdentityExtHelper.getUserInfoListByDeptPostIds(CollUtil.newHashSet(upDeptPostLeaderId)); + this.buildUserMapList(upUserInfoList, loginNameSet, resultUserMapList); + break; + default: + break; + } + List candidateUsernames; + if (StrUtil.isBlank(flowTaskExt.getCandidateUsernames())) { + candidateUsernames = Collections.emptyList(); + } else { + if (!StrUtil.equals(flowTaskExt.getCandidateUsernames(), " + FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR + ")) { + candidateUsernames = StrUtil.split(flowTaskExt.getCandidateUsernames(), ","); + } else { + Object v = flowApiService.getExecutionVariableStringWithSafe(executionId, FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR); + candidateUsernames = v == null ? null : StrUtil.split(v.toString(), ","); + } + } + if (CollUtil.isNotEmpty(candidateUsernames)) { + usernameSet.addAll(candidateUsernames); + } + if (CollUtil.isNotEmpty(usernameSet)) { + List userInfoList = flowIdentityExtHelper.getUserInfoListByUsernameSet(usernameSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + Tuple2, Set> tuple2 = + flowApiService.getDeptPostIdAndPostIds(flowTaskExt, processInstanceId, false); + Set postIdSet = tuple2.getSecond(); + Set deptPostIdSet = tuple2.getFirst(); + if (CollUtil.isNotEmpty(postIdSet)) { + List userInfoList = flowIdentityExtHelper.getUserInfoListByPostIds(postIdSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + if (CollUtil.isNotEmpty(deptPostIdSet)) { + List userInfoList = flowIdentityExtHelper.getUserInfoListByDeptPostIds(deptPostIdSet); + this.buildUserMapList(userInfoList, loginNameSet, resultUserMapList); + } + return resultUserMapList; + } + + private void buildUserMapList( + List userInfoList, Set loginNameSet, List userMapList) { + if (CollUtil.isEmpty(userInfoList)) { + return; + } + for (FlowUserInfoVo userInfo : userInfoList) { + if (!loginNameSet.contains(userInfo.getLoginName())) { + loginNameSet.add(userInfo.getLoginName()); + userMapList.add(userInfo); + } + } + } + + @Override + public FlowTaskExt buildTaskExtByUserTask(UserTask userTask) { + FlowTaskExt flowTaskExt = new FlowTaskExt(); + flowTaskExt.setTaskId(userTask.getId()); + String formKey = userTask.getFormKey(); + if (StrUtil.isNotBlank(formKey)) { + TaskInfoVo taskInfoVo = JSON.parseObject(formKey, TaskInfoVo.class); + flowTaskExt.setGroupType(taskInfoVo.getGroupType()); + } + JSONObject extraDataJson = this.buildFlowTaskExtensionData(userTask); + if (extraDataJson != null) { + flowTaskExt.setExtraDataJson(extraDataJson.toJSONString()); + } + Map> extensionMap = userTask.getExtensionElements(); + if (MapUtil.isEmpty(extensionMap)) { + return flowTaskExt; + } + List operationList = this.buildOperationListExtensionElement(extensionMap); + if (CollUtil.isNotEmpty(operationList)) { + flowTaskExt.setOperationListJson(JSON.toJSONString(operationList)); + } + List variableList = this.buildVariableListExtensionElement(extensionMap); + if (CollUtil.isNotEmpty(variableList)) { + flowTaskExt.setVariableListJson(JSON.toJSONString(variableList)); + } + JSONObject assigneeListObject = this.buildAssigneeListExtensionElement(extensionMap); + if (assigneeListObject != null) { + flowTaskExt.setAssigneeListJson(JSON.toJSONString(assigneeListObject)); + } + List deptPostList = this.buildDeptPostListExtensionElement(extensionMap); + if (deptPostList != null) { + flowTaskExt.setDeptPostListJson(JSON.toJSONString(deptPostList)); + } + List copyList = this.buildCopyListExtensionElement(extensionMap); + if (copyList != null) { + flowTaskExt.setCopyListJson(JSON.toJSONString(copyList)); + } + JSONObject candidateGroupObject = this.buildUserCandidateGroupsExtensionElement(extensionMap); + if (candidateGroupObject != null) { + String type = candidateGroupObject.getString(TYPE); + String value = candidateGroupObject.getString(VALUE); + switch (type) { + case "DEPT": + flowTaskExt.setDeptIds(value); + break; + case "ROLE": + flowTaskExt.setRoleIds(value); + break; + case "USERS": + flowTaskExt.setCandidateUsernames(value); + break; + default: + break; + } + } + return flowTaskExt; + } + + @Override + public List buildTaskExtList(BpmnModel bpmnModel) { + List processList = bpmnModel.getProcesses(); + List flowTaskExtList = new LinkedList<>(); + for (Process process : processList) { + for (FlowElement element : process.getFlowElements()) { + if (element instanceof UserTask) { + FlowTaskExt flowTaskExt = this.buildTaskExtByUserTask((UserTask) element); + flowTaskExtList.add(flowTaskExt); + } + } + } + return flowTaskExtList; + } + + @Override + public List buildOperationListExtensionElement(Map> extensionMap) { + List formOperationElements = + this.getMyExtensionElementList(extensionMap, "operationList", "formOperation"); + if (CollUtil.isEmpty(formOperationElements)) { + return Collections.emptyList(); + } + List resultList = new LinkedList<>(); + for (ExtensionElement e : formOperationElements) { + JSONObject operationJsonData = new JSONObject(); + operationJsonData.put(ID, e.getAttributeValue(null, ID)); + operationJsonData.put(LABEL, e.getAttributeValue(null, LABEL)); + operationJsonData.put(TYPE, e.getAttributeValue(null, TYPE)); + operationJsonData.put("showOrder", e.getAttributeValue(null, "showOrder")); + operationJsonData.put("latestApprovalStatus", e.getAttributeValue(null, "latestApprovalStatus")); + String multiSignAssignee = e.getAttributeValue(null, "multiSignAssignee"); + if (StrUtil.isNotBlank(multiSignAssignee)) { + operationJsonData.put("multiSignAssignee", + JSON.parseObject(multiSignAssignee, FlowTaskMultiSignAssign.class)); + } + resultList.add(operationJsonData); + } + return resultList; + } + + @Override + public List buildVariableListExtensionElement(Map> extensionMap) { + List formVariableElements = + this.getMyExtensionElementList(extensionMap, "variableList", "formVariable"); + if (CollUtil.isEmpty(formVariableElements)) { + return Collections.emptyList(); + } + Set variableIdSet = new HashSet<>(); + for (ExtensionElement e : formVariableElements) { + String id = e.getAttributeValue(null, ID); + variableIdSet.add(Long.parseLong(id)); + } + List variableList = flowEntryVariableService.getInList(variableIdSet); + List resultList = new LinkedList<>(); + for (FlowEntryVariable variable : variableList) { + resultList.add((JSONObject) JSON.toJSON(variable)); + } + return resultList; + } + + @Override + public FlowElementExtProperty buildFlowElementExt(FlowElement element) { + JSONObject propertiesData = this.buildFlowElementExtToJson(element); + return propertiesData == null ? null : propertiesData.toJavaObject(FlowElementExtProperty.class); + } + + @Override + public JSONObject buildFlowElementExtToJson(FlowElement element) { + Map> extensionMap = element.getExtensionElements(); + List propertiesElements = + this.getMyExtensionElementList(extensionMap, "properties", "property"); + if (CollUtil.isEmpty(propertiesElements)) { + return null; + } + JSONObject propertiesData = new JSONObject(); + for (ExtensionElement e : propertiesElements) { + String name = e.getAttributeValue(null, NAME); + String value = e.getAttributeValue(null, VALUE); + propertiesData.put(name, value); + } + return propertiesData; + } + + private void buildFlowTaskTimeoutExtensionData( + Map> attributeMap, JSONObject extraDataJson) { + List timeoutHandleWayAttributes = attributeMap.get(FlowConstant.TASK_TIMEOUT_HANDLE_WAY); + if (CollUtil.isNotEmpty(timeoutHandleWayAttributes)) { + String handleWay = timeoutHandleWayAttributes.get(0).getValue(); + extraDataJson.put(FlowConstant.TASK_TIMEOUT_HANDLE_WAY, handleWay); + List timeoutHoursAttributes = attributeMap.get(FlowConstant.TASK_TIMEOUT_HOURS); + if (CollUtil.isEmpty(timeoutHoursAttributes)) { + throw new MyRuntimeException("没有设置任务超时小时数!"); + } + Integer timeoutHours = Integer.valueOf(timeoutHoursAttributes.get(0).getValue()); + extraDataJson.put(FlowConstant.TASK_TIMEOUT_HOURS, timeoutHours); + if (StrUtil.equals(handleWay, FlowUserTaskExtData.TIMEOUT_AUTO_COMPLETE)) { + List defaultAssigneeAttributes = + attributeMap.get(FlowConstant.TASK_TIMEOUT_DEFAULT_ASSIGNEE); + if (CollUtil.isEmpty(defaultAssigneeAttributes)) { + throw new MyRuntimeException("没有设置超时任务处理人!"); + } + extraDataJson.put(FlowConstant.TASK_TIMEOUT_DEFAULT_ASSIGNEE, defaultAssigneeAttributes.get(0).getValue()); + } + } + } + + private void buildFlowTaskEmptyUserExtensionData( + Map> attributeMap, JSONObject extraDataJson) { + List emptyUserHandleWayAttributes = attributeMap.get(FlowConstant.EMPTY_USER_HANDLE_WAY); + if (CollUtil.isNotEmpty(emptyUserHandleWayAttributes)) { + String handleWay = emptyUserHandleWayAttributes.get(0).getValue(); + extraDataJson.put(FlowConstant.EMPTY_USER_HANDLE_WAY, handleWay); + if (StrUtil.equals(handleWay, FlowUserTaskExtData.EMPTY_USER_TO_ASSIGNEE)) { + List emptyUserToAssigneeAttributes = attributeMap.get(FlowConstant.EMPTY_USER_TO_ASSIGNEE); + if (CollUtil.isEmpty(emptyUserToAssigneeAttributes)) { + throw new MyRuntimeException("没有设置空审批人的指定处理人!"); + } + extraDataJson.put(FlowConstant.EMPTY_USER_TO_ASSIGNEE, emptyUserToAssigneeAttributes.get(0).getValue()); + } + } + } + + private JSONObject buildFlowTaskExtensionData(UserTask userTask) { + JSONObject extraDataJson = this.buildFlowElementExtToJson(userTask); + Map> attributeMap = userTask.getAttributes(); + if (MapUtil.isEmpty(attributeMap)) { + return extraDataJson; + } + if (extraDataJson == null) { + extraDataJson = new JSONObject(); + } + this.buildFlowTaskTimeoutExtensionData(attributeMap, extraDataJson); + this.buildFlowTaskEmptyUserExtensionData(attributeMap, extraDataJson); + List rejectTypeAttributes = attributeMap.get(FlowConstant.USER_TASK_REJECT_TYPE_KEY); + if (CollUtil.isNotEmpty(rejectTypeAttributes)) { + extraDataJson.put(FlowConstant.USER_TASK_REJECT_TYPE_KEY, rejectTypeAttributes.get(0).getValue()); + } + List sendMsgTypeAttributes = attributeMap.get("sendMessageType"); + if (CollUtil.isNotEmpty(sendMsgTypeAttributes)) { + ExtensionAttribute attribute = sendMsgTypeAttributes.get(0); + extraDataJson.put(FlowConstant.USER_TASK_NOTIFY_TYPES_KEY, StrUtil.split(attribute.getValue(), ",")); + } + return extraDataJson; + } + + private JSONObject buildUserCandidateGroupsExtensionElement(Map> extensionMap) { + JSONObject jsonData = null; + List elementCandidateGroupsList = extensionMap.get("userCandidateGroups"); + if (CollUtil.isEmpty(elementCandidateGroupsList)) { + return jsonData; + } + jsonData = new JSONObject(); + ExtensionElement ee = elementCandidateGroupsList.get(0); + jsonData.put(TYPE, ee.getAttributeValue(null, TYPE)); + jsonData.put(VALUE, ee.getAttributeValue(null, VALUE)); + return jsonData; + } + + private JSONObject buildAssigneeListExtensionElement(Map> extensionMap) { + JSONObject jsonData = null; + List elementAssigneeList = extensionMap.get("assigneeList"); + if (CollUtil.isEmpty(elementAssigneeList)) { + return jsonData; + } + ExtensionElement ee = elementAssigneeList.get(0); + Map> childExtensionMap = ee.getChildElements(); + if (MapUtil.isEmpty(childExtensionMap)) { + return jsonData; + } + List assigneeElements = childExtensionMap.get("assignee"); + if (CollUtil.isEmpty(assigneeElements)) { + return jsonData; + } + JSONArray assigneeIdArray = new JSONArray(); + for (ExtensionElement e : assigneeElements) { + assigneeIdArray.add(e.getAttributeValue(null, ID)); + } + jsonData = new JSONObject(); + String assigneeType = ee.getAttributeValue(null, TYPE); + jsonData.put("assigneeType", assigneeType); + jsonData.put("assigneeList", assigneeIdArray); + return jsonData; + } + + private List buildDeptPostListExtensionElement(Map> extensionMap) { + List deptPostElements = + this.getMyExtensionElementList(extensionMap, "deptPostList", "deptPost"); + if (CollUtil.isEmpty(deptPostElements)) { + return Collections.emptyList(); + } + List resultList = new LinkedList<>(); + for (ExtensionElement e : deptPostElements) { + JSONObject deptPostJsonData = new JSONObject(); + deptPostJsonData.put(ID, e.getAttributeValue(null, ID)); + deptPostJsonData.put(TYPE, e.getAttributeValue(null, TYPE)); + String postId = e.getAttributeValue(null, "postId"); + if (postId != null) { + deptPostJsonData.put("postId", postId); + } + String deptPostId = e.getAttributeValue(null, "deptPostId"); + if (deptPostId != null) { + deptPostJsonData.put("deptPostId", deptPostId); + } + resultList.add(deptPostJsonData); + } + return resultList; + } + + private List buildCopyListExtensionElement(Map> extensionMap) { + List copyElements = + this.getMyExtensionElementList(extensionMap, "copyItemList", "copyItem"); + if (CollUtil.isEmpty(copyElements)) { + return Collections.emptyList(); + } + List resultList = new LinkedList<>(); + for (ExtensionElement e : copyElements) { + JSONObject copyJsonData = new JSONObject(); + String type = e.getAttributeValue(null, TYPE); + copyJsonData.put(TYPE, type); + if (!StrUtil.equalsAny(type, FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR, + FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR, + FlowConstant.GROUP_TYPE_USER_VAR, + FlowConstant.GROUP_TYPE_ROLE_VAR, + FlowConstant.GROUP_TYPE_DEPT_VAR, + FlowConstant.GROUP_TYPE_DEPT_POST_VAR, + FlowConstant.GROUP_TYPE_ALL_DEPT_POST_VAR, + FlowConstant.GROUP_TYPE_SIBLING_DEPT_POST_VAR, + FlowConstant.GROUP_TYPE_SELF_DEPT_POST_VAR, + FlowConstant.GROUP_TYPE_UP_DEPT_POST_VAR)) { + throw new MyRuntimeException("Invalid TYPE [" + type + " ] for CopyItenList Extension!"); + } + String id = e.getAttributeValue(null, ID); + if (StrUtil.isNotBlank(id)) { + copyJsonData.put(ID, id); + } + resultList.add(copyJsonData); + } + return resultList; + } + + private List getMyExtensionElementList( + Map> extensionMap, String rootName, String childName) { + if (extensionMap == null) { + return Collections.emptyList(); + } + List elementList = extensionMap.get(rootName); + if (CollUtil.isEmpty(elementList)) { + return Collections.emptyList(); + } + if (StrUtil.isBlank(childName)) { + return elementList; + } + ExtensionElement ee = elementList.get(0); + Map> childExtensionMap = ee.getChildElements(); + if (MapUtil.isEmpty(childExtensionMap)) { + return Collections.emptyList(); + } + List childrenElements = childExtensionMap.get(childName); + if (CollUtil.isEmpty(childrenElements)) { + return Collections.emptyList(); + } + return childrenElements; + } + + private List getAssigneeList(String executionId, String taskId) { + FlowMultiInstanceTrans flowMultiInstanceTrans = + flowMultiInstanceTransService.getByExecutionId(executionId, taskId); + String multiInstanceExecId; + if (flowMultiInstanceTrans == null) { + multiInstanceExecId = flowApiService.getTaskVariableStringWithSafe( + taskId, FlowConstant.MULTI_SIGN_TASK_EXECUTION_ID_VAR); + } else { + multiInstanceExecId = flowMultiInstanceTrans.getMultiInstanceExecId(); + } + flowMultiInstanceTrans = + flowMultiInstanceTransService.getWithAssigneeListByMultiInstanceExecId(multiInstanceExecId); + return flowMultiInstanceTrans == null ? null + : StrUtil.split(flowMultiInstanceTrans.getAssigneeList(), ","); + } + + private boolean buildTransferUserList(TaskInfo taskInfo, List resultUserMapList) { + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + List taskCommentList = flowTaskCommentService.getFlowTaskCommentListByExecutionId( + taskInfo.getProcessInstanceId(), taskInfo.getId(), taskInfo.getExecutionId()); + if (CollUtil.isEmpty(taskCommentList)) { + return false; + } + FlowTaskComment transferComment = null; + for (int i = taskCommentList.size() - 1; i >= 0; i--) { + FlowTaskComment comment = taskCommentList.get(i); + if (StrUtil.equalsAny(comment.getApprovalType(), + FlowApprovalType.TRANSFER, FlowApprovalType.INTERVENE)) { + transferComment = comment; + break; + } + } + if (transferComment == null || StrUtil.isBlank(transferComment.getDelegateAssignee())) { + return false; + } + Set loginNameSet = new HashSet<>(StrUtil.split(transferComment.getDelegateAssignee(), ",")); + resultUserMapList.addAll(flowIdentityExtHelper.getUserInfoListByUsernameSet(loginNameSet)); + return true; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskTimeoutJobServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskTimeoutJobServiceImpl.java new file mode 100644 index 0000000..839193e --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskTimeoutJobServiceImpl.java @@ -0,0 +1,137 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.dao.FlowTaskTimeoutJobMapper; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowTaskTimeoutJob; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.flow.model.constant.FlowTaskTimeoutJobStatus; +import apelet.common.flow.object.FlowApiOption; +import apelet.common.flow.object.FlowUserTaskExtData; +import apelet.common.flow.service.*; +import apelet.common.flow.util.BaseFlowIdentityExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.vo.FlowUserInfoVo; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.flowable.task.api.Task; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowTaskTimeoutJobService") +public class FlowTaskTimeoutJobServiceImpl extends BaseService implements FlowTaskTimeoutJobService { + + @Autowired + private FlowTaskTimeoutJobMapper flowTaskTimeoutJobMapper; + @Autowired + private FlowWorkOrderService flowWorkOrderService; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowMessageService flowMessageService; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private IdGeneratorWrapper idGenerator; + + @Override + protected BaseDaoMapper mapper() { + return flowTaskTimeoutJobMapper; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void saveNew(FlowTaskTimeoutJob job) { + Date now = new Date(); + job.setId(idGenerator.nextLongId()); + job.setCreateTime(now); + job.setUpdateTime(now); + job.setExecTime(DateUtil.offsetHour(now, job.getTimeoutHours())); + job.setStatus(FlowTaskTimeoutJobStatus.NO_EXEC); + flowTaskTimeoutJobMapper.insert(job); + } + + @Override + public void executeJob(FlowTaskTimeoutJob job, Task task) { + try { + if (job.getHandleWay().equals(FlowUserTaskExtData.TIMEOUT_SEND_MSG)) { + FlowWorkOrder workOrder = + flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(task.getProcessInstanceId()); + if (workOrder != null) { + flowMessageService.saveNewRemindMessage(workOrder); + } + } else { + FlowTaskComment comment = new FlowTaskComment(); + comment.setProcessInstanceId(task.getProcessInstanceId()); + comment.setExecutionId(task.getExecutionId()); + comment.setTaskKey(task.getTaskDefinitionKey()); + comment.setTaskName(task.getName()); + comment.setTaskId(task.getId()); + comment.setApprovalType(FlowApprovalType.TIMEOUT_AUTO_COMPLETE); + comment.setTaskComment("任务超时自动审批"); + comment.setCreateLoginName(job.getDefaultAssignee()); + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + List userInfoList = + flowIdentityExtHelper.getUserInfoListByUsernameSet(CollUtil.newHashSet(job.getDefaultAssignee())); + if (CollUtil.isNotEmpty(userInfoList)) { + FlowUserInfoVo userInfo = userInfoList.get(0); + comment.setCreateUserId(userInfo.getUserId()); + comment.setCreateUsername(userInfo.getShowName()); + } + FlowApiOption option = new FlowApiOption(); + option.setDeleteTimeoutTaskJob(false); + flowApiService.completeTask(task, comment, new JSONObject(), option); + } + job.setStatus(FlowTaskTimeoutJobStatus.SUCCESS); + } catch (Exception e) { + log.error("Failed to call FlowTaskTimeoutJobServiceImpl.executeJob", e); + job.setStatus(FlowTaskTimeoutJobStatus.FAIL); + job.setErrorMessage(e.getClass().getName()); + } + job.setUpdateTime(new Date()); + flowTaskTimeoutJobMapper.updateById(job); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void deleteByTaskId(String taskId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTaskTimeoutJob::getStatus, FlowTaskTimeoutJobStatus.NO_EXEC); + queryWrapper.eq(FlowTaskTimeoutJob::getTaskId, taskId); + flowTaskTimeoutJobMapper.delete(queryWrapper); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void deleteByProcessInstanceId(String processInstanceId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.ne(FlowTaskTimeoutJob::getStatus, FlowTaskTimeoutJobStatus.FAIL); + queryWrapper.eq(FlowTaskTimeoutJob::getProcessInstanceId, processInstanceId); + flowTaskTimeoutJobMapper.delete(queryWrapper); + } + + @Override + public List getExecutableList() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowTaskTimeoutJob::getStatus, FlowTaskTimeoutJobStatus.NO_EXEC); + queryWrapper.le(FlowTaskTimeoutJob::getExecTime, new Date()); + return flowTaskTimeoutJobMapper.selectList(queryWrapper); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTransProducerServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTransProducerServiceImpl.java new file mode 100644 index 0000000..1c7ebe6 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTransProducerServiceImpl.java @@ -0,0 +1,70 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.object.TokenData; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.dao.FlowTransProducerMapper; +import apelet.common.flow.model.FlowTransProducer; +import apelet.common.flow.service.FlowTransProducerService; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Set; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowTransProducerService") +public class FlowTransProducerServiceImpl extends BaseService implements FlowTransProducerService { + + @Autowired + private FlowTransProducerMapper flowTransProducerMapper; + + @Override + protected BaseDaoMapper mapper() { + return flowTransProducerMapper; + } + + @Override + public List getListByProcessInstanceIds(Set processInstanceIds) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.in(FlowTransProducer::getProcessInstanceId, processInstanceIds); + qw.orderByDesc(FlowTransProducer::getTransId); + return flowTransProducerMapper.selectList(qw); + } + + @Override + public FlowTransProducer getByProcessInstanceIdAndTaskId(String processInstanceId, String taskId) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(FlowTransProducer::getProcessInstanceId, processInstanceId); + qw.eq(FlowTransProducer::getTaskId, taskId); + String appCode = TokenData.takeFromRequest().getAppCode(); + if (StrUtil.isBlank(appCode)) { + qw.isNull(FlowTransProducer::getAppCode); + } else { + qw.eq(FlowTransProducer::getAppCode, appCode); + } + return flowTransProducerMapper.selectOne(qw); + } + + @Override + public void deleteByProcessInstanceId(String processInstanceId) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(FlowTransProducer::getProcessInstanceId, processInstanceId); + String appCode = TokenData.takeFromRequest().getAppCode(); + if (StrUtil.isBlank(appCode)) { + qw.isNull(FlowTransProducer::getAppCode); + } else { + qw.eq(FlowTransProducer::getAppCode, appCode); + } + flowTransProducerMapper.delete(qw); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowVariableLogServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowVariableLogServiceImpl.java new file mode 100644 index 0000000..d5c5530 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowVariableLogServiceImpl.java @@ -0,0 +1,58 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.dao.FlowVariableLogMapper; +import apelet.common.flow.model.FlowVariableLog; +import apelet.common.flow.service.FlowVariableLogService; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowVariableLogService") +public class FlowVariableLogServiceImpl extends BaseService implements FlowVariableLogService { + + @Autowired + private FlowVariableLogMapper flowVariableLogMapper; + @Autowired + private IdGeneratorWrapper idGenerator; + + @Override + protected BaseDaoMapper mapper() { + return flowVariableLogMapper; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void saveNew(FlowVariableLog o) { + o.setId(idGenerator.nextLongId()); + o.setCreateTime(new Date()); + flowVariableLogMapper.insert(o); + } + + @Override + public Map getVariableMap(String processInstanceId, List taskKeys) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(FlowVariableLog::getProcessInstanceId, processInstanceId); + qw.in(FlowVariableLog::getTaskKey, taskKeys); + qw.orderByDesc(FlowVariableLog::getId); + List dataList = flowVariableLogMapper.selectList(qw); + return dataList.stream().collect(Collectors.toMap( + FlowVariableLog::getTaskKey, FlowVariableLog::getVariableData, (existOne, newOne) -> existOne)); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowWorkOrderServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowWorkOrderServiceImpl.java new file mode 100644 index 0000000..7bb29b1 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowWorkOrderServiceImpl.java @@ -0,0 +1,382 @@ +package apelet.common.flow.service.impl; + +import apelet.common.core.annotation.DisableDataFilter; +import apelet.common.core.annotation.MyDataSourceResolver; +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.core.base.service.BaseService; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.constant.GlobalDeletedFlag; +import apelet.common.core.object.*; +import apelet.common.core.util.DefaultDataSourceResolver; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.constant.FlowTaskStatus; +import apelet.common.flow.dao.FlowWorkOrderExtMapper; +import apelet.common.flow.dao.FlowWorkOrderMapper; +import apelet.common.flow.model.FlowEntry; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.flow.model.FlowWorkOrderExt; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowEntryService; +import apelet.common.flow.service.FlowWorkOrderService; +import apelet.common.flow.util.BaseFlowIdentityExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.vo.FlowWorkOrderVo; +import apelet.common.redis.util.CommonRedisUtil; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@MyDataSourceResolver( + resolver = DefaultDataSourceResolver.class, + intArg = ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowWorkOrderService") +public class FlowWorkOrderServiceImpl extends BaseService implements FlowWorkOrderService { + + @Autowired + private FlowWorkOrderMapper flowWorkOrderMapper; + @Autowired + private FlowWorkOrderExtMapper flowWorkOrderExtMapper; + @Autowired + private IdGeneratorWrapper idGenerator; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowEntryService flowEntryService; + @Autowired + private CommonRedisUtil commonRedisUtil; + + /** + * 返回当前Service的主表Mapper对象。 + * + * @return 主表Mapper对象。 + */ + @Override + protected BaseDaoMapper mapper() { + return flowWorkOrderMapper; + } + + /** + * 保存新增对象。 + * + * @param instance 流程实例对象。 + * @param dataId 流程实例的BusinessKey。 + * @param onlineTableId 在线数据表的主键Id。 + * @param tableName 面向静态表单所使用的表名。 + * @return 新增的工作流工单对象。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public FlowWorkOrder saveNew(ProcessInstance instance, Object dataId, Long onlineTableId, String tableName) { + // 正常插入流程工单数据。 + FlowWorkOrder flowWorkOrder = this.createWith(instance); + if( instance != null ){ + flowWorkOrder.setWorkOrderCode(this.generateWorkOrderCode(instance.getProcessDefinitionKey())); + }else{ + flowWorkOrder.setWorkOrderCode(""); + } + flowWorkOrder.setBusinessKey(dataId.toString()); + flowWorkOrder.setOnlineTableId(onlineTableId); + flowWorkOrder.setTableName(tableName); + flowWorkOrder.setFlowStatus(FlowTaskStatus.SUBMITTED); + try { + flowWorkOrderMapper.insert(flowWorkOrder); + } catch (DuplicateKeyException e) { + // 数据插入过程中,如果抛出 “数据重复值 (DuplicationKeyException)” 时,会捕捉该异常。 + // 执行 SQL 查询操作,判断本次计算的工单编码是否已经存在。如不存在,该异常则为其他字段值重复所引起,可直接再次抛出。 + if (flowWorkOrderMapper.getCountByWorkOrderCode(flowWorkOrder.getWorkOrderCode()) == 0) { + throw e; + } + log.info("WorkOrderCode [{}] exists and recalculate.", flowWorkOrder.getWorkOrderCode()); + // 如存在该工单编码的数据,则可以理解为负责计算工单编码的 Redis 出现了问题, + // 需要为该工单字段所关联的 Redis 原子计数器重新设置初始值。 + this.recalculateWorkOrderCode(instance.getProcessDefinitionKey()); + // 重新初始化后,再次执行generateWorkOrderCode方法计算出新的工单编码。 + flowWorkOrder.setWorkOrderCode(this.generateWorkOrderCode(instance.getProcessDefinitionKey())); + // 并再次提交当前的工单数据。 + flowWorkOrderMapper.insert(flowWorkOrder); + } + return flowWorkOrder; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public FlowWorkOrder saveNewWithDraft( + ProcessInstance instance, Long onlineTableId, String tableName, String masterData, String slaveData) { + FlowWorkOrder flowWorkOrder = this.createWith(instance); + flowWorkOrder.setWorkOrderCode(this.generateWorkOrderCode(instance.getProcessDefinitionKey())); + flowWorkOrder.setOnlineTableId(onlineTableId); + flowWorkOrder.setTableName(tableName); + flowWorkOrder.setFlowStatus(FlowTaskStatus.DRAFT); + JSONObject draftData = new JSONObject(); + if (masterData != null) { + draftData.put(FlowConstant.MASTER_DATA_KEY, masterData); + } + if (slaveData != null) { + draftData.put(FlowConstant.SLAVE_DATA_KEY, slaveData); + } + FlowWorkOrderExt flowWorkOrderExt = + BeanUtil.copyProperties(flowWorkOrder, FlowWorkOrderExt.class); + flowWorkOrderExt.setId(idGenerator.nextLongId()); + flowWorkOrderExt.setDraftData(JSON.toJSONString(draftData)); + flowWorkOrderExtMapper.insert(flowWorkOrderExt); + flowWorkOrderMapper.insert(flowWorkOrder); + return flowWorkOrder; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void updateDraft(Long workOrderId, String masterData, String slaveData) { + JSONObject draftData = new JSONObject(); + if (masterData != null) { + draftData.put(FlowConstant.MASTER_DATA_KEY, masterData); + } + if (slaveData != null) { + draftData.put(FlowConstant.SLAVE_DATA_KEY, slaveData); + } + FlowWorkOrderExt flowWorkOrderExt = new FlowWorkOrderExt(); + flowWorkOrderExt.setDraftData(JSON.toJSONString(draftData)); + flowWorkOrderExt.setUpdateTime(new Date()); + flowWorkOrderExtMapper.update(flowWorkOrderExt, + new LambdaQueryWrapper().eq(FlowWorkOrderExt::getWorkOrderId, workOrderId)); + } + + /** + * 删除指定数据。 + * + * @param workOrderId 主键Id。 + * @return 成功返回true,否则false。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public boolean remove(Long workOrderId) { + return flowWorkOrderMapper.deleteById(workOrderId) == 1; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void removeByProcessInstanceId(String processInstanceId) { + FlowWorkOrder filter = new FlowWorkOrder(); + filter.setProcessInstanceId(processInstanceId); + super.removeBy(filter); + } + + @Override + public List getFlowWorkOrderList(FlowWorkOrder filter, String orderBy) { + if (filter == null) { + filter = new FlowWorkOrder(); + } + TokenData tokenData = TokenData.takeFromRequest(); + filter.setTenantId(tokenData.getTenantId()); + filter.setAppCode(tokenData.getAppCode()); + return flowWorkOrderMapper.getFlowWorkOrderList(filter, orderBy); + } + + @Override + public List getFlowWorkOrderListWithRelation(FlowWorkOrder filter, String orderBy) { + List resultList = this.getFlowWorkOrderList(filter, orderBy); + this.buildRelationForDataList(resultList, MyRelationParam.dictOnly()); + return resultList; + } + + @Override + public FlowWorkOrder getFlowWorkOrderByProcessInstanceId(String processInstanceId) { + FlowWorkOrder filter = new FlowWorkOrder(); + filter.setProcessInstanceId(processInstanceId); + return flowWorkOrderMapper.selectOne(new QueryWrapper<>(filter)); + } + + @Override + public boolean existByBusinessKey(String tableName, Object businessKey, boolean unfinished) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowWorkOrder::getBusinessKey, businessKey.toString()); + queryWrapper.eq(FlowWorkOrder::getTableName, tableName); + if (unfinished) { + queryWrapper.notIn(FlowWorkOrder::getFlowStatus, + FlowTaskStatus.FINISHED, FlowTaskStatus.CANCELLED, FlowTaskStatus.STOPPED); + } + return flowWorkOrderMapper.selectCount(queryWrapper) > 0; + } + + @Override + public boolean existUnfinished(String processDefinitionKey, Object businessKey) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowWorkOrder::getBusinessKey, businessKey.toString()); + queryWrapper.eq(FlowWorkOrder::getProcessDefinitionKey, processDefinitionKey); + queryWrapper.notIn(FlowWorkOrder::getFlowStatus, + FlowTaskStatus.FINISHED, FlowTaskStatus.CANCELLED, FlowTaskStatus.STOPPED); + return flowWorkOrderMapper.selectCount(queryWrapper) > 0; + } + + @DisableDataFilter + @Transactional(rollbackFor = Exception.class) + @Override + public void updateFlowStatusByProcessInstanceId(String processInstanceId, Integer flowStatus) { + if (flowStatus == null) { + return; + } + FlowWorkOrder flowWorkOrder = new FlowWorkOrder(); + flowWorkOrder.setFlowStatus(flowStatus); + if (FlowTaskStatus.FINISHED != flowStatus) { + flowWorkOrder.setUpdateTime(new Date()); + flowWorkOrder.setUpdateUserId(TokenData.takeFromRequest().getUserId()); + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowWorkOrder::getProcessInstanceId, processInstanceId); + flowWorkOrderMapper.update(flowWorkOrder, queryWrapper); + } + + @DisableDataFilter + @Transactional(rollbackFor = Exception.class) + @Override + public void updateLatestApprovalStatusByProcessInstanceId(String processInstanceId, Integer approvalStatus) { + if (approvalStatus == null) { + return; + } + FlowWorkOrder flowWorkOrder = this.getFlowWorkOrderByProcessInstanceId(processInstanceId); + flowWorkOrder.setLatestApprovalStatus(approvalStatus); + flowWorkOrder.setUpdateTime(new Date()); + flowWorkOrder.setUpdateUserId(TokenData.takeFromRequest().getUserId()); + flowWorkOrderMapper.updateById(flowWorkOrder); + if (flowWorkOrder.getOnlineTableId() != null) { + // 处理在线表单工作流的自定义状态更新。 + flowCustomExtFactory.getOnlineBusinessDataExtHelper().updateFlowStatus(flowWorkOrder); + } else { + // 处理路由表单工作里的自定义状态更新。 + flowCustomExtFactory.getBusinessDataExtHelper().updateFlowStatus(flowWorkOrder); + } + } + + @Override + public boolean hasDataPermOnFlowWorkOrder(String processInstanceId) { + // 开启数据权限,并进行验证。 + boolean originalFlag = GlobalThreadLocal.setDataFilter(true); + long count; + try { + FlowWorkOrder filter = new FlowWorkOrder(); + filter.setProcessInstanceId(processInstanceId); + count = flowWorkOrderMapper.selectCount(new QueryWrapper<>(filter)); + } finally { + // 恢复之前的数据权限标记 + GlobalThreadLocal.setDataFilter(originalFlag); + } + return count > 0; + } + + @Override + public void fillUserShowNameByLoginName(List dataList) { + BaseFlowIdentityExtHelper identityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + Set loginNameSet = dataList.stream() + .map(FlowWorkOrderVo::getSubmitUsername).collect(Collectors.toSet()); + if (CollUtil.isEmpty(loginNameSet)) { + return; + } + Map userNameMap = identityExtHelper.mapUserShowNameByLoginName(loginNameSet); + dataList.forEach(workOrder -> { + if (StrUtil.isNotBlank(workOrder.getSubmitUsername())) { + workOrder.setUserShowName(userNameMap.get(workOrder.getSubmitUsername())); + } + }); + } + + @Override + public FlowWorkOrderExt getFlowWorkOrderExtByWorkOrderId(Long workOrderId) { + return flowWorkOrderExtMapper.selectOne( + new LambdaQueryWrapper().eq(FlowWorkOrderExt::getWorkOrderId, workOrderId)); + } + + @Override + public List getFlowWorkOrderExtByWorkOrderIds(Set workOrderIds) { + return flowWorkOrderExtMapper.selectList( + new LambdaQueryWrapper().in(FlowWorkOrderExt::getWorkOrderId, workOrderIds)); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public CallResult removeDraft(FlowWorkOrder flowWorkOrder) { + CallResult r = flowApiService.stopProcessInstance(flowWorkOrder.getProcessInstanceId(), "撤销草稿", true); + if (!r.isSuccess()) { + return r; + } + flowWorkOrderMapper.deleteById(flowWorkOrder.getWorkOrderId()); + return CallResult.ok(); + } + + private FlowWorkOrder createWith(ProcessInstance instance) { + TokenData tokenData = TokenData.takeFromRequest(); + Date now = new Date(); + FlowWorkOrder flowWorkOrder = new FlowWorkOrder(); + flowWorkOrder.setWorkOrderId(idGenerator.nextLongId()); + + flowWorkOrder.setProcessDefinitionKey(instance != null ? instance.getProcessDefinitionKey() : ""); + flowWorkOrder.setProcessDefinitionName(instance != null ? instance.getProcessDefinitionName() : ""); + flowWorkOrder.setProcessDefinitionId(instance != null ? instance.getProcessDefinitionId() : ""); + flowWorkOrder.setProcessInstanceId(instance != null ? instance.getId() : ""); + + flowWorkOrder.setSubmitUsername(tokenData.getLoginName()); + flowWorkOrder.setDeptId(tokenData.getDeptId()); + flowWorkOrder.setAppCode(tokenData.getAppCode()); + flowWorkOrder.setTenantId(tokenData.getTenantId()); + flowWorkOrder.setCreateUserId(tokenData.getUserId()); + flowWorkOrder.setUpdateUserId(tokenData.getUserId()); + flowWorkOrder.setCreateTime(now); + flowWorkOrder.setUpdateTime(now); + flowWorkOrder.setDeletedFlag(GlobalDeletedFlag.NORMAL); + return flowWorkOrder; + } + + + @Override + public String generateWorkOrderCode(String processDefinitionKey) { + if (StringUtils.isEmpty(processDefinitionKey)) { + return ""; + } + FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(processDefinitionKey); + if (StrUtil.isBlank(flowEntry.getEncodedRule())) { + return null; + } + ColumnEncodedRule rule = JSON.parseObject(flowEntry.getEncodedRule(), ColumnEncodedRule.class); + if (rule.getIdWidth() == null) { + rule.setIdWidth(10); + } + return commonRedisUtil.generateTransId( + rule.getPrefix(), rule.getPrecisionTo(), rule.getMiddle(), rule.getIdWidth()); + } + + private void recalculateWorkOrderCode(String processDefinitionKey) { + FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(processDefinitionKey); + if (StrUtil.isBlank(flowEntry.getEncodedRule())) { + return; + } + // 获取当前流程定义中,为工单编码字段设置的规则配置信息。 + ColumnEncodedRule rule = JSON.parseObject(flowEntry.getEncodedRule(), ColumnEncodedRule.class); + if (rule.getIdWidth() == null) { + rule.setIdWidth(10); + } + // 根据当前规则中的数据,计算出该规则在Redis中AtomicLong对象的键。 + String prefix = commonRedisUtil.calculateTransIdPrefix(rule.getPrefix(), rule.getPrecisionTo(), rule.getMiddle()); + // 根据该键(规则前缀)计算出符合该前缀的工单编码的最大值。 + String maxWorkOrderCode = flowWorkOrderMapper.getMaxWorkOrderCodeByPrefix(prefix + "%"); + // 移除前缀部分,剩余部分即为计数器的最大值。 + String maxValue = StrUtil.removePrefix(maxWorkOrderCode, prefix); + // 用当前的最大值,为该key的AtomicLong对象设置初始值,后面的请求都会在该值上原子性加一了。 + commonRedisUtil.initTransId(prefix, Long.valueOf(maxValue)); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/timer/FlowTaskTimeoutTimer.java b/common/common-flow/src/main/java/apelet/common/flow/timer/FlowTaskTimeoutTimer.java new file mode 100644 index 0000000..4a8a411 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/timer/FlowTaskTimeoutTimer.java @@ -0,0 +1,71 @@ +package apelet.common.flow.timer; + +import apelet.common.flow.model.FlowTaskTimeoutJob; +import apelet.common.flow.service.FlowTaskTimeoutJobService; +import cn.hutool.core.collection.CollUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.TaskService; +import org.flowable.task.api.Task; +import org.flowable.task.api.TaskQuery; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.List; + +/** + * 流程任务超时处理定时器。 + * + * @author guifc + * @date 2023-08-04 + */ +@EnableScheduling +@Component +@Slf4j +public class FlowTaskTimeoutTimer { + + @Autowired + private FlowTaskTimeoutJobService flowTaskTimeoutJobService; + @Autowired + private RedissonClient redissonClient; + @Autowired + private TaskService taskService; + + private RLock distLock; + + @PostConstruct + public void init() { + distLock = redissonClient.getLock("FLOW_TASK_TIMEOUT_TIMEER"); + } + + /** + * 每五分钟执行一次。 + */ + @Scheduled(cron = "0 0/5 * * * ?") + public void execute() { + if (!distLock.tryLock()) { + return; + } + try { + List jobList = flowTaskTimeoutJobService.getExecutableList(); + if (CollUtil.isEmpty(jobList)) { + return; + } + for (FlowTaskTimeoutJob job : jobList) { + TaskQuery query = taskService.createTaskQuery().active().taskId(job.getTaskId()); + Task task = query.singleResult(); + if (task != null) { + flowTaskTimeoutJobService.executeJob(job, task); + } + } + } catch (Exception e) { + log.error("Failed to call FlowTaskTimeoutTimer.execute", e); + } finally { + distLock.unlock(); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/util/BaseBusinessDataExtHelper.java b/common/common-flow/src/main/java/apelet/common/flow/util/BaseBusinessDataExtHelper.java new file mode 100644 index 0000000..f2157bb --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/util/BaseBusinessDataExtHelper.java @@ -0,0 +1,124 @@ +package apelet.common.flow.util; + +import apelet.common.flow.base.service.BaseFlowService; +import apelet.common.flow.model.FlowWorkOrder; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +/** + * 面向路由表单工作流的业务数据扩展帮助实现类。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class BaseBusinessDataExtHelper { + + private Map> serviceMap = MapUtil.newHashMap(); + + /** + * 子类要基于自身所处理的流程定义标识,把子类的this对象,注册到父类的map中。 + * + * @param processDefinitionKey 流程定义标识。 + * @param service 流程服务实现基类。 + */ + public synchronized void doRegister(String processDefinitionKey, BaseFlowService service) { + Assert.isTrue(StrUtil.isNotBlank(processDefinitionKey)); + Assert.notNull(service); + serviceMap.put(processDefinitionKey, service); + } + + /** + * + * 流程结束监听器(FlowFinishedListener) 会在流程结束时调用该方法。 + * + * @param workOrder 工单对象。 + */ + public void triggerSync(FlowWorkOrder workOrder) { + BaseFlowService service = serviceMap.get(workOrder.getProcessDefinitionKey()); + if (service != null) { + try { + service.syncBusinessData(workOrder); + } catch (Exception e) { + String errorMessage = String.format( + "Failed to call syncBusinessData with processDefinitionKey {%s}, businessKey {%s}", + workOrder.getProcessDefinitionKey(), workOrder.getBusinessKey()); + log.error(errorMessage, e); + throw e; + } + } + } + + /** + * 更新主表状态字段的数据。没有业务表的服务实现类,可以自行实现。 + * + * @param workOrder 工单对象。 + */ + public void updateFlowStatus(FlowWorkOrder workOrder) { + BaseFlowService service = serviceMap.get(workOrder.getProcessDefinitionKey()); + if (service != null) { + try { + service.updateFlowStatus(workOrder); + } catch (Exception e) { + String errorMessage = String.format( + "Failed to call updateFlowStatus with processDefinitionKey {%s}, businessKey {%s}", + workOrder.getProcessDefinitionKey(), workOrder.getBusinessKey()); + log.error(errorMessage, e); + throw e; + } + } + } + + /** + * 根据工单对象级联删除业务数据。 + * + * @param workOrder 工单对象。 + */ + public void deleteBusinessData(FlowWorkOrder workOrder) { + BaseFlowService service = serviceMap.get(workOrder.getProcessDefinitionKey()); + if (service != null) { + try { + service.removeByWorkOrder(workOrder); + } catch (Exception e) { + String errorMessage = String.format( + "Failed to call deleteBusinessData with processDefinitionKey {%s}, businessKey {%s}", + workOrder.getProcessDefinitionKey(), workOrder.getBusinessKey()); + log.error(errorMessage, e); + throw e; + } + } + } + + /** + * 获取详细的业务数据,包括主表、一对一、一对多、多对多从表及其字典数据。 + * + * @param processDefinitionKey 流程定义标识。 + * @param processInstanceId 流程实例Id。 + * @param businessKey 业务主表的主键Id。 + * @return JSON格式化后的业务数据。 + */ + public String getBusinessData(String processDefinitionKey, String processInstanceId, String businessKey) { + BaseFlowService service = serviceMap.get(processDefinitionKey); + return service == null ? null : service.getBusinessData(processInstanceId, businessKey); + } + + /** + * 格式化参数中的草稿数据,以用于前端显示。包括主表、一对一、一对多、多对多从表及其字典数据。 + * + * @param processDefinitionKey 流程定义标识。 + * @param processInstanceId 流程实例Id。 + * @param masterData 草稿中的主表数据。 + * @param slaveData 草稿中的全部关联从表数据。 + * @return 格式化后用于前端显示的业务草稿数据。 + */ + public String getNormalizedDraftData( + String processDefinitionKey, String processInstanceId, JSONObject masterData, JSONObject slaveData) { + BaseFlowService service = serviceMap.get(processDefinitionKey); + return service == null ? null : service.getNormalizedDraftData(processInstanceId, masterData, slaveData); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/util/BaseFlowIdentityExtHelper.java b/common/common-flow/src/main/java/apelet/common/flow/util/BaseFlowIdentityExtHelper.java new file mode 100644 index 0000000..4aef401 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/util/BaseFlowIdentityExtHelper.java @@ -0,0 +1,253 @@ +package apelet.common.flow.util; + +import apelet.common.flow.listener.DeptPostLeaderListener; +import apelet.common.flow.listener.UpDeptPostLeaderListener; +import apelet.common.flow.vo.FlowUserInfoVo; +import org.flowable.engine.delegate.TaskListener; + +import java.util.*; + +/** + * 工作流与用户身份相关的自定义扩展接口,需要业务模块自行实现该接口。也可以根据实际需求扩展该接口的方法。 + * 目前支持的主键类型为字符型和长整型,所以这里提供了两套实现接口。可根据实际情况实现其中一套即可。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface BaseFlowIdentityExtHelper { + + /** + * 根据(字符型)部门Id,获取当前用户部门领导所有的部门岗位Id。 + * + * @param deptId 用户所在部门Id。 + * @return 当前用户部门领导所有的部门岗位Id。 + */ + default String getLeaderDeptPostId(String deptId) { + return null; + } + + /** + * 根据(字符型)部门Id,获取当前用户上级部门领导所有的部门岗位Id。 + * + * @param deptId 用户所在部门Id。 + * @return 当前用户上级部门领导所有的部门岗位Id。 + */ + default String getUpLeaderDeptPostId(String deptId) { + return null; + } + + /** + * 获取(字符型)指定部门上级部门的指定岗位集合的DeptPostId集合。 + * + * @param deptId 指定的部门Id。 + * @param postIdSet 指定的岗位Id集合。 + * @return 与该部门Id上级部门关联的岗位Id集合,key对应参数中的postId,value是与key对应的deptPostId。 + */ + default Map getUpDeptPostIdMap(String deptId, Set postIdSet) { + return null; + } + + /** + * 获取(字符型)指定部门的指定岗位集合的DeptPostId集合。 + * + * @param deptId 指定的部门Id。 + * @param postIdSet 指定的岗位Id集合。 + * @return 与部门关联的岗位Id集合,key对应参数中的postId,value是与key对应的deptPostId。 + */ + default Map getDeptPostIdMap(String deptId, Set postIdSet) { + return null; + } + + /** + * 获取(字符型)指定同级部门的指定岗位集合的DeptPostId集合。 + * + * @param deptId 指定的同级部门Id。 + * @param postIdSet 指定的岗位Id集合。 + * @return 与同级部门关联的岗位Id集合,key对应参数中的postId,value是与key对应的deptPostId。 + */ + default Map getSiblingDeptPostIdMap(String deptId, Set postIdSet) { + return null; + } + + /** + * 根据(长整型)部门Id,获取当前用户部门领导所有的部门岗位Id。 + * + * @param deptId 用户所在部门Id。 + * @return 当前用户部门领导所有的部门岗位Id。 + */ + default Long getLeaderDeptPostId(Long deptId) { + return null; + } + + /** + * 根据(长整型)部门Id,获取当前用户上级部门领导所有的部门岗位Id。 + * + * @param deptId 用户所在部门Id。 + * @return 当前用户上级部门领导所有的部门岗位Id。 + */ + default Long getUpLeaderDeptPostId(Long deptId) { + return null; + } + + /** + * 获取(长整型)指定部门的指定岗位集合的DeptPostId集合。 + * + * @param deptId 指定的部门Id。 + * @param postIdSet 指定的岗位Id集合。 + * @return 与部门关联的岗位Id集合,key对应参数中的postId,value是与key对应的deptPostId。 + */ + default Map getDeptPostIdMap(Long deptId, Set postIdSet) { + return null; + } + + /** + * 获取(长整型)指定同级部门的指定岗位集合的DeptPostId集合。 + * + * @param deptId 指定的同级部门Id。 + * @param postIdSet 指定的岗位Id集合。 + * @return 与同级部门关联的岗位Id集合,key对应参数中的postId,value是与key对应的deptPostId。 + */ + default Map getSiblingDeptPostIdMap(Long deptId, Set postIdSet) { + return null; + } + + /** + * 获取(长整型)指定部门上级部门的指定岗位集合的DeptPostId集合。 + * + * @param deptId 指定的部门Id。 + * @param postIdSet 指定的岗位Id集合。 + * @return 与该部门Id上级部门关联的岗位Id集合,key对应参数中的postId,value是与key对应的deptPostId。 + */ + default Map getUpDeptPostIdMap(Long deptId, Set postIdSet) { + return null; + } + + /** + * 根据角色Id集合,查询所属的用户名列表。 + * + * @param roleIdSet 角色Id集合。 + * @return 所属的用户列表。 + */ + default Set getUsernameListByRoleIds(Set roleIdSet) { + return Collections.emptySet(); + } + + /** + * 根据角色Id集合,查询所属的用户对象信息列表。返回的具体数据,用户可自定义。 + * + * @param roleIdSet 角色Id集合。 + * @return 所属的用户对象信息列表。 + */ + default List getUserInfoListByRoleIds(Set roleIdSet) { + return Collections.emptyList(); + } + + /** + * 根据部门Id集合,查询所属的用户名列表。 + * + * @param deptIdSet 部门Id集合。 + * @return 所属的用户列表。 + */ + default Set getUsernameListByDeptIds(Set deptIdSet) { + return Collections.emptySet(); + } + + /** + * 根据部门Id集合,查询所属的用户对象信息列表。返回的具体数据,用户可自定义。 + * + * @param deptIdSet 部门Id集合。 + * @return 所属的用户对象信息列表。 + */ + default List getUserInfoListByDeptIds(Set deptIdSet) { + return Collections.emptyList(); + } + + /** + * 根据岗位Id集合,查询所属的用户名列表。 + * + * @param postIdSet 岗位Id集合。 + * @return 所属的用户列表。 + */ + default Set getUsernameListByPostIds(Set postIdSet) { + return Collections.emptySet(); + } + + /** + * 根据岗位Id集合,查询所属的用户对象信息列表。返回的具体数据,用户可自定义。 + * + * @param postIdSet 岗位Id集合。 + * @return 所属的用户对象信息列表。 + */ + default List getUserInfoListByPostIds(Set postIdSet) { + return Collections.emptyList(); + } + + /** + * 根据部门岗位Id集合,查询所属的用户名列表。 + * + * @param deptPostIdSet 部门岗位Id集合。 + * @return 所属的用户列表。 + */ + default Set getUsernameListByDeptPostIds(Set deptPostIdSet) { + return Collections.emptySet(); + } + + /** + * 根据部门岗位Id集合,查询所属的用户对象信息列表。返回的具体数据,用户可自定义。 + * + * @param deptPostIdSet 部门岗位Id集合。 + * @return 所属的用户对象信息列表。 + */ + default List getUserInfoListByDeptPostIds(Set deptPostIdSet) { + return Collections.emptyList(); + } + + /** + * 根据用户登录名集合,查询所属的用户对象信息列表。返回的具体数据,用户可自定义。 + * + * @param usernameSet 用户登录名集合。 + * @return 用户对象信息列表。 + */ + default List getUserInfoListByUsernameSet(Set usernameSet) { + return Collections.emptyList(); + } + + /** + * 当前服务是否支持数据权限。 + * + * @return true表示支持,否则false。 + */ + default Boolean supprtDataPerm() { + return false; + } + + /** + * 映射用户的登录名到用户的显示名。 + * + * @param loginNameSet 用户登录名集合。 + * @return 用户登录名和显示名的Map,key为登录名,value是显示名。 + */ + default Map mapUserShowNameByLoginName(Set loginNameSet) { + return new HashMap<>(1); + } + + /** + * 获取任务执行人是当前部门领导岗位的任务监听器。 + * 通常会在没有找到领导部门岗位Id的时候,为当前任务指定其他的指派人、候选人或候选组。 + * + * @return 任务监听器。 + */ + default Class getDeptPostLeaderListener() { + return DeptPostLeaderListener.class; + } + + /** + * 获取任务执行人是上级部门领导岗位的任务监听器。 + * 通常会在没有找到领导部门岗位Id的时候,为当前任务指定其他的指派人、候选人或候选组。 + * + * @return 任务监听器。 + */ + default Class getUpDeptPostLeaderListener() { + return UpDeptPostLeaderListener.class; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/util/BaseFlowNotifyExtHelper.java b/common/common-flow/src/main/java/apelet/common/flow/util/BaseFlowNotifyExtHelper.java new file mode 100644 index 0000000..a259b89 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/util/BaseFlowNotifyExtHelper.java @@ -0,0 +1,28 @@ +package apelet.common.flow.util; + +import apelet.common.flow.vo.FlowTaskVo; +import apelet.common.flow.vo.FlowUserInfoVo; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * 流程通知扩展帮助实现类。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class BaseFlowNotifyExtHelper { + + /** + * 处理消息。 + * + * @param notifyType 通知类型,具体值可参考FlowUserTaskExtData中NOTIFY_TYPE开头的常量。 + * @param userInfoList 待通知的用户信息列表。 + */ + public void doNotify(String notifyType, List userInfoList, FlowTaskVo taskInfo) { + userInfoList.forEach(u -> log.info( + "The user [{}] of Task [{}] is notified by [{}].", u.getLoginName(), taskInfo.getTaskKey(), notifyType)); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java b/common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java new file mode 100644 index 0000000..fe7d36a --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java @@ -0,0 +1,51 @@ +package apelet.common.flow.util; + +import apelet.common.flow.base.service.BaseFlowOnlineService; +import apelet.common.flow.model.FlowWorkOrder; +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; + +/** + * 面向在线表单工作流的业务数据扩展帮助实现类。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +public class BaseOnlineBusinessDataExtHelper { + + private BaseFlowOnlineService onlineBusinessService; + + /** + * 设置在线表单的业务处理服务。 + * + * @param onlineBusinessService 在线表单业务处理服务实现类。 + */ + public void setOnlineBusinessService(BaseFlowOnlineService onlineBusinessService) { + this.onlineBusinessService = onlineBusinessService; + } + + /** + * 更新在线表单主表数据的流程状态字段值。 + * + * @param workOrder 工单对象。 + */ + public void updateFlowStatus(FlowWorkOrder workOrder) { + Assert.notNull(workOrder.getOnlineTableId()); + if (this.onlineBusinessService != null && workOrder.getBusinessKey() != null) { + onlineBusinessService.updateFlowStatus(workOrder); + } + } + + /** + * 根据工单对象级联删除业务数据。 + * + * @param workOrder 工单对象。 + */ + public void deleteBusinessData(FlowWorkOrder workOrder) { + Assert.notNull(workOrder.getOnlineTableId()); + if (this.onlineBusinessService != null && workOrder.getBusinessKey() != null) { + onlineBusinessService.deleteBusinessData(workOrder); + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/util/FlowCustomExtFactory.java b/common/common-flow/src/main/java/apelet/common/flow/util/FlowCustomExtFactory.java new file mode 100644 index 0000000..d777214 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/util/FlowCustomExtFactory.java @@ -0,0 +1,78 @@ +package apelet.common.flow.util; + +import org.springframework.stereotype.Component; + +/** + * 工作流自定义扩展工厂类。 + * + * @author guifc + * @date 2023-08-04 + */ +@Component +public class FlowCustomExtFactory { + + private BaseFlowIdentityExtHelper flowIdentityExtHelper; + + private BaseBusinessDataExtHelper businessDataExtHelper = new BaseBusinessDataExtHelper(); + + private BaseOnlineBusinessDataExtHelper onlineBusinessDataExtHelper = new BaseOnlineBusinessDataExtHelper(); + + private BaseFlowNotifyExtHelper flowNotifyExtHelper; + + /** + * 获取业务模块自行实现的用户身份相关的扩展帮助实现类。 + * + * @return 业务模块自行实现的用户身份相关的扩展帮助实现类。 + */ + public BaseFlowIdentityExtHelper getFlowIdentityExtHelper() { + return flowIdentityExtHelper; + } + + /** + * 注册业务模块自行实现的用户身份扩展帮助实现类。 + * + * @param helper 业务模块自行实现的用户身份扩展帮助实现类。 + */ + public void registerFlowIdentityExtHelper(BaseFlowIdentityExtHelper helper) { + this.flowIdentityExtHelper = helper; + } + + /** + * 获取有关业务数据的扩展帮助实现类。 + * + * @return 有关业务数据的扩展帮助实现类。 + */ + public BaseBusinessDataExtHelper getBusinessDataExtHelper() { + return businessDataExtHelper; + } + + /** + * 获取有关在线表单业务数据的扩展帮助实现类。 + * + * @return 有关业务数据的扩展帮助实现类。 + */ + public BaseOnlineBusinessDataExtHelper getOnlineBusinessDataExtHelper() { + return onlineBusinessDataExtHelper; + } + + /** + * 注册流程通知扩展帮助实现类。 + * + * @param helper 流程通知扩展帮助实现类。 + */ + public void registerNotifyExtHelper(BaseFlowNotifyExtHelper helper) { + this.flowNotifyExtHelper = helper; + } + + /** + * 获取流程通知扩展帮助实现类。 + * + * @return 流程消息通知扩展帮助实现类。 + */ + public BaseFlowNotifyExtHelper getFlowNotifyExtHelper() { + if (this.flowNotifyExtHelper == null) { + this.flowNotifyExtHelper = new BaseFlowNotifyExtHelper(); + } + return flowNotifyExtHelper; + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/util/FlowOperationHelper.java b/common/common-flow/src/main/java/apelet/common/flow/util/FlowOperationHelper.java new file mode 100644 index 0000000..e582570 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/util/FlowOperationHelper.java @@ -0,0 +1,430 @@ +package apelet.common.flow.util; + +import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.object.CallResult; +import apelet.common.core.object.ResponseResult; +import apelet.common.core.object.TokenData; +import apelet.common.core.util.MyModelUtil; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.constant.FlowTaskStatus; +import apelet.common.flow.dto.FlowTaskCommentDto; +import apelet.common.flow.dto.FlowWorkOrderDto; +import apelet.common.flow.model.FlowEntry; +import apelet.common.flow.model.FlowEntryPublish; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.flow.model.constant.FlowEntryStatus; +import apelet.common.flow.object.FlowEntryExtensionData; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowEntryService; +import apelet.common.flow.service.FlowWorkOrderService; +import apelet.common.flow.vo.FlowWorkOrderVo; +import apelet.common.flow.vo.TaskInfoVo; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.task.api.TaskInfo; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 工作流操作的通用帮助对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +@Component +public class FlowOperationHelper { + + @Autowired + private FlowEntryService flowEntryService; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowWorkOrderService flowWorkOrderService; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + + /** + * 验证并获取流程对象。 + * + * @param processDefinitionKey 流程引擎的流程定义标识。 + * @return 流程对象。 + */ + public ResponseResult verifyAndGetFlowEntry(String processDefinitionKey) { + String errorMessage; + FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(processDefinitionKey); + if (flowEntry == null) { + errorMessage = "数据验证失败,该流程并不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + if (!flowEntry.getStatus().equals(FlowEntryStatus.PUBLISHED)) { + errorMessage = "数据验证失败,该流程尚未发布,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowEntryPublish flowEntryPublish = + flowEntryService.getFlowEntryPublishFromCache(flowEntry.getMainEntryPublishId()); + flowEntry.setMainFlowEntryPublish(flowEntryPublish); + return ResponseResult.success(flowEntry); + } + + /** + * 工作流静态表单的参数验证工具方法。根据流程定义标识,获取关联的流程并对其进行合法性验证。 + * + * @param processDefinitionKey 流程定义标识。 + * @return 返回流程对象。 + */ + public ResponseResult verifyFullAndGetFlowEntry(String processDefinitionKey) { + String errorMessage; + // 验证流程管理数据状态的合法性。 + ResponseResult flowEntryResult = this.verifyAndGetFlowEntry(processDefinitionKey); + if (!flowEntryResult.isSuccess()) { + return ResponseResult.errorFrom(flowEntryResult); + } + // 验证流程一个用户任务的合法性。 + FlowEntryPublish flowEntryPublish = flowEntryResult.getData().getMainFlowEntryPublish(); + if (BooleanUtil.isFalse(flowEntryPublish.getActiveStatus())) { + errorMessage = "数据验证失败,当前流程发布对象已被挂起,不能启动新流程!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + ResponseResult taskInfoResult = + this.verifyAndGetInitialTaskInfo(flowEntryPublish, true); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + return flowEntryResult; + } + + /** + * 工作流静态表单的参数验证工具方法。根据参数验证并获取指定的流程任务对象。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @param flowTaskComment 流程审批对象。 + * @return 验证后的流程任务对象。 + */ + public ResponseResult verifySubmitAndGetTask( + String processInstanceId, String taskId, FlowTaskCommentDto flowTaskComment) { + // 验证流程任务的合法性。 + Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + ResponseResult taskInfoResult = this.verifyAndGetRuntimeTaskInfo(task); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + CallResult assigneeVerifyResult = flowApiService.verifyAssigneeOrCandidateAndClaim(task); + if (!assigneeVerifyResult.isSuccess()) { + return ResponseResult.errorFrom(assigneeVerifyResult); + } + ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId); + if (StrUtil.isBlank(instance.getBusinessKey())) { + return ResponseResult.success(task); + } + String errorMessage; + if (flowTaskComment != null + && StrUtil.equals(flowTaskComment.getApprovalType(), FlowApprovalType.TRANSFER) + && StrUtil.isBlank(flowTaskComment.getDelegateAssignee())) { + errorMessage = "数据验证失败,加签或转办任务指派人不能为空!!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(task); + } + + /** + * 工作流静态表单的参数验证工具方法。根据参数验证并获取指定的历史流程实例对象。 + * 仅当登录用户为任务的分配人时,才能通过验证。 + * + * @param processInstanceId 历史流程实例Id。 + * @param taskId 历史流程任务Id。 + * @return 验证后并返回的历史流程实例对象。 + */ + public ResponseResult verifyAndGetHistoricProcessInstance(String processInstanceId, String taskId) { + String errorMessage; + // 验证流程实例的合法性。 + HistoricProcessInstance instance = flowApiService.getHistoricProcessInstance(processInstanceId); + if (instance == null) { + errorMessage = "数据验证失败,指定的流程实例Id并不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + String loginName = TokenData.takeFromRequest().getLoginName(); + if (StrUtil.isBlank(taskId)) { + if (!StrUtil.equals(loginName, instance.getStartUserId()) + && !flowWorkOrderService.hasDataPermOnFlowWorkOrder(processInstanceId)) { + errorMessage = "数据验证失败,指定历史流程的发起人与当前用户不匹配,或者没有查看该工单详情的数据权限!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + } else { + HistoricTaskInstance taskInstance = flowApiService.getHistoricTaskInstance(processInstanceId, taskId); + if (taskInstance == null) { + errorMessage = "数据验证失败,指定的任务Id并不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!StrUtil.equals(loginName, taskInstance.getAssignee()) + && !flowWorkOrderService.hasDataPermOnFlowWorkOrder(processInstanceId)) { + errorMessage = "数据验证失败,历史任务的指派人与当前用户不匹配,或者没有查看该工单详情的数据权限!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + } + return ResponseResult.success(instance); + } + + /** + * 验证并获取流程的实时任务信息。 + * + * @param task 流程引擎的任务对象。 + * @return 任务信息对象。 + */ + public ResponseResult verifyAndGetRuntimeTaskInfo(Task task) { + String errorMessage; + if (task == null) { + errorMessage = "数据验证失败,指定的任务Id不存在,请刷新后重试!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowApiService.isAssigneeOrCandidate(task)) { + errorMessage = "数据验证失败,当前用户不是指派人也不是候选人之一!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (StrUtil.isBlank(task.getFormKey())) { + errorMessage = "数据验证失败,指定任务的formKey属性不存在,请重新修改流程图!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + TaskInfoVo taskInfo = JSON.parseObject(task.getFormKey(), TaskInfoVo.class); + taskInfo.setTaskKey(task.getTaskDefinitionKey()); + return ResponseResult.success(taskInfo); + } + + /** + * 验证并获取启动任务的对象信息。 + * + * @param flowEntryPublish 流程发布对象。 + * @param checkStarter 是否检查发起用户。 + * @return 第一个可执行的任务信息。 + */ + public ResponseResult verifyAndGetInitialTaskInfo( + FlowEntryPublish flowEntryPublish, boolean checkStarter) { + String errorMessage; + if (StrUtil.isBlank(flowEntryPublish.getInitTaskInfo())) { + errorMessage = "数据验证失败,当前流程发布的数据中,没有包含初始任务信息!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + TaskInfoVo taskInfo = JSON.parseObject(flowEntryPublish.getInitTaskInfo(), TaskInfoVo.class); + if (checkStarter) { + String loginName = TokenData.takeFromRequest().getLoginName(); + if (!StrUtil.equalsAny(taskInfo.getAssignee(), loginName, FlowConstant.START_USER_NAME_VAR)) { + errorMessage = "数据验证失败,该工作流第一个用户任务的指派人并非当前用户,不能执行该操作!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + } + return ResponseResult.success(taskInfo); + } + + /** + * 判断当前用户是否有当前流程实例的数据上传或下载权限。 + * 如果taskId为空,则验证当前用户是否为当前流程实例的发起人,否则判断是否为当前任务的指派人或候选人。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @return 验证结果。 + */ + public ResponseResult verifyUploadOrDownloadPermission(String processInstanceId, String taskId) { + if (flowApiService.isProcessInstanceStarter(processInstanceId)) { + return ResponseResult.success(); + } + String errorMessage; + if (StrUtil.isBlank(taskId)) { + errorMessage = "数据验证失败,当前用户没有权限下载!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + TaskInfo task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + if (task == null) { + task = flowApiService.getHistoricTaskInstance(processInstanceId, taskId); + if (task == null) { + errorMessage = "数据验证失败,指定任务Id不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + } + if (!flowApiService.isAssigneeOrCandidate(task)) { + errorMessage = "数据验证失败,当前用户并非指派人或候选人,因此没有权限下载!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + return ResponseResult.success(); + } + + /** + * 根据已有的过滤对象,补充添加缺省过滤条件。如流程标识、创建用户等。 + * + * @param filterDto 工单过滤对象。 + * @param processDefinitionKey 流程标识。 + * @return 创建并转换后的流程工单过滤对象。 + */ + public FlowWorkOrder makeWorkOrderFilter(FlowWorkOrderDto filterDto, String processDefinitionKey) { + FlowWorkOrder filter = MyModelUtil.copyTo(filterDto, FlowWorkOrder.class); + if (filter == null) { + filter = new FlowWorkOrder(); + } + filter.setProcessDefinitionKey(processDefinitionKey); + // 下面的方法会帮助构建工单的数据权限过滤条件,和业务希望相比,如果当前系统没有支持数据权限, + // 用户则只能看到自己发起的工单,否则按照数据权限过滤。然而需要特殊处理的是,如果用户的数据 + // 权限中,没有包含能看自己,这里也需要自动给加上。 + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + if (BooleanUtil.isFalse(flowIdentityExtHelper.supprtDataPerm())) { + filter.setCreateUserId(TokenData.takeFromRequest().getUserId()); + } + return filter; + } + + /** + * 组装工作流工单列表中的流程任务数据。 + * + * @param flowWorkOrderVoList 工作流工单列表。 + */ + public void buildWorkOrderTaskInfo(List flowWorkOrderVoList) { + if (CollUtil.isEmpty(flowWorkOrderVoList)) { + return; + } + Set definitionIdSet = + flowWorkOrderVoList.stream().map(FlowWorkOrderVo::getProcessDefinitionId).collect(Collectors.toSet()); + List flowEntryPublishList = flowEntryService.getFlowEntryPublishList(definitionIdSet); + Map flowEntryPublishMap = + flowEntryPublishList.stream().collect(Collectors.toMap(FlowEntryPublish::getProcessDefinitionId, c -> c)); + for (FlowWorkOrderVo flowWorkOrderVo : flowWorkOrderVoList) { + FlowEntryPublish flowEntryPublish = flowEntryPublishMap.get(flowWorkOrderVo.getProcessDefinitionId()); + flowWorkOrderVo.setInitTaskInfo(flowEntryPublish.getInitTaskInfo()); + } + List unfinishedProcessInstanceIds = flowWorkOrderVoList.stream() + .filter(c -> !c.getFlowStatus().equals(FlowTaskStatus.FINISHED)) + .map(FlowWorkOrderVo::getProcessInstanceId) + .collect(Collectors.toList()); + if (CollUtil.isEmpty(unfinishedProcessInstanceIds)) { + return; + } + List taskList = flowApiService.getTaskListByProcessInstanceIds(unfinishedProcessInstanceIds); + Map> taskMap = + taskList.stream().collect(Collectors.groupingBy(Task::getProcessInstanceId)); + for (FlowWorkOrderVo flowWorkOrderVo : flowWorkOrderVoList) { + List instanceTaskList = taskMap.get(flowWorkOrderVo.getProcessInstanceId()); + if (instanceTaskList == null) { + continue; + } + JSONArray taskArray = new JSONArray(); + for (Task task : instanceTaskList) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("taskId", task.getId()); + jsonObject.put("taskName", task.getName()); + jsonObject.put("taskKey", task.getTaskDefinitionKey()); + jsonObject.put("assignee", task.getAssignee()); + taskArray.add(jsonObject); + } + flowWorkOrderVo.setRuntimeTaskInfoList(taskArray); + } + } + + /** + * 组装工作流工单中的业务数据。 + * + * @param workOrderVoList 工单列表。 + * @param dataList 业务数据列表。 + * @param idGetter 获取业务对象主键字段的返回方法。 + * @param 业务主对象类型。 + * @param 业务主对象的主键字段类型。 + */ + public void buildWorkOrderBusinessData( + List workOrderVoList, List dataList, Function idGetter) { + if (CollUtil.isEmpty(dataList)) { + return; + } + Map dataMap = dataList.stream().collect(Collectors.toMap(idGetter, c -> c)); + K id = idGetter.apply(dataList.get(0)); + for (FlowWorkOrderVo flowWorkOrderVo : workOrderVoList) { + if (StrUtil.isBlank(flowWorkOrderVo.getBusinessKey())) { + continue; + } + Object dataId = flowWorkOrderVo.getBusinessKey(); + if (id instanceof Long) { + dataId = Long.valueOf(flowWorkOrderVo.getBusinessKey()); + } else if (id instanceof Integer) { + dataId = Integer.valueOf(flowWorkOrderVo.getBusinessKey()); + } + T data = dataMap.get(dataId); + if (data != null) { + flowWorkOrderVo.setMasterData(BeanUtil.beanToMap(data)); + } + } + } + + /** + * 验证并根据流程实例Id获取处于草稿状态的流程工单。 + * + * @param processDefinitionKey 流程定义标识。 + * @param processInstanceId 流程实例Id。 + * @return 流程工单。 + */ + public ResponseResult verifyAndGetFlowWorkOrderWithDraft( + String processDefinitionKey, String processInstanceId) { + String errorMessage; + FlowWorkOrder flowWorkOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId); + if (flowWorkOrder == null) { + errorMessage = "数据验证失败,流程实例关联的工单不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT)) { + errorMessage = "数据验证失败,当前流程工单并不处于草稿保存状态!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowWorkOrder.getCreateUserId().equals(TokenData.takeFromRequest().getUserId())) { + errorMessage = "数据验证失败,草稿数据保存用户与当前用户不一致!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (processDefinitionKey != null && !flowWorkOrder.getProcessDefinitionKey().equals(processDefinitionKey)) { + errorMessage = "数据验证失败,流程实例和流程定义标识不匹配!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(flowWorkOrder); + } + + /** + * 根据流程定义的扩展数据中的审批状态字典列表数据,组装工单列表中,每个工单对象的审批状态字典数据。 + * @param processDefinitionKey 流程定义标识。 + * @param workOrderVoList 待组装的工单列表。 + */ + public void buildWorkOrderApprovalStatus(String processDefinitionKey, List workOrderVoList) { + FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(processDefinitionKey); + if (StrUtil.isBlank(flowEntry.getExtensionData())) { + return; + } + FlowEntryExtensionData extensionData = + JSON.parseObject(flowEntry.getExtensionData(), FlowEntryExtensionData.class); + if (CollUtil.isEmpty(extensionData.getApprovalStatusDict())) { + return; + } + Map dictMap = new HashMap<>(extensionData.getApprovalStatusDict().size()); + for (Map m : extensionData.getApprovalStatusDict()) { + dictMap.put(Integer.valueOf(m.get("id")), m.get("name")); + } + for (FlowWorkOrderVo workOrderVo : workOrderVoList) { + if (workOrderVo.getLatestApprovalStatus() != null) { + String name = dictMap.get(workOrderVo.getLatestApprovalStatus()); + if (name != null) { + Map lastestApprovalStatusDictMap = MapUtil.newHashMap(); + lastestApprovalStatusDictMap.put("id", workOrderVo.getLatestApprovalStatus()); + lastestApprovalStatusDictMap.put("name", name); + workOrderVo.setLatestApprovalStatusDictMap(lastestApprovalStatusDictMap); + } + } + } + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/util/FlowRedisKeyUtil.java b/common/common-flow/src/main/java/apelet/common/flow/util/FlowRedisKeyUtil.java new file mode 100644 index 0000000..af752a8 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/util/FlowRedisKeyUtil.java @@ -0,0 +1,52 @@ +package apelet.common.flow.util; + +import apelet.common.core.object.TokenData; +import cn.hutool.core.util.StrUtil; + +/** + * 工作流 Redis 键生成工具类。 + * + * @author guifc + * @date 2023-08-04 + */ +public class FlowRedisKeyUtil { + + /** + * 计算流程对象缓存在Redis中的键值。 + * + * @param processDefinitionKey 流程标识。 + * @return 流程对象缓存在Redis中的键值。 + */ + public static String makeFlowEntryKey(String processDefinitionKey) { + String prefix = "FLOW_ENTRY:"; + TokenData tokenData = TokenData.takeFromRequest(); + if (tokenData == null) { + return prefix + processDefinitionKey; + } + String appCode = tokenData.getAppCode(); + if (StrUtil.isBlank(appCode)) { + Long tenantId = tokenData.getTenantId(); + if (tenantId == null) { + return prefix + processDefinitionKey; + } + return prefix + tenantId.toString() + ":" + processDefinitionKey; + } + return prefix + appCode + ":" + processDefinitionKey; + } + + /** + * 流程发布对象缓存在Redis中的键值。 + * + * @param flowEntryPublishId 流程发布主键Id。 + * @return 流程发布对象缓存在Redis中的键值。 + */ + public static String makeFlowEntryPublishKey(Long flowEntryPublishId) { + return "FLOW_ENTRY_PUBLISH:" + flowEntryPublishId; + } + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private FlowRedisKeyUtil() { + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowCategoryVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowCategoryVo.java new file mode 100644 index 0000000..0137579 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowCategoryVo.java @@ -0,0 +1,71 @@ +package apelet.common.flow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 流程分类的Vo对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程分类的Vo对象") +@Data +public class FlowCategoryVo { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + private Long categoryId; + + /** + * 应用编码。为空时,表示非第三方应用接入。 + */ + @Schema(description = "应用编码") + private String appCode; + + /** + * 显示名称。 + */ + @Schema(description = "显示名称") + private String name; + + /** + * 分类编码。 + */ + @Schema(description = "分类编码") + private String code; + + /** + * 实现顺序。 + */ + @Schema(description = "实现顺序") + private Integer showOrder; + + /** + * 更新时间。 + */ + @Schema(description = "更新时间") + private Date updateTime; + + /** + * 更新者Id。 + */ + @Schema(description = "更新者Id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @Schema(description = "创建时间") + private Date createTime; + + /** + * 创建者Id。 + */ + @Schema(description = "创建者Id") + private Long createUserId; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryPublishVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryPublishVo.java new file mode 100644 index 0000000..0b22c17 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryPublishVo.java @@ -0,0 +1,59 @@ +package apelet.common.flow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 流程发布信息的Vo对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程发布信息的Vo对象") +@Data +public class FlowEntryPublishVo { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + private Long entryPublishId; + + /** + * 发布版本。 + */ + @Schema(description = "发布版本") + private Integer publishVersion; + + /** + * 流程引擎中的流程定义Id。 + */ + @Schema(description = "流程引擎中的流程定义Id") + private String processDefinitionId; + + /** + * 激活状态。 + */ + @Schema(description = "激活状态") + private Boolean activeStatus; + + /** + * 是否为主版本。 + */ + @Schema(description = "是否为主版本") + private Boolean mainVersion; + + /** + * 创建者Id。 + */ + @Schema(description = "创建者Id") + private Long createUserId; + + /** + * 发布时间。 + */ + @Schema(description = "发布时间") + private Date publishTime; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryVariableVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryVariableVo.java new file mode 100644 index 0000000..3e7632d --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryVariableVo.java @@ -0,0 +1,77 @@ +package apelet.common.flow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 流程变量Vo对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程变量Vo对象") +@Data +public class FlowEntryVariableVo { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + private Long variableId; + + /** + * 流程Id。 + */ + @Schema(description = "流程Id") + private Long entryId; + + /** + * 变量名。 + */ + @Schema(description = "变量名") + private String variableName; + + /** + * 显示名。 + */ + @Schema(description = "显示名") + private String showName; + + /** + * 变量类型。 + */ + @Schema(description = "变量类型") + private Integer variableType; + + /** + * 绑定数据源Id。 + */ + @Schema(description = "绑定数据源Id") + private Long bindDatasourceId; + + /** + * 绑定数据源关联Id。 + */ + @Schema(description = "绑定数据源关联Id") + private Long bindRelationId; + + /** + * 绑定字段Id。 + */ + @Schema(description = "绑定字段Id") + private Long bindColumnId; + + /** + * 是否内置。 + */ + @Schema(description = "是否内置") + private Boolean builtin; + + /** + * 创建时间。 + */ + @Schema(description = "创建时间") + private Date createTime; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryVo.java new file mode 100644 index 0000000..3accc1c --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowEntryVo.java @@ -0,0 +1,163 @@ +package apelet.common.flow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 流程的Vo对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程的Vo对象") +@Data +public class FlowEntryVo { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + private Long entryId; + + /** + * 应用编码。为空时,表示非第三方应用接入。 + */ + @Schema(description = "应用编码") + private String appCode; + + /** + * 流程名称。 + */ + @Schema(description = "流程名称") + private String processDefinitionName; + + /** + * 流程标识Key。 + */ + @Schema(description = "流程标识Key") + private String processDefinitionKey; + + /** + * 流程分类。 + */ + @Schema(description = "流程分类") + private Long categoryId; + + /** + * 工作流部署的发布主版本Id。 + */ + @Schema(description = "工作流部署的发布主版本Id") + private Long mainEntryPublishId; + + /** + * 最新发布时间。 + */ + @Schema(description = "最新发布时间") + private Date latestPublishTime; + + /** + * 流程状态。 + */ + @Schema(description = "流程状态") + private Integer status; + + /** + * 流程定义的xml。 + */ + @Schema(description = "流程定义的xml") + private String bpmnXml; + + /** + * 流程图类型。0: 普通流程图,1: 钉钉风格的流程图。 + */ + @Schema(description = "流程图类型。0: 普通流程图,1: 钉钉风格的流程图。") + private Integer diagramType; + + /** + * 绑定表单类型。 + */ + @Schema(description = "绑定表单类型") + private Integer bindFormType; + + /** + * 在线表单的页面Id。 + */ + @Schema(description = "在线表单的页面Id") + private Long pageId; + + /** + * 在线表单Id。 + */ + @Schema(description = "在线表单Id") + private Long defaultFormId; + + /** + * 在线表单的缺省路由名称。 + */ + @Schema(description = "在线表单的缺省路由名称") + private String defaultRouterName; + + /** + * 工单表编码字段的编码规则,如果为空则不计算工单编码。 + */ + @Schema(description = "工单表编码字段的编码规则") + private String encodedRule; + + /** + * 流程的自定义扩展数据(JSON格式)。 + */ + @Schema(description = "流程的自定义扩展数据") + private String extensionData; + + /** + * 更新时间。 + */ + @Schema(description = "更新时间") + private Date updateTime; + + /** + * 更新者Id。 + */ + @Schema(description = "更新者Id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @Schema(description = "创建时间") + private Date createTime; + + /** + * 创建者Id。 + */ + @Schema(description = "创建者Id") + private Long createUserId; + + /** + * categoryId 的一对一关联数据对象,数据对应类型为FlowCategoryVo。 + */ + @Schema(description = "categoryId 的一对一关联数据对象") + private Map flowCategory; + + /** + * mainEntryPublishId 的一对一关联数据对象,数据对应类型为FlowEntryPublishVo。 + */ + @Schema(description = "mainEntryPublishId 的一对一关联数据对象") + private Map mainFlowEntryPublish; + + /** + * 关联的在线表单列表。 + */ + @Schema(description = "关联的在线表单列表") + private List> formList; + + /** + * 流程启动条件 + */ + @Schema(description = "流程启动条件") + private String startConditions; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowMessageVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowMessageVo.java new file mode 100644 index 0000000..9afaecc --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowMessageVo.java @@ -0,0 +1,137 @@ +package apelet.common.flow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 工作流通知消息Vo对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "工作流通知消息Vo对象") +@Data +public class FlowMessageVo { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + private Long messageId; + + /** + * 消息类型。 + */ + @Schema(description = "消息类型") + private Integer messageType; + + /** + * 消息内容。 + */ + @Schema(description = "消息内容") + private String messageContent; + + /** + * 催办次数。 + */ + @Schema(description = "催办次数") + private Integer remindCount; + + /** + * 工单Id。 + */ + @Schema(description = "工单Id") + private Long workOrderId; + + /** + * 流程定义Id。 + */ + @Schema(description = "流程定义Id") + private String processDefinitionId; + + /** + * 流程定义标识。 + */ + @Schema(description = "流程定义标识") + private String processDefinitionKey; + + /** + * 流程名称。 + */ + @Schema(description = "流程名称") + private String processDefinitionName; + + /** + * 流程实例Id。 + */ + @Schema(description = "流程实例Id") + private String processInstanceId; + + /** + * 流程实例发起者。 + */ + @Schema(description = "流程实例发起者") + private String processInstanceInitiator; + + /** + * 流程任务Id。 + */ + @Schema(description = "流程任务Id") + private String taskId; + + /** + * 流程任务定义标识。 + */ + @Schema(description = "流程任务定义标识") + private String taskDefinitionKey; + + /** + * 流程任务名称。 + */ + @Schema(description = "流程任务名称") + private String taskName; + + /** + * 创建时间。 + */ + @Schema(description = "创建时间") + private Date taskStartTime; + + /** + * 业务数据快照。 + */ + @Schema(description = "业务数据快照") + private String businessDataShot; + + /** + * 更新时间。 + */ + @Schema(description = "更新时间") + private Date updateTime; + + /** + * 更新者Id。 + */ + @Schema(description = "更新者Id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @Schema(description = "创建时间") + private Date createTime; + + /** + * 创建者Id。 + */ + @Schema(description = "创建者Id") + private Long createUserId; + + /** + * 创建者显示名。 + */ + @Schema(description = "创建者显示名") + private String createUsername; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskCommentVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskCommentVo.java new file mode 100644 index 0000000..599519e --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskCommentVo.java @@ -0,0 +1,113 @@ +package apelet.common.flow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * FlowTaskCommentVO对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "FlowTaskCommentVO对象") +@Data +public class FlowTaskCommentVo { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + private Long id; + + /** + * 流程实例Id。 + */ + @Schema(description = "流程实例Id") + private String processInstanceId; + + /** + * 任务Id。 + */ + @Schema(description = "任务Id") + private String taskId; + + /** + * 任务标识。 + */ + @Schema(description = "任务标识") + private String taskKey; + + /** + * 任务名称。 + */ + @Schema(description = "任务名称") + private String taskName; + + /** + * 任务的执行Id。 + */ + @Schema(description = "任务的执行Id") + private String executionId; + + /** + * 会签任务的执行Id。 + */ + @Schema(description = "会签任务的执行Id") + private String multiInstanceExecId; + + /** + * 审批类型。 + */ + @Schema(description = "审批类型") + private String approvalType; + + /** + * 批注内容。 + */ + @Schema(description = "批注内容") + private String taskComment; + + /** + * 委托指定人,比如加签、转办等。 + */ + @Schema(description = "委托指定人,比如加签、转办等") + private String delegateAssignee; + + /** + * 自定义数据。开发者可自行扩展,推荐使用JSON格式数据。 + */ + @Schema(description = "自定义数据") + private String customBusinessData; + + /** + * 审批人头像。 + */ + @Schema(description = "审批人头像") + private String headImageUrl; + + /** + * 创建者Id。 + */ + @Schema(description = "创建者Id") + private Long createUserId; + + /** + * 创建者登录名。 + */ + @Schema(description = "创建者登录名") + private String createLoginName; + + /** + * 创建者显示名。 + */ + @Schema(description = "创建者显示名") + private String createUsername; + + /** + * 创建时间。 + */ + @Schema(description = "创建时间") + private Date createTime; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java new file mode 100644 index 0000000..b17cece --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java @@ -0,0 +1,125 @@ +package apelet.common.flow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 流程任务Vo对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程任务Vo对象") +@Data +public class FlowTaskVo { + + /** + * 流程任务Id。 + */ + @Schema(description = "流程任务Id") + private String taskId; + + /** + * 流程任务名称。 + */ + @Schema(description = "流程任务名称") + private String taskName; + + /** + * 流程任务标识。 + */ + @Schema(description = "流程任务标识") + private String taskKey; + + /** + * 任务的表单信息。 + */ + @Schema(description = "任务的表单信息") + private String taskFormKey; + + /** + * 待办任务开始时间。 + */ + @Schema(description = "待办任务开始时间") + private Date taskStartTime; + + /** + * 流程Id。 + */ + @Schema(description = "流程Id") + private Long entryId; + + /** + * 流程定义Id。 + */ + @Schema(description = "流程定义Id") + private String processDefinitionId; + + /** + * 流程定义名称。 + */ + @Schema(description = "流程定义名称") + private String processDefinitionName; + + /** + * 流程定义标识。 + */ + @Schema(description = "流程定义标识") + private String processDefinitionKey; + + /** + * 流程定义版本。 + */ + @Schema(description = "流程定义版本") + private Integer processDefinitionVersion; + + /** + * 流程实例Id。 + */ + @Schema(description = "流程实例Id") + private String processInstanceId; + + /** + * 流程实例发起人。 + */ + @Schema(description = "流程实例发起人") + private String processInstanceInitiator; + + /** + * 流程实例发起人显示名。 + */ + @Schema(description = "流程实例发起人显示名") + private String showName; + + /** + * 用户头像信息。 + */ + @Schema(description = "用户头像信息") + private String headImageUrl; + + /** + * 流程实例创建时间。 + */ + @Schema(description = "流程实例创建时间") + private Date processInstanceStartTime; + + /** + * 流程实例主表业务数据主键。 + */ + @Schema(description = "流程实例主表业务数据主键") + private String businessKey; + + /** + * 工单编码。 + */ + @Schema(description = "工单编码") + private String workOrderCode; + + /** + * 是否为草稿状态。 + */ + @Schema(description = "是否为草稿状态") + private Boolean isDraft; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowUserInfoVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowUserInfoVo.java new file mode 100644 index 0000000..7f9d680 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowUserInfoVo.java @@ -0,0 +1,77 @@ +package apelet.common.flow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 流程任务的用户信息。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程任务的用户信息") +@Data +public class FlowUserInfoVo { + + /** + * 用户Id。 + */ + @Schema(description = "用户Id") + private Long userId; + + /** + * 登录用户名。 + */ + @Schema(description = "登录用户名") + private String loginName; + + /** + * 用户部门Id。 + */ + @Schema(description = "用户部门Id") + private Long deptId; + + /** + * 用户显示名称。 + */ + @Schema(description = "用户显示名称") + private String showName; + + /** + * 用户类型(0: 管理员 1: 系统管理用户 2: 系统业务用户)。 + */ + @Schema(description = "用户类型(0: 管理员 1: 系统管理用户 2: 系统业务用户)") + private Integer userType; + + /** + * 用户头像的Url。 + */ + @Schema(description = "用户头像的Url") + private String headImageUrl; + + /** + * 用户状态(0: 正常 1: 锁定)。 + */ + @Schema(description = "用户状态(0: 正常 1: 锁定)") + private Integer userStatus; + + /** + * 最后审批时间。 + */ + @Schema(description = "最后审批时间") + private Date lastApprovalTime; + + /** + * 用户邮箱。 + */ + @Schema(description = "用户邮箱") + private String email; + + /** + * 用户手机。 + */ + @Schema(description = "用户手机") + private String mobile; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowWorkOrderVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowWorkOrderVo.java new file mode 100644 index 0000000..522df34 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowWorkOrderVo.java @@ -0,0 +1,158 @@ +package apelet.common.flow.vo; + +import com.alibaba.fastjson.JSONArray; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; +import java.util.Map; + +/** + * 工作流工单VO对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "工作流工单Vo对象") +@Data +public class FlowWorkOrderVo { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + private Long workOrderId; + + /** + * 应用编码。为空时,表示非第三方应用接入。 + */ + @Schema(description = "应用编码") + private String appCode; + + /** + * 工单编码字段。 + */ + @Schema(description = "工单编码字段") + private String workOrderCode; + + /** + * 流程定义标识。 + */ + @Schema(description = "流程定义标识") + private String processDefinitionKey; + + /** + * 流程名称。 + */ + @Schema(description = "流程名称") + private String processDefinitionName; + + /** + * 流程引擎的定义Id。 + */ + @Schema(description = "流程引擎的定义Id") + private String processDefinitionId; + + /** + * 流程实例Id。 + */ + @Schema(description = "流程实例Id") + private String processInstanceId; + + /** + * 在线表单的主表Id。 + */ + @Schema(description = "在线表单的主表Id") + private Long onlineTableId; + + /** + * 业务主键值。 + */ + @Schema(description = "业务主键值") + private String businessKey; + + /** + * 最近的审批状态。 + */ + @Schema(description = "最近的审批状态") + private Integer latestApprovalStatus; + + /** + * 流程状态。参考FlowTaskStatus常量值对象。 + */ + @Schema(description = "流程状态") + private Integer flowStatus; + + /** + * 提交用户登录名称。 + */ + @Schema(description = "提交用户登录名称") + private String submitUsername; + + /** + * 提交用户所在部门Id。 + */ + @Schema(description = "提交用户所在部门Id") + private Long deptId; + + /** + * 更新时间。 + */ + @Schema(description = "更新时间") + private Date updateTime; + + /** + * 更新者Id。 + */ + @Schema(description = "更新者Id") + private Long updateUserId; + + /** + * 创建时间。 + */ + @Schema(description = "创建时间") + private Date createTime; + + /** + * 创建者Id。 + */ + @Schema(description = "创建者Id") + private Long createUserId; + + /** + * latestApprovalStatus 关联的字典数据。 + */ + @Schema(description = "latestApprovalStatus 常量字典关联数据") + private Map latestApprovalStatusDictMap; + + /** + * flowStatus 常量字典关联数据。 + */ + @Schema(description = "flowStatus 常量字典关联数据") + private Map flowStatusDictMap; + + /** + * 用户的显示名。 + */ + @Schema(description = "用户的显示名") + private String userShowName; + + /** + * FlowEntryPublish对象中的同名字段。 + */ + @Schema(description = "FlowEntryPublish对象中的同名字段") + private String initTaskInfo; + + /** + * 当前实例的运行时任务列表。 + * 正常情况下只有一个,在并行网关下可能存在多个。 + */ + @Schema(description = "实例的运行时任务列表") + private JSONArray runtimeTaskInfoList; + + /** + * 业务主表数据。 + */ + @Schema(description = "业务主表数据") + private Map masterData; +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/TaskInfoVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/TaskInfoVo.java new file mode 100644 index 0000000..e495e83 --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/TaskInfoVo.java @@ -0,0 +1,79 @@ +package apelet.common.flow.vo; + +import com.alibaba.fastjson.JSONObject; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 流程任务信息Vo对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Schema(description = "流程任务信息Vo对象") +@Data +public class TaskInfoVo { + + /** + * 流程节点任务类型。具体值可参考FlowTaskType常量值。 + */ + @Schema(description = "流程节点任务类型") + private Integer taskType; + + /** + * 指定人。 + */ + @Schema(description = "指定人") + private String assignee; + + /** + * 任务标识。 + */ + @Schema(description = "任务标识") + private String taskKey; + + /** + * 是否分配给当前登录用户的标记。 + * 当该值为true时,登录用户启动流程时,就自动完成了第一个用户任务。 + */ + @Schema(description = "是否分配给当前登录用户的标记") + private Boolean assignedMe; + + /** + * 动态表单Id。 + */ + @Schema(description = "动态表单Id") + private Long formId; + + /** + * 静态表单路由。 + */ + @Schema(description = "静态表单路由") + private String routerName; + + /** + * 候选组类型。 + */ + @Schema(description = "候选组类型") + private String groupType; + + /** + * 只读标记。 + */ + @Schema(description = "只读标记") + private Boolean readOnly; + + /** + * 前端所需的操作列表。 + */ + @Schema(description = "前端所需的操作列表") + List operationList; + + /** + * 任务节点的自定义变量列表。 + */ + @Schema(description = "任务节点的自定义变量列表") + List variableList; +} diff --git a/common/common-flow/src/main/resources/META-INF/services/org.flowable.common.engine.impl.EngineConfigurator b/common/common-flow/src/main/resources/META-INF/services/org.flowable.common.engine.impl.EngineConfigurator new file mode 100644 index 0000000..1725e77 --- /dev/null +++ b/common/common-flow/src/main/resources/META-INF/services/org.flowable.common.engine.impl.EngineConfigurator @@ -0,0 +1 @@ +apelet.common.flow.config.CustomEngineConfigurator \ No newline at end of file diff --git a/common/common-flow/src/main/resources/META-INF/spring.factories b/common/common-flow/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..b5880ea --- /dev/null +++ b/common/common-flow/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +apelet.common.flow.config.FlowAutoConfig \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml index 3f9da2c..1f2d3c0 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -12,7 +12,7 @@ pom - + common-flow common-online common-tenant