diff --git a/.gitignore b/.gitignore index a66b8e2f1..8b86a6454 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ target/ *.iws *.iml *.ipr +WorkflowCoreService.java +WorkflowManageService.java ### NetBeans ### /nbproject/private/ diff --git a/pom.xml b/pom.xml index 130212e4f..f8f3df719 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ workflow-engine - 1.4.1-SNAPSHOT + 1.4.2-SNAPSHOT 2.0.0-SNAPSHOT 2.0.0-SNAPSHOT 11.8 diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/annotation/WorkflowEngineFeignClient.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/annotation/WorkflowEngineFeignClient.java new file mode 100644 index 000000000..f6dc5a700 --- /dev/null +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/annotation/WorkflowEngineFeignClient.java @@ -0,0 +1,22 @@ +package cn.axzo.workflow.client.annotation; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 用于开启原生 FeignClient + * + * @author wangli + * @since 2024-09-10 11:30 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface WorkflowEngineFeignClient { +} diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessActivityApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessActivityApi.java index 0e460def2..7cdfa0b6c 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessActivityApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessActivityApi.java @@ -1,9 +1,11 @@ package cn.axzo.workflow.client.feign.bpmn; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.common.annotation.Manageable; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; import cn.azxo.framework.common.model.CommonResponse; import io.swagger.v3.oas.annotations.Operation; import org.springframework.validation.annotation.Validated; @@ -21,16 +23,27 @@ import javax.validation.constraints.NotBlank; * @since 2023/11/17 16:28 */ //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient public interface ProcessActivityApi { /** - * 业务节点唤醒 + * 业务节点唤醒, 该节点废弃,请换成 {@link ProcessActivityApi#trigger(cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO)} 接口 *

* 当模型中使用了“业务节点”,且设置了“不设置审批人”模式,则当业务监听到 PROCESS_ACTIVITY_START 事件时,可通过该接口推动流程继续运行 */ + @Deprecated @GetMapping("/api/process/activity/trigger") CommonResponse trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId); + /** + * 业务节点唤醒 + * + * @param dto + * @return + */ + @PostMapping("/api/process/activity/trigger") + CommonResponse trigger(@Validated @RequestBody BpmnActivityTriggerDTO dto); + /** * 业务节点设置审批人, 不支持重复设置 diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessDefinitionApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessDefinitionApi.java index 7a04b923a..ccdb2696a 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessDefinitionApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessDefinitionApi.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.client.feign.bpmn; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.client.config.CommonFeignConfiguration; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; @@ -28,6 +29,7 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; * @since 2023/9/21 16:25 */ //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient @Manageable public interface ProcessDefinitionApi { diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java index e2368600f..4b43d1b74 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java @@ -1,6 +1,6 @@ package cn.axzo.workflow.client.feign.bpmn; -import cn.axzo.workflow.client.config.CommonFeignConfiguration; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; @@ -10,18 +10,19 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCar import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; import cn.azxo.framework.common.model.CommonResponse; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.v3.oas.annotations.Operation; -import org.springframework.cloud.openfeign.FeignClient; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -45,6 +46,7 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; * @since 2023/9/21 16:26 */ //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient public interface ProcessInstanceApi { /** * 创建审批流程 @@ -241,4 +243,15 @@ public interface ProcessInstanceApi { @Manageable @InvokeMode(SYNC) CommonResponse checkInstanceApprover(@Validated @RequestBody BpmnProcessInstanceCheckApproverDTO dto); + + /** + * 获取指定流程的日志 + * + * @param dto + * @return + */ + @Operation(summary = "获取指定流程的日志") + @PostMapping("/api/process/instance/logs") + @InvokeMode(SYNC) + CommonResponse getProcessInstanceLogs(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto); } diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessJobApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessJobApi.java index 906bbc1f3..5e6da7692 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessJobApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessJobApi.java @@ -1,16 +1,13 @@ package cn.axzo.workflow.client.feign.bpmn; -import cn.axzo.workflow.client.config.CommonFeignConfiguration; -import cn.axzo.workflow.common.annotation.InvokeMode; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.common.annotation.Manageable; import cn.azxo.framework.common.model.CommonResponse; -import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; -import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; - //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient public interface ProcessJobApi { /** @@ -27,4 +24,22 @@ public interface ProcessJobApi { @Manageable CommonResponse executeDeadLetterJobAction(@RequestParam(required = false) String jobId, @RequestParam(required = false) String procInstId); + + /** + * 查询死信消息数据 + * @param procInstId 流程实例id + * @return + */ + @GetMapping("/dead-letter/exception/stacktrace") + @Manageable + String getDeadLetterJobExceptionStacktrace(@RequestParam String procInstId); + + /** + * 查询死信消息数据 + * @param jobId 死信job的id + * @return + */ + @GetMapping("/dead-letter/exception/stacktrace/byId") + @Manageable + String getDeadLetterJobExceptionStacktraceByJobId(@RequestParam String jobId); } diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessModelApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessModelApi.java index 8fc7f7f00..d06e7c0ea 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessModelApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessModelApi.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.client.feign.bpmn; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.client.config.CommonFeignConfiguration; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; @@ -33,6 +34,7 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; * @since 2023/9/21 15:47 */ //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient @Manageable public interface ProcessModelApi { diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessTaskApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessTaskApi.java index 80d57d980..e311d8c1a 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessTaskApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessTaskApi.java @@ -1,12 +1,14 @@ package cn.axzo.workflow.client.feign.bpmn; -import cn.axzo.workflow.client.config.CommonFeignConfiguration; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskPageSearchDTO; @@ -21,7 +23,6 @@ import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO; import cn.azxo.framework.common.model.CommonResponse; import io.swagger.v3.oas.annotations.Operation; -import org.springframework.cloud.openfeign.FeignClient; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -44,6 +45,7 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; * @since 2023/9/21 16:26 */ //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient public interface ProcessTaskApi { /** @@ -71,6 +73,24 @@ public interface ProcessTaskApi { @PostMapping("/api/process/task/batch/approve") CommonResponse batchApproveTask(@Validated @RequestBody List dtos); + /** + * 获取当前节点可回退节点选项列表 + * @param taskId 当前任务id + * @return 可以回退节点列表 + */ + @Operation(summary = "获取当前节点可回退节点选项列表") + @GetMapping("/api/process/task/back/optional/nodes") + CommonResponse> getBackOptionalNodes(@RequestParam @NotBlank(message = "任务id不能为空") String taskId); + + /** + * 回退到指定节点 + * @param dto + * @return + */ + @Operation(summary = "回退") + @PostMapping("/api/process/task/back") + CommonResponse backTask(@Validated @RequestBody BpmnTaskBackAuditDTO dto); + /** * 驳回 * diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessVariableApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessVariableApi.java index 2c219f88b..614216a7a 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessVariableApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessVariableApi.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.client.feign.bpmn; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.client.config.CommonFeignConfiguration; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; @@ -21,6 +22,7 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; * 流程变量api */ //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient @Manageable public interface ProcessVariableApi { diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessCategoryApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessCategoryApi.java index aa1d51447..7dea9ce08 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessCategoryApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessCategoryApi.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.client.feign.manage; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.client.config.CommonFeignConfiguration; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; @@ -36,6 +37,7 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; */ //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient @Manageable public interface ProcessCategoryApi { diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessConfigApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessConfigApi.java index 5f56d5d64..13bf5e8e0 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessConfigApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessConfigApi.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.client.feign.manage; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; import cn.axzo.workflow.client.config.CommonFeignConfiguration; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; @@ -22,6 +23,7 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; */ //@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient @Manageable public interface ProcessConfigApi { diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/BpmnConstants.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/BpmnConstants.java index 6c132c786..49661639a 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/BpmnConstants.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/BpmnConstants.java @@ -40,6 +40,7 @@ public interface BpmnConstants { @Deprecated String OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT = "[_ASSIGNEE_INFO_SNAPSHOT_]"; String INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT = "[_ACTIVITY_INFO_SNAPSHOT_]"; + String BIZ_NODE_ALTER = "[_BIZ_NODE_ALTER_]"; String PROCESS_PREFIX = "Flowable"; @Deprecated String OLD_TASK_ASSIGNEE_SKIP_FLAT = "taskSkip"; @@ -108,6 +109,7 @@ public interface BpmnConstants { String BPM_MODEL_CATEGORY = "bpm_model_category"; String BPM_ALLOW_SKIP_USER_TASK = "_INTERNAL_SKIP_USER_TASK_"; String AUTO_APPROVAL_TYPE = "autoApprovalType"; + String PROCESS_CLOSING_TYPE = "[_PROCESS_CLOSING_TYPE]"; /** * 用于国内审批节点填写审批建议 *

@@ -120,6 +122,8 @@ public interface BpmnConstants { String NUMBER_OF_INSTANCES = "nrOfInstances"; String MULTI_INSTANCE_LOOP_COUNTER = "loopCounter"; String TASK_COMPLETE_OPERATION_TYPE = "_TASK_COMPLETE_TYPE"; + String TASK_ATTACHMENTS_VAR_NAME = "TASK_ATTACHMENTS"; + /** * 会签表达式 */ @@ -178,4 +182,9 @@ public interface BpmnConstants { * 加签显示人员数量 */ Integer COUNTERSIGN_ASSIGNER_SHOW_NUMBER = 2; + + /** + * 回退操作次数上限 + */ + Integer MAX_BACKED_OPERATE_COUNT = 20; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApprovalMethodEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApprovalMethodEnum.java index 81f213001..2d6451b8e 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApprovalMethodEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApprovalMethodEnum.java @@ -1,5 +1,8 @@ package cn.axzo.workflow.common.enums; +import java.util.Arrays; +import java.util.Objects; + /** * 审批方式枚举 * @@ -48,4 +51,11 @@ public enum ApprovalMethodEnum { public void setRemark(String remark) { this.remark = remark; } + + public static ApprovalMethodEnum valueOfType(String type) { + return Arrays.stream(ApprovalMethodEnum.values()) + .filter(i -> Objects.equals(i.getType(), type)) + .findAny() + .orElse(null); + } } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeMode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeMode.java index e812d2986..00bad8570 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeMode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeMode.java @@ -1,6 +1,9 @@ package cn.axzo.workflow.common.enums; +import java.util.Arrays; +import java.util.Objects; + public enum BpmnFlowNodeMode { GENERAL("GENERAL", "普通节点"), OR("OR", "或签节点"), @@ -35,4 +38,11 @@ public enum BpmnFlowNodeMode { public void setDesc(String desc) { this.desc = desc; } + + public static BpmnFlowNodeMode valueOfType(String type) { + return Arrays.stream(BpmnFlowNodeMode.values()) + .filter(i -> Objects.equals(i.getType(), type)) + .findAny() + .orElse(null); + } } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeType.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeType.java index 1dc7eb85d..1856447f6 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeType.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnFlowNodeType.java @@ -1,6 +1,8 @@ package cn.axzo.workflow.common.enums; +import org.springframework.util.StringUtils; + import java.util.Arrays; import java.util.Objects; @@ -51,6 +53,19 @@ public enum BpmnFlowNodeType { this.desc = desc; } + public static BpmnFlowNodeType getByType(String type) { + if (!StringUtils.hasText(type)) { + return null; + } + BpmnFlowNodeType[] values = BpmnFlowNodeType.values(); + for (BpmnFlowNodeType value : values) { + if (value.getType().equals(type)) { + return value; + } + } + return null; + } + public static BpmnFlowNodeType valueOfType(String type) { return Arrays.stream(BpmnFlowNodeType.values()) .filter(i -> Objects.equals(i.getType(), type)) diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessInstanceResultEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessInstanceResultEnum.java index 81ba92e01..150ecbc35 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessInstanceResultEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessInstanceResultEnum.java @@ -6,6 +6,7 @@ public enum BpmnProcessInstanceResultEnum { PROCESSING("PROCESSING", "审批中"), APPROVED("APPROVED", "已通过"), REJECTED("REJECTED", "已驳回"), + BACKED("BACKED", "已回退"), CANCELLED("CANCELLED", "已撤回"), ABORTED("ABORTED", "已中止"), TRANSFER("TRANSFER", "已转交"), diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessTaskEventEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessTaskEventEnum.java index 124af9a6e..cfe9b8531 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessTaskEventEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessTaskEventEnum.java @@ -10,8 +10,8 @@ import cn.axzo.framework.rocketmq.Event; */ public enum ProcessTaskEventEnum { - PROCESS_TASK_CREATED("process-task", "process-task-created", "流程任务已创建"), PROCESS_TASK_ASSIGNED("process-task", "process-task-assigned", "流程任务已分配"), + PROCESS_TASK_CREATED("process-task", "process-task-created", "流程任务已创建"), PROCESS_TASK_COMPLETED("process-task", "process-task-completed", "流程任务已结束"), PROCESS_TASK_DELETED("process-task", "process-task-deleted", "流程任务已删除"), ; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/AlterDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/AlterDTO.java new file mode 100644 index 000000000..e0408a44e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/AlterDTO.java @@ -0,0 +1,25 @@ +package cn.axzo.workflow.common.model.dto; + +import lombok.Data; + +import java.util.Date; + +/** + * 告警对象 + * + * @author wangli + * @since 2024-09-13 11:37 + */ +@Data +public class AlterDTO { + private String processInstanceId; + + private String activityId; + + private String taskId; + + private Date startTime; + + private String prettyStartTime; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/TermNodePausingDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/TermNodePausingDTO.java new file mode 100644 index 000000000..6444bf20a --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/TermNodePausingDTO.java @@ -0,0 +1,34 @@ +package cn.axzo.workflow.common.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 卡住节点的查询条件模型 + * + * @author wangli + * @since 2024-09-11 13:57 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TermNodePausingDTO implements Serializable { + + private static final long serialVersionUID = -1L; + private String processInstanceId; + + private String activityId; + + /** + * 重试次数 + */ + private Integer retries; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmnApproveConf.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmnApproveConf.java index abc5fe45e..ed9af9805 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmnApproveConf.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmnApproveConf.java @@ -3,18 +3,23 @@ package cn.axzo.workflow.common.model.request; import cn.axzo.workflow.common.enums.AutoApprovalTypeEnum; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; import lombok.Data; -import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import javax.validation.Valid; @ApiModel("JSON 版本的 BPMN 协议模型中流程配置管理") @Data -@NoArgsConstructor +@AllArgsConstructor @Accessors(chain = true) public class BpmnApproveConf { + public BpmnApproveConf() { + this.supportBatchOperation = false; + this.userAgreeSignature = false; + } + /** * 是否支持批量审批 */ diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnButtonConf.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnButtonConf.java index cf78a06ed..61f052ac0 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnButtonConf.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnButtonConf.java @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; /** @@ -25,25 +26,25 @@ public class BpmnButtonConf implements Serializable { * 发起人的按钮配置信息, 需要给全量按钮的配置 */ @ApiModelProperty(value = "发起人的按钮配置信息") - private List initiator; + private List initiator = new ArrayList<>(); /** * 当前审批人的按钮配置信息, JSON 格式 */ @ApiModelProperty(value = "当前审批人的按钮配置信息") - private List current; + private List current = new ArrayList<>(); /** * 历史审批人的按钮配置信息, JSON 格式 */ @ApiModelProperty(value = "历史审批人的按钮配置信息") - private List history; + private List history = new ArrayList<>(); /** * 抄送人的按钮配置信息, JSON 格式 */ @ApiModelProperty(value = "抄送人的按钮配置信息") - private List carbonCopy; + private List carbonCopy = new ArrayList<>(); public List getInitiator() { return initiator; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCancelDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCancelDTO.java index 5b44b327c..4bf8718b5 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCancelDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCancelDTO.java @@ -36,6 +36,7 @@ public class BpmnProcessInstanceCancelDTO { * 工作台 ID */ @ApiModelProperty(value = "工作台 ID") + @NotBlank(message = "工作台不能为空") private String tenantId; /** diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateDTO.java index b948cb521..1dd607cb9 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateDTO.java @@ -43,9 +43,9 @@ public class BpmnProcessInstanceCreateDTO { * 发起流程实例归属租户 ID *

* 为空时,默认是编辑公共流程模型, 如果是代运营创建,则必填 + *

建议都传值,在安心筑中对应工作台 ID

*/ @ApiModelProperty(value = "发起的审批是属于哪个租户") -// @NotBlank(message = "审批实例归属租户 ID 不能为空") private String tenantId; /** diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceLogQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceLogQueryDTO.java new file mode 100644 index 000000000..17e7ad583 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceLogQueryDTO.java @@ -0,0 +1,54 @@ +package cn.axzo.workflow.common.model.request.bpmn.process; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotBlank; + +/** + * 查询流程实例日志 + * + * @author wangli + * @since 2024-09-07 17:32 + */ +@ApiModel("查询流程实例日志") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnProcessInstanceLogQueryDTO { + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例 ID") + @NotBlank(message = "流程实例 ID 不能为空") + private String processInstanceId; + + /** + * 谁来访问该实例日志,如果为空,则始终不就返回按钮信息 + *

+ * 注意,为了确保历史审批数据的查询,需要将除 avatar 外的其他所有属性补全 + */ + @ApiModelProperty(value = "访问者信息", notes = "如果为空,则始终不就返回按钮信息") + private BpmnTaskDelegateAssigner visitor; + + /** + * 返回结果中是否包含按钮 + */ + @ApiModelProperty(value = "返回结果中是否包含按钮", notes = "如果访问者为空,该属性为 true 时,同样也不会返回按钮") + private Boolean hasButton = false; + + /** + * 是否需要加密(同一个实例的日志,在不同端[cms/oms]下,审批人的信息需要按一定规则进行隐藏控制) + */ + @ApiModelProperty(value = "是否需要加密", notes = "同一个实例的日志,在不同端[cms/oms]下,审批人的信息需要按一定规则进行隐藏控制") + private Boolean encrypt; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/AttachmentDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/AttachmentDTO.java index 0ce3b76f0..6fd66819b 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/AttachmentDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/AttachmentDTO.java @@ -3,10 +3,14 @@ package cn.axzo.workflow.common.model.request.bpmn.task; import cn.axzo.workflow.common.enums.AttachmentTypeEnum; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import java.io.Serializable; /** * 附件模型 @@ -16,8 +20,12 @@ import javax.validation.constraints.NotNull; */ @ApiModel("附件模型") @Data -public class AttachmentDTO { +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AttachmentDTO implements Serializable { + private static final long serialVersionUID = 6954179791395744269L; /** * 附件 ID */ diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnActivitySetAssigneeDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnActivitySetAssigneeDTO.java index 6f5a22aff..14ab875fe 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnActivitySetAssigneeDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnActivitySetAssigneeDTO.java @@ -53,7 +53,6 @@ public class BpmnActivitySetAssigneeDTO { * 业务如果传入的 assigners 集合为空, 引擎则会对该流程进行自动驳回处理,且驳回意见为“业务未指定审批人” */ @ApiModelProperty(value = "审批人集合信息", notes = "业务传参时,需要注意去重") - @Valid @Size(max = 60, message = "指定审批人数量限制为60") private List assigners; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnActivityTriggerDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnActivityTriggerDTO.java new file mode 100644 index 000000000..8165317fe --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnActivityTriggerDTO.java @@ -0,0 +1,37 @@ +package cn.axzo.workflow.common.model.request.bpmn.task; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + * 推动业务节点继续执行 + * + * @author wangli + * @since 2024-09-09 13:46 + */ +@ApiModel("业务节点设置审批人") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnActivityTriggerDTO { + + /** + * 业务节点的触发 ID + */ + @NotBlank(message = "触发 ID 不能为空") + @ApiModelProperty(value = "触发 ID", notes = "数据来源于事件") + private String triggerId; + + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + private Boolean async = true; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnOptionalNodeDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnOptionalNodeDTO.java new file mode 100644 index 000000000..935f5d201 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnOptionalNodeDTO.java @@ -0,0 +1,52 @@ +package cn.axzo.workflow.common.model.request.bpmn.task; + +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 回退到指定节点,可选节点模型 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class BpmnOptionalNodeDTO { + + /** + * 对应流程实例id + */ + private String processInstanceId; + + /** + * 对应流程定义id + */ + private String processDefinitionId; + + /** + * 节点id + */ + private String processActivityId; + + /** + * 节点名称 + */ + private String processActivityName; + + /** + * 节点描述,用于页面展示 + */ + private String processNodeDesc; + + /** + * 节点类型 + */ + private BpmnFlowNodeType nodeType; + + /** + * 序号,越小越靠近发起节点 + */ + private Integer ordinal; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskBackAuditDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskBackAuditDTO.java new file mode 100644 index 000000000..b4a2aed67 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskBackAuditDTO.java @@ -0,0 +1,27 @@ +package cn.axzo.workflow.common.model.request.bpmn.task; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +@EqualsAndHashCode(callSuper = true) +@ApiModel("回退到指定节点入参模型") +@Data +@Validated +@AllArgsConstructor +@NoArgsConstructor +public class BpmnTaskBackAuditDTO extends BpmnTaskAuditDTO implements Serializable { + + private static final long serialVersionUID = -4160538355403179298L; + + @ApiModelProperty(value = "目标流程节点id", required = true) + @NotBlank(message = "目标流程节点id不能为空") + private String toActivityId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceLogVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceLogVO.java new file mode 100644 index 000000000..8ddcb835b --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceLogVO.java @@ -0,0 +1,159 @@ +package cn.axzo.workflow.common.model.response.bpmn.process; + +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.enums.WorkspaceType; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceLogVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.List; + +/** + * 流程实例日志模型 + * + * @author wangli + * @since 2024-09-07 17:07 + */ +@ApiModel("流程实例日志模型") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class BpmnProcessInstanceLogVO { + /** + * 流程实例的编号 + */ + @ApiModelProperty(value = "流程实例的编号", example = "1024") + private String id; + + /** + * 流程名称 + */ + @ApiModelProperty(value = "流程名称", example = "权限点申请") + private String name; + + /** + * 流程分类 + */ + @ApiModelProperty(value = "流程分类", notes = "关联的业务分类", example = "1") + private String category; + + /** + * 审核状态 + */ + @ApiModelProperty(value = "审核状态(PROCESSING:审核中,APPROVED:已通过,REJECTED:已拒绝,CANCELLED:已取消)", example = "APPROVED") + private BpmnProcessInstanceResultEnum result; + + /** + * 发起时间 + */ + @ApiModelProperty("发起时间") + private Date startTime; + + /** + * 结束时间 + */ + @ApiModelProperty("结束时间") + private Date endTime; + + /** + * 流程定义 KEY + */ + @ApiModelProperty("流程定义 KEY") + private String processDefinitionKey; + + /** + * 流程定义 ID + */ + @ApiModelProperty("流程定义 ID") + private String processDefinitionId; + + /** + * 业务的唯一标识 + */ + @ApiModelProperty(value = "业务的唯一标识", example = "1", notes = "例如说,请假申请的编号") + private String businessKey; + + /** + * 流程最终状态 + */ + @ApiModelProperty("流程最终状态") + private String businessStatus; + + /** + * 发起人 + */ + @ApiModelProperty("发起人") + private BpmnTaskDelegateAssigner initiator; + + /** + * 当前流程发起租户 + */ + @ApiModelProperty("当前流程发起租户") + private String tenantId; + + /** + * 是代运营的流程 + */ + @ApiModelProperty("是代运营的流程") + private Boolean agented; + + /** + * 任务信息集合 + */ + @ApiModelProperty("任务信息集合") + private List taskDetails; + + /** + * 当前实例对应模型的全局兜底按钮配置 + */ + @ApiModelProperty(value = "当前实例对应模型的全局兜底按钮配置") + private BpmnButtonConf defaultButtonConf; + + /** + * 指定人访问实例日志时,计算其流程应该有权限操作的按钮 + */ + @ApiModelProperty(value = "指定人访问实例日志时,计算其流程应该有权限操作的按钮", notes = "流程有权限,不代表待办消息中一定能看到按钮") + private List currentUserButtons; + + /** + * 需要隐藏的自定义按钮集合 + */ + @ApiModelProperty(value = "需要隐藏的自定义按钮集合") + private List customHiddenButtons; + + /** + * 是否支持批量审批 + */ + @ApiModelProperty(value = "是否支持批量审批") + private Boolean supportBatchOperation; + + /** + * 审批同意录入手写签名 + */ + @ApiModelProperty(value = "审批同意录入手写签名") + private Boolean userAgreeSignature; + + /** + * 数据产生版本 + */ + @ApiModelProperty(value = "数据产生版本") + private String workflowEngineVersion; + + /** + * 当前流程对应工作台类型 + */ + @ApiModelProperty(value = "工作台类型") + private WorkspaceType workspaceType; + + @ApiModelProperty(value = "程序计算按钮使用,非对外使用", hidden = true) + private transient BpmnButtonConf calculatingButtonConf; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskInstanceLogVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskInstanceLogVO.java new file mode 100644 index 000000000..2c191b8d4 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskInstanceLogVO.java @@ -0,0 +1,127 @@ +package cn.axzo.workflow.common.model.response.bpmn.task; + +import cn.axzo.workflow.common.enums.ApprovalMethodEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +import java.util.Date; +import java.util.List; + +/** + * 流程任务日志模型 + * + * @author wangli + * @since 2024-09-07 17:08 + */ +@ApiModel("流程任务日志模型") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class BpmnTaskInstanceLogVO { + + /** + * 审批任务 ID + */ + @ApiModelProperty(value = "审批任务 ID") + private String taskId; + /** + * 审批任务节点定义KEY + */ + @ApiModelProperty(value = "审批任务节点定义KEY") + private String taskDefinitionKey; + /** + * 审批任务节点名称 + */ + @ApiModelProperty(value = "审批任务节点名称") + private String name; + /** + * 任务创建时间 + */ + @ApiModelProperty(value = "任务创建时间") + private Date createTime; + /** + * 任务结束时间 + */ + @ApiModelProperty(value = "任务结束时间") + private Date endTime; + /** + * 审批方式 + */ + @ApiModelProperty(value = "审批方式") + private ApprovalMethodEnum approvalMethod; + /** + * 节点类型 + */ + @ApiModelProperty(value = "节点类型") + private BpmnFlowNodeType nodeType; + /** + * 审批任务节点的类型 + */ + @ApiModelProperty(value = "审批任务节点的类型") + private BpmnFlowNodeMode nodeMode; + /** + * 任务状态 + */ + @ApiModelProperty(value = "任务状态") + private BpmnProcessInstanceResultEnum result; + + /** + * 操作描述 + */ + @ApiModelProperty(value = "操作描述") + private String operationDesc; + /** + * 审批建议 + */ + @ApiModelProperty(value = "审批建议") + private String advice; + /** + * 一些扩展信息 + */ + @ApiModelProperty(value = "一些扩展信息") + private String commentExt; + /** + * 图片列表 + */ + @ApiModelProperty(value = "图片列表") + private List imageList; + /** + * 附件列表 + */ + @ApiModelProperty(value = "附件列表") + private List fileList; + /** + * 手写签名地址 + */ + @ApiModelProperty(value = "手写签名地址") + private String signatureUrl; + /** + * 审批人快照信息 + */ + @ApiModelProperty(value = "审批人快照信息") + private BpmnTaskDelegateAssigner assigneeSnapshot; + /** + * 未完成节点多实例模式的审批人信息 + */ + @ApiModelProperty(value = "未完成节点多实例模式的审批人信息") + private List forecastAssignees; + + @ApiModelProperty(value = "程序计算按钮使用,非对外使用", hidden = true) + private transient BpmnButtonConf buttonConf; + + public boolean isVirtual() { + return StringUtils.isBlank(this.taskId); + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/ProcessTaskDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/ProcessTaskDTO.java index 0dfef6a2f..2822419c5 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/ProcessTaskDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/ProcessTaskDTO.java @@ -3,6 +3,7 @@ package cn.axzo.workflow.common.model.response.mq; import cn.axzo.workflow.common.enums.BpmnNoticeEnum; import cn.axzo.workflow.common.enums.ProcessTaskEventEnum; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import lombok.Data; import lombok.experimental.Accessors; @@ -44,6 +45,10 @@ public class ProcessTaskDTO implements Serializable { * 流程实例 ID */ private String processInstanceId; + /** + * 流程实例的businessKey + */ + private String businessKey; /** * 流程实例所属业务分类,同时也等于流程模型对应的业务分类 ID */ @@ -103,4 +108,13 @@ public class ProcessTaskDTO implements Serializable { * 当前数据的流程引擎版本 */ private String workflowEngineVersion; + /** + * 任务关联的附件 + */ + private List attachments; + + /** + * 审批意见 + */ + private String advice; } diff --git a/workflow-engine-core/pom.xml b/workflow-engine-core/pom.xml index 00e1896d7..5b41b626b 100644 --- a/workflow-engine-core/pom.xml +++ b/workflow-engine-core/pom.xml @@ -89,17 +89,25 @@ cn.axzo.workflow workflow-engine-api + cn.axzo.workflow workflow-engine-common + org.apache.maven maven-artifact + jakarta.servlet jakarta.servlet-api + + + com.aliyun + alibaba-dingtalk-service-sdk + diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnInstanceRespCode.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnInstanceRespCode.java index 281569769..e1e8d2bbe 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnInstanceRespCode.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnInstanceRespCode.java @@ -27,6 +27,7 @@ public enum BpmnInstanceRespCode implements IModuleRespCode { TASK_CANT_COMMENT_INSTANCE_NOT_EXISTS("012", "流程实例【{}】不存在, 不能评论"), RUNNING_INSTANCE_ONLY_FORECAST("013", "仅运行中的实例可以推测"), ENGINE_EXEC_EXCEPTION("014", "引擎内部异常"), + PROCESS_TASK_NOT_EXISTS("015", "流程任务不存在或已处理"), ; private final String code; private final String message; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnTaskRespCode.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnTaskRespCode.java index 3fdbeea92..732e6be42 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnTaskRespCode.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnTaskRespCode.java @@ -37,6 +37,9 @@ public enum BpmnTaskRespCode implements IModuleRespCode { TASK_TYPE_MISMATCH("020", "节点类型不匹配,当前节点类型:【{}】,指定节点类型:【{}】!"), PROCESS_CANT_SET_ASSIGNEE("021", "当前审批状态不允许设置审批人"), ASSIGNER_NUMBER_EXCEEDS_NUMBER_LIMIT("022", String.format("人员数量超过限制,节点审批人限制数量为: %d!", APPROVAL_ASSIGNER_LIMIT_NUMBER)), + BACK_TARGET_ACTIVITY_NOT_EXISTS("023", "回退到指定节点【{}】失败!"), + BACK_NODE_CANNOT_REACHABLE("024", "退回节点【{}】不可达,不允许退回"), + REACHED_BACKED_MAXIMUM_NUM("025", "达到回退操作次数上限【{}】次"), ; private final String code; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FlowableEngineRespCode.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FlowableEngineRespCode.java index 7fbdc0886..319e88a95 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FlowableEngineRespCode.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FlowableEngineRespCode.java @@ -19,7 +19,7 @@ public enum FlowableEngineRespCode implements IModuleRespCode { ENGINE_USER_TASK_PARAM_ERROR("004", "构建后的查询审批人入参为空. 任务节点【nodeId:{}】, 该节点选择的\"审批人所在范围\"是:【{}】,请检查 cooperationOrg 参数"), ENGINE_NOTICE_CUSTOM_FLOW_ELEMENT_ERROR("005", "查询通知目标用户前参数发生异常,未获取到 WorkspaceType"), ENGINE_ASYNC_COMMAND_EXECUTION_ERROR("006", "引擎出现 SQL 相关异常, 异常信息:【{}】"), - ENGINE_ASYNC_COMMAND_EXECUTION_RETRY_GIVE_UP("007", "命令重试尝试【{}】次仍然失败,并出现异常, 将放弃"), + ENGINE_ASYNC_COMMAND_EXECUTION_RETRY_GIVE_UP("007", "命令重试尝试【{}】次仍然失败,并出现异常, 将放弃, 错误信息:{}"), ; private final String code; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/enums/BpmnProcessTaskResultEnum.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/enums/BpmnProcessTaskResultEnum.java index 3b6532d74..23eadcd0f 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/enums/BpmnProcessTaskResultEnum.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/enums/BpmnProcessTaskResultEnum.java @@ -9,7 +9,8 @@ import java.util.Arrays; * @since 2023/9/18 17:11 */ public enum BpmnProcessTaskResultEnum { - + PENDING("PENDING", "待处理"), + PROCESSED("PROCESSED", "已处理"), AUTO_SKIP("AUTO_SKIP", "任务自动跳过"), // 引擎默认的标识,不允许修改 MI_END("MI_END", "多实例任务运行结束"), @@ -17,8 +18,7 @@ public enum BpmnProcessTaskResultEnum { DELETE_MI_EXECUTION("Delete MI execution", "多实例任务被删除"), INITIATOR_REVOCATION("INITIATOR_REVOCATION", "发起者主动撤回"), REJECTION_AUTO_COMPLETED("REJECTION_AUTO_COMPLETED", "审批驳回自动结束"), - BACKED("BACKED", "退回"); - + ; private final String status; /** * 描述 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnModelUtils.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnModelUtils.java new file mode 100644 index 000000000..80ace39f9 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnModelUtils.java @@ -0,0 +1,91 @@ +package cn.axzo.workflow.core.common.utils; + +import org.flowable.bpmn.model.EventSubProcess; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowElementsContainer; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.bpmn.model.SubProcess; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class BpmnModelUtils { + + /** + * 节点是否可达 + * @param process + * @param sourceElement + * @param targetElement + * @return + */ + public static boolean isReachable(org.flowable.bpmn.model.Process process, FlowNode sourceElement, FlowNode targetElement) { + return isReachable(process, sourceElement, targetElement, new HashSet<>()); + } + + public static boolean isReachable(org.flowable.bpmn.model.Process process, FlowNode sourceElement, FlowNode targetElement, Set visitedElements) { + // Special case: start events in an event subprocess might exist as an execution and are most likely be able to + // reach the target + // when the target is in the event subprocess, but should be ignored as they are not 'real' runtime executions + // (but rather waiting for trigger) + if (sourceElement instanceof StartEvent && isInEventSubprocess(sourceElement)) { + return false; + } + // No outgoing seq flow: could be the end of eg . the process or an embedded subprocess + if (sourceElement.getOutgoingFlows().isEmpty()) { + visitedElements.add(sourceElement.getId()); + FlowElementsContainer parentElement = process.findParent(sourceElement); + if (parentElement instanceof SubProcess) { + sourceElement = (SubProcess) parentElement; + // 子流程的结束节点,若目标节点在该子流程中,说明无法到达,返回false + if (((SubProcess) sourceElement).getFlowElement(targetElement.getId()) != null) { + return false; + } + } else { + return false; + } + } + if (sourceElement.getId().equals(targetElement.getId())) { + return true; + } + // To avoid infinite looping, we must capture every node we visit + // and check before going further in the graph if we have already + // visited the node. + visitedElements.add(sourceElement.getId()); + // 当前节点能够到达子流程,且目标节点在子流程中,说明可以到达,返回true + if (sourceElement instanceof SubProcess && ((SubProcess) sourceElement).getFlowElement(targetElement.getId()) != null) { + return true; + } + List sequenceFlows = sourceElement.getOutgoingFlows(); + if (sequenceFlows != null && !sequenceFlows.isEmpty()) { + for (SequenceFlow sequenceFlow : sequenceFlows) { + String targetRef = sequenceFlow.getTargetRef(); + FlowNode sequenceFlowTarget = (FlowNode) process.getFlowElement(targetRef, true); + if (sequenceFlowTarget != null && !visitedElements.contains(sequenceFlowTarget.getId())) { + boolean reachable = isReachable(process, sequenceFlowTarget, targetElement, visitedElements); + if (reachable) { + return true; + } + } + } + } + return false; + } + + protected static boolean isInEventSubprocess(FlowNode flowNode) { + FlowElementsContainer flowElementsContainer = flowNode.getParentContainer(); + while (flowElementsContainer != null) { + if (flowElementsContainer instanceof EventSubProcess) { + return true; + } + if (flowElementsContainer instanceof FlowElement) { + flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer(); + } else { + flowElementsContainer = null; + } + } + return false; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java index 8fc32f0b3..195d53b6e 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java @@ -5,15 +5,18 @@ import cn.axzo.workflow.core.engine.behavior.CustomActivityBehaviorFactory; import cn.axzo.workflow.core.engine.cmd.CustomCommandContextFactory; import cn.axzo.workflow.core.engine.id.BasedNacosSnowflakeIdGenerator; import cn.axzo.workflow.core.engine.interceptor.CustomRetryInterceptor; -import cn.axzo.workflow.core.engine.job.AsyncAbortProcessInstanceHandler; import cn.axzo.workflow.core.engine.job.AsyncActivityCallbackJobHandler; import cn.axzo.workflow.core.engine.job.AsyncActivityLeaveJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncAbortProcessInstanceJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncActivitySetAssigneeJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncActivityTriggerJobHandler; import cn.axzo.workflow.core.engine.job.AsyncApproveTaskJobHandler; -import cn.axzo.workflow.core.engine.job.AsyncBpmnProcessActivityJobHandler; -import cn.axzo.workflow.core.engine.job.AsyncCancelProcessInstanceHandler; +import cn.axzo.workflow.core.engine.job.AsyncBackTaskJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncCancelProcessInstanceJobHandler; import cn.axzo.workflow.core.engine.job.AsyncCountersignUserTaskJobHandler; import cn.axzo.workflow.core.engine.job.AsyncExtTaskInstJobHandler; import cn.axzo.workflow.core.engine.job.AsyncRejectTaskJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncTermNodeAlterJobHandler; import cn.axzo.workflow.core.engine.job.AsyncTransferUserTaskJobHandler; import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncJobLogClearTraceExceptionHandler; import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncRunnableExceptionExceptionHandler; @@ -67,7 +70,7 @@ public class FlowableConfiguration { List jobProcessors, NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties, - StringRedisTemplate redisTemplate) { + SupportRefreshProperties refreshProperties) { return configuration -> { configuration.setEnableHistoricTaskLogging(true); configuration.setHistoryLevel(HistoryLevel.AUDIT); @@ -83,14 +86,18 @@ public class FlowableConfiguration { configuration.setIdGenerator(new BasedNacosSnowflakeIdGenerator(nacosServiceManager, nacosDiscoveryProperties)); configuration.setHistoricProcessInstanceDataManager(new CustomMybatisHistoricProcessInstanceDataManager(configuration)); // 自定义的异步任务处理器 + configuration.addCustomJobHandler(new AsyncAbortProcessInstanceJobHandler(extAxHiTaskInstService)); + configuration.addCustomJobHandler(new AsyncActivitySetAssigneeJobHandler()); + configuration.addCustomJobHandler(new AsyncActivityTriggerJobHandler()); configuration.addCustomJobHandler(new AsyncApproveTaskJobHandler()); - configuration.addCustomJobHandler(new AsyncRejectTaskJobHandler(extAxHiTaskInstService)); - configuration.addCustomJobHandler(new AsyncExtTaskInstJobHandler(extAxHiTaskInstService)); - configuration.addCustomJobHandler(new AsyncTransferUserTaskJobHandler()); + configuration.addCustomJobHandler(new AsyncBackTaskJobHandler()); + configuration.addCustomJobHandler(new AsyncCancelProcessInstanceJobHandler(extAxHiTaskInstService)); + configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService)); + configuration.addCustomJobHandler(new AsyncExtTaskInstJobHandler(extAxHiTaskInstService)); + configuration.addCustomJobHandler(new AsyncRejectTaskJobHandler(extAxHiTaskInstService)); + configuration.addCustomJobHandler(new AsyncTransferUserTaskJobHandler()); + configuration.addCustomJobHandler(new AsyncTermNodeAlterJobHandler(refreshProperties)); configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService)); - configuration.addCustomJobHandler(new AsyncCancelProcessInstanceHandler(extAxHiTaskInstService)); - configuration.addCustomJobHandler(new AsyncAbortProcessInstanceHandler(extAxHiTaskInstService)); - configuration.addCustomJobHandler(new AsyncBpmnProcessActivityJobHandler(bpmnProcessActivityService)); configuration.addCustomJobHandler(new AsyncActivityLeaveJobHandler(bpmnProcessActivityService)); configuration.addCustomJobHandler(new AsyncActivityCallbackJobHandler()); // 异步任务异常重试时间间隔 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java index 36bceb854..cc4db9e20 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java @@ -1,13 +1,14 @@ package cn.axzo.workflow.core.conf; -import com.google.common.collect.Lists; +import com.alibaba.nacos.api.config.annotation.NacosValue; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component; -import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * 支持动态刷新配置属性 @@ -28,4 +29,49 @@ public class SupportRefreshProperties { @Value("${workflow.apiLog.filterApiType:}") private String filterApiType; + + @Value("${workflow.api.timeout:10}") + private Long apiTimeout; + @Value("${workflow.mock:false}") + private Boolean mock; + @Value("${workflow.assignee.global:true}") + private Boolean global; + @Value("${workflow.assignee.category:''}") + private String category; + @Value("#{${workflow.assignee.map:{}}}") + private Map assigneeMap; + + @Value(value = "${workflow.alter.enable:false}") + private Boolean alterEnable; + /** + * 节点卡住多久才告警 + */ + @Value("${workflow.alter.delay:10}") + private Integer pauseDelay; + /** + * 业务节点暂停告警的次数,该值不建议改小 + */ + @Value(value = "${workflow.alter.retries:1000}") + private Integer alterRetries; + + /** + * 业务节点暂停告警次数间的间隔 + */ + @Value(value = "${workflow.alter.interval:10}") + private Integer alterInterval; + + /** + * 业务节点暂停告警次数间隔的时间单位 + */ + @Value(value = "${workflow.alter.intervalUnit:minutes}") + private TimeUnit alterIntervalUnit; + + @Value(value = "${workflow.alter.mobiles:}") + private List alterMobiles; + + /** + * 用于控制转交管理员的 API + */ + @Value("${workflow.useNewToAdminApi:true}") + private Boolean useNewToAdminApi; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ButtonConfTypeHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ButtonConfTypeHandler.java new file mode 100644 index 000000000..c235e42c9 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ButtonConfTypeHandler.java @@ -0,0 +1,57 @@ +package cn.axzo.workflow.core.conf.handler; + +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.io.IOException; +import java.util.List; + +/** + * BpmnButtonConf 数据映射转换 + * + * @author wangli + * @since 2024-09-07 22:40 + */ +@Slf4j +@MappedTypes({List.class}) +@MappedJdbcTypes({JdbcType.VARCHAR}) +public class ButtonConfTypeHandler extends AbstractJsonTypeHandler { + private static ObjectMapper objectMapper = new ObjectMapper(); + + public ButtonConfTypeHandler(Class type) { + if (log.isTraceEnabled()) { + log.trace("JacksonTypeHandler(" + type + ")"); + } + Assert.notNull(type, "Type argument cannot be null", new Object[0]); + } + + protected BpmnButtonConf parse(String json) { + try { + // 这里进行了json解析,同样在这里也可以进行字段查询后的处理,如对象内部的手机号字段的加密展示等 + return objectMapper.readValue(json, new TypeReference() { + }); + } catch (IOException var3) { + throw new RuntimeException(var3); + } + } + + protected String toJson(BpmnButtonConf obj) { + try { + return objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException var3) { + throw new RuntimeException(var3); + } + } + + public static void setObjectMapper(ObjectMapper om) { + objectMapper = om; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListAssigneeTypeHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListAssigneeTypeHandler.java new file mode 100644 index 000000000..cd54a7f40 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListAssigneeTypeHandler.java @@ -0,0 +1,57 @@ +package cn.axzo.workflow.core.conf.handler; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.io.IOException; +import java.util.List; + +/** + * BpmnTaskDelegateAssigner 数据映射转换 + * + * @author wangli + * @since 2024-09-07 22:40 + */ +@Slf4j +@MappedTypes({List.class}) +@MappedJdbcTypes({JdbcType.VARCHAR}) +public class ListAssigneeTypeHandler extends AbstractJsonTypeHandler> { + private static ObjectMapper objectMapper = new ObjectMapper(); + + public ListAssigneeTypeHandler(Class type) { + if (log.isTraceEnabled()) { + log.trace("JacksonTypeHandler(" + type + ")"); + } + Assert.notNull(type, "Type argument cannot be null", new Object[0]); + } + + protected List parse(String json) { + try { + // 这里进行了json解析,同样在这里也可以进行字段查询后的处理,如对象内部的手机号字段的加密展示等 + return objectMapper.readValue(json, new TypeReference>() { + }); + } catch (IOException var3) { + throw new RuntimeException(var3); + } + } + + protected String toJson(List obj) { + try { + return objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException var3) { + throw new RuntimeException(var3); + } + } + + public static void setObjectMapper(ObjectMapper om) { + objectMapper = om; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomReceiveTaskActivityBehavior.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomReceiveTaskActivityBehavior.java index 3004da535..1837174b1 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomReceiveTaskActivityBehavior.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomReceiveTaskActivityBehavior.java @@ -12,12 +12,12 @@ import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.TaskHelper; -import org.flowable.task.api.Task; import org.flowable.task.service.TaskService; import org.flowable.task.service.impl.persistence.entity.TaskEntity; import java.util.Objects; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; @@ -48,7 +48,8 @@ public class CustomReceiveTaskActivityBehavior extends ReceiveTaskActivityBehavi task.setTaskDefinitionKey(receiveTask.getId()); task.setPropagatedStageInstanceId(execution.getPropagatedStageInstanceId()); task.setName(receiveTask.getName()); - TaskHelper.insertTask(task, (ExecutionEntity) execution, false, false); + TaskHelper.insertTask(task, (ExecutionEntity) execution, true, false); + // 添加 taskInst 扩展表数据 FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); @@ -65,14 +66,17 @@ public class CustomReceiveTaskActivityBehavior extends ReceiveTaskActivityBehavi FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); org.flowable.engine.TaskService taskService = processEngineConfiguration.getTaskService(); - Task task = taskService.createTaskQuery().executionId(execution.getId()) + TaskEntity task = (TaskEntity) taskService.createTaskQuery().executionId(execution.getId()) .taskDefinitionKey(execution.getCurrentActivityId()).singleResult(); if (Objects.nonNull(task)) { + // 用于新版日志 + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), APPROVED.getStatus()); + eventDispatcher.dispatchEvent(new ExtTaskInstUpdateEvent(execution.getProcessInstanceId(), receiveTask.getId(), task.getId(), APPROVED), processEngineConfiguration.getEngineCfgKey()); } else { - log.warn("task is null, executionId: {}, activityId: {}", execution.getId(), + log.warn("ReceiveTask is null, executionId: {}, activityId: {}", execution.getId(), execution.getCurrentActivityId()); } super.leave(execution); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomServiceTaskDelegateExpressionActivityBehavior.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomServiceTaskDelegateExpressionActivityBehavior.java index 378fa8998..b7aa7ce82 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomServiceTaskDelegateExpressionActivityBehavior.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomServiceTaskDelegateExpressionActivityBehavior.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.core.engine.behavior; import cn.axzo.workflow.core.engine.event.ExtTaskInstCreateEvent; import cn.axzo.workflow.core.engine.event.ExtTaskInstUpdateEvent; +import cn.axzo.workflow.core.engine.listener.EngineCarbonCopyEventListener; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.MapExceptionEntry; import org.flowable.bpmn.model.ServiceTask; @@ -19,14 +20,16 @@ import org.flowable.task.service.TaskService; import org.flowable.task.service.impl.persistence.entity.TaskEntity; import java.util.List; +import java.util.Objects; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; /** * 自定义的服务任务活动行为处理器 *

- * 主要用来创建审批日志 + * 主要用来创建抄送节点审批日志,真实计算抄送人的集合是由 {@link EngineCarbonCopyEventListener} 来完成的 * * @author wangli * @since 13/03/2024 14:17 @@ -35,6 +38,7 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCES public class CustomServiceTaskDelegateExpressionActivityBehavior extends ServiceTaskDelegateExpressionActivityBehavior { protected final ServiceTask serviceTask; + // thread safe private TaskEntity task; public CustomServiceTaskDelegateExpressionActivityBehavior(String serviceTaskId, Expression expression, @@ -57,7 +61,7 @@ public class CustomServiceTaskDelegateExpressionActivityBehavior extends Service task.setTaskDefinitionKey(serviceTask.getId()); task.setPropagatedStageInstanceId(execution.getPropagatedStageInstanceId()); task.setName(serviceTask.getName()); - TaskHelper.insertTask(task, (ExecutionEntity) execution, false, false); + TaskHelper.insertTask(task, (ExecutionEntity) execution, true, false); // 添加 taskInst 扩展表数据 FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); @@ -72,11 +76,24 @@ public class CustomServiceTaskDelegateExpressionActivityBehavior extends Service CommandContext commandContext = CommandContextUtil.getCommandContext(); ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); - FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); - eventDispatcher.dispatchEvent(new ExtTaskInstUpdateEvent(execution.getProcessInstanceId(), - execution.getCurrentActivityId(), task.getId(), APPROVED), + + org.flowable.engine.TaskService taskService = processEngineConfiguration.getTaskService(); + TaskEntity serviceTask = (TaskEntity) taskService.createTaskQuery().taskId(task.getId()) + .taskDefinitionKey(execution.getCurrentActivityId()).singleResult(); + if (Objects.nonNull(serviceTask)) { + // 用于新版日志 + serviceTask.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + serviceTask.getId(), APPROVED.getStatus()); + + eventDispatcher.dispatchEvent(new ExtTaskInstUpdateEvent(execution.getProcessInstanceId(), + execution.getCurrentActivityId(), serviceTask.getId(), APPROVED), processEngineConfiguration.getEngineCfgKey()); + TaskHelper.deleteTask(serviceTask, "complete carbon", false, true, true); + } else { + log.warn("ServiceTask is null, executionId: {}, activityId: {}", execution.getId(), + execution.getCurrentActivityId()); + } + super.leave(execution); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/AbstractCommand.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/AbstractCommand.java index bd6eb0de8..4f9495fcd 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/AbstractCommand.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/AbstractCommand.java @@ -8,7 +8,7 @@ import org.flowable.common.engine.impl.interceptor.CommandContext; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.ENGINE_EXEC_EXCEPTION; /** - * TODO + * 抽象的命令,用于将在 Command 中执行的逻辑所抛出的非 WorkflowEngineException 的异常都包装为 WorkflowEngineException * * @author wangli * @since 2024/7/1 13:59 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceAsyncCmd.java index ec87d66fa..e11e5ea2f 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceAsyncCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceAsyncCmd.java @@ -2,7 +2,7 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; -import cn.axzo.workflow.core.engine.job.AsyncAbortProcessInstanceHandler; +import cn.axzo.workflow.core.engine.job.AsyncAbortProcessInstanceJobHandler; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.interceptor.CommandContext; @@ -73,9 +73,9 @@ public class CustomAbortProcessInstanceAsyncCmd extends AbstractCommand im job.setExecutionId(instance.getId()); job.setProcessInstanceId(instance.getId()); job.setProcessDefinitionId(instance.getProcessDefinitionId()); - job.setElementId(AsyncAbortProcessInstanceHandler.TYPE); + job.setElementId(AsyncAbortProcessInstanceJobHandler.TYPE); job.setElementName(instance.getName()); - job.setJobHandlerType(AsyncAbortProcessInstanceHandler.TYPE); + job.setJobHandlerType(AsyncAbortProcessInstanceJobHandler.TYPE); job.setTenantId(instance.getTenantId()); // 携带自定义的数据 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceCmd.java index cb5836728..2ccd617a4 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceCmd.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.engine.model.AddComment; import cn.axzo.workflow.core.engine.operation.DeleteProcessInstanceOperation; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import com.alibaba.fastjson.JSON; @@ -20,7 +21,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_DELETE_PROCESS_FLAG; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_TENANT_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_ID; @@ -35,7 +35,7 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.CANCEL import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANT_ABORT; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; -import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.completeVirtualTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVirtualTask; /** @@ -101,14 +101,15 @@ public class CustomAbortProcessInstanceCmd extends AbstractCommand impleme runtimeService.setVariables(instance.getId(), variables); CommandContextUtil.getAgenda(commandContext).planOperation(new DeleteProcessInstanceOperation(commandContext, - processInstanceId, extAxHiTaskInstService)); + processInstanceId, extAxHiTaskInstService, ABORTED)); // 添加自定义的节点,用于展示最后的操作 Task task = createVirtualTask(commandContext, extAxHiTaskInstService, processInstanceId, "系统中止", NODE_ABORT.getType(), null, BpmnTaskDelegateAssigner.buildDummyAssigner("system", - TASK_ASSIGNEE_SKIP_FLAT, "系统"), ABORTED.getStatus()); - addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, reason); + TASK_ASSIGNEE_SKIP_FLAT, "系统"), ABORTED.getStatus(), new AddComment(reason)); runtimeService.setVariable(task.getProcessInstanceId(), TASK_COMPLETE_OPERATION_TYPE + task.getId(), ABORTED); + + completeVirtualTask(commandContext, task); return null; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerAsyncCmd.java new file mode 100644 index 000000000..1e194e008 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerAsyncCmd.java @@ -0,0 +1,85 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; +import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.engine.job.AsyncActivityTriggerJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncApproveTaskJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.runtime.Execution; +import org.flowable.job.service.JobService; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.task.api.Task; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.Objects; + +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_TRIGGER_NOT_EXISTS; + +/** + * 自定义(异步)流转业务姐弟那的命令器实现 + * + * @author wangli + * @since 2024-09-09 13:58 + */ +public class CustomActivityTriggerAsyncCmd extends AbstractCommand implements Serializable { + + private static final Logger log = LoggerFactory.getLogger(CustomActivityTriggerAsyncCmd.class); + private final BpmnActivityTriggerDTO dto; + + public CustomActivityTriggerAsyncCmd(BpmnActivityTriggerDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public String execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + Execution execution = runtimeService.createExecutionQuery().executionId(dto.getTriggerId()).singleResult(); + if (Objects.isNull(execution)) { + throw new WorkflowEngineException(ACTIVITY_TRIGGER_NOT_EXISTS, dto.getTriggerId()); + } + + return startAsync(commandContext); + } + + private String startAsync(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + TaskService taskService = processEngineConfiguration.getTaskService(); + TaskEntity task = (TaskEntity) taskService.createTaskQuery().executionId(dto.getTriggerId()).singleResult(); + JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService(); + + JobEntity job = jobService.createJob(); + // 这里的 executionId 可为 null + job.setExecutionId(task.getExecutionId()); + job.setProcessInstanceId(task.getProcessInstanceId()); + job.setProcessDefinitionId(task.getProcessDefinitionId()); + job.setElementId(task.getTaskDefinitionKey()); + job.setElementName(task.getName()); + job.setJobHandlerType(AsyncActivityTriggerJobHandler.TYPE); + job.setTenantId(task.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + return job.getId(); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerCmd.java new file mode 100644 index 000000000..918e0cfb7 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerCmd.java @@ -0,0 +1,56 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; +import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import com.alibaba.fastjson.JSON; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_TRIGGER_NOT_EXISTS; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; + +/** + * 自定义(同步)流转业务姐弟那的命令器实现 + * + * @author wangli + * @since 2024-09-09 13:58 + */ +public class CustomActivityTriggerCmd extends AbstractCommand implements Serializable { + + private static final Logger log = LoggerFactory.getLogger(CustomActivityTriggerCmd.class); + private final BpmnActivityTriggerDTO dto; + + public CustomActivityTriggerCmd(BpmnActivityTriggerDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + + TaskEntity task = (TaskEntity) processEngineConfiguration.getTaskService() + .createTaskQuery().executionId(dto.getTriggerId()).singleResult(); + if (Objects.isNull(task)) { + throw new WorkflowEngineException(ACTIVITY_TRIGGER_NOT_EXISTS, dto.getTriggerId()); + } + addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, "已处理"); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + runtimeService.trigger(dto.getTriggerId()); + return null; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskAsyncCmd.java index f7bccb3ab..c89d20c74 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskAsyncCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskAsyncCmd.java @@ -29,8 +29,10 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask * @since 2024/1/4 15:50 */ public class CustomApproveTaskAsyncCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = -4706627700694867170L; private static final Logger log = LoggerFactory.getLogger(CustomApproveTaskAsyncCmd.class); + private final BpmnTaskAuditDTO dto; public CustomApproveTaskAsyncCmd(BpmnTaskAuditDTO dto) { diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java index e6e961069..f04a60914 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java @@ -101,7 +101,7 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria if (Objects.nonNull(operationDesc)) { this.operationDesc = operationDesc; } else { - this.operationDesc = "已通过"; + this.operationDesc = "(已通过)"; } } @@ -115,8 +115,8 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria HistoricTaskInstance historicTaskInstance = taskQuery.taskId(taskId).singleResult(); - Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); - validTask(historicTaskInstance, (TaskEntity) task, approver, nodeTypes); + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult(); + validTask(historicTaskInstance, task, approver, nodeTypes); // TODO 所有的跟 Task 相关的动作都可以在这里进行扩展,用于扩展八大按钮标准动作以外的一些逻辑,但这里需要结合 Spring 能力,需设计好扩展点,否则无法进行扩展 // 其他动态也应该在类似的地方预留扩展点 @@ -127,7 +127,7 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria Authentication.setAuthenticatedUserId(null); } - batchAddAttachment(commandContext, task.getProcessInstanceId(), taskId, attachmentList, approver); + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, approver); Authentication.setAuthenticatedUserId(Objects.nonNull(approver) ? approver.buildAssigneeId() : null); addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, operationDesc); @@ -139,8 +139,7 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria runtimeService.setVariable(task.getProcessInstanceId(), INTERNAL_SPECIFY_NEXT_APPROVER, nextApprover); } - ((TaskEntity) task).setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus()); - + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus()); executeSynchronous(task, taskService, runtimeService); return null; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBackTaskAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBackTaskAsyncCmd.java new file mode 100644 index 000000000..dcf368651 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBackTaskAsyncCmd.java @@ -0,0 +1,95 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; +import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.common.utils.BpmnModelUtils; +import cn.axzo.workflow.core.engine.job.AsyncBackTaskJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.bpmn.model.Process; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.job.service.JobService; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.flowable.task.service.TaskService; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; + +import java.io.Serializable; +import java.util.Objects; + +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_TARGET_ACTIVITY_NOT_EXISTS; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; + +@Slf4j +public class CustomBackTaskAsyncCmd extends AbstractCommand implements Serializable { + + private static final long serialVersionUID = 1773108485033787095L; + + private final BpmnTaskBackAuditDTO dto; + + public CustomBackTaskAsyncCmd(BpmnTaskBackAuditDTO dto) { + this.dto = dto; + } + + @Override + public String executeInternal(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + TaskService taskService = processEngineConfiguration.getTaskServiceConfiguration().getTaskService(); + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult(); + + TaskEntity taskEntity = taskService.getTask(dto.getTaskId()); + validTask(historicTaskInstance, taskEntity, dto.getApprover(), dto.getNodeTypes()); + + Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId()); + FlowElement targetFlowElement = process.getFlowElement(dto.getToActivityId(), true); + if (Objects.isNull(targetFlowElement)) { + throw new WorkflowEngineException(BACK_TARGET_ACTIVITY_NOT_EXISTS, dto.getToActivityId()); + } + FlowElement sourceFlowElement = process.getFlowElement(taskEntity.getTaskDefinitionKey(), true); + // 退回节点到当前节点不可达到,不允许退回 + if (!BpmnModelUtils.isReachable(process, (FlowNode) targetFlowElement, (FlowNode) sourceFlowElement)) { + throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId()); + } + + return startAsync(processEngineConfiguration, taskEntity); + } + + private String startAsync(ProcessEngineConfigurationImpl processEngineConfiguration, Task task) { + JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService(); + + JobEntity job = jobService.createJob(); + // 这里的 executionId 可为 null + job.setExecutionId(task.getExecutionId()); + job.setProcessInstanceId(task.getProcessInstanceId()); + job.setProcessDefinitionId(task.getProcessDefinitionId()); + job.setElementId(task.getTaskDefinitionKey()); + job.setElementName(task.getName()); + job.setJobHandlerType(AsyncBackTaskJobHandler.TYPE); + job.setTenantId(task.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + return job.getId(); + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBackTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBackTaskCmd.java new file mode 100644 index 000000000..31df9ca27 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBackTaskCmd.java @@ -0,0 +1,108 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; +import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.common.utils.BpmnModelUtils; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.bpmn.model.Process; +import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.flowable.task.service.TaskService; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.BACKED; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_TARGET_ACTIVITY_NOT_EXISTS; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; + +/** + * 回退命令 + */ +@Slf4j +public class CustomBackTaskCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = -1241290344311892346L; + + private final BpmnTaskBackAuditDTO dto; + + private static final String OPERATION_DESC = "回退至"; + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("taskId", dto.getTaskId()); + params.put("advice", dto.getAdvice()); + params.put("operationDesc", OPERATION_DESC); + params.put("attachmentList", JSON.toJSONString(dto.getAttachmentList())); + params.put("approver", JSON.toJSONString(dto.getApprover())); + params.put("nodeTypes", JSON.toJSONString(dto.getNodeTypes())); + return JSON.toJSONString(params); + } + + public CustomBackTaskCmd(BpmnTaskBackAuditDTO dto) { + this.dto = dto; + } + + @Override + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + TaskService taskService = processEngineConfiguration.getTaskServiceConfiguration().getTaskService(); + + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult(); + + TaskEntity task = taskService.getTask(dto.getTaskId()); + validTask(historicTaskInstance, task, dto.getApprover(), dto.getNodeTypes()); + + Process process = ProcessDefinitionUtil.getProcess(task.getProcessDefinitionId()); + FlowElement targetFlowElement = process.getFlowElement(dto.getToActivityId(), true); + if (Objects.isNull(targetFlowElement)) { + throw new WorkflowEngineException(BACK_TARGET_ACTIVITY_NOT_EXISTS, dto.getToActivityId()); + } + FlowElement sourceFlowElement = process.getFlowElement(task.getTaskDefinitionKey(), true); + // 退回节点到当前节点不可达到,不允许退回 + if (!BpmnModelUtils.isReachable(process, (FlowNode) targetFlowElement, (FlowNode) sourceFlowElement)) { + throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId()); + } + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, dto.getAttachmentList(), dto.getApprover()); + + Authentication.setAuthenticatedUserId(dto.getApprover().buildAssigneeId()); + addComment(commandContext, task, COMMENT_TYPE_ADVICE, dto.getAdvice()); + addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, OPERATION_DESC + targetFlowElement.getName()); + Authentication.setAuthenticatedUserId(null); + + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + dto.getTaskId(), BACKED.getStatus()); + + // 移除回退到的指定节点的变量,让 EngineExecutionStartListener 重新计算该节点的人 + runtimeService.removeVariable(task.getProcessInstanceId(), INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + dto.getToActivityId()); + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveActivityIdsToSingleActivityId(Collections.singletonList(task.getTaskDefinitionKey()), dto.getToActivityId()) + .changeState(); + return null; + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBizSpecifyAssigneeToTaskAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBizSpecifyAssigneeToTaskAsyncCmd.java new file mode 100644 index 000000000..af1e5d076 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBizSpecifyAssigneeToTaskAsyncCmd.java @@ -0,0 +1,102 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper; +import cn.axzo.workflow.core.engine.job.AsyncActivitySetAssigneeJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncApproveTaskJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.HistoryService; +import org.flowable.engine.ProcessEngineConfiguration; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.job.service.JobService; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.task.api.Task; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.springframework.util.CollectionUtils; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; +import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_BIZ_SET_ASSIGNEE_ERROR; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_CANT_SET_ASSIGNEE; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.PROCESS_CANT_SET_ASSIGNEE; +import static cn.axzo.workflow.core.engine.cmd.CustomBizSpecifyAssigneeToTaskCmd.getOperateTask; +import static cn.axzo.workflow.core.engine.cmd.CustomBizSpecifyAssigneeToTaskCmd.validProcessInstance; +import static cn.axzo.workflow.core.engine.cmd.CustomBizSpecifyAssigneeToTaskCmd.validate; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerCount; + +/** + * 自定的业务指定审批人命令实现 + * + * @author wangli + * @since 2023/12/22 13:51 + */ +public class CustomBizSpecifyAssigneeToTaskAsyncCmd extends AbstractCommand implements Serializable { + + private final BpmnActivitySetAssigneeDTO dto; + + public CustomBizSpecifyAssigneeToTaskAsyncCmd(BpmnActivitySetAssigneeDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public String execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + TaskService taskService = processEngineConfiguration.getTaskService(); + TaskEntity task = (TaskEntity) getOperateTask(taskService, dto.getTriggerId()); + //校验 + validate(processEngineConfiguration.getRuntimeService(), dto.getTriggerId(), task, dto.getAssigners()); + + validProcessInstance(commandContext, task); + + return startAsync(processEngineConfiguration, task); + } + + private String startAsync(ProcessEngineConfigurationImpl processEngineConfiguration, TaskEntity task) { + JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService(); + + JobEntity job = jobService.createJob(); + // 这里的 executionId 可为 null + job.setExecutionId(task.getExecutionId()); + job.setProcessInstanceId(task.getProcessInstanceId()); + job.setProcessDefinitionId(task.getProcessDefinitionId()); + job.setElementId(task.getTaskDefinitionKey()); + job.setElementName(task.getName()); + job.setJobHandlerType(AsyncActivitySetAssigneeJobHandler.TYPE); + job.setTenantId(task.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + return job.getId(); + } + + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBizSpecifyAssigneeToTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBizSpecifyAssigneeToTaskCmd.java index f108311bf..e6b891e63 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBizSpecifyAssigneeToTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBizSpecifyAssigneeToTaskCmd.java @@ -6,11 +6,14 @@ import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper; import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.HistoryService; +import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.job.api.Job; +import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; import org.flowable.task.api.Task; import org.flowable.task.service.impl.persistence.entity.TaskEntity; import org.springframework.util.CollectionUtils; @@ -59,8 +62,8 @@ public class CustomBizSpecifyAssigneeToTaskCmd extends AbstractCommand public static Task getOperateTask(TaskService taskService, String executionId) { return taskService.createTaskQuery().executionId(executionId) - .taskAssignee(NO_ASSIGNEE) - .singleResult(); + .taskAssignee(NO_ASSIGNEE) + .singleResult(); } /** @@ -79,9 +82,9 @@ public class CustomBizSpecifyAssigneeToTaskCmd extends AbstractCommand @Override public Boolean execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); TaskService taskService = processEngineConfiguration.getTaskService(); - Task task = getOperateTask(taskService, executionId); + TaskEntity task = (TaskEntity) getOperateTask(taskService, executionId); //校验 validate(processEngineConfiguration.getRuntimeService(), executionId, task, addedAssigners); @@ -91,12 +94,29 @@ public class CustomBizSpecifyAssigneeToTaskCmd extends AbstractCommand addAssignee(commandContext, taskService, task); + clearAlterTimeJob(commandContext, task); + return true; } - private void validProcessInstance(CommandContext commandContext, Task task) { + /** + * 清空告警的任务 + * + * @param commandContext + * @param task + */ + private void clearAlterTimeJob(CommandContext commandContext, TaskEntity task) { + ManagementService managementService = CommandContextUtil.getProcessEngineConfiguration(commandContext).getManagementService(); + Job timerJob = managementService.createTimerJobQuery().elementId(task.getTaskDefinitionKey()).processInstanceId(task.getProcessInstanceId()).singleResult(); + if (Objects.nonNull(timerJob)) { + CommandContextUtil.getTimerJobService().deleteTimerJob((TimerJobEntity) timerJob); + } + + } + + public static void validProcessInstance(CommandContext commandContext, Task task) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); HistoryService historyService = processEngineConfiguration.getHistoryService(); HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); if (Objects.isNull(processInstance)) { @@ -110,11 +130,11 @@ public class CustomBizSpecifyAssigneeToTaskCmd extends AbstractCommand private void changeAssigneeSnapshot(CommandContext commandContext, Task task) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); List originAssingeeList = runtimeService.getVariable(task.getProcessInstanceId(), - INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class); + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class); for (BpmnTaskDelegateAssigner assigner : originAssingeeList) { if (Objects.equals(assigner.buildAssigneeId(), NO_ASSIGNEE)) { @@ -124,8 +144,8 @@ public class CustomBizSpecifyAssigneeToTaskCmd extends AbstractCommand } originAssingeeList.addAll(addedAssigners); runtimeService.setVariable(task.getProcessInstanceId(), - INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), - originAssingeeList); + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), + originAssingeeList); } private void addAssignee(CommandContext commandContext, TaskService taskService, Task task) { diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceAsyncCmd.java index 38e8174f8..effb4cebf 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceAsyncCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceAsyncCmd.java @@ -3,7 +3,7 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; -import cn.axzo.workflow.core.engine.job.AsyncCancelProcessInstanceHandler; +import cn.axzo.workflow.core.engine.job.AsyncCancelProcessInstanceJobHandler; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.interceptor.CommandContext; @@ -74,9 +74,9 @@ public class CustomCancelProcessInstanceAsyncCmd extends AbstractCommand i job.setExecutionId(instance.getId()); job.setProcessInstanceId(instance.getId()); job.setProcessDefinitionId(instance.getProcessDefinitionId()); - job.setElementId(AsyncCancelProcessInstanceHandler.TYPE); + job.setElementId(AsyncCancelProcessInstanceJobHandler.TYPE); job.setElementName(instance.getName()); - job.setJobHandlerType(AsyncCancelProcessInstanceHandler.TYPE); + job.setJobHandlerType(AsyncCancelProcessInstanceJobHandler.TYPE); job.setTenantId(instance.getTenantId()); // 携带自定义的数据 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceCmd.java index e235d8fdd..f32129f5c 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceCmd.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.engine.model.AddComment; import cn.axzo.workflow.core.engine.operation.DeleteProcessInstanceOperation; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import com.alibaba.fastjson.JSON; @@ -18,7 +19,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_DELETE_PROCESS_FLAG; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_TENANT_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_ID; @@ -32,7 +32,7 @@ import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INS import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANT_CANCEL; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; -import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.completeVirtualTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVirtualTask; /** @@ -104,12 +104,12 @@ public class CustomCancelProcessInstanceCmd extends AbstractCommand implem runtimeService.setVariables(instance.getId(), variables); CommandContextUtil.getAgenda(commandContext).planOperation(new DeleteProcessInstanceOperation(commandContext, - processInstanceId, extAxHiTaskInstService)); + processInstanceId, extAxHiTaskInstService, CANCELLED)); // 添加自定义的节点,用于展示最后的操作 Task task = createVirtualTask(commandContext, extAxHiTaskInstService, processInstanceId, - "发起人撤回", NODE_CANCEL.getType(), reason, initiator, CANCELLED.getStatus()); - addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, "已撤回"); + "发起人撤回", NODE_CANCEL.getType(), reason, initiator, CANCELLED.getStatus(), new AddComment(CANCELLED.getDesc())); + completeVirtualTask(commandContext, task); return null; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContext.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContext.java index 18e70a31c..5af132216 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContext.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContext.java @@ -1,12 +1,16 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import org.apache.ibatis.exceptions.PersistenceException; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableOptimisticLockingException; import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.util.Arrays; /** * 对 CommandContext 中的 WorkflowEngineException 进行日志降级 @@ -15,7 +19,9 @@ import org.slf4j.LoggerFactory; * @since 2024/5/21 09:46 */ public class CustomCommandContext extends CommandContext { - private static final Logger LOGGER = LoggerFactory.getLogger(CommandContext.class); + private static final Logger LOGGER = LoggerFactory.getLogger(CustomCommandContext.class); + + private static final String[] PERSISTENCE_EXCEPTION_WARN_MESSAGE = new String[]{"act_ru_job"}; public CustomCommandContext(Command command) { super(command); @@ -36,6 +42,10 @@ public class CustomCommandContext extends CommandContext { LOGGER.info("Error while closing command context", exception); } else if (exception instanceof WorkflowEngineException) { LOGGER.warn("Workflow error while closing command context", exception); + } else if (exception instanceof PersistenceException && + StringUtils.hasText(exception.getMessage()) && + Arrays.stream(PERSISTENCE_EXCEPTION_WARN_MESSAGE).anyMatch(m -> exception.getMessage().contains(m))) { + LOGGER.warn("persistence error while closing command context", exception); } else { LOGGER.error("Error while closing command context", exception); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContextFactory.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContextFactory.java index df53139bf..6e3cd116e 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContextFactory.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContextFactory.java @@ -5,7 +5,7 @@ import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.common.engine.impl.interceptor.CommandContextFactory; /** - * TODO + * CommandContextFactory * * @author wangli * @since 2024/5/21 09:45 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommentTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommentTaskCmd.java index 0faf9d4ee..cd34b8cf7 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommentTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommentTaskCmd.java @@ -4,13 +4,11 @@ import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentExtDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; -import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.cfg.IdGenerator; import org.flowable.common.engine.impl.identity.Authentication; -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.TaskService; @@ -22,7 +20,6 @@ import org.flowable.task.service.impl.persistence.entity.TaskEntity; import org.flowable.variable.service.HistoricVariableService; import org.flowable.variable.service.impl.persistence.entity.HistoricVariableInstanceEntity; import org.flowable.variable.service.impl.types.StringType; -import org.springframework.util.StringUtils; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; @@ -36,9 +33,13 @@ import java.util.Objects; import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_COMMENT_EXT; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_COMMENT; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.COMMENTED; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.TASK_CANT_COMMENT_INSTANCE_NOT_EXISTS; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createExtTaskInst; import static org.flowable.task.api.Task.DEFAULT_PRIORITY; /** @@ -80,11 +81,11 @@ public class CustomCommentTaskCmd extends AbstractCommand implements Seria @Override public Void execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); HistoryService historyService = processEngineConfiguration.getHistoryService(); HistoricProcessInstance processInstance = - historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); if (Objects.isNull(processInstance)) { throw new WorkflowEngineException(TASK_CANT_COMMENT_INSTANCE_NOT_EXISTS, processInstanceId); } @@ -103,32 +104,34 @@ public class CustomCommentTaskCmd extends AbstractCommand implements Seria task.setTaskDefinitionKey(NODE_COMMENT.getType()); task.setPriority(DEFAULT_PRIORITY); task.setCreateTime(new Date()); - // 创建临时节点 - taskService.saveTask(task); + // 处理该评论节点的评论人 buildAndInsertHistoryVariable(task, processInstance, processEngineConfiguration); CommandContextUtil.getEntityCache().findInCache(HistoricTaskInstanceEntity.class).stream() - .filter(i -> Objects.equals(i.getId(), task.getId())).findAny() - .ifPresent(i -> i.setAssignee(operator.buildAssigneeId())); - // 完成临时节点 - taskService.complete(task.getId()); + .filter(i -> Objects.equals(i.getId(), task.getId())).findAny() + .ifPresent(i -> i.setAssignee(operator.buildAssigneeId())); + + createExtTaskInst(extAxHiTaskInstService, processInstanceId, + task.getId(), task.getTaskDefinitionKey(), operator, COMMENTED.getStatus()); + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), COMMENTED.getStatus()); + + // 保存临时节点 + taskService.saveTask(task); // 新增评论 Authentication.setAuthenticatedUserId(operator.buildAssigneeId()); - if (StringUtils.hasText(comment)) { - CustomTaskHelper.addComment(commandContext, task, COMMENT_TYPE_ADVICE, comment); - } - if (Objects.nonNull(commentExt)) { - CustomTaskHelper.addComment(commandContext, task, COMMENT_TYPE_COMMENT_EXT, JSONUtil.toJsonStr(commentExt)); - } + addComment(commandContext, task, COMMENT_TYPE_ADVICE, comment); + addComment(commandContext, task, COMMENT_TYPE_COMMENT_EXT, JSONUtil.toJsonStr(commentExt)); Authentication.setAuthenticatedUserId(null); - // 处理附件 - CustomTaskHelper.batchAddAttachment(commandContext, processInstanceId, task.getId(), attachmentList, operator); + batchAddAttachment(commandContext, processInstanceId, task, attachmentList, operator); - CustomTaskHelper.createExtTaskInst(extAxHiTaskInstService, processInstanceId, - task.getId(), task.getTaskDefinitionKey(), operator, COMMENTED.getStatus()); + // 设置快照信息 + task.setTransientVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + task.getId(), operator.toJson()); + + // 完成临时节点 + taskService.complete(task.getId()); return null; } @@ -136,9 +139,9 @@ public class CustomCommentTaskCmd extends AbstractCommand implements Seria HistoricProcessInstance processInstance, ProcessEngineConfigurationImpl processEngineConfiguration) { HistoricVariableService historicVariableService = - processEngineConfiguration.getVariableServiceConfiguration().getHistoricVariableService(); + processEngineConfiguration.getVariableServiceConfiguration().getHistoricVariableService(); HistoricVariableInstanceEntity historicVariableInstance = - historicVariableService.createHistoricVariableInstance(); + historicVariableService.createHistoricVariableInstance(); historicVariableInstance.setTaskId(task.getId()); historicVariableInstance.setExecutionId(task.getExecutionId()); historicVariableInstance.setProcessInstanceId(processInstance.getId()); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCompleteDummyTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCompleteDummyTaskCmd.java index a00361cbf..d193f80cb 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCompleteDummyTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCompleteDummyTaskCmd.java @@ -7,12 +7,12 @@ import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.identity.Authentication; -import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.TaskService; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.task.api.Task; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -65,7 +65,7 @@ public class CustomCompleteDummyTaskCmd extends AbstractCommand implements CommandContextUtil.getProcessEngineConfiguration(commandContext); TaskService taskService = processEngineConfiguration.getTaskService(); - Task task = taskService.createTaskQuery().processInstanceId(processInstanceId) + TaskEntity task = (TaskEntity) taskService.createTaskQuery().processInstanceId(processInstanceId) .taskId(taskId).singleResult(); if (Objects.isNull(task)) { throw new WorkflowEngineException(DUMMY_TASK_NOT_EXISTS, processInstanceId, taskId); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskCmd.java index 7d5f7c2bb..4d5396dd9 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskCmd.java @@ -4,6 +4,7 @@ import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper; +import cn.axzo.workflow.core.engine.model.AddComment; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; @@ -23,12 +24,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_ASSIGNER_SHOW_NUMBER; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.COUNTERSIGN; -import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.completeVirtualTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVirtualTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerCount; @@ -81,24 +81,24 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen @Override public Void execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); HistoricTaskInstanceQuery taskQuery = - processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); HistoricTaskInstance historicTaskInstance = taskQuery.taskId(originTaskId).singleResult(); TaskService taskService = processEngineConfiguration.getTaskService(); - Task task = taskService.createTaskQuery().taskId(originTaskId).singleResult(); + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(originTaskId).singleResult(); - validTask(historicTaskInstance, (TaskEntity) task, originTaskAssignee, null); + validTask(historicTaskInstance, task, originTaskAssignee, null); - validTaskAssignerCount(processEngineConfiguration.getRuntimeService(), (TaskEntity) task, targetTaskAssigneeList); + validTaskAssignerCount(processEngineConfiguration.getRuntimeService(), task, targetTaskAssigneeList); List taskDelegateAssigners = - validTaskAssignerDuplicated(commandContext, (TaskEntity) task, targetTaskAssigneeList); + validTaskAssignerDuplicated(commandContext, task, targetTaskAssigneeList); resolveOriginTask(commandContext, extAxHiTaskInstService, taskService, task); - batchAddAttachment(commandContext, task.getProcessInstanceId(), task.getId(), attachmentList, + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, originTaskAssignee); switch (countersignType) { @@ -110,7 +110,7 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen break; default: // share_counterSign - shareCountSign(commandContext, (TaskEntity) task, taskDelegateAssigners); + shareCountSign(commandContext, task, taskDelegateAssigners); break; } @@ -127,11 +127,11 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen private void shareCountSign(CommandContext commandContext, TaskEntity taskEntity, List taskDelegateAssigners) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); // 这个节点下所有审批人快照 String activityListSnapshot = - INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(); + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(); taskDelegateAssigners.addAll(targetTaskAssigneeList); runtimeService.setVariable(taskEntity.getProcessInstanceId(), activityListSnapshot, taskDelegateAssigners); log.info("正在进行加签任务:{},待加签人合并列表:{}", taskEntity.getId(), JSONUtil.toJsonStr(taskDelegateAssigners)); @@ -142,22 +142,22 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand implemen } private void resolveOriginTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService, - TaskService taskService, Task task) { + TaskService taskService, TaskEntity task) { // 构建评论内容 StringBuilder message = new StringBuilder("添加"); int end = Math.min(targetTaskAssigneeList.size(), COUNTERSIGN_ASSIGNER_SHOW_NUMBER); //加签人员数量显示指定个数 for (int i = 0; i < end; i++) { message.append(targetTaskAssigneeList.get(i).getAssignerName()); - if (i < targetTaskAssigneeList.size() - 1) { + if (i < end - 1) { message.append("、"); } } message.append("等").append(targetTaskAssigneeList.size()).append("人进行审批"); Task virtualTask = createVirtualTask(commandContext, extAxHiTaskInstService, task.getProcessInstanceId(), task.getName(), - task.getTaskDefinitionKey(), advice, originTaskAssignee, COUNTERSIGN.getStatus()); - addComment(commandContext, virtualTask, COMMENT_TYPE_OPERATION_DESC, message.toString()); - batchAddAttachment(commandContext, task.getProcessInstanceId(), task.getId(), attachmentList, originTaskAssignee); + task.getTaskDefinitionKey(), advice, originTaskAssignee, COUNTERSIGN.getStatus(), new AddComment(message.toString())); + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, originTaskAssignee); + completeVirtualTask(commandContext, virtualTask); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java index 259d69036..f73f1ccff 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java @@ -4,6 +4,7 @@ import cn.axzo.workflow.common.enums.BpmnFlowNodeType; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.engine.model.AddComment; import cn.axzo.workflow.core.engine.operation.DeleteProcessInstanceOperation; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import com.alibaba.fastjson.JSON; @@ -23,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_DELETE_PROCESS_FLAG; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_TENANT_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_ID; @@ -31,9 +31,10 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_N import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_DELETE_REASON; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_REJECT; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED; -import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.completeVirtualTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVirtualTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; @@ -69,7 +70,7 @@ public class CustomRejectionTaskCmd extends AbstractCommand implements Ser if (Objects.nonNull(operationDesc)) { this.operationDesc = operationDesc; } else { - this.operationDesc = "已驳回"; + this.operationDesc = "(已驳回)"; } this.attachmentList = dto.getAttachmentList(); this.approver = dto.getApprover(); @@ -97,17 +98,18 @@ public class CustomRejectionTaskCmd extends AbstractCommand implements Ser HistoricTaskInstance historicTaskInstance = taskQuery.taskId(taskId).singleResult(); TaskService taskService = processEngineConfiguration.getTaskService(); - Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult(); - validTask(historicTaskInstance, (TaskEntity) task, approver, nodeTypes); + validTask(historicTaskInstance, task, approver, nodeTypes); - ((TaskEntity) task).setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), REJECTED.getStatus()); - Task virtualTask = createVirtualTask(commandContext, extAxHiTaskInstService, task.getProcessInstanceId(), task.getName(), - task.getTaskDefinitionKey(), advice, Objects.equals(operationDesc, "自动驳回") ? null : approver, REJECTED.getStatus()); + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), DELETED.getStatus()); + TaskEntity virtualTask = createVirtualTask(commandContext, extAxHiTaskInstService, task.getProcessInstanceId(), task.getName(), + task.getTaskDefinitionKey(), advice, + Objects.equals(operationDesc, "自动驳回") ? null : approver, REJECTED.getStatus(), + new AddComment(operationDesc)); - addComment(commandContext, virtualTask, COMMENT_TYPE_OPERATION_DESC, operationDesc); - - batchAddAttachment(commandContext, task.getProcessInstanceId(), virtualTask.getId(), attachmentList, approver); + batchAddAttachment(commandContext, task.getProcessInstanceId(), virtualTask, attachmentList, approver); + completeVirtualTask(commandContext, virtualTask); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); finishProcessInstance(commandContext, runtimeService, task, advice); @@ -125,7 +127,7 @@ public class CustomRejectionTaskCmd extends AbstractCommand implements Ser runtimeService.setVariables(task.getProcessInstanceId(), variables); CommandContextUtil.getAgenda(commandContext) .planOperation(new DeleteProcessInstanceOperation(commandContext, task.getProcessInstanceId(), - extAxHiTaskInstService)); + extAxHiTaskInstService, REJECTED)); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTermNodePausingAlertCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTermNodePausingAlertCmd.java new file mode 100644 index 000000000..3b6a2262a --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTermNodePausingAlertCmd.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.core.engine.cmd; + +import org.flowable.bpmn.model.TimerEventDefinition; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.io.Serializable; + +/** + * 自定义的节点长时间卡住的告警 + * + * @author wangli + * @since 2024-09-11 13:44 + */ +public class CustomTermNodePausingAlertCmd extends AbstractCommand implements Serializable { + @Override + public String paramToJsonString() { + return ""; + } + + @Override + public Void executeInternal(CommandContext commandContext) { + + + return null; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTransferUserTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTransferUserTaskCmd.java index 5cf121307..770238200 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTransferUserTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTransferUserTaskCmd.java @@ -6,7 +6,6 @@ import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import com.alibaba.fastjson.JSON; import com.google.common.collect.Lists; import org.flowable.common.engine.impl.identity.Authentication; -import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; @@ -16,7 +15,6 @@ import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstanceQuery; import org.flowable.task.service.impl.persistence.entity.TaskEntity; -import org.springframework.util.StringUtils; import java.io.Serializable; import java.util.HashMap; @@ -77,52 +75,54 @@ public class CustomTransferUserTaskCmd extends AbstractCommand implements @Override public Void execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); HistoricTaskInstanceQuery taskQuery = - processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); HistoricTaskInstance historicTaskInstance = taskQuery.taskId(originTaskId).singleResult(); TaskService taskService = processEngineConfiguration.getTaskService(); - Task task = taskService.createTaskQuery().taskId(originTaskId).singleResult(); + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(originTaskId).singleResult(); - validTask(historicTaskInstance, (TaskEntity) task, originTaskAssignee, null); + validTask(historicTaskInstance, task, originTaskAssignee, null); - validTaskAssignerDuplicated(commandContext, (TaskEntity) task, Lists.newArrayList(targetTaskAssignee)); + validTaskAssignerDuplicated(commandContext, task, Lists.newArrayList(targetTaskAssignee)); + // 修改节点对应的审批人集合快照信息 processAssignee(processEngineConfiguration, task); + // 对被转交的任务进行建议和附件的处理 resolveOriginTask(commandContext, taskService, task); + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, + originTaskAssignee); - batchAddAttachment(commandContext, task.getProcessInstanceId(), task.getId(), attachmentList, - originTaskAssignee); - - addMultiTask(commandContext, (TaskEntity) task, targetTaskAssignee); - ((TaskEntity) task).setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), TRANSFER.getStatus()); - deleteMultiTask(commandContext, (TaskEntity) task); + // 生成转交任务 + addMultiTask(commandContext, task, targetTaskAssignee); + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), TRANSFER.getStatus()); + // 结束被转交任务 + deleteMultiTask(commandContext, task); return null; } - private void resolveOriginTask(CommandContext commandContext, TaskService taskService, Task task) { + private void resolveOriginTask(CommandContext commandContext, TaskService taskService, TaskEntity task) { BpmnTaskDelegateAssigner assigner = buildDummyAssigner("transfer", TASK_ASSIGNEE_SKIP_FLAT, "dummyApprover"); task.setAssignee(assigner.buildAssigneeId()); - ((TaskEntity) task).setScopeType("TRANSFER"); - taskService.saveTask(task); + task.setScopeType("TRANSFER"); Authentication.setAuthenticatedUserId(originTaskAssignee.buildAssigneeId()); addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, "转交给" + targetTaskAssignee.getAssignerName()); - if (StringUtils.hasLength(advice)) { - addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice); - } + addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice); Authentication.setAuthenticatedUserId(null); + + taskService.saveTask(task); } public void processAssignee(ProcessEngineConfigurationImpl processEngineConfiguration, Task task) { RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); List originAssingeeList = runtimeService.getVariable(task.getProcessInstanceId(), - INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class); + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class); Optional exists = originAssingeeList.stream() - .filter(i -> Objects.equals(i.buildAssigneeId(), targetTaskAssignee.buildAssigneeId())).findAny(); + .filter(i -> Objects.equals(i.buildAssigneeId(), targetTaskAssignee.buildAssigneeId())).findAny(); if (exists.isPresent()) { throw new WorkflowEngineException(ASSIGNEE_HAS_BEEN_EXISTS); } @@ -135,8 +135,8 @@ public class CustomTransferUserTaskCmd extends AbstractCommand implements } originAssingeeList.add(targetTaskAssignee); runtimeService.setVariable(task.getProcessInstanceId(), - INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), - originAssingeeList); + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), + originAssingeeList); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomTaskHelper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomTaskHelper.java index b9a134431..5cb212b31 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomTaskHelper.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/CustomTaskHelper.java @@ -6,6 +6,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.model.AddComment; import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter; @@ -27,7 +28,6 @@ import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; import org.flowable.engine.runtime.Execution; import org.flowable.engine.task.Attachment; -import org.flowable.engine.task.Comment; import org.flowable.engine.task.Event; import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; @@ -55,6 +55,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_R import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE; import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ATTACHMENTS_VAR_NAME; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EMPTY; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; @@ -63,6 +64,7 @@ import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ASSIGNER_NUMBER import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF; import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS; import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_TYPE_MISMATCH; import static org.flowable.task.api.Task.DEFAULT_PRIORITY; /** @@ -154,7 +156,7 @@ public class CustomTaskHelper { //不包含对应的任务 if (!nodeTypes.contains(nodeType)) { // log.warn(TASK_TYPE_MISMATCH.getMessage(), nodeType.getDesc(), nodeTypes.stream().map(BpmnFlowNodeType::getDesc).collect(Collectors.joining(","))); - throw new WorkflowEngineException(ASSIGNER_NUMBER_EXCEEDS_NUMBER_LIMIT, nodeType.getDesc(), nodeTypes.stream().map(BpmnFlowNodeType::getDesc).collect(Collectors.joining(","))); + throw new WorkflowEngineException(TASK_TYPE_MISMATCH, nodeType.getDesc(), nodeTypes.stream().map(BpmnFlowNodeType::getDesc).collect(Collectors.joining(","))); } } } @@ -219,10 +221,10 @@ public class CustomTaskHelper { * * @param commandContext * @param processInstanceId - * @param taskId + * @param task * @param attachmentList */ - public static void batchAddAttachment(CommandContext commandContext, String processInstanceId, String taskId, + public static void batchAddAttachment(CommandContext commandContext, String processInstanceId, TaskEntity task, List attachmentList, BpmnTaskDelegateAssigner assigner) { if (CollectionUtils.isEmpty(attachmentList)) { return; @@ -232,22 +234,30 @@ public class CustomTaskHelper { TaskService taskService = processEngineConfiguration.getTaskService(); Authentication.setAuthenticatedUserId(assigner.buildAssigneeId()); attachmentList.forEach(dto -> { - Attachment attachment = taskService.createAttachment(dto.getType().getType(), taskId, processInstanceId, + Attachment attachment = taskService.createAttachment(dto.getType().getType(), task.getId(), processInstanceId, dto.getName(), dto.getDescription(), dto.getUrl()); taskService.saveAttachment(attachment); }); + task.setTransientVariableLocal(TASK_ATTACHMENTS_VAR_NAME, attachmentList); Authentication.setAuthenticatedUserId(null); } - public static Comment addComment(CommandContext commandContext, String taskId, String processInstanceId, - String type, String message) { + public static void addComment(CommandContext commandContext, String taskId, String processInstanceId, + String type, String message) { TaskEntity task = new TaskEntityImpl(); task.setId(taskId); task.setProcessInstanceId(processInstanceId); - return addComment(commandContext, task, type, message); + addComment(commandContext, task, type, message); } - public static Comment addComment(CommandContext commandContext, Task task, String type, String message) { + public static void addComment(CommandContext commandContext, TaskEntity task, AddComment addComment) { + addComment(commandContext, task, addComment.getCommentType(), addComment.getContent()); + } + + public static void addComment(CommandContext commandContext, TaskEntity task, String type, String message) { + if (!StringUtils.hasText(type) || !StringUtils.hasText(message)) { + return; + } ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); @@ -269,8 +279,7 @@ public class CustomTaskHelper { comment.setFullMessage(message); processEngineConfiguration.getCommentEntityManager().insert(comment); - - return comment; + task.setTransientVariableLocal(type, message); } public static Attachment addAttachment(CommandContext commandContext, Task task, AttachmentDTO attachmentDto) { @@ -313,10 +322,10 @@ public class CustomTaskHelper { * @param extTaskInstStatus 节点状态 * @return */ - public static Task createVirtualTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService + public static TaskEntity createVirtualTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService , String processInstanceId, String nodeName, String taskDefinitionKey, String advice, BpmnTaskDelegateAssigner assigner, - String extTaskInstStatus) { + String extTaskInstStatus, AddComment addComment) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); HistoryService historyService = processEngineConfiguration.getHistoryService(); @@ -336,8 +345,6 @@ public class CustomTaskHelper { task.setTaskDefinitionKey(taskDefinitionKey); task.setPriority(DEFAULT_PRIORITY); task.setCreateTime(new Date()); - // 创建临时节点 - taskService.saveTask(task); if (Objects.nonNull(assigner)) { CommandContextUtil.getEntityCache().findInCache(TaskEntity.class).stream() @@ -351,23 +358,36 @@ public class CustomTaskHelper { // 添加审批意见 addAdvice(commandContext, task, advice, Objects.nonNull(assigner) ? assigner.buildAssigneeId() : null); + // 添加操作描述 + addComment(commandContext, task, addComment); CustomTaskHelper.createExtTaskInst(extAxHiTaskInstService, task.getProcessInstanceId(), task.getId(), task.getTaskDefinitionKey(), assigner, extTaskInstStatus); task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), extTaskInstStatus); + // 保存任务 + taskService.saveTask(task); + if (Objects.nonNull(assigner)) { // 设置快照信息 task.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + task.getId(), assigner.toJson()); } - // 完成临时节点 - taskService.complete(task.getId()); + // 完成临时节点, 1.4.2虚拟节点创建方法不再默认完成,需主动调用 completeVirtualTask 完成 + // taskService.complete(task.getId()); return task; } - private static void addAdvice(CommandContext commandContext, Task task, String comment, String userId) { + public static Task completeVirtualTask(CommandContext commandContext, Task task) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + TaskService taskService = processEngineConfiguration.getTaskService(); + taskService.complete(task.getId()); + return task; + } + + private static void addAdvice(CommandContext commandContext, TaskEntity task, String comment, String userId) { if (StringUtils.hasLength(comment)) { Authentication.setAuthenticatedUserId(userId); addComment(commandContext, task, COMMENT_TYPE_ADVICE, comment); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java index 04671e6f4..1c5d4e989 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java @@ -10,6 +10,8 @@ import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandConfig; import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import java.util.Objects; + import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_ASYNC_COMMAND_EXECUTION_RETRY_GIVE_UP; /** @@ -29,6 +31,7 @@ public class CustomRetryInterceptor extends AbstractCommandInterceptor { public T execute(CommandConfig config, Command command, CommandExecutor commandExecutor) { long waitTime = waitTimeInMs; int failedAttempts = 0; + Throwable lastException = null; do { if (failedAttempts > 0) { log.warn("Waiting for {}ms before retrying the command.", waitTime); @@ -38,9 +41,6 @@ public class CustomRetryInterceptor extends AbstractCommandInterceptor { try { // try to execute the command - if (log.isDebugEnabled()) { - log.debug("assignableFrom result: {}", AbstractCommand.class.isAssignableFrom(command.getClass())); - } if (AbstractCommand.class.isAssignableFrom(command.getClass())) { // 如果在以后,重试三次也不能解决的话, 可以利用这里的拿到的参数,重新自动构造CMD,并执行. log.info("Executing command params: {} traceId:{} ", TraceUtil.traceId(), @@ -50,12 +50,13 @@ public class CustomRetryInterceptor extends AbstractCommandInterceptor { } catch (PersistenceException e) { log.warn("Caught persistence exception: {}", e.getMessage(), e); + lastException = e; } failedAttempts++; } while (failedAttempts <= numOfRetries); - throw new WorkflowEngineException(ENGINE_ASYNC_COMMAND_EXECUTION_RETRY_GIVE_UP, String.valueOf(numOfRetries)); + throw new WorkflowEngineException(ENGINE_ASYNC_COMMAND_EXECUTION_RETRY_GIVE_UP, String.valueOf(numOfRetries), lastException.getMessage()); } protected void waitBeforeRetry(long waitTime) { diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceJobHandler.java similarity index 88% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceHandler.java rename to workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceJobHandler.java index 27b269614..979f305fd 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceHandler.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceJobHandler.java @@ -13,11 +13,11 @@ import org.flowable.job.service.impl.persistence.entity.JobEntity; import org.flowable.variable.api.delegate.VariableScope; @Slf4j -public class AsyncAbortProcessInstanceHandler extends AbstractExecuteWithLockJobHandler implements JobHandler { +public class AsyncAbortProcessInstanceJobHandler extends AbstractExecuteWithLockJobHandler implements JobHandler { public static final String TYPE = "async-abort-instance"; private final ExtAxHiTaskInstService extAxHiTaskInstService; - public AsyncAbortProcessInstanceHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + public AsyncAbortProcessInstanceJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { this.extAxHiTaskInstService = extAxHiTaskInstService; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityLeaveJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityLeaveJobHandler.java index 399513265..ea5f658eb 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityLeaveJobHandler.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityLeaveJobHandler.java @@ -1,6 +1,7 @@ package cn.axzo.workflow.core.engine.job; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; import cn.axzo.workflow.core.service.BpmnProcessActivityService; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; @@ -33,6 +34,6 @@ public class AsyncActivityLeaveJobHandler extends AbstractJobHandler implements public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { log.warn("AsyncActivityLeaveJobHandler exec start..."); BpmnActivityTimeoutTriggerDTO dto = JSON.parseObject(job.getCustomValues(), BpmnActivityTimeoutTriggerDTO.class); - bpmnProcessActivityService.trigger(dto.getTriggerId()); + bpmnProcessActivityService.trigger(BpmnActivityTriggerDTO.builder().async(false).triggerId(dto.getTriggerId()).build()); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivitySetAssigneeJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivitySetAssigneeJobHandler.java new file mode 100644 index 000000000..13b91524e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivitySetAssigneeJobHandler.java @@ -0,0 +1,37 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.core.engine.cmd.CustomBizSpecifyAssigneeToTaskCmd; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.job.service.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.variable.api.delegate.VariableScope; + +/** + * 异步处理业务节点设置审批人的处理器 + * + * @author wangli + * @since 2024-09-09 14:54 + */ +@Slf4j +public class AsyncActivitySetAssigneeJobHandler extends AbstractJobHandler implements JobHandler { + public static String TYPE = "async-activity-set-assignee"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncActivitySetAssigneeJobHandler executing..."); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnActivitySetAssigneeDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnActivitySetAssigneeDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomBizSpecifyAssigneeToTaskCmd(dto.getTriggerId(), dto.getAssigners())); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityTriggerJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityTriggerJobHandler.java new file mode 100644 index 000000000..c59fe7897 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityTriggerJobHandler.java @@ -0,0 +1,37 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; +import cn.axzo.workflow.core.engine.cmd.CustomActivityTriggerCmd; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.job.service.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.variable.api.delegate.VariableScope; + +/** + * 异步处理业务节点触发的处理器 + * + * @author wangli + * @since 2024-09-09 14:36 + */ +@Slf4j +public class AsyncActivityTriggerJobHandler extends AbstractJobHandler implements JobHandler { + public static final String TYPE = "async-activity-trigger"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncActivityTriggerJobHandler executing..."); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnActivityTriggerDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnActivityTriggerDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomActivityTriggerCmd(dto)); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncBackTaskJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncBackTaskJobHandler.java new file mode 100644 index 000000000..ec466959c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncBackTaskJobHandler.java @@ -0,0 +1,41 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; +import cn.axzo.workflow.core.engine.cmd.CustomBackTaskCmd; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.job.service.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.task.api.Task; +import org.flowable.variable.api.delegate.VariableScope; + +import java.util.Objects; + +@Slf4j +public class AsyncBackTaskJobHandler extends AbstractExecuteWithLockJobHandler implements JobHandler { + + public static final String TYPE = "async-back-task"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void executeInternal(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncBackTaskJobHandler executing..."); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnTaskBackAuditDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnTaskBackAuditDTO.class); + Task task = processEngineConfiguration.getTaskService().createTaskQuery().taskId(dto.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + return; + } + processEngineConfiguration.getCommandExecutor().execute(new CustomBackTaskCmd(dto)); + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncBpmnProcessActivityJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncBpmnProcessActivityJobHandler.java deleted file mode 100644 index fffdbb6ba..000000000 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncBpmnProcessActivityJobHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.axzo.workflow.core.engine.job; - -import cn.axzo.workflow.core.service.BpmnProcessActivityService; -import lombok.extern.slf4j.Slf4j; -import org.flowable.common.engine.impl.interceptor.CommandContext; -import org.flowable.job.service.JobHandler; -import org.flowable.job.service.impl.persistence.entity.JobEntity; -import org.flowable.variable.api.delegate.VariableScope; - -@Slf4j -public class AsyncBpmnProcessActivityJobHandler extends AbstractJobHandler implements JobHandler { - - public static final String TYPE = "async-bpmn-process-activity"; - - private final BpmnProcessActivityService activityService; - - public AsyncBpmnProcessActivityJobHandler(BpmnProcessActivityService activityService) { - this.activityService = activityService; - } - - @Override - public String getType() { - return TYPE; - } - - @Override - public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { - log(job); - activityService.executeAsyncJob(job); - } -} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceJobHandler.java similarity index 89% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceHandler.java rename to workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceJobHandler.java index 1557e3b60..6407d79ad 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceHandler.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceJobHandler.java @@ -13,13 +13,13 @@ import org.flowable.job.service.impl.persistence.entity.JobEntity; import org.flowable.variable.api.delegate.VariableScope; @Slf4j -public class AsyncCancelProcessInstanceHandler extends AbstractJobHandler implements JobHandler { +public class AsyncCancelProcessInstanceJobHandler extends AbstractJobHandler implements JobHandler { public static final String TYPE = "async-cancel-process"; private final ExtAxHiTaskInstService extAxHiTaskInstService; - public AsyncCancelProcessInstanceHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + public AsyncCancelProcessInstanceJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { this.extAxHiTaskInstService = extAxHiTaskInstService; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncTermNodeAlterJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncTermNodeAlterJobHandler.java new file mode 100644 index 000000000..897d855e4 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncTermNodeAlterJobHandler.java @@ -0,0 +1,77 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.dto.AlterDTO; +import cn.axzo.workflow.common.model.dto.TermNodePausingDTO; +import cn.axzo.workflow.core.common.utils.SpringContextUtils; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.listener.Alter; +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.job.service.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.task.api.Task; +import org.flowable.variable.api.delegate.VariableScope; + +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_NODE_ALTER; + +/** + * 检查指定节点是否长时间卡住,如果卡住则进行钉钉告警 + * + * @author wangli + * @since 2024-09-11 13:50 + */ +@Slf4j +public class AsyncTermNodeAlterJobHandler extends AbstractJobHandler implements JobHandler { + public static final String TYPE = "term-node-alter-cycle"; + private final SupportRefreshProperties refreshProperties; + + public AsyncTermNodeAlterJobHandler(SupportRefreshProperties refreshProperties) { + this.refreshProperties = refreshProperties; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.warn("AsyncActivityLeaveJobHandler exec start..."); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + JSONObject jsonObject = JSON.parseObject(job.getJobHandlerConfiguration()); + if (!jsonObject.containsKey("activityId")) { + return; + } + String activityId = jsonObject.getString("activityId"); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + TermNodePausingDTO dto = runtimeService.getVariable(job.getProcessInstanceId(), BIZ_NODE_ALTER + activityId, TermNodePausingDTO.class); + TaskService taskService = processEngineConfiguration.getTaskService(); + Task task = taskService.createTaskQuery() + .processInstanceId(dto.getProcessInstanceId()) + .taskDefinitionKey(dto.getActivityId()) + .singleResult(); + if (Objects.isNull(task)) { + return; + } + if (DateUtil.compare(DateUtil.date(), DateUtil.offsetMinute(task.getCreateTime(), refreshProperties.getPauseDelay())) > 0) { + // 发送告警对象 + Alter alter = SpringContextUtils.getBean(Alter.class); + AlterDTO alterDTO = new AlterDTO(); + alterDTO.setProcessInstanceId(dto.getProcessInstanceId()); + alterDTO.setActivityId(dto.getActivityId()); + alterDTO.setTaskId(task.getId()); + alterDTO.setStartTime(task.getCreateTime()); + alterDTO.setPrettyStartTime(DateUtil.formatDateTime(task.getCreateTime())); + alter.invoke(alterDTO); + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineExecutionStartListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineExecutionStartListener.java index b1a66b45b..4a8e8ff8b 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineExecutionStartListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineExecutionStartListener.java @@ -4,12 +4,18 @@ import cn.axzo.workflow.common.constant.BpmnConstants; import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; import cn.axzo.workflow.core.deletage.BpmnTaskAssigneeSelector; import cn.axzo.workflow.core.deletage.BpmnTaskCalculateDTO; import cn.axzo.workflow.core.deletage.BpmnTaskDelegate; import cn.axzo.workflow.core.deletage.MockTaskAssigneeSelector; +import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceAsyncCmd; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import cn.axzo.workflow.core.util.DingTalkUtils; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.google.common.collect.Lists; @@ -20,6 +26,7 @@ import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.ExecutionListener; +import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; @@ -38,6 +45,7 @@ import java.util.Objects; import java.util.Optional; import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVAL_ASSIGNER_LIMIT_NUMBER; +import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_ALLOW_SKIP_USER_TASK; import static cn.axzo.workflow.common.constant.BpmnConstants.DUMMY_ASSIGNEE; import static cn.axzo.workflow.common.constant.BpmnConstants.DUMMY_ASSIGNEE_TYPE; @@ -68,22 +76,17 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.removeDup @Slf4j public class EngineExecutionStartListener implements ExecutionListener { private static final long serialVersionUID = 1L; + @Value("${spring.profiles.active}") + private String profile; @Resource @Deprecated private ObjectProvider bpmTaskDelegate; @Resource @Lazy private List selectors; - @Value("${workflow.api.timeout:10}") - private Long apiTimeout; - @Value("${workflow.mock:false}") - private Boolean mock; - @Value("${workflow.assignee.global:true}") - private Boolean global; - @Value("${workflow.assignee.category:''}") - private String category; - @Value("#{${workflow.assignee.map:{}}}") - private Map assigneeMap; + @Resource + private SupportRefreshProperties refreshProperties; + @Override public void notify(DelegateExecution execution) { @@ -180,6 +183,7 @@ public class EngineExecutionStartListener implements ExecutionListener { } log.info("当前节点id: [{}], name: [{}] 审批人为空, 将执行审批人为空的兜底配置!", userTask.getId(), userTask.getName()); getApproverEmptyHandleType(userTask).ifPresent(type -> { + log.info("节点兜底的配置模式:[{}]", type.getType()); switch (type) { case autoPassed: case autoRejection: @@ -189,19 +193,32 @@ public class EngineExecutionStartListener implements ExecutionListener { case transferToAdmin: assigners.addAll(approverSelect(ApproverEmptyHandleTypeEnum.transferToAdmin.getType(), userTask, execution, true)); + finalEmptyAssigneeHandle(assigners, userTask, execution, "转交管理员失败,系统中止"); break; case specifyAssignee: - List emptyAssignees = - BpmnMetaParserHelper.getEmptyApproverSpecify(userTask) - .map(listStr -> JSON.parseArray(listStr, BpmnTaskDelegateAssigner.class)) - .orElse(Collections.emptyList()); - assigners.addAll(emptyAssignees); + assigners.addAll(approverSelect(ApproverEmptyHandleTypeEnum.specifyAssignee.getType(), userTask, + execution, true)); + finalEmptyAssigneeHandle(assigners, userTask, execution, "转交指定人员失败,系统自动中止"); default: break; } + }); } + private void finalEmptyAssigneeHandle(List assigners, UserTask userTask, DelegateExecution execution, String operationDesc) { + if (CollectionUtils.isEmpty(assigners)) { + CooperationOrgDTO orgScopes = execution.getVariable(BIZ_ORG_RELATION, CooperationOrgDTO.class); + DingTalkUtils.sendDingTalkForTransferToAdminError(profile, execution.getProcessInstanceId(), userTask.getId(), orgScopes); + BpmnProcessInstanceAbortDTO abortDTO = new BpmnProcessInstanceAbortDTO(); + abortDTO.setProcessInstanceId(execution.getProcessInstanceId()); + abortDTO.setReason("转交管理员失败,系统中止"); + CommandContextUtil.getProcessEngineConfiguration().getCommandExecutor() + .execute(new CustomAbortProcessInstanceAsyncCmd(abortDTO)); + } + } + + /** * 根据审批人指定类型查询审批人 * @@ -235,6 +252,10 @@ public class EngineExecutionStartListener implements ExecutionListener { * \"personId\":\"89508\",\"assignee\":\"2000560\",\"assigneeType\":\"3\"}]' * }" */ + Boolean mock = refreshProperties.getMock(); + Boolean global = refreshProperties.getGlobal(); + String category = refreshProperties.getCategory(); + Map assigneeMap = refreshProperties.getAssigneeMap(); if ((mock && global) || (mock && !global && Objects.equals(category, execution.getProcessDefinitionId().split(":")[0]))) { log.info("当前系统 Nacos 配置中开启了 mock: {}, 将使用 mock 方式查找审批人", mock); @@ -308,7 +329,4 @@ public class EngineExecutionStartListener implements ExecutionListener { }); } - public Long getApiTimeout() { - return apiTimeout; - } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/EngineEntityEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/EngineEntityEventListener.java new file mode 100644 index 000000000..7dd04b9e3 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/EngineEntityEventListener.java @@ -0,0 +1,115 @@ +package cn.axzo.workflow.core.engine.listener.entity; + +import com.google.common.collect.ImmutableSet; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.AbstractFlowableEventListener; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEntityEvent; +import org.flowable.common.engine.api.delegate.event.FlowableEvent; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static org.flowable.common.engine.api.delegate.event.FlowableEngineEventType.ENTITY_ACTIVATED; +import static org.flowable.common.engine.api.delegate.event.FlowableEngineEventType.ENTITY_CREATED; +import static org.flowable.common.engine.api.delegate.event.FlowableEngineEventType.ENTITY_DELETED; +import static org.flowable.common.engine.api.delegate.event.FlowableEngineEventType.ENTITY_INITIALIZED; +import static org.flowable.common.engine.api.delegate.event.FlowableEngineEventType.ENTITY_SUSPENDED; +import static org.flowable.common.engine.api.delegate.event.FlowableEngineEventType.ENTITY_UPDATED; + +/** + * TODO + * + * @author wangli + * @since 2024-09-02 15:34 + */ +@Component +@Slf4j +@AllArgsConstructor +public class EngineEntityEventListener extends AbstractFlowableEventListener { + + private final List handles; + public static final Set SUPPORTED = + ImmutableSet.builder() + .add(ENTITY_CREATED) + .add(ENTITY_INITIALIZED) + .add(ENTITY_UPDATED) + .add(ENTITY_DELETED) + .add(ENTITY_SUSPENDED) + .add(ENTITY_ACTIVATED) + .build(); + + @Override + public void onEvent(FlowableEvent event) { + if (event instanceof FlowableEntityEvent && SUPPORTED.contains(event.getType())) { + FlowableEntityEvent entityEvent = (FlowableEntityEvent) event; +// log.warn("entity event type: {}, class: {}",entityEvent.getType(), entityEvent.getEntity().getClass()); + handles.forEach(handle -> { + Object entity = entityEvent.getEntity(); + if (handle.support(entity)) { + Object convert = handle.convert(entity); + if (Objects.equals(event.getType(), ENTITY_CREATED)) { + handle.onCreate(convert); + } else if (Objects.equals(event.getType(), ENTITY_INITIALIZED)) { + handle.onInitialized(convert); + } else if (Objects.equals(event.getType(), ENTITY_UPDATED)) { + handle.onUpdated(convert); + } else if (Objects.equals(event.getType(), ENTITY_DELETED)) { + handle.onDeleted(convert); + } else if (Objects.equals(event.getType(), ENTITY_SUSPENDED)) { + handle.onSuspended(convert); + } else if (Objects.equals(event.getType(), ENTITY_ACTIVATED)) { + handle.onActivated(convert); + } + } + }); +// if (entityEvent.getEntity() instanceof TaskEntity) { +// TaskEntity taskEntity = (TaskEntity) entityEvent.getEntity(); +// log.error("event taskId :{}, taskDefKey: {}", taskEntity.getId(), taskEntity.getTaskDefinitionKey()); +// +// if (Objects.equals(event.getType(), ENTITY_CREATED)) { +// onCreate(taskEntity); +// } else if (Objects.equals(event.getType(), ENTITY_INITIALIZED)) { +// onInitialized(taskEntity); +// } else if (Objects.equals(event.getType(), ENTITY_UPDATED)) { +// onUpdated(taskEntity); +// } else if (Objects.equals(event.getType(), ENTITY_DELETED)) { +// onDeleted(taskEntity); +// } else if (Objects.equals(event.getType(), ENTITY_SUSPENDED)) { +// onSuspended(taskEntity); +// } else if (Objects.equals(event.getType(), ENTITY_ACTIVATED)) { +// onActivated(taskEntity); +// } +// } else if(entityEvent.getEntity() instanceof CommentEntity) { +// CommentEntity commentEntity = (CommentEntity) entityEvent.getEntity(); +// log.error("event taskId :{}", commentEntity.getId()); +// +// if (Objects.equals(event.getType(), ENTITY_CREATED)) { +// onCreate(commentEntity); +// } else if (Objects.equals(event.getType(), ENTITY_INITIALIZED)) { +// onInitialized(commentEntity); +// } else if (Objects.equals(event.getType(), ENTITY_UPDATED)) { +// onUpdated(commentEntity); +// } else if (Objects.equals(event.getType(), ENTITY_DELETED)) { +// onDeleted(commentEntity); +// } else if (Objects.equals(event.getType(), ENTITY_SUSPENDED)) { +// onSuspended(commentEntity); +// } else if (Objects.equals(event.getType(), ENTITY_ACTIVATED)) { +// onActivated(commentEntity); +// } +// } + + } + } + + + + + @Override + public boolean isFailOnException() { + return true; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/EntityEventHandle.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/EntityEventHandle.java new file mode 100644 index 000000000..e3ae053ff --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/EntityEventHandle.java @@ -0,0 +1,27 @@ +package cn.axzo.workflow.core.engine.listener.entity; + +/** + * TODO + * + * @author wangli + * @since 2024-09-06 00:03 + */ +public interface EntityEventHandle { + + boolean support(Object entity); + + T convert(Object entity); + + void onCreate(T entity); + + void onInitialized(T entity); + + void onUpdated(T entity); + + void onDeleted(T entity); + + void onSuspended(T entity); + + void onActivated(T entity); + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/type/CommentEntityEventHandle.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/type/CommentEntityEventHandle.java new file mode 100644 index 000000000..c7e03e86d --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/type/CommentEntityEventHandle.java @@ -0,0 +1,81 @@ +package cn.axzo.workflow.core.engine.listener.entity.type; + +import cn.axzo.workflow.core.engine.listener.entity.EntityEventHandle; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.impl.persistence.entity.CommentEntity; + +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; + +/** + * TODO + * + * @author wangli + * @since 2024-09-06 00:14 + */ +@Slf4j +//@Component +@AllArgsConstructor +public class CommentEntityEventHandle implements EntityEventHandle { + private final ExtAxProcessLogService processLogService; + + @Override + public boolean support(Object entity) { + return entity instanceof CommentEntity; + } + + @Override + public CommentEntity convert(Object entity) { + return (CommentEntity) entity; + } + + @Override + public void onCreate(CommentEntity entity) { + log.info("comment event onCreate: {}", entity.getId()); + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(entity.getProcessInstanceId()); + queryLog.setTaskId(entity.getId()); + ExtAxProcessLog update = new ExtAxProcessLog(); + if (Objects.equals(COMMENT_TYPE_ADVICE, entity.getType())) { + update.setAdvice(entity.getFullMessage()); + } else if (Objects.equals(COMMENT_TYPE_OPERATION_DESC, entity.getType())) { + update.setOperationDesc(entity.getFullMessage()); + } + processLogService.update(queryLog, update); + } + + @Override + public void onInitialized(CommentEntity entity) { + log.info("comment event onInitialized: {}", entity.getId()); + + } + + @Override + public void onUpdated(CommentEntity entity) { + log.info("comment event onUpdated: {}", entity.getId()); + + } + + @Override + public void onDeleted(CommentEntity entity) { + log.info("comment event onDeleted: {}", entity.getId()); + + } + + @Override + public void onSuspended(CommentEntity entity) { + log.info("comment event onSuspended: {}", entity.getId()); + + } + + @Override + public void onActivated(CommentEntity entity) { + log.info("comment event onSuspended: {}", entity.getId()); + + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/type/TaskEntityEventHandle.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/type/TaskEntityEventHandle.java new file mode 100644 index 000000000..c3984bfa9 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/type/TaskEntityEventHandle.java @@ -0,0 +1,241 @@ +package cn.axzo.workflow.core.engine.listener.entity.type; + +import cn.axzo.framework.jackson.utility.JSON; +import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.listener.entity.EntityEventHandle; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION; +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; +import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.nobody; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.AND; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.GENERAL; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.OR; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; +import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.PENDING; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprovalMethod; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNodeType; + +/** + * 同意、评论、加签、转交、驳回、撤回、中止、抄送 + *

+ * 回退 + * + * @author wangli + * @since 2024-09-06 00:02 + */ +@Slf4j +@Component +@AllArgsConstructor +public class TaskEntityEventHandle implements EntityEventHandle { + private final ExtAxProcessLogService processLogService; + + @Override + public boolean support(Object entity) { + return entity instanceof TaskEntity; + } + + @Override + public TaskEntity convert(Object entity) { + return (TaskEntity) entity; + } + + @Override + public void onActivated(TaskEntity taskEntity) { + log.debug("onActivated"); + } + + public void onSuspended(TaskEntity taskEntity) { + log.debug("onSuspended"); + } + + public void onDeleted(TaskEntity taskEntity) { + log.debug("onDeleted"); + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId()); + queryLog.setTaskId(taskEntity.getId()); + queryLog.setOperationDesc(PENDING.getDesc()); + ExtAxProcessLog update = new ExtAxProcessLog(); + + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + BpmnTaskDelegateAssigner assignee = BpmnTaskDelegateAssigner.toObjectCompatible(taskEntity.getVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + taskEntity.getId())); + if (Objects.nonNull(assignee) && !Objects.equals(NO_ASSIGNEE, assignee.buildAssigneeId())) { + update.setAssigneeFull(Lists.newArrayList(assignee)); + update.setAssigneeId(Long.valueOf(assignee.getPersonId())); + update.setAssigneeTenantId(assignee.getTenantId()); + update.setAssigneeName(assignee.getAssignerName()); + update.setAssigneeOuId(assignee.getOuId()); + } + + boolean needDelete = false; + if (Objects.equals(taskEntity.getTaskDefinitionKey(), NODE_STARTER.getType())) { + update.setStatus(APPROVED.getStatus()); + } else { + + Object advice = taskEntity.getTransientVariableLocal(COMMENT_TYPE_ADVICE); + if (Objects.nonNull(advice) && StringUtils.hasText(advice.toString())) { + log.info("COMMENT_TYPE_ADVICE: {}", advice); + update.setAdvice(advice.toString()); + } + Object operationDesc = taskEntity.getTransientVariableLocal(COMMENT_TYPE_OPERATION_DESC); + if (Objects.nonNull(operationDesc) && StringUtils.hasText(operationDesc.toString())) { + log.info("COMMENT_TYPE_OPERATION_DESC: {}", operationDesc); + update.setOperationDesc(Objects.nonNull(assignee) ? assignee.getAssignerName() + operationDesc : operationDesc.toString()); + } else { + update.setOperationDesc(Objects.nonNull(assignee) ? assignee.getAssignerName() : ""); + // 评论节点会给 operationDesc 赋 COMMENTED 的值,所以注释 +// update.setOperationDesc(BpmnProcessInstanceResultEnum.valueOfStatus(completionType).getDesc()); + } + + + String completionType = taskEntity.getVariable(TASK_COMPLETE_OPERATION_TYPE + taskEntity.getId(), String.class); + if (StringUtils.hasText(completionType) && !Objects.equals(DELETED.getStatus(), completionType)) { + log.info("TASK_COMPLETE_OPERATION_TYPE: {}", completionType); + update.setStatus(completionType); + } else { + // 多实例除操作人以外的任务,直接删除日志, 例如一个节点有两个人或签,A 人驳回了,那么 B 人不再需要操作,任务自动删除。而会签也同理 + update.setStatus(DELETED.getStatus());// delete标志着是多实例删除 + needDelete = true; + } + } + update.setEndTime(new Date()); + + // 判断是否抄送节点,如果是的话,需要将抄送人集合放入对应字段 + if (isCarbonCopyNode(queryLog)) { + // 抄送人集合 + List carbonCopies = runtimeService.getVariable(taskEntity.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(), List.class); + update.setAssigneeFull(carbonCopies); + update.setOperationDesc("抄送" + carbonCopies.size() + "人"); + } + + processLogService.update(queryLog, update); + + if (needDelete) { + // 再逻辑删除该记录 + ExtAxProcessLog deleteLog = new ExtAxProcessLog(); + deleteLog.setProcessInstanceId(taskEntity.getProcessInstanceId()); + deleteLog.setTaskId(taskEntity.getId()); + processLogService.delete(deleteLog); + } + } + + private boolean isCarbonCopyNode(ExtAxProcessLog queryLog) { + List logs = processLogService.genericQuery(queryLog); + if (CollectionUtils.isEmpty(logs) || logs.size() != 1) { + return false; + } + return Objects.equals(logs.get(0).getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()); + } + + public void onUpdated(TaskEntity taskEntity) { + log.debug("onUpdated"); + if (Objects.equals(HIDDEN_ASSIGNEE_ID, taskEntity.getAssignee())) { + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId()); + queryLog.setTaskId(taskEntity.getId()); + processLogService.delete(queryLog); + } else { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + List assigneeList = runtimeService.getVariable(taskEntity.getProcessInstanceId(), + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(), List.class); + ListUtils.emptyIfNull(assigneeList).stream().filter(e -> Objects.equals(e.buildAssigneeId(), taskEntity.getAssignee())).findAny() + .ifPresent(assignee -> { + log.debug("审批人: {}", JSON.toJSONString(assignee)); + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId()); + queryLog.setTaskId(taskEntity.getId()); + processLogService.updateAssignee(queryLog, assignee); + }); + } + } + + public void onInitialized(TaskEntity taskEntity) { + log.debug("onInitialized"); + BpmnMetaParserHelper.getButtonConfig(ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId()), taskEntity.getTaskDefinitionKey()) + .ifPresent(buttons -> { + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId()); + queryLog.setTaskId(taskEntity.getId()); + + ExtAxProcessLog updateLog = new ExtAxProcessLog(); + updateLog.setButtonConf(buttons); + processLogService.update(queryLog, updateLog); + }); + } + + public void onCreate(TaskEntity taskEntity) { + log.debug("onCreate"); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + // 记录发起人 + boolean isNodeStarter = Objects.equals(taskEntity.getTaskDefinitionKey(), NODE_STARTER.getType()); + + RepositoryService repositoryService = processEngineConfiguration.getRepositoryService(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(taskEntity.getProcessDefinitionId()); + FlowElement flowElement = bpmnModel.getFlowElement(taskEntity.getTaskDefinitionKey()); + + ExtAxProcessLog log = new ExtAxProcessLog(); + log.setProcessInstanceId(taskEntity.getProcessInstanceId()); + log.setTenantId(taskEntity.getTenantId()); + log.setActivityId(taskEntity.getTaskDefinitionKey()); + log.setActivityName(taskEntity.getName()); + log.setApprovalMethod((isNodeStarter ? nobody : getApprovalMethod(flowElement).orElse(nobody)).getType()); + log.setNodeType((getNodeType(flowElement).orElse(BpmnFlowNodeType.NODE_EMPTY)).getType()); + log.setNodeMode((isNodeStarter ? BpmnFlowNodeMode.GENERAL : getNodeMode(flowElement)).getType()); + log.setTaskId(taskEntity.getId()); + log.setOperationDesc(PENDING.getDesc()); + log.setStartTime(taskEntity.getCreateTime()); + log.setStatus(PROCESSING.getStatus()); + + processLogService.insert(log); + } + + private BpmnFlowNodeMode getNodeMode(FlowElement flowElement) { + BpmnFlowNodeMode node = GENERAL; + if (flowElement instanceof UserTask) { + UserTask userTask = (UserTask) flowElement; + if (userTask.getBehavior() instanceof MultiInstanceActivityBehavior) { + MultiInstanceActivityBehavior behavior = + (MultiInstanceActivityBehavior) userTask.getBehavior(); + node = Objects.equals(AND_SIGN_EXPRESSION, behavior.getCompletionCondition()) ? AND : OR; + } + } + return node; + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/model/AddComment.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/model/AddComment.java new file mode 100644 index 000000000..acbce8564 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/model/AddComment.java @@ -0,0 +1,40 @@ +package cn.axzo.workflow.core.engine.model; + +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; + +/** + * 评论模型 + * + * @author wangli + * @since 2024-09-05 23:18 + */ +public class AddComment { + private String commentType; + private String content; + + public AddComment(String commentType, String content) { + this.commentType = commentType; + this.content = content; + } + + public AddComment(String content) { + this.commentType = COMMENT_TYPE_OPERATION_DESC; + this.content = content; + } + + public String getCommentType() { + return commentType; + } + + public void setCommentType(String commentType) { + this.commentType = commentType; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/operation/DeleteProcessInstanceOperation.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/operation/DeleteProcessInstanceOperation.java index df7346997..d1e14ba0b 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/operation/DeleteProcessInstanceOperation.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/operation/DeleteProcessInstanceOperation.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.operation; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.HistoryService; @@ -16,6 +17,7 @@ import java.util.List; import java.util.Objects; import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_CLOSING_TYPE; /** * 通用的在 Command 内执行删除流程实例时的额外操作 @@ -27,21 +29,25 @@ public class DeleteProcessInstanceOperation extends AbstractOperation { private final String processInstanceId; private final ExtAxHiTaskInstService extAxHiTaskInstService; private String customDeleteReason; + private final BpmnProcessInstanceResultEnum closingType; public DeleteProcessInstanceOperation(CommandContext commandContext, String processInstanceId, - ExtAxHiTaskInstService extAxHiTaskInstService) { + ExtAxHiTaskInstService extAxHiTaskInstService, + BpmnProcessInstanceResultEnum closingType) { super(commandContext, null); this.processInstanceId = processInstanceId; this.extAxHiTaskInstService = extAxHiTaskInstService; + this.closingType = closingType; } public DeleteProcessInstanceOperation(CommandContext commandContext, String processInstanceId, ExtAxHiTaskInstService extAxHiTaskInstService, - String customDeleteReason) { + String customDeleteReason, BpmnProcessInstanceResultEnum closingType) { super(commandContext, null); this.processInstanceId = processInstanceId; this.extAxHiTaskInstService = extAxHiTaskInstService; this.customDeleteReason = customDeleteReason; + this.closingType = closingType; } @Override @@ -65,6 +71,7 @@ public class DeleteProcessInstanceOperation extends AbstractOperation { } RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + runtimeService.setVariableLocal(processInstanceId, PROCESS_CLOSING_TYPE, closingType); runtimeService.deleteProcessInstance(processInstanceId, StringUtils.hasText(customDeleteReason) ? customDeleteReason : HIDDEN_ASSIGNEE_ID); // 将结束流程实例的原因记录下来 // runtimeService.deleteProcessInstance(processInstanceId, reason); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java index 886585c98..f6f7b4259 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java @@ -59,7 +59,7 @@ public class AutoPassTransactionListener implements TransactionListener { pass.setTaskId(delegateTask.getId()); pass.setAdvice(advice); pass.setApprover(assigner); - pass.setOperationDesc("自动通过"); + pass.setOperationDesc("(自动通过)"); String jobId = commandExecutor.execute(commandConfig, new CustomApproveTaskAsyncCmd(pass)); // 重置任务,因为上面的 cmd 和这个 cmd 的 lock 对象不一致 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/Alter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/Alter.java new file mode 100644 index 000000000..5ad691f57 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/Alter.java @@ -0,0 +1,14 @@ +package cn.axzo.workflow.core.listener; + +import cn.axzo.workflow.common.model.dto.AlterDTO; + +/** + * Core 往外转发的钩子 + * + * @author wangli + * @since 2024-09-13 11:33 + */ +public interface Alter { + + void invoke(AlterDTO alterDTO); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalBpmnActivityEventListener_lo_Listener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalBpmnActivityEventListener_lo_Listener.java new file mode 100644 index 000000000..fb136ee72 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalBpmnActivityEventListener_lo_Listener.java @@ -0,0 +1,131 @@ +package cn.axzo.workflow.core.listener.impl; + +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.model.dto.TermNodePausingDTO; +import cn.axzo.workflow.core.common.context.ActivityOperationContext; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.engine.job.AsyncTermNodeAlterJobHandler; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +import cn.axzo.workflow.core.listener.BpmnActivityEventListener; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.TimerEventDefinition; +import org.flowable.engine.ManagementService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.jobexecutor.TimerEventHandler; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.engine.impl.util.TimerUtil; +import org.flowable.job.api.Job; +import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_NODE_ALTER; + +/** + * Core 包内置的活动事件处理,可共用与 Jar 包集成和微服务集成 + *

+ * 该监听主要是监听启动“无人”的业务节点超时告警功能 + * + * @author wangli + * @since 2024-09-11 11:44 + */ +@Slf4j +@Component +@Scope("prototype") +@AllArgsConstructor +public class InternalBpmnActivityEventListener_lo_Listener extends AbstractBpmnEventListener implements BpmnActivityEventListener, Ordered { + private final SupportRefreshProperties refreshProperties; + + @Override + public int getOrder() { + return Integer.MIN_VALUE; + } + + /** + * 节点已启动 + *

+ * 创建一个周期性监控业务节点执行状态的任务 + * + * @param execution + */ + @Override + public void onStart(DelegateExecution execution) { + if (!Boolean.TRUE.equals(refreshProperties.getAlterEnable())) { + return; + } + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + TermNodePausingDTO dto = new TermNodePausingDTO(execution.getProcessInstanceId(), execution.getCurrentActivityId(), refreshProperties.getAlterRetries()); + runtimeService.setVariable(execution.getProcessInstanceId(), BIZ_NODE_ALTER + execution.getCurrentActivityId(), dto); + BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId()); + FlowElement flowElement = bpmnModel.getFlowElement(execution.getCurrentActivityId()); + BpmnMetaParserHelper.getNodeType(flowElement).ifPresent(e -> { + if (Objects.equals(BpmnFlowNodeType.NODE_BUSINESS, e)) { + BpmnMetaParserHelper.getApprovalMethod(flowElement).ifPresent(method -> { + switch (method) { + case nobody: + case bizSpecify: + // FIXME 业务指定审批人,需要在业务设置了人后,清除定时 + TimerEventDefinition timerEventDefinition = new TimerEventDefinition(); + String timeUnit; + switch (refreshProperties.getAlterIntervalUnit()) { + case SECONDS: + timeUnit = "S"; + break; + case HOURS: + timeUnit = "H"; + break; + case DAYS: + timeUnit = "D"; + break; + default: + // MINUTES + timeUnit = "M"; + break; + } + timerEventDefinition.setTimeCycle("R" + refreshProperties.getAlterRetries() + "/PT" + refreshProperties.getAlterInterval() + timeUnit); + + TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, execution.getCurrentFlowElement(), + false, (ExecutionEntity) execution, AsyncTermNodeAlterJobHandler.TYPE, + TimerEventHandler.createConfiguration(execution.getCurrentActivityId(), null, timerEventDefinition.getCalendarName())); + if (timerJob != null) { + CommandContextUtil.getTimerJobService().scheduleTimerJob(timerJob); + } + break; + default: + break; + } + }); + } + }); + } + + /** + * 节点已取消 + * + * @param execution + */ + @Override + public void onEnd(DelegateExecution execution) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + ManagementService managementService = processEngineConfiguration.getManagementService(); + Job timerJob = managementService.createTimerJobQuery() + .elementId(execution.getCurrentActivityId()) + .processInstanceId(execution.getProcessInstanceId()) + .singleResult(); + if (Objects.nonNull(timerJob)) { + CommandContextUtil.getTimerJobService().deleteTimerJob((TimerJobEntity) timerJob); + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProcessLog.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProcessLog.java new file mode 100644 index 000000000..4c5476f90 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProcessLog.java @@ -0,0 +1,114 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.conf.handler.ButtonConfTypeHandler; +import cn.axzo.workflow.core.conf.handler.ListAssigneeTypeHandler; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; +import java.util.List; + +/** + * 审批日志 + * + * @author wangli + * @since 2024-08-30 15:29 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_process_log", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxProcessLog extends BaseEntity { + + /** + * 流程实例 ID + */ + private String processInstanceId; + /** + * 实例归属租户 + */ + private String tenantId; + /** + * 活动节点 ID + */ + private String activityId; + /** + * 活动节点名称 + */ + private String activityName; + /** + * 审批方式:配置审批人/业务指定/业务触发(不含人) + */ + private String approvalMethod; + /** + * 节点类型:审批节点/业务节点/评论节点/抄送节点 + */ + private String nodeType; + /** + * 节点模式:会签/或签 + */ + private String nodeMode; + /** + * 任务 ID + */ + private String taskId; + /** + * 操作建议 + */ + private String advice; + /** + * 操作描述 + */ + private String operationDesc; + /** + * 审批人对象信息 + */ + @TableField(typeHandler = ListAssigneeTypeHandler.class) + private List assigneeFull; + /** + * 审批人标识 + */ + private Long assigneeId; + /** + * 审批人归属租户 + */ + private String assigneeTenantId; + /** + * 审批人姓名 + */ + private String assigneeName; + /** + * 审批人归属单位 + */ + private String assigneeOuId; + /** + * 任务开始时间 + */ + private Date startTime; + /** + * 任务结束时间 + */ + private Date endTime; + /** + * 节点按钮的全量配置 + */ + @TableField(typeHandler = ButtonConfTypeHandler.class) + private BpmnButtonConf buttonConf; + /** + * 任务状态:审批中/通过/驳回/转交/加签/回退 + */ + private String status; + /** + * 扩展字段 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private JSONObject extra; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessLogMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessLogMapper.java new file mode 100644 index 000000000..cf9ebd84f --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessLogMapper.java @@ -0,0 +1,8 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ExtAxProcessLogMapper extends BaseMapperX { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessActivityService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessActivityService.java index fa21f62cb..f47c4bd2e 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessActivityService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessActivityService.java @@ -3,6 +3,7 @@ package cn.axzo.workflow.core.service; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; import org.flowable.job.service.impl.persistence.entity.JobEntity; /** @@ -13,26 +14,14 @@ import org.flowable.job.service.impl.persistence.entity.JobEntity; */ public interface BpmnProcessActivityService { - /** - * 执行异步任务 - * - * @param job 需要执行的任务 - */ - void executeAsyncJob(JobEntity job); - /** * 唤醒业务节点 * - * @param executionId 活动 ID + * @param dto */ - void trigger(String executionId); + void trigger(BpmnActivityTriggerDTO dto); + - /** - * 唤醒业务节点-异步 - * - * @param executionId 活动 ID - */ - void triggerAsync(String executionId); /** * 给指定实例的指定节点重设审批人 @@ -41,13 +30,6 @@ public interface BpmnProcessActivityService { */ void setAssignee(BpmnActivitySetAssigneeDTO dto); - /** - * 给指定实例的指定节点重设审批人 - * - * @param dto - */ - void setAssigneeAsync(BpmnActivitySetAssigneeDTO dto); - /** * 设置指定业务接口继续往下流转的触发时间 * diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessDefinitionService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessDefinitionService.java index 88e3a7648..e67398dcc 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessDefinitionService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessDefinitionService.java @@ -4,6 +4,9 @@ import cn.axzo.workflow.common.model.request.bpmn.definition.BpmnProcessDefiniti import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessDefinitionPageDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowNode; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.repository.Model; import org.flowable.engine.repository.ProcessDefinition; @@ -82,4 +85,12 @@ public interface BpmnProcessDefinitionService { List getProcessDefinitionListByDeploymentIds(Set deploymentIds); void delete(String deploymentId, Boolean cascade); + + List findEndFlowElement(String processDefinitionId); + + List findFlowNodes(String processDefinitionId); + + List findFlowElements(String processDefinitionId); + + List findFlowElementsByIds(String processDefinitionId, List flowElementIds); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceService.java index 82e629a77..4de469d2a 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceService.java @@ -7,18 +7,21 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCar import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.HistoricProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; import com.fasterxml.jackson.databind.node.ObjectNode; import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; import org.flowable.form.api.FormInfo; import javax.annotation.Nullable; @@ -107,7 +110,7 @@ public interface BpmnProcessInstanceService { * 获得流程实例 * * @param processDefinitionId 流程实例的编号 - * @param status 状态 + * @param status 状态 * @link SuspensionState.ACTIVE.getStateCode() */ Boolean updateProcessStatus(String processDefinitionId, Integer status); @@ -143,9 +146,28 @@ public interface BpmnProcessInstanceService { */ List getProcessInstanceNodeForecast(String processInstanceId, String tenantId); + /** + * 对指定流程的指定节点开始推送未来的结点,并结合变量计算正确的分支 + * + * @param processInstanceId 实例编号 + * @param startNodeDefinitionKey 从该节点开始推断后续的节点 + * @param containSelf 是否包含起始节点 + * @param checkAliveThrowException 如果给的实例编号已到终态,不会执行推测,用该参数为 true 时,抛出实例完结的异常信息,如果为 false时,直接返回空集合 + * @return + */ + List getProcessInstanceNodeForecastWithSpecifyTaskDefinitionKey(String processInstanceId, ProcessInstance instance, String startNodeDefinitionKey, Boolean containSelf, Boolean checkAliveThrowException); + List getProcessInstanceNodeFilterForecast(String processInstanceId, String tenantId, List nodeDefinitionKeys); List getTenantIds(); Boolean checkInstanceApprover(BpmnProcessInstanceCheckApproverDTO dto); + + /** + * 获取指定流程实例的日志 + * + * @param dto + * @return + */ + BpmnProcessInstanceLogVO getProcessInstanceLog(BpmnProcessInstanceLogQueryDTO dto); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessJobService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessJobService.java index d3b8db1f9..1bad2f2cd 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessJobService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessJobService.java @@ -5,4 +5,8 @@ public interface BpmnProcessJobService { void executeDeadLetterJobActionByJobId(String jobId); void executeDeadLetterJobActionByProcInstId(String processInstanceId); + + String getDeadLetterJobExceptionStacktrace(String processInstId); + + String getDeadLetterJobExceptionStacktraceByJobId(String jobId); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessTaskService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessTaskService.java index cdcd624df..7e1d18f6f 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessTaskService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessTaskService.java @@ -1,10 +1,11 @@ package cn.axzo.workflow.core.service; -import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskPageSearchDTO; @@ -41,6 +42,11 @@ public interface BpmnProcessTaskService { */ void approveTask(BpmnTaskAuditDTO taskAuditDTO); + /** + * 回退 + */ + void backTask(BpmnTaskBackAuditDTO taskAuditDTO); + /** * 批量同意 * @@ -48,6 +54,12 @@ public interface BpmnProcessTaskService { */ BatchOperationResultVO batchApproveTask(List taskAuditDTOS); + /** + * 回退到指定节点,可以回退节点选项 + * @param taskId 任务id + */ + List getBackOptionalNodes(String taskId); + /** * 驳回 */ diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxProcessLogService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxProcessLogService.java new file mode 100644 index 000000000..ef2791f63 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxProcessLogService.java @@ -0,0 +1,56 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; + +import java.util.List; + +/** + * Api Log 表操作服务 + * + * @author wangli + * @since 2024/4/3 10:40 + */ +public interface ExtAxProcessLogService { + /** + * 新增审批流程日志 + * + * @param log + * @return + */ + Long insert(ExtAxProcessLog log); + + /** + * 根据参数删除指定任务 + * + * @param deleteLog 查询条件 + */ + void delete(ExtAxProcessLog deleteLog); + + /** + * 根据有 ID 的实体直接更新 + * + * @param updateLog + */ + void updateById(ExtAxProcessLog updateLog); + + /** + * 根据条件更新指定对象的列 + * + * @param query + * @param update + */ + void update(ExtAxProcessLog query, ExtAxProcessLog update); + + /** + * 更新指定任务的审批人 + * + * @param updateLog + * @param assignee + */ + void updateAssignee(ExtAxProcessLog updateLog, BpmnTaskDelegateAssigner assignee); + + void updateAssignee(ExtAxProcessLog updateLog, BpmnTaskDelegateAssigner assignee, String operationDesc); + + List genericQuery(ExtAxProcessLog query); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessInstanceAdminPageItemConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessInstanceAdminPageItemConverter.java index 42ff1140f..a4a51262e 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessInstanceAdminPageItemConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessInstanceAdminPageItemConverter.java @@ -30,12 +30,12 @@ import static org.mapstruct.NullValueCheckStrategy.ALWAYS; * @since 2024/1/25 16:31 */ @Mapper( - componentModel = "spring", - nullValueCheckStrategy = ALWAYS, - imports = Arrays.class + componentModel = "spring", + nullValueCheckStrategy = ALWAYS, + imports = Arrays.class ) public interface BpmnProcessInstanceAdminPageItemConverter extends EntityConverter { + HistoricProcessInstance> { @Mapping(target = "processInstanceId", source = "id") @Mapping(target = "processInstanceName", source = "name") @@ -58,8 +58,8 @@ public interface BpmnProcessInstanceAdminPageItemConverter extends EntityConvert if (Objects.equals(PROCESSING.getStatus(), i.getBusinessStatus())) { List flowElements = instanceFlowElementMap.get(i.getId()).stream() - .filter(j -> j instanceof UserTask || j instanceof ReceiveTask || j instanceof ServiceTask) - .collect(Collectors.toList()); + .filter(j -> j instanceof UserTask || j instanceof ReceiveTask || j instanceof ServiceTask) + .collect(Collectors.toList()); vo.setTotalNodeCount(flowElements.size()); // 进行中的节点 @@ -85,7 +85,7 @@ public interface BpmnProcessInstanceAdminPageItemConverter extends EntityConvert vo.setCategoryDesc(category.getLabel()); vo.setWorkspaceTypeCode(category.getWorkspaceTypeCode()); vo.setBusinessStatusDesc(BpmnProcessInstanceResultEnum.valueOfStatus(vo.getBusinessStatus()).getDesc()); - vo.setWorkspaceType(WorkspaceType.getType(Integer.valueOf(vo.getWorkspaceTypeCode()))); + vo.setWorkspaceType(WorkspaceType.getType(Integer.valueOf(Objects.nonNull(vo.getWorkspaceTypeCode()) ? vo.getWorkspaceTypeCode() : "0"))); result.add(vo); }); return result; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessActivityServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessActivityServiceImpl.java index b259166b1..838bbf0ed 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessActivityServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessActivityServiceImpl.java @@ -3,34 +3,31 @@ package cn.axzo.workflow.core.service.impl; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceCmd; +import cn.axzo.workflow.core.engine.cmd.CustomActivityTriggerAsyncCmd; +import cn.axzo.workflow.core.engine.cmd.CustomActivityTriggerCmd; +import cn.axzo.workflow.core.engine.cmd.CustomBizSpecifyAssigneeToTaskAsyncCmd; import cn.axzo.workflow.core.engine.cmd.CustomBizSpecifyAssigneeToTaskCmd; import cn.axzo.workflow.core.engine.cmd.CustomBusinessNodeTimeoutCallbackCmd; import cn.axzo.workflow.core.engine.cmd.CustomBusinessNodeTimeoutTriggerCmd; -import cn.axzo.workflow.core.engine.job.AsyncBpmnProcessActivityJobHandler; import cn.axzo.workflow.core.service.BpmnProcessActivityService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; -import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; -import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandExecutor; import org.flowable.engine.RuntimeService; import org.flowable.engine.runtime.Execution; -import org.flowable.engine.runtime.ProcessInstance; -import org.flowable.job.service.JobService; -import org.flowable.job.service.impl.persistence.entity.JobEntity; import org.flowable.spring.SpringProcessEngineConfiguration; -import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.util.Objects; import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_TRIGGER_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.PROCESS_INSTANCE_IS_NOT_EXIST; @Service @@ -46,42 +43,15 @@ public class BpmnProcessActivityServiceImpl implements BpmnProcessActivityServic @Lazy private SpringProcessEngineConfiguration processEngineConfiguration; - private static final String JOB_TRIGGER_ASYNC_NAME = "asyncTrigger"; - private static final String JOB_ASSIGNEE_ASYNC_NAME = "asyncSetAssignee"; - @Override - public void executeAsyncJob(JobEntity job) { - if (job == null) { - log.warn("job is null"); - throw new NullPointerException("job is null"); + @Transactional(rollbackFor = Exception.class) + public void trigger(BpmnActivityTriggerDTO dto) { + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + if (Boolean.TRUE.equals(dto.getAsync())) { + commandExecutor.execute(new CustomActivityTriggerAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomActivityTriggerCmd(dto)); } - String type = job.getElementName(); - String customValues = job.getCustomValues(); - switch (type) { - case JOB_TRIGGER_ASYNC_NAME: - trigger(customValues); - break; - case JOB_ASSIGNEE_ASYNC_NAME: - BpmnActivitySetAssigneeDTO dto = JSONUtil.toBean(customValues, BpmnActivitySetAssigneeDTO.class); - setAssignee(dto); - break; - default: - throw new UnsupportedOperationException(String.format("不支持'%s'类型操作", type)); - } - } - - @Override - public void trigger(String executionId) { - Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult(); - if (Objects.isNull(execution)) { - throw new WorkflowEngineException(ACTIVITY_TRIGGER_NOT_EXISTS, executionId); - } - runtimeService.trigger(executionId); - } - - @Override - public void triggerAsync(String executionId) { - validateAndStartAsyncJob(executionId, executionId, JOB_TRIGGER_ASYNC_NAME); } @Override @@ -95,43 +65,12 @@ public class BpmnProcessActivityServiceImpl implements BpmnProcessActivityServic commandExecutor.execute(new CustomAbortProcessInstanceCmd(execution.getProcessInstanceId(), null, "业务未指定审批人", extAxHiTaskInstService)); return; } - commandExecutor.execute(new CustomBizSpecifyAssigneeToTaskCmd(dto.getTriggerId(), dto.getAssigners())); - } - @Override - public void setAssigneeAsync(BpmnActivitySetAssigneeDTO dto) { - //查询任务 - Task task = CustomBizSpecifyAssigneeToTaskCmd.getOperateTask(processEngineConfiguration.getTaskService(), dto.getTriggerId()); - //先校验 - CustomBizSpecifyAssigneeToTaskCmd.validate(processEngineConfiguration.getRuntimeService(), dto.getTriggerId(), task, dto.getAssigners()); - validateAndStartAsyncJob(dto.getTriggerId(), dto, JOB_ASSIGNEE_ASYNC_NAME); - } - - private void validateAndStartAsyncJob(String executionId, Object customValue, String actionName) { - processEngineConfiguration.getCommandExecutor().execute((Command) commandContext -> { - Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult(); - if (Objects.isNull(execution)) { - throw new WorkflowEngineException(ACTIVITY_TRIGGER_NOT_EXISTS, executionId); - } - ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult(); - if (Objects.isNull(processInstance)) { - throw new WorkflowEngineException(PROCESS_INSTANCE_IS_NOT_EXIST, execution.getId()); - } - JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService(); - JobEntity job = jobService.createJob(); - // 这里的 executionId 可为 null - job.setExecutionId(executionId); - job.setProcessInstanceId(execution.getProcessInstanceId()); - job.setProcessDefinitionId(processInstance.getProcessDefinitionId()); - job.setElementId(AsyncBpmnProcessActivityJobHandler.TYPE); - job.setElementName(actionName); - job.setJobHandlerType(AsyncBpmnProcessActivityJobHandler.TYPE); - job.setTenantId(execution.getTenantId()); - job.setCustomValues(JSONUtil.toJsonStr(customValue)); - jobService.createAsyncJob(job, false); - jobService.scheduleAsyncJob(job); - return null; - }); + if(Boolean.TRUE.equals(dto.getAsync())) { + commandExecutor.execute(new CustomBizSpecifyAssigneeToTaskAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomBizSpecifyAssigneeToTaskCmd(dto.getTriggerId(), dto.getAssigners())); + } } @Override diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessDefinitionServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessDefinitionServiceImpl.java index 55956d45c..e2df82aa3 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessDefinitionServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessDefinitionServiceImpl.java @@ -14,6 +14,9 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowNode; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.RepositoryService; import org.flowable.engine.repository.Deployment; @@ -27,6 +30,7 @@ import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; @@ -282,4 +286,50 @@ public class BpmnProcessDefinitionServiceImpl implements BpmnProcessDefinitionSe } return repositoryService.createDeploymentQuery().deploymentId(id).singleResult(); } + + @Override + public List findEndFlowElement(String processDefinitionId) { + return getNodesByType(processDefinitionId, EndEvent.class); + } + + @Override + public List findFlowNodes(String processDefinitionId) { + return getNodesByType(processDefinitionId, FlowNode.class); + } + + @Override + public List findFlowElements(String processDefinitionId) { + return getNodesByType(processDefinitionId, FlowElement.class); + } + + @Override + public List findFlowElementsByIds(String processDefinitionId, List flowElementIds) { + if (CollectionUtils.isEmpty(flowElementIds)) { + return emptyList(); + } + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(processDefinitionId) + .singleResult(); + if (processDefinition == null) { + return emptyList(); + } + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + return flowElementIds.stream().map(bpmnModel::getFlowElement).collect(Collectors.toList()); + } + + private List getNodesByType(String processDefinitionId, Class clazz) { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(processDefinitionId) + .singleResult(); + if (processDefinition == null) { + return emptyList(); + } + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + Collection flowNodes = bpmnModel.getMainProcess().findFlowElementsOfType(clazz); + if (CollectionUtils.isEmpty(flowNodes)) { + return emptyList(); + } + return new ArrayList<>(flowNodes); + } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java index 50ef58cf9..426b1f7d6 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java @@ -1,8 +1,15 @@ package cn.axzo.workflow.core.service.impl; import cn.axzo.workflow.common.constant.BpmnConstants; +import cn.axzo.workflow.common.enums.ApprovalMethodEnum; +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.enums.WorkspaceType; +import cn.axzo.workflow.common.model.request.BpmnApproveConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO; @@ -10,9 +17,11 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCar import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.request.category.CategorySearchDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; @@ -20,10 +29,12 @@ import cn.axzo.workflow.common.model.response.bpmn.BatchOperationItemResultVO; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.HistoricProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceLogVO; import cn.axzo.workflow.common.model.response.category.CategoryItemVO; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.common.utils.BpmnCollectionUtils; @@ -34,10 +45,12 @@ import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd; import cn.axzo.workflow.core.engine.cmd.CustomCarbonCopyUserSelectorCmd; import cn.axzo.workflow.core.engine.cmd.CustomForecastUserTaskAssigneeCmd; import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; import cn.axzo.workflow.core.service.BpmnProcessInstanceService; import cn.axzo.workflow.core.service.CategoryService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; import cn.axzo.workflow.core.service.converter.BpmnHistoricProcessInstanceConverter; import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter; import cn.axzo.workflow.core.service.converter.BpmnProcessInstanceAdminPageItemConverter; @@ -77,6 +90,7 @@ import org.flowable.engine.runtime.NativeActivityInstanceQuery; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstanceBuilder; import org.flowable.engine.runtime.ProcessInstanceQuery; +import org.flowable.engine.task.Attachment; import org.flowable.form.api.FormInfo; import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.task.api.Task; @@ -95,6 +109,7 @@ import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -109,6 +124,10 @@ import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_S import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION; import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CARBON_COPY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CURRENT; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_HISTORY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.CREATE_INSTANCE_PARAMS; import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; @@ -125,8 +144,14 @@ import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.AND; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.EXCEPTIONAL; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.GENERAL; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.OR; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_BUSINESS; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CARBON_COPY; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_TASK; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED; import static cn.axzo.workflow.common.enums.WorkspaceType.GOVERNMENT; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; @@ -187,6 +212,8 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic @Resource @Lazy private BpmnProcessInstanceService bpmnProcessInstanceService; + @Resource + private ExtAxProcessLogService processLogService; @Override public HistoricProcessInstance getProcessInstanceByBusinessKey(String businessKey, @Nullable String tenantId, @@ -844,6 +871,30 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic return getProcessInstanceNodeFilterForecast(processInstanceId, tenantId, Collections.emptyList()); } + @Override + public List getProcessInstanceNodeForecastWithSpecifyTaskDefinitionKey(String processInstanceId, + ProcessInstance instance, + String startNodeDefinitionKey, + Boolean containSelf, + Boolean checkAliveThrowException) { + if (Objects.isNull(instance)) { + instance = runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId).singleResult(); + if (Objects.isNull(instance)) { + if (checkAliveThrowException) { + throw new WorkflowEngineException(RUNNING_INSTANCE_ONLY_FORECAST); + } else { + return Collections.emptyList(); + } + } + } + + List flowElements = forecastService.performProcessForecasting(instance.getProcessInstanceId(), instance, startNodeDefinitionKey, containSelf); + + return buildNodeDetailVos(processInstanceId, Collections.emptyList(), instance, flowElements); + } + + @Override public List getProcessInstanceNodeFilterForecast(String processInstanceId, String tenantId, List nodeDefinitionKeys) { ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery() @@ -857,6 +908,10 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic } List flowElements = forecastService.performProcessForecasting(processInstanceId, instance); + return buildNodeDetailVos(processInstanceId, nodeDefinitionKeys, instance, flowElements); + } + + private List buildNodeDetailVos(String processInstanceId, List nodeDefinitionKeys, ProcessInstance instance, List flowElements) { BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId()); List resultList = new ArrayList<>(flowElements.size()); // 发起人节点,也是一个 UserTask 节点, 所以这里默认就包含了发起人节点 @@ -961,4 +1016,361 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic .list(); return !CollectionUtils.isEmpty(list); } + + @Override + public BpmnProcessInstanceLogVO getProcessInstanceLog(BpmnProcessInstanceLogQueryDTO dto) { + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(dto.getProcessInstanceId()) + .includeProcessVariables().singleResult(); + if (Objects.isNull(historicProcessInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, dto.getProcessInstanceId()); + } + + ExtAxProcessLog query = new ExtAxProcessLog(); + query.setProcessInstanceId(dto.getProcessInstanceId()); + List logs = processLogService.genericQuery(query).stream() + .sorted(Comparator.comparing(ExtAxProcessLog::getEndTime, Comparator.nullsLast(Comparator.naturalOrder()))) + .collect(Collectors.toList()); + + List forecasting = new ArrayList<>(); + // 只有还在运行中的实例才需要推测后续节点 + if (Objects.equals(historicProcessInstance.getBusinessStatus(), PROCESSING.getStatus())) { + ProcessInstance instance = runtimeService.createProcessInstanceQuery() + .processInstanceId(dto.getProcessInstanceId()) + .includeProcessVariables() + .singleResult(); + logs.stream().reduce((f, s) -> s).ifPresent(e -> forecasting.addAll( + getProcessInstanceNodeForecastWithSpecifyTaskDefinitionKey(dto.getProcessInstanceId(), instance, e.getActivityId(), false, false)) + ); + } + + BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId()); + + Map variables = historicProcessInstance.getProcessVariables(); + BpmnProcessInstanceLogVO logVO = BpmnProcessInstanceLogVO.builder() + .id(historicProcessInstance.getId()) + .name(historicProcessInstance.getName()) + .result(BpmnProcessInstanceResultEnum.valueOfStatus(historicProcessInstance.getBusinessStatus())) + .startTime(historicProcessInstance.getStartTime()) + .endTime(historicProcessInstance.getEndTime()) + .processDefinitionKey(historicProcessInstance.getProcessDefinitionKey()) + .processDefinitionId(historicProcessInstance.getProcessDefinitionId()) + .businessKey(historicProcessInstance.getBusinessKey()) + .businessStatus(historicProcessInstance.getBusinessStatus()) + .initiator(BpmnTaskDelegateAssigner.toObjectCompatible(Optional.ofNullable(variables.getOrDefault(INTERNAL_INITIATOR, null)) + .orElse(variables.getOrDefault(OLD_INTERNAL_INITIATOR, null)))) + .tenantId(historicProcessInstance.getTenantId()) + .agented((Boolean) Optional.ofNullable(variables.get(INTERNAL_PROCESS_AGENT)).orElse(false)) + .taskDetails(genericTaskLogVos(historicProcessInstance.getId(), logs, forecasting, dto)) + .defaultButtonConf(getButtonConfig(bpmnModel.getMainProcess()).orElse(new BpmnButtonConf())) + .supportBatchOperation(getProcessApproveConf(bpmnModel.getMainProcess()).orElse(new BpmnApproveConf()).getSupportBatchOperation()) + .userAgreeSignature(getProcessApproveConf(bpmnModel.getMainProcess()).orElse(new BpmnApproveConf()).getUserAgreeSignature()) + .workflowEngineVersion((String) variables.getOrDefault(WORKFLOW_ENGINE_VERSION, FLOW_SERVER_VERSION_121)) + .build(); + + + categoryService.get(BPM_MODEL_CATEGORY, historicProcessInstance.getProcessDefinitionKey()).ifPresent(category -> { + logVO.setWorkspaceType(WorkspaceType.getType(Integer.valueOf(category.getWorkspaceTypeCode()))); + logVO.setCategory(category.getValue()); + }); + + // 根据传入的访问人计算有权限的按钮 + calcAuthorizedButtons(logVO, dto.getVisitor()); + return logVO; + } + + private void calcAuthorizedButtons(BpmnProcessInstanceLogVO logVO, BpmnTaskDelegateAssigner visitor) { + List authorizedButtons = new ArrayList<>(); + if (Objects.nonNull(logVO.getDefaultButtonConf()) + && !CollectionUtils.isEmpty(logVO.getDefaultButtonConf().getCarbonCopy())) { + authorizedButtons.addAll(logVO.getDefaultButtonConf().getCarbonCopy()); + } + + if (Objects.equals(PROCESSING, logVO.getResult()) && Objects.nonNull(visitor)) { + String ge130Assignee = getGe130Assignee(visitor); + String le130Assignee = getLe130Assignee(visitor); + + // 运行到的当前节点的按钮配置 + logVO.getTaskDetails().stream() + .filter(i -> Objects.equals(PROCESSING, i.getResult())) + .findFirst() + .ifPresent(i -> logVO.setCalculatingButtonConf(i.getButtonConf())); + + + // 比对发起人 + if (Objects.nonNull(logVO.getInitiator()) && + (Objects.equals(logVO.getInitiator().buildAssigneeId_1_2_1(), le130Assignee) + || logVO.getInitiator().buildAssigneeId().contains(ge130Assignee))) { + authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_INITIATOR)); + } + + + // 比对当前审批人 + logVO.getTaskDetails().stream().filter(i -> Objects.equals(PROCESSING, i.getResult()) + || (Objects.equals(DELETED, i.getResult()) && Objects.isNull(i.getEndTime()))) + .findFirst() + .map(i -> { + List list = new ArrayList<>(); + if (Objects.nonNull(i.getAssigneeSnapshot())) { + list.add(i.getAssigneeSnapshot()); + } + if (!CollectionUtils.isEmpty(i.getForecastAssignees())) { + list.addAll(i.getForecastAssignees()); + } + return list; + }) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee)) + .findAny() + .ifPresent(i -> authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_CURRENT))); + + + // 比对历史审批人 + logVO.getTaskDetails().stream() + .filter(i -> Objects.equals(i.getNodeType(), NODE_TASK) || Objects.equals(i.getNodeType(), NODE_BUSINESS)) + .filter(i -> !Objects.equals(PROCESSING, i.getResult())) + .map(BpmnTaskInstanceLogVO::getAssigneeSnapshot) + .filter(Objects::nonNull) + .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee)) + .findAny() + .ifPresent(i -> authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_HISTORY))); + + // 比对抄送人 + logVO.getTaskDetails().stream() + .filter(i -> Objects.equals(i.getNodeType(), NODE_CARBON_COPY)) + .flatMap(i -> ListUtils.emptyIfNull(i.getForecastAssignees()).stream()) + .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee)) + .findAny() + .ifPresent(i -> authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_CARBON_COPY))); + } + + logVO.setCurrentUserButtons(authorizedButtons); + // 有权限访问的自定义按钮 + List customButtonKeys = authorizedButtons.stream() + .filter(i -> Objects.equals(i.getType(), "CUSTOM")) + .map(BpmnButtonMetaInfo::getBtnKey) + .distinct().collect(Collectors.toList()); + List customButtons = logVO.getDefaultButtonConf().getInitiator().stream() + .filter(i -> Objects.equals(i.getType(), "CUSTOM")) + .filter(i -> !customButtonKeys.contains(i.getBtnKey())) + .collect(Collectors.toList()); + logVO.setCustomHiddenButtons(customButtons); + } + + /** + * 按钮的通用处理, 有限使用节点的按钮配置,如果没有则按兜底按钮配置 + * + * @param logVO 该对象中的 calcButtonConf 字段为当前节点的按钮配置 + * @param buttonConfigName String CONFIG_BUTTON_TYPE_INITIATOR = "initiator"; + * String CONFIG_BUTTON_TYPE_CURRENT = "current"; + * String CONFIG_BUTTON_TYPE_HISTORY = "history"; + * String CONFIG_BUTTON_TYPE_CARBON_COPY = "carbonCopy"; + * @return + */ + private List chooseButtons(BpmnProcessInstanceLogVO logVO, String buttonConfigName) { + List mergeButtons = new ArrayList<>(); + if (Objects.isNull(logVO.getCalculatingButtonConf())) { + BpmnButtonConf defaultButtonConf = logVO.getDefaultButtonConf(); + if (Objects.isNull(defaultButtonConf)) { + return mergeButtons; + } + logVO.setCalculatingButtonConf(defaultButtonConf); + } + switch (buttonConfigName) { + case CONFIG_BUTTON_TYPE_INITIATOR: + mergeButtons.addAll(logVO.getCalculatingButtonConf().getInitiator()); + break; + case CONFIG_BUTTON_TYPE_CURRENT: + mergeButtons.addAll(logVO.getCalculatingButtonConf().getCurrent()); + break; + case CONFIG_BUTTON_TYPE_HISTORY: + mergeButtons.addAll(logVO.getCalculatingButtonConf().getHistory()); + break; + case CONFIG_BUTTON_TYPE_CARBON_COPY: + mergeButtons.addAll(logVO.getCalculatingButtonConf().getCarbonCopy()); + break; + default: + break; + } + return mergeButtons; + } + + public static String getLe130Assignee(BpmnTaskDelegateAssigner visitor) { + return visitor.getTenantId() + "|" + visitor.getAssignee() + "|" + visitor.getAssigneeType(); + } + + public static String getGe130Assignee(BpmnTaskDelegateAssigner visitor) { + // String ge130Assignee = contextInfo.getOuId() + "|" + contextInfo.getUserInfo().getPersonId(); + // 130版本以上,产品要求仅校验 personId + return "|" + visitor.getPersonId(); + } + + private List genericTaskLogVos(String processInstanceId, + List logs, + List forecasting, + BpmnProcessInstanceLogQueryDTO dto) { + List tasks = new ArrayList<>(); + Map> attachmentByTaskMap = + taskService.getProcessInstanceAttachments(processInstanceId).stream() + .collect(Collectors.groupingBy(Attachment::getTaskId)); + // 已完成的和进行中的 + getHistoricTasks(logs, tasks, attachmentByTaskMap, dto.getVisitor()); + // 未来节点 + getFutureTasks(forecasting, tasks); + // 处理是否加密 + handleEncrypt(dto.getEncrypt(), tasks); + // reset field forecastAssignees Empty to null; + resetCollectionToNull(tasks); + return tasks; + } + + private void resetCollectionToNull(List tasks) { + tasks.forEach(i -> { + if (ListUtils.emptyIfNull(i.getForecastAssignees()).isEmpty()) { + i.setForecastAssignees(null); + } + }); + } + + private static void handleEncrypt(Boolean encrypt, List tasks) { + if (Boolean.FALSE.equals(encrypt)) { + return; + } + tasks.forEach(i -> { + if (Objects.equals(NODE_STARTER.getType(), i.getTaskDefinitionKey())) { + i.setOperationDesc(i.getAssigneeSnapshot().getAssignerName()); + } else if (Objects.equals(i.getResult(), APPROVED)) { + i.setOperationDesc(APPROVED.getDesc()); + } else if (Objects.equals(i.getResult(), REJECTED)) { + i.setOperationDesc(REJECTED.getDesc()); + } else if (Objects.equals(i.getResult(), PROCESSING) || Objects.isNull(i.getTaskId())) { + i.setOperationDesc("待处理"); + } else { + i.setOperationDesc("已处理"); + } + // 统一将多人节点数据全部置空 + i.setForecastAssignees(null); + // 统一将签名数据置空 + i.setSignatureUrl(null); + }); + } + + private static void getFutureTasks(List forecasting, List tasks) { + ListUtils.emptyIfNull(forecasting).forEach(e -> { + BpmnTaskInstanceLogVO build = BpmnTaskInstanceLogVO.builder() + .taskDefinitionKey(e.getId()) + .name(e.getName()) + .approvalMethod(e.getApprovalMethod()) + .nodeType(e.getNodeType()) + .nodeMode(e.getNodeMode()) + .forecastAssignees(e.getForecastAssigners()) + .build(); + if (Objects.nonNull(e.getApprovalMethod())) { + switch (e.getApprovalMethod()) { + case bizSpecify: + case nobody: + build.setOperationDesc("待处理"); + break; + case autoPassed: + case autoRejection: + break; + case human: + if (Objects.equals(e.getNodeMode(), EXCEPTIONAL)) { + build.setOperationDesc("节点异常"); + } else { + int countPerson = e.getForecastAssigners().size(); + if (Objects.equals(BpmnFlowNodeMode.AND, e.getNodeMode())) { + build.setOperationDesc(countPerson + "人会签,需要全部同意"); + } else if (Objects.equals(BpmnFlowNodeMode.OR, e.getNodeMode())) { + build.setOperationDesc(countPerson + "人或签,仅一人同意即可"); + } + } + break; + } + } + if (Objects.equals(e.getNodeType(), NODE_CARBON_COPY)) { + build.setOperationDesc("抄送" + e.getForecastAssigners().size() + "人"); + } + tasks.add(build); + }); + } + + private void getHistoricTasks(List logs, + List tasks, + Map> attachmentByTaskMap, + BpmnTaskDelegateAssigner visitor) { + ListUtils.emptyIfNull(logs).forEach(e -> { + Optional processingTask = tasks.stream().filter(i -> Objects.equals(PROCESSING, i.getResult())) + .filter(i -> Objects.equals(i.getTaskDefinitionKey(), e.getActivityId())).findAny(); + + if (processingTask.isPresent()) { + // 多实例的情况,需要合并节点 + processingTask.ifPresent(i -> { + List assigners = new ArrayList<>(ListUtils.emptyIfNull(i.getForecastAssignees())); + if (CollectionUtils.isEmpty(assigners)) { + if (Objects.nonNull(i.getAssigneeSnapshot())) { + assigners.add(i.getAssigneeSnapshot()); + } + } + assigners.add(BpmnTaskDelegateAssigner.toObjectCompatible(CollectionUtils.isEmpty(ListUtils.emptyIfNull(e.getAssigneeFull())) ? null : e.getAssigneeFull().get(0))); + switch (i.getNodeMode()) { + case AND: + i.setOperationDesc(assigners.size() + "人会签,需要全部同意"); + break; + case OR: + i.setOperationDesc(assigners.size() + "人或签,仅一人同意即可"); + break; + default: + // 不修改操作描述 + break; + } + i.setAssigneeSnapshot(null); + i.setForecastAssignees(assigners); + i.setButtonConf(e.getButtonConf()); + // 根据当前登录人重设聚合后的节点 taskId + assigners.stream().filter(user -> Objects.equals(user.getPersonId(), visitor.getPersonId())).findFirst() + .ifPresent(user -> i.setTaskId(e.getTaskId())); + }); + } else { + tasks.add(BpmnTaskInstanceLogVO.builder() + .taskId(e.getTaskId()) + .taskDefinitionKey(e.getActivityId()) + .name(e.getActivityName()) + .createTime(e.getStartTime()) + .endTime(e.getEndTime()) + .approvalMethod(ApprovalMethodEnum.valueOfType(e.getApprovalMethod())) + .nodeType(BpmnFlowNodeType.valueOfType(e.getNodeType())) + .nodeMode(BpmnFlowNodeMode.valueOfType(e.getNodeMode())) + .result(BpmnProcessInstanceResultEnum.valueOfStatus(e.getStatus())) + .operationDesc(e.getOperationDesc()) + .advice(e.getAdvice()) + .commentExt("") + .buttonConf(e.getButtonConf()) + .imageList(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.image)) + .fileList(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.file)) + .signatureUrl(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.signature).stream().findFirst().orElse(new AttachmentDTO()).getUrl()) + .assigneeSnapshot(Objects.equals(e.getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()) ? null : + BpmnTaskDelegateAssigner.toObjectCompatible(CollectionUtils.isEmpty(ListUtils.emptyIfNull(e.getAssigneeFull())) ? null : e.getAssigneeFull().get(0))) + .forecastAssignees(Objects.equals(e.getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()) ? ListUtils.emptyIfNull(e.getAssigneeFull()) : Collections.emptyList()) + .build()); + } + }); + + } + + public List getAttachmentByType(Map> attachmentByTaskMap, String taskId, AttachmentTypeEnum type) { + return ListUtils.emptyIfNull(attachmentByTaskMap.get(taskId)).stream() + .filter(attachment -> Objects.equals(type.getType(), attachment.getType())) + .map(e -> AttachmentDTO.builder() + .id(e.getId()) + .type(type) + .name(e.getName()) + .description(e.getDescription()) + .url(e.getUrl()) + .build()) + .collect(Collectors.toList()); + } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessJobServiceImp.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessJobServiceImp.java index c320b7a73..c336f5f9a 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessJobServiceImp.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessJobServiceImp.java @@ -8,6 +8,7 @@ import org.flowable.engine.ManagementService; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.job.api.Job; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.List; @@ -39,6 +40,31 @@ public class BpmnProcessJobServiceImp implements BpmnProcessJobService { } } + @Override + public String getDeadLetterJobExceptionStacktrace(String processInstId) { + List jobs = managementService.createDeadLetterJobQuery().processInstanceId(processInstId).list(); + if (CollectionUtils.isEmpty(jobs)) { + return ""; + } + StringBuilder builder = new StringBuilder(); + jobs.forEach(job -> { + String deadLetterJobExceptionStacktrace = managementService.getDeadLetterJobExceptionStacktrace(job.getId()); + if (StringUtils.hasText(deadLetterJobExceptionStacktrace)) { + builder.append(deadLetterJobExceptionStacktrace); + builder.append("\n"); + } + }); + return builder.toString(); + } + + @Override + public String getDeadLetterJobExceptionStacktraceByJobId(String jobId) { + if (!StringUtils.hasText(jobId)) { + return ""; + } + return managementService.getDeadLetterJobExceptionStacktrace(jobId); + } + protected Job getDeadLetterJobById(String jobId) { Job job = managementService.createDeadLetterJobQuery().jobId(jobId).singleResult(); if (job == null) { diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskServiceImpl.java index 86f7d06f8..8930b24a0 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskServiceImpl.java @@ -1,11 +1,16 @@ package cn.axzo.workflow.core.service.impl; +import cn.axzo.framework.domain.ServiceException; import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; @@ -25,6 +30,8 @@ import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskAsyncCmd; import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd; +import cn.axzo.workflow.core.engine.cmd.CustomBackTaskAsyncCmd; +import cn.axzo.workflow.core.engine.cmd.CustomBackTaskCmd; import cn.axzo.workflow.core.engine.cmd.CustomCommentTaskCmd; import cn.axzo.workflow.core.engine.cmd.CustomCompleteDummyTaskCmd; import cn.axzo.workflow.core.engine.cmd.CustomCountersignUserTaskAsyncCmd; @@ -38,6 +45,7 @@ import cn.axzo.workflow.core.engine.event.MessagePushEventBuilder; import cn.axzo.workflow.core.engine.event.MessagePushEventImpl; import cn.axzo.workflow.core.engine.event.MessagePushEventType; import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; import cn.axzo.workflow.core.service.BpmnProcessTaskService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter; @@ -48,7 +56,11 @@ import cn.axzo.workflow.core.service.converter.BpmnTaskTodoPageItemConverter; import cn.hutool.core.collection.CollUtil; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.flowable.bpmn.model.BaseElement; import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; import org.flowable.common.engine.impl.identity.Authentication; import org.flowable.common.engine.impl.interceptor.CommandExecutor; @@ -80,7 +92,6 @@ import org.flowable.variable.service.impl.persistence.entity.HistoricVariableIns import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import javax.annotation.Resource; @@ -89,11 +100,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -103,16 +116,27 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_COMMEN import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; +import static cn.axzo.workflow.common.constant.BpmnConstants.MAX_BACKED_OPERATE_COUNT; import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE; import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT; import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_BUSINESS; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CARBON_COPY; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EMPTY; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_TASK; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.BACKED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.valueOfStatus; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; +import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; +import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_TASK_NOT_EXISTS; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE; import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.FIND_TASK_BY_PERSON_ID_ERROR; +import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.REACHED_BACKED_MAXIMUM_NUM; import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_REMIND_ERROR_NOT_EXISTS; import static cn.axzo.workflow.core.common.utils.BpmnCollectionUtils.convertSet; import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql; @@ -151,6 +175,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { @Resource @Lazy private BpmnProcessTaskService bpmnProcessTaskService; + @Resource + private BpmnProcessDefinitionService bpmnProcessModelService; @Override public BpmPageResult getTodoTaskPage(BpmnTaskPageSearchDTO dto) { @@ -158,8 +184,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { Long resultTotalCount; if (CollectionUtils.isEmpty(dto.getResults())) { HistoricTaskInstanceQuery query = - historyService.createHistoricTaskInstanceQuery().unfinished().taskAssignee(dto.getUserId()) // 分配给自己 - .orderByTaskCreateTime().desc(); + historyService.createHistoricTaskInstanceQuery().unfinished().taskAssignee(dto.getUserId()) // 分配给自己 + .orderByTaskCreateTime().desc(); populateQuery(dto, query); tasks = query.listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize()); resultTotalCount = query.count(); @@ -178,7 +204,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { // 获得 ProcessInstance Map Map processInstanceMap = - processInstanceService.getProcessInstanceMap(processInstanceIds); + processInstanceService.getProcessInstanceMap(processInstanceIds); List vos = todoPageItemConverter.toVos(tasks, processInstanceMap); return new BpmPageResult<>(vos, resultTotalCount); @@ -192,8 +218,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { if (CollectionUtils.isEmpty(dto.getResults())) { // 查询已办任务 HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().finished() // 已完成 - .taskAssignee(dto.getUserId()) // 分配给自己 - .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 + .taskAssignee(dto.getUserId()) // 分配给自己 + .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序 populateQuery(dto, query); // 执行查询 tasks = query.listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize()); @@ -210,7 +236,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { } Set processInstanceIds = convertSet(tasks, HistoricTaskInstance::getProcessInstanceId); Map historicProcessInstanceMap = - processInstanceService.getHistoricProcessInstanceMap(processInstanceIds); + processInstanceService.getHistoricProcessInstanceMap(processInstanceIds); List vos = donePageItemConverter.toVos(tasks, historicProcessInstanceMap); return new BpmPageResult<>(vos, resultTotalCount); } @@ -234,7 +260,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { NativeHistoricTaskInstanceQuery nativeQuery = historyService.createNativeHistoricTaskInstanceQuery(); String tableName = managementService.getTableName(HistoricTaskInstance.class); baseQuerySql.append("SELECT a.* FROM ").append(tableName).append(" a JOIN " + "ACT_HI_PROCINST b").append(" " + - "ON b.PROC_INST_ID_ = a.PROC_INST_ID_"); + "ON b.PROC_INST_ID_ = a.PROC_INST_ID_"); if (Objects.nonNull(dto.getTenantId())) { baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" b.TENANT_ID_ = #{tenantId}").append(sqlConnectors(baseQuerySql)).append(" a.TENANT_ID_ = #{tenantId}"); @@ -253,7 +279,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { } if (StringUtils.hasLength(dto.getCategories())) { List categories = - Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList()); + Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList()); baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" a.CATEGORY_ in ("); for (int i = 0; i < categories.size(); i++) { baseQuerySql.append("#{category").append(i).append("}"); @@ -294,7 +320,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { } if (StringUtils.hasLength(dto.getCategories())) { List categories = - Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList()); + Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList()); query.processCategoryIn(categories); } } @@ -303,13 +329,140 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { @Transactional(rollbackFor = Exception.class) public void approveTask(BpmnTaskAuditDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); - if (dto.getAsync()) { + if (Boolean.TRUE.equals(dto.getAsync())) { commandExecutor.execute(new CustomApproveTaskAsyncCmd(dto)); } else { commandExecutor.execute(new CustomApproveTaskCmd(dto)); } } + @Override + @Transactional(rollbackFor = Exception.class) + public void backTask(BpmnTaskBackAuditDTO dto) { + List backOptionalNodes = getBackOptionalNodes(dto.getTaskId()); + if (CollectionUtils.isEmpty(backOptionalNodes)) { + throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId()); + } + List activityList = backOptionalNodes.stream().map(BpmnOptionalNodeDTO::getProcessActivityId).collect(Collectors.toList()); + if (!activityList.contains(dto.getToActivityId())) { + throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId()); + } + Optional instOpt = backOptionalNodes.stream() + .map(BpmnOptionalNodeDTO::getProcessInstanceId) + .filter(StringUtils::hasText) + .findAny(); + if (instOpt.isPresent()) { + ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); + searchDTO.setProcessInstanceId(instOpt.get()); + List extAxHiTaskInsts = extAxHiTaskInstService.queryList(searchDTO); + if (CollectionUtils.isNotEmpty(extAxHiTaskInsts)) { + long backOpeCount = extAxHiTaskInsts.stream() + .filter(ext -> BACKED.getStatus().equals(ext.getStatus())) + .count(); + if (backOpeCount > MAX_BACKED_OPERATE_COUNT) { + throw new WorkflowEngineException(REACHED_BACKED_MAXIMUM_NUM, String.valueOf(MAX_BACKED_OPERATE_COUNT)); + } + } + } + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + if (Boolean.TRUE.equals(dto.getAsync())) { + commandExecutor.execute(new CustomBackTaskAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomBackTaskCmd(dto)); + } + } + + @Override + public List getBackOptionalNodes(String taskId) { + Task task = processEngineConfiguration.getTaskService().createTaskQuery().taskId(taskId).singleResult(); + if (task == null) { + throw new WorkflowEngineException(PROCESS_TASK_NOT_EXISTS); + } + String processInstanceId = task.getProcessInstanceId(); + //1.获取当前的流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (processInstance == null) { + //流程为空,已经结束,返回空 + throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS); + } + List tasks = this.getHistoricTaskListByProcessInstanceId(processInstanceId, null); + tasks = tasks.stream() + .filter(t -> t.getNodeType() == NODE_STARTER || t.getNodeType() == NODE_TASK || t.getNodeType() == NODE_BUSINESS) + .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(tasks)) { + return Collections.emptyList(); + } + tasks.sort(Comparator.comparing(BpmnHistoricTaskInstanceVO::getCreateTime)); + LinkedList>> executedList = new LinkedList<>(); + for (BpmnHistoricTaskInstanceVO vo : tasks) { + Pair> last = org.springframework.util.CollectionUtils.isEmpty(executedList) ? null : executedList.getLast(); + if (last != null && last.getLeft().equals(vo.getTaskDefinitionKey())) { + last.getRight().add(vo); + continue; + } + ArrayList objects = new ArrayList<>(); + objects.add(vo); + executedList.addLast(Pair.of(vo.getTaskDefinitionKey(), objects)); + } + List>> valuableList = new LinkedList<>(); + for (Pair> pair : executedList) { + List taskInstanceVOList = pair.getRight(); + Optional backTaskOpt = taskInstanceVOList + .stream() + .filter(t -> t.getResult() != null) + .filter(t -> t.getResult() == BpmnProcessInstanceResultEnum.BACKED) + .findFirst(); + if (backTaskOpt.isPresent()) { + String deleteReason = backTaskOpt.get().getDeleteReason(); + String changeParentActivityTo = deleteReason + .replace("Change parent activity to ", "") + .replace("Change activity to ", ""); + if (org.springframework.util.CollectionUtils.isEmpty(valuableList)) { + throw new ServiceException("状态异常,首个节点进行了退回操作"); + } + int j = valuableList.size() - 1; + for (; j >= 0; j--) { + Pair> vPair = valuableList.get(j); + if (vPair.getLeft().equals(changeParentActivityTo)) { + break; + } + } + valuableList = valuableList.subList(0, j); + } else { + valuableList.add(pair); + } + } + List flowElements = bpmnProcessModelService.findFlowElements(processInstance.getProcessDefinitionId()); + Map flowElementMap = flowElements.stream().collect(Collectors.toMap(BaseElement::getId, f -> f)); + AtomicInteger index = new AtomicInteger(0); + List resultList = valuableList + .stream() + .filter(pair -> !task.getTaskDefinitionKey().equals(pair.getLeft())) //排除当前节点 + .map(pair -> flowElementMap.get(pair.getLeft())) + .filter(flowElement -> { + BpmnFlowNodeType currentNodeType = BpmnMetaParserHelper.getNodeType(flowElement).orElse(NODE_EMPTY); + return currentNodeType == NODE_TASK || currentNodeType == NODE_BUSINESS; + }) + .filter(flowElement -> !NODE_STARTER.getType().equals(flowElement.getId())) + .map(flowElement -> BpmnOptionalNodeDTO + .builder() + .processInstanceId(task.getProcessInstanceId()) + .processDefinitionId(task.getProcessDefinitionId()) + .processActivityId(flowElement.getId()) + .processActivityName(flowElement.getName()) + .processNodeDesc(flowElement.getName()) + .nodeType(BpmnMetaParserHelper.getNodeType(flowElement).orElse(NODE_EMPTY)) + .ordinal(index.incrementAndGet()) + .build()) + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(resultList)) { + BpmnOptionalNodeDTO bpmnOptionalNodeDTO = resultList.get(resultList.size() - 1); + bpmnOptionalNodeDTO.setProcessNodeDesc(bpmnOptionalNodeDTO.getProcessActivityName() + "(上一步)"); + } + resultList.sort((o1, o2) -> o2.getOrdinal() - o1.getOrdinal()); + return resultList; + } + @Override public BatchOperationResultVO batchApproveTask(List dtos) { return batchOperation(dtos, bpmnProcessTaskService::approveTask); @@ -335,13 +488,13 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { public List getHistoricTaskListByProcessInstanceId(String processInstanceId, String tenantId) { HistoricTaskInstanceQuery query = - historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId); + historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId); if (StringUtils.hasLength(tenantId)) { query.taskTenantId(tenantId); } HistoricProcessInstanceQuery instanceQuery = - historyService.createHistoricProcessInstanceQuery() - .processInstanceId(processInstanceId); + historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId); // .includeProcessVariables(); if (StringUtils.hasLength(tenantId)) { instanceQuery.processInstanceTenantId(tenantId); @@ -352,67 +505,72 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { } List taskInstances = query.orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序 - .list(); + .list(); taskInstances.forEach(task -> ((HistoricTaskInstanceEntity) task).setCreateTime(((HistoricTaskInstanceEntity) task).getLastUpdateTime())); taskInstances.sort(Comparator.comparing(p -> ((HistoricTaskInstanceEntity) p).getLastUpdateTime())); Map variableInstanceMap = - // 不能使用框架提供的历史变量 API 查询,有 BUG - historyService.createNativeHistoricVariableInstanceQuery() - .sql("select * from ACT_HI_VARINST t where t.proc_inst_id_= #{processInstanceId}") - .parameter("processInstanceId", processInstanceId) - .list().stream() - .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, Function.identity(), - (s, t) -> s)); + // 不能使用框架提供的历史变量 API 查询,有 BUG + historyService.createNativeHistoricVariableInstanceQuery() + .sql("select * from ACT_HI_VARINST t where t.proc_inst_id_= #{processInstanceId}") + .parameter("processInstanceId", processInstanceId) + .list().stream() + .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, Function.identity(), + (s, t) -> s)); HistoricVariableInstance instanceVersion = variableInstanceMap.getOrDefault(WORKFLOW_ENGINE_VERSION, null); // 过滤了多实例或签自动完成的任务 List vos = historicTaskInstanceConverter.toVosSkipSystemOperation(taskInstances, - Objects.isNull(instanceVersion) ? null : - ((HistoricVariableInstanceEntityImpl) instanceVersion).getTextValue()); + Objects.isNull(instanceVersion) ? null : + ((HistoricVariableInstanceEntityImpl) instanceVersion).getTextValue()); Map> commentByTaskIdMap = - taskService.getProcessInstanceComments(processInstanceId).stream() - .collect(Collectors.groupingBy(Comment::getTaskId)); + taskService.getProcessInstanceComments(processInstanceId).stream() + .collect(Collectors.groupingBy(Comment::getTaskId)); Map> attachmentByTaskIdMap = - taskService.getProcessInstanceAttachments(processInstanceId).stream() - .collect(Collectors.groupingBy(Attachment::getTaskId)); + taskService.getProcessInstanceAttachments(processInstanceId).stream() + .collect(Collectors.groupingBy(Attachment::getTaskId)); ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); searchDTO.setProcessInstanceId(processInstanceId); Map extTaskInstMap = extAxHiTaskInstService.queryList(searchDTO).stream() - .collect(Collectors.toMap(ExtAxHiTaskInst::getTaskId, Function.identity(), (s, t) -> s)); + .collect(Collectors.toMap(ExtAxHiTaskInst::getTaskId, Function.identity(), (s, t) -> s)); BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId()); + List resultList = new ArrayList<>(); for (BpmnHistoricTaskInstanceVO vo : vos) { ExtAxHiTaskInst taskInst = extTaskInstMap.getOrDefault(vo.getTaskId(), new ExtAxHiTaskInst()); + if (Objects.equals(taskInst.getStatus(), DELETED.getStatus())) { + continue; + } + resultList.add(vo); vo.setResult(valueOfStatus(taskInst.getStatus())); List taskComments = commentByTaskIdMap.getOrDefault(vo.getTaskId(), Collections.emptyList()); // 处理 advice taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId())) - .filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_ADVICE)).findFirst() - .ifPresent(i -> vo.setAdvice(i.getFullMessage())); + .filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_ADVICE)).findFirst() + .ifPresent(i -> vo.setAdvice(i.getFullMessage())); // 处理 operationDesc taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId())) - .filter(i -> Objects.equals(COMMENT_TYPE_OPERATION_DESC, i.getType())) - .max(Comparator.comparing(Comment::getTime)) - .ifPresent(i -> vo.setOperationDesc(i.getFullMessage())); + .filter(i -> Objects.equals(COMMENT_TYPE_OPERATION_DESC, i.getType())) + .max(Comparator.comparing(Comment::getTime)) + .ifPresent(i -> vo.setOperationDesc(i.getFullMessage())); // 处理 CommentExt taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId())) - .filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_COMMENT_EXT)).findFirst() - .ifPresent(i -> vo.setCommentExt(i.getFullMessage())); + .filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_COMMENT_EXT)).findFirst() + .ifPresent(i -> vo.setCommentExt(i.getFullMessage())); List attachments = attachmentByTaskIdMap.getOrDefault(vo.getTaskId(), Collections.emptyList()); vo.setAttachments(attachmentConverter.toVos(attachments)); HistoricVariableInstance assginerSnapshot = - variableInstanceMap.getOrDefault(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + vo.getTaskId(), - null); + variableInstanceMap.getOrDefault(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + vo.getTaskId(), + null); if (Objects.isNull(assginerSnapshot)) { assginerSnapshot = - variableInstanceMap.getOrDefault(OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + vo.getTaskId(), null); + variableInstanceMap.getOrDefault(OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + vo.getTaskId(), null); } BpmnTaskDelegateAssigner assigner = null; @@ -426,17 +584,17 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { vo.setNodeType(nodeType); if (Objects.equals(NODE_CARBON_COPY, nodeType)) { HistoricVariableInstance carbonUsers = - variableInstanceMap.getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + vo.getTaskDefinitionKey(), null); + variableInstanceMap.getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + vo.getTaskDefinitionKey(), null); vo.setForecastAssignees(Objects.isNull(carbonUsers) ? Collections.emptyList() : - (List) carbonUsers.getValue()); + (List) carbonUsers.getValue()); } else { vo.setForecastAssignees(Collections.emptyList()); } }); BpmnMetaParserHelper.getApprovalMethod(bpmnModel.getFlowElement(vo.getTaskDefinitionKey())) - .ifPresent(vo::setApprovalMethod); + .ifPresent(vo::setApprovalMethod); } - return vos; + return resultList; } /** @@ -448,11 +606,11 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { */ @Override public List getHistoricTaskListGroupByProcessInstanceId(String processInstanceId - , String tenantId) { + , String tenantId) { List vos = getHistoricTaskListByProcessInstanceId(processInstanceId, tenantId); Map> voMapByTaskDefKey = - vos.stream().collect(Collectors.groupingBy(BpmnHistoricTaskInstanceVO::getTaskDefinitionKey)); + vos.stream().collect(Collectors.groupingBy(BpmnHistoricTaskInstanceVO::getTaskDefinitionKey)); List groupVos = new ArrayList<>(); for (Map.Entry> entry : voMapByTaskDefKey.entrySet()) { @@ -490,9 +648,9 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { } List vos = bpmnTaskConverter.toVos(query.list()); List snapshotTaskIds = - vos.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).collect(Collectors.toList()); + vos.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).collect(Collectors.toList()); Map instanceMap = runtimeService.getVariableInstances(processInstanceId, - snapshotTaskIds); + snapshotTaskIds); vos.forEach(i -> i.setAssigner(BpmnTaskDelegateAssigner.toObjectCompatible(instanceMap.get(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).getValue()))); return vos; } @@ -526,7 +684,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { commandExecutor.execute(new CustomTransferUserTaskAsyncCmd(dto)); } else { commandExecutor.execute(new CustomTransferUserTaskCmd(dto.getTaskId(), - dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssigner())); + dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssigner())); } } @@ -568,8 +726,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { public void commentTask(BpmnTaskCommentDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); commandExecutor.execute(new CustomCommentTaskCmd(dto.getProcessInstanceId(), - dto.getOperator(), dto.getComment(), dto.getCommentExt(), dto.getAttachmentList(), - extAxHiTaskInstService)); + dto.getOperator(), dto.getComment(), dto.getCommentExt(), dto.getAttachmentList(), + extAxHiTaskInstService)); } @Override @@ -577,8 +735,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { public void attachmentTask(BpmnTaskAttachmentDTO dto, String assignee) { Authentication.setAuthenticatedUserId(assignee); Attachment attachment = taskService.createAttachment(dto.getType(), dto.getTaskId(), - dto.getProcessInstanceId(), dto.getName(), dto.getDescription(), - dto.getUrl()); + dto.getProcessInstanceId(), dto.getName(), dto.getDescription(), + dto.getUrl()); taskService.saveAttachment(attachment); Authentication.setAuthenticatedUserId(null); } @@ -592,8 +750,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { commandExecutor.execute(new CustomCountersignUserTaskAsyncCmd(dto)); } else { commandExecutor.execute(new CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum.valueOfType(dto.getCountersignType()), dto.getTaskId(), - dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(), - extAxHiTaskInstService)); + dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(), + extAxHiTaskInstService)); } } @@ -614,22 +772,22 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); ProcessInstance processInstance = - runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult(); + runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult(); Optional noticeConfig = - BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId())); + BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId())); List assigners = - (List) runtimeService.getVariable(dto.getProcessInstanceId(), - INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + dto.getTaskDefinitionKey()); + (List) runtimeService.getVariable(dto.getProcessInstanceId(), + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + dto.getTaskDefinitionKey()); // 过滤出未审批的任何,用选择的方式去发送消息 dto.getRemindTypes().forEach(type -> { list.forEach(task -> { assigners.stream().filter(i -> Objects.equals(task.getAssignee(), i.buildAssigneeId())).findAny().ifPresent(assigner -> { MessagePushEventImpl event = MessagePushEventBuilder.createEvent(MessagePushEventType.valueOf(type), - Lists.newArrayList(assigner), noticeConfig.orElse(null), - processInstance.getProcessInstanceId(), - processInstance.getProcessDefinitionKey(), - processInstance.getTenantId(), task.getId()); + Lists.newArrayList(assigner), noticeConfig.orElse(null), + processInstance.getProcessInstanceId(), + processInstance.getProcessDefinitionKey(), + processInstance.getTenantId(), task.getId()); event.setProcessInstanceId(processInstance.getProcessInstanceId()); event.setTenantId(processInstance.getTenantId()); event.setTaskId(task.getId()); @@ -644,8 +802,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { public String createRobotTask(BpmnRobotTaskCreateDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); return commandExecutor.execute(new CustomCreateDummyTaskCmd(dto.getProcessInstanceId(), - dto.getRobotNode().getFlowNodeName(), dto.getRobotNode().getOperationDesc(), dto.getApprover(), - extAxHiTaskInstService)); + dto.getRobotNode().getFlowNodeName(), dto.getRobotNode().getOperationDesc(), dto.getApprover(), + extAxHiTaskInstService)); } @Override @@ -653,17 +811,17 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { public void completeRobotTask(BpmnRobotTaskCompleteDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); commandExecutor.execute(new CustomCompleteDummyTaskCmd(dto.getProcessInstanceId(), dto.getTaskId(), - Objects.isNull(dto.getRobotNode()) ? null : dto.getRobotNode().getFlowNodeName(), - Objects.isNull(dto.getRobotNode()) ? null : dto.getRobotNode().getOperationDesc(), - extAxHiTaskInstService)); + Objects.isNull(dto.getRobotNode()) ? null : dto.getRobotNode().getFlowNodeName(), + Objects.isNull(dto.getRobotNode()) ? null : dto.getRobotNode().getOperationDesc(), + extAxHiTaskInstService)); } @Override public String findTaskIdByInstanceIdAndPersonId(String processInstanceId, String personId) { List list = taskService.createTaskQuery().processInstanceId(processInstanceId) - .taskAssigneeLike("%" + personId) - .active() - .list(); + .taskAssigneeLike("%" + personId) + .active() + .list(); if (CollectionUtils.isEmpty(list) || list.size() > 1) { throw new WorkflowEngineException(FIND_TASK_BY_PERSON_ID_ERROR, processInstanceId, personId); } @@ -673,9 +831,9 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { @Override public Map findTaskIdByInstanceIdsAndPersonId(List processInstanceIds, String personId) { List tasks = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds) - .taskAssigneeLike("%" + personId) - .active() - .list(); + .taskAssigneeLike("%" + personId) + .active() + .list(); if (CollectionUtils.isEmpty(tasks)) { return new HashMap<>(); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxProcessLogServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxProcessLogServiceImpl.java new file mode 100644 index 000000000..c9c1e8005 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxProcessLogServiceImpl.java @@ -0,0 +1,91 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.repository.mapper.ExtAxProcessLogMapper; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.DUMMY_ASSIGNEE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE; +import static cn.axzo.workflow.common.constant.BpmnConstants.ROBOT_ASSIGNEE_ID; + +/** + * Api Log 表操服务实现 + * + * @author wangli + * @since 2024/4/3 10:41 + */ +@Service +@Slf4j +public class ExtAxProcessLogServiceImpl implements ExtAxProcessLogService { + @Resource + private ExtAxProcessLogMapper extAxProcessLogMapper; + + @Override + public Long insert(ExtAxProcessLog log) { + extAxProcessLogMapper.insert(log); + return log.getId(); + } + + @Override + public void delete(ExtAxProcessLog deleteLog) { + extAxProcessLogMapper.delete(buildQueryWrapper(deleteLog)); + } + + public void updateById(ExtAxProcessLog updateLog) { + extAxProcessLogMapper.updateById(updateLog); + } + + public void update(ExtAxProcessLog query, ExtAxProcessLog update) { + extAxProcessLogMapper.update(update, buildQueryWrapper(query)); + } + + @Override + public void updateAssignee(ExtAxProcessLog queryLog, BpmnTaskDelegateAssigner assignee) { + updateAssignee(queryLog, assignee, assignee.getAssignerName()); + + } + + @Override + public void updateAssignee(ExtAxProcessLog queryLog, BpmnTaskDelegateAssigner assignee, String operationDesc) { + List filterAssignee = Lists.newArrayList(NO_ASSIGNEE, HIDDEN_ASSIGNEE_ID, ROBOT_ASSIGNEE_ID, + DUMMY_ASSIGNEE_ID); + if (Objects.isNull(assignee) || filterAssignee.contains(assignee.buildAssigneeId())) { + return; + } + ExtAxProcessLog update = new ExtAxProcessLog(); + update.setOperationDesc(StringUtils.hasText(operationDesc) ? operationDesc : assignee.getAssignerName()); + update.setAssigneeFull(Lists.newArrayList(assignee)); + update.setAssigneeId(Long.valueOf(assignee.getPersonId())); + update.setAssigneeTenantId(assignee.getTenantId()); + update.setAssigneeOuId(assignee.getOuId()); + update.setAssigneeName(assignee.getAssignerName()); + extAxProcessLogMapper.update(update, buildQueryWrapper(queryLog)); + } + + @Override + public List genericQuery(ExtAxProcessLog query) { + return extAxProcessLogMapper.selectList(buildQueryWrapper(query)); + } + + LambdaQueryWrapper buildQueryWrapper(ExtAxProcessLog log) { + return new LambdaQueryWrapper() + .eq(Objects.nonNull(log.getId()), ExtAxProcessLog::getId, log.getId()) + .eq(StringUtils.hasText(log.getProcessInstanceId()), ExtAxProcessLog::getProcessInstanceId, log.getProcessInstanceId()) + .eq(StringUtils.hasText(log.getActivityId()), ExtAxProcessLog::getActivityId, log.getActivityId()) + .eq(StringUtils.hasText(log.getActivityName()), ExtAxProcessLog::getActivityName, log.getActivityName()) + .eq(StringUtils.hasText(log.getTaskId()), ExtAxProcessLog::getTaskId, log.getTaskId()) + .eq(StringUtils.hasText(log.getTenantId()), ExtAxProcessLog::getTenantId, log.getTenantId()) + .eq(ExtAxProcessLog::getIsDelete, 0); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/FlowNodeForecastService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/FlowNodeForecastService.java index 96ebf4e17..a28d48fbd 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/FlowNodeForecastService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/FlowNodeForecastService.java @@ -6,7 +6,6 @@ import cn.axzo.workflow.core.service.support.forecast.Forecast; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.StartEvent; -import org.flowable.engine.HistoryService; import org.flowable.engine.RepositoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.runtime.ProcessInstance; @@ -98,8 +97,6 @@ public class FlowNodeForecastService implements InitializingBean { @Resource private RepositoryService repositoryService; @Resource - private HistoryService historyService; - @Resource private RuntimeService runtimeService; @Resource private List> forecasts; @@ -114,15 +111,31 @@ public class FlowNodeForecastService implements InitializingBean { */ public List performProcessForecasting(@Nullable String processInstanceId, @Nullable ProcessInstance instance) { + return performProcessForecasting(processInstanceId, instance, null, null); + } + + /** + * 执行运行中的流程预测 + *

+ * 已完成的流程可以直接查询流程审批记录就行 + * + * @param processInstanceId 指定运行时的流程实例 ID + * @param instance 外部传入流程实例 (与另外一个参数必须二选一) + * @param taskDefinitionKey 从哪个节点开始推测 + * @param containSelf 配合第三个参数使用,为 true 时,推送的起始节点为第三个参数,否则,以第三个参数的下个节点开始 + */ + public List performProcessForecasting(@Nullable String processInstanceId, + @Nullable ProcessInstance instance, + @Nullable String taskDefinitionKey, + Boolean containSelf) { if (Objects.nonNull(instance)) { processInstanceId = instance.getProcessInstanceId(); } else if (!StringUtils.hasLength(processInstanceId)) { throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS); } else { instance = runtimeService.createProcessInstanceQuery() - .processInstanceId(processInstanceId) - // .includeProcessVariables() - .singleResult(); + .processInstanceId(processInstanceId) + .singleResult(); } if (Objects.isNull(instance)) { throw new WorkflowEngineException(RUNNING_INSTANCE_ONLY_FORECAST); @@ -134,10 +147,27 @@ public class FlowNodeForecastService implements InitializingBean { // 流程定义中所有的FlowElement Collection flowElements = bpmnModel.getMainProcess().getFlowElements(); - // 开始节点 - findStartNode(flowElements).ifPresent(startNode -> { - addOrderFlowNodes(orderedNodes, startNode); - }); + if (StringUtils.hasText(taskDefinitionKey)) { + if (Boolean.TRUE.equals(containSelf)) { + flowElements.stream().filter(e -> Objects.equals(e.getId(), taskDefinitionKey)).findFirst().ifPresent(orderedNodes::add); + } else { + FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); + String finalProcessInstanceId = processInstanceId; + forecasts.forEach(i -> { + if (i.support(flowElement)) { + List list = i.nextFlowElement(flowElement, finalProcessInstanceId); + if (!CollectionUtils.isEmpty(list)) { + addOrderFlowNodes(orderedNodes, list.get(0)); + } + } + }); + } + } else { + // 开始节点 + findStartNode(flowElements).ifPresent(startNode -> { + addOrderFlowNodes(orderedNodes, startNode); + }); + } startForecasting(orderedNodes, processInstanceId); return orderedNodes; @@ -165,8 +195,8 @@ public class FlowNodeForecastService implements InitializingBean { */ private Optional findStartNode(Collection flowElements) { return Optional.ofNullable(flowElements.stream().filter(StartEvent.class::isInstance).findFirst() - .map(StartEvent.class::cast) - .orElseThrow(() -> new IllegalArgumentException("非法的参数, 正确的流程定义一定有一个 StartEvent 节点"))); + .map(StartEvent.class::cast) + .orElseThrow(() -> new IllegalArgumentException("非法的参数, 正确的流程定义一定有一个 StartEvent 节点"))); } @@ -187,7 +217,7 @@ public class FlowNodeForecastService implements InitializingBean { public void afterPropertiesSet() { forecasts.forEach(i -> { Class rawClass = ResolvableType.forClass(i.getClass(), Forecast.class) - .getSuperType().getGenerics()[0].getRawClass(); + .getSuperType().getGenerics()[0].getRawClass(); FORECAST_MAP.put(rawClass, (AbstractForecast) i); }); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/util/DingTalkUtils.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/util/DingTalkUtils.java new file mode 100644 index 000000000..55f0c5e92 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/util/DingTalkUtils.java @@ -0,0 +1,170 @@ +package cn.axzo.workflow.core.util; + +import cn.axzo.workflow.common.model.dto.AlterDTO; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.dingtalk.api.DefaultDingTalkClient; +import com.dingtalk.api.DingTalkClient; +import com.dingtalk.api.request.OapiRobotSendRequest; +import com.dingtalk.api.response.OapiRobotSendResponse; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC; + +/** + * 钉钉告警处理 + * + * @author wangli + * @since 2024/1/12 14:34 + */ +@Slf4j +public class DingTalkUtils { + + private static final String dingtalk_robot_webhook = "https://oapi.dingtalk" + + ".com/robot/send?access_token=341ee2907f3ebc15dc495fb7771a646230058710999fec7838066c109849878e"; + + private static final Map ENV_URL_MAPPING = new HashMap<>(); + private static final Map WEB_URL_MAPPING = new HashMap<>(); + + static { + ENV_URL_MAPPING.put("local", "https://dev-app.axzo.cn"); + ENV_URL_MAPPING.put("dev", "https://dev-app.axzo.cn"); + ENV_URL_MAPPING.put("test", "https://test-api.axzo.cn"); + ENV_URL_MAPPING.put("pre", "https://pre-api.axzo.cn"); + ENV_URL_MAPPING.put("live", "https://live-api.axzo.cn"); + ENV_URL_MAPPING.put("default", "https://api.axzo.cn"); + + WEB_URL_MAPPING.put("local", "https://dev-new-workflow-web.axzo.cn"); + WEB_URL_MAPPING.put("dev", "https://dev-new-workflow-web.axzo.cn"); + WEB_URL_MAPPING.put("test", "https://test-new-workflow-web.axzo.cn"); + WEB_URL_MAPPING.put("pre", "https://pre-new-workflow-web.axzo.cn"); + WEB_URL_MAPPING.put("live", "https://live-new-workflow-web.axzo.cn"); + WEB_URL_MAPPING.put("default", "https://new-workflow-web.axzo.cn"); + } + + public static String getEnvUrl(String profile) { + String urlPrefix = ENV_URL_MAPPING.get(profile); + if (!StringUtils.hasText(urlPrefix)) { + urlPrefix = ENV_URL_MAPPING.get("default"); + } + return urlPrefix; + } + + public static String getWebUrl(String profile) { + String urlPrefix = WEB_URL_MAPPING.get(profile); + if (!StringUtils.hasText(urlPrefix)) { + urlPrefix = WEB_URL_MAPPING.get("default"); + } + return urlPrefix; + } + + @SneakyThrows + public static void sendDingTalk(String profile, String title, Object req, String invokeServerName, Throwable throwable) { + OapiRobotSendRequest request = new OapiRobotSendRequest(); + request.setMsgtype("markdown"); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle("Notice " + title + ", Env: " + profile); + String text = "#### [" + profile + "]" + title + "\n" + + "> 入参: " + JSONUtil.toJsonStr(req) + "\n\n" + + "> ###### 异常信息: " + JSONUtil.toJsonStr(Objects.isNull(throwable.getCause()) ? throwable.getMessage() : + throwable.getCause().getMessage()) + " \n\n" + + "> ##### traceId: " + MDC.get(CTX_LOG_ID_MDC) + " \n" + + "> ##### 调用方服务名称:" + invokeServerName + " \n"; + String deadLetterStacktrace = getDeadLetterStacktrace(profile, req); + text = text + (StringUtils.hasText(deadLetterStacktrace) ? "> ##### [点击查看异常明细](" + deadLetterStacktrace + ") \n" : ""); + markdown.setText(text); + request.setMarkdown(markdown); + sendDingTalk(request); + } + + private static String getDeadLetterStacktrace(String profile, Object req) { + try { + JSONObject entries = JSONUtil.parseObj(req); + String jobId = entries.getStr("id"); + if (!StringUtils.hasText(jobId)) { + return ""; + } + return getEnvUrl(profile) + "/workflow-engine/web/v1/api/process/job/dead-letter/exception/stacktrace/byId?jobId=" + jobId; + } catch (Exception e) { + log.warn("构造查询错误堆栈地址异常", e); + return ""; + } + } + + @SneakyThrows + public static void sendDingTalkForSlowUrl(String profile, Double time, String apiUrl, Object requestParam, Object responseBody) { + OapiRobotSendRequest request = new OapiRobotSendRequest(); + request.setMsgtype("markdown"); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle("Notice 请求二方接口慢 URL, Env: " + profile); + markdown.setText("#### [" + profile + "]请求二方接口过慢\n" + + "> 接口地址: " + apiUrl + ",经过了" + time + "秒\n\n" + + "> 请求参数: " + JSONUtil.toJsonStr(requestParam) + "\n\n" + + "> ###### 结果: " + JSONUtil.toJsonStr(responseBody) + " \n"); + request.setMarkdown(markdown); + sendDingTalk(request); + } + + @SneakyThrows + public static void sendDingTalkForSlowApi(String profile, Double time, String apiUrl, String uniqueId) { + OapiRobotSendRequest request = new OapiRobotSendRequest(); + request.setMsgtype("markdown"); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle("Notice API 处理耗时报告, Env: " + profile); + markdown.setText("#### [" + profile + "]API 处理耗时报告\n" + + "> 接口地址: " + apiUrl + ",经过了" + time + "秒\n\n" + + "> ###### 标识: " + uniqueId + " \n"); + request.setMarkdown(markdown); + sendDingTalk(request); + } + + @SneakyThrows + private static void sendDingTalk(OapiRobotSendRequest request) { + DingTalkClient client = new DefaultDingTalkClient(dingtalk_robot_webhook); + OapiRobotSendResponse response = client.execute(request); + } + + public static void sendDingTalkForBizNodeAlter(String profile, AlterDTO alterDTO, List atMobiles) { + if (CollectionUtils.isEmpty(atMobiles)) { + return; + } + String processInstanceId = alterDTO.getProcessInstanceId(); + OapiRobotSendRequest request = new OapiRobotSendRequest(); + request.setMsgtype("markdown"); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle("Notice 业务节点长时间停止告警, Env: " + profile); + markdown.setText("#### [" + profile + "]业务节点长时间停止\n" + + "> 节点相关信息: " + JSONUtil.toJsonStr(alterDTO) + "\n\n" + + "> ##### [点击查看审批日志](" + getWebUrl(profile) + "/#/workflow/examples?processInstanceId=" + processInstanceId + ") \n\n" + + "> ##### 提示:如果以上地址提示未登录,请在对应环境 OMS 系统登录后并点击审批管理的功能后,再使用。或者复制实例 ID 到 OMS 中手动查询"); + request.setMarkdown(markdown); + OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); + at.setAtMobiles(atMobiles); + at.setIsAtAll(false); + request.setAt(at); +// sendDingTalk(request); + } + + public static void sendDingTalkForTransferToAdminError(String profile, String processInstanceId, String taskDefinitionKey, Object orgScopes) { + OapiRobotSendRequest request = new OapiRobotSendRequest(); + request.setMsgtype("markdown"); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle("Notice 转交管理员后的审批人为空, Env: " + profile); + markdown.setText("#### [" + profile + "]转交管理员后的审批人为空\n" + + "> 流程实例 ID: " + processInstanceId + "\n\n" + + "> 节点参数(节点 ID): " + taskDefinitionKey + "\n\n" + + "> OrgScopes 参数信息: " + JSONUtil.toJsonStr(orgScopes) + "\n\n" + + "> ##### 提示:仅作为提示信息,不代表是异常"); + request.setMarkdown(markdown); + sendDingTalk(request); + } +} diff --git a/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.2.sql b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.2.sql new file mode 100644 index 000000000..78de441ee --- /dev/null +++ b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.2.sql @@ -0,0 +1,33 @@ +create table `workflow-engine`.ext_ax_process_log +( + id bigint auto_increment comment '主键' + primary key, + process_instance_id varchar(64) default '' not null comment '流程实例 ID', + tenant_id varchar(64) default '' not null comment '实例归属租户', + activity_id varchar(64) default '' not null comment '节点 ID', + activity_name varchar(255) default '' not null comment '节点名称', + approval_method varchar(32) default '' not null comment '审批方式:配置审批人/业务指定/业务触发(不含人)', + node_type varchar(32) default '' not null comment '节点类型:审批节点/业务节点/评论节点/抄送节点', + node_mode varchar(32) default '' not null comment '节点模式:会签/或签', + task_id varchar(64) default '' not null comment '任务 ID', + advice varchar(4000) default '' not null comment '操作建议', + operation_desc varchar(4000) default '' not null comment '操作描述', + assignee_full json null comment '审批人(JSON)', + assignee_id bigint default 0 not null comment '审批人标识(axzo=personId)', + assignee_tenant_id varchar(255) default '' not null comment '审批人归属租户', + assignee_name varchar(255) default '' not null comment '审批人姓名', + assignee_ou_id varchar(64) default '' not null comment '审批人归属单位', + start_time datetime(3) default CURRENT_TIMESTAMP(3) not null comment '任务开始时间', + end_time datetime(3) null comment '任务结束时间', + button_conf json null comment '按钮配置', + status varchar(16) default '' not null comment '任务状态:审批中/通过/驳回/转交/加签', + extra json null comment '扩展字段', + create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间', + update_at datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + is_delete bigint default 0 not null comment '是否删除' +) comment '审批日志持久化'; + +create unique index idx_process_task_status + on ext_ax_process_log (process_instance_id asc, task_id asc, status asc, create_at desc); + + diff --git a/workflow-engine-server/pom.xml b/workflow-engine-server/pom.xml index afb7ae83a..46a9a9419 100644 --- a/workflow-engine-server/pom.xml +++ b/workflow-engine-server/pom.xml @@ -21,10 +21,6 @@ 3.25.0 - cn.axzo.framework axzo-web-spring-boot-starter @@ -75,7 +71,6 @@ mysql mysql-connector-java - cn.axzo.framework axzo-processor-spring-boot-starter @@ -100,18 +95,15 @@ cn.axzo.maokai maokai-api - - - - cn.axzo.karma karma-api - com.aliyun - alibaba-dingtalk-service-sdk + cn.axzo.oss + oss-http-api + com.taobao.arthas arthas-spring-boot-starter diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEnginApplication.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java similarity index 73% rename from workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEnginApplication.java rename to workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java index 697702527..a05fdf236 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEnginApplication.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java @@ -1,23 +1,24 @@ package cn.axzo.workflow.server; -import liquibase.pro.packaged.E; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; import org.springframework.transaction.annotation.EnableTransactionManagement; @MapperScan({"cn.axzo.workflow.core.**.mapper"}) -@ComponentScan({"cn.axzo.workflow", "cn.axzo.maokai"}) +@ComponentScan({"cn.axzo.workflow"}) +@EnableFeignClients("cn.axzo.oss") @SpringBootApplication(exclude = RabbitAutoConfiguration.class) @EnableTransactionManagement @EnableCaching -public class WorkflowEnginApplication { +public class WorkflowEngineApplication { public static void main(String[] args) { - SpringApplication.run(WorkflowEnginApplication.class, args); + SpringApplication.run(WorkflowEngineApplication.class, args); } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/alter/DingTalkAlter.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/alter/DingTalkAlter.java new file mode 100644 index 000000000..9a6fd7516 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/alter/DingTalkAlter.java @@ -0,0 +1,31 @@ +package cn.axzo.workflow.server.alter; + +import cn.axzo.workflow.common.model.dto.AlterDTO; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.listener.Alter; +import cn.axzo.workflow.core.util.DingTalkUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 钉钉告警实现 + * + * @author wangli + * @since 2024-09-13 11:40 + */ +@Component +public class DingTalkAlter implements Alter { + + @Value("${spring.profiles.active}") + private String profile; + @Resource + private SupportRefreshProperties refreshProperties; + + @Override + public void invoke(AlterDTO alterDTO) { + DingTalkUtils.sendDingTalkForBizNodeAlter(profile, alterDTO, refreshProperties.getAlterMobiles()); + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/ReporterType.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/ReporterType.java index e42b9df9d..f7cb0d71f 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/ReporterType.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/ReporterType.java @@ -1,6 +1,6 @@ package cn.axzo.workflow.server.common.annotation; -import cn.axzo.workflow.server.common.util.DingTalkUtils; +import cn.axzo.workflow.core.util.DingTalkUtils; import lombok.extern.slf4j.Slf4j; import static cn.axzo.workflow.server.common.aspectj.RepeatSubmitAspect.argsArrayToString; @@ -20,9 +20,9 @@ public enum ReporterType { */ ONLY_DING_TALK { @Override - public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, Throwable e, Boolean downgrade) { + public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, String invokeServerName, Throwable e, Boolean downgrade) { if (sendDingTalk) { - DingTalkUtils.sendDingTalk(profile, title, argsArrayToString(args), e); + DingTalkUtils.sendDingTalk(profile, title, argsArrayToString(args), invokeServerName, e); } } @@ -32,7 +32,7 @@ public enum ReporterType { */ ONLY_LOG { @Override - public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, Throwable e, Boolean downgrade) { + public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, String invokeServerName, Throwable e, Boolean downgrade) { if (downgrade) { log.warn("ER: {}", e.getMessage()); } else { @@ -46,9 +46,9 @@ public enum ReporterType { */ BOTH { @Override - public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, Throwable e, Boolean downgrade) { + public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, String invokeServerName, Throwable e, Boolean downgrade) { if (sendDingTalk) { - DingTalkUtils.sendDingTalk(profile, title, argsArrayToString(args), e); + DingTalkUtils.sendDingTalk(profile, title, argsArrayToString(args),invokeServerName, e); } if (downgrade) { log.warn("ER: {}", e.getMessage()); @@ -66,7 +66,7 @@ public enum ReporterType { * @param shortString 用于日志输出的关键信息 * @param e 异常对象 */ - public abstract void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, Throwable e, Boolean downgrade); + public abstract void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, String invokeServerName, Throwable e, Boolean downgrade); private static void logWarn(Throwable throwable, String shortString) { // 由于框架底层 AbstractExceptionApiResultHandler 默认会打印,所以此处都只是降级打印 diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/aspectj/ErrorReportAspect.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/aspectj/ErrorReportAspect.java index 55b246a3b..4b4a3d20b 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/aspectj/ErrorReportAspect.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/aspectj/ErrorReportAspect.java @@ -20,6 +20,7 @@ import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.util.StopWatch; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -90,13 +91,13 @@ public class ErrorReportAspect implements Ordered { if (!signature.toShortString().contains("ExtAxApiLogServiceImpl")) { String type = getType(joinPoint); ApiLogEvent event = new ApiLogEvent(MDC.get(CTX_LOG_ID_MDC), - signature.toShortString(), - Objects.isNull(getOriginRequest()) ? "" : getOriginRequest().getHeader(HEADER_SERVER_NAME), - Objects.isNull(getOriginRequest()) ? "" : getOriginRequest().getHeader(HEADER_API_VERSION), - Objects.equals(type, "Controller") ? joinPoint.getArgs() : null, - Objects.equals(type, "Controller") ? result : null, - watch.getTotalTimeSeconds(), - type); + signature.toShortString(), + Objects.isNull(getOriginRequest()) ? "" : getOriginRequest().getHeader(HEADER_SERVER_NAME), + Objects.isNull(getOriginRequest()) ? "" : getOriginRequest().getHeader(HEADER_API_VERSION), + Objects.equals(type, "Controller") ? joinPoint.getArgs() : null, + Objects.equals(type, "Controller") ? result : null, + watch.getTotalTimeSeconds(), + type); applicationEventPublisher.publishEvent(event); } @@ -144,13 +145,33 @@ public class ErrorReportAspect implements Ordered { if (workflowProperties.getFilterSendDingTalk().contains(operation.summary())) { filterSendDingTalk = false; } - envConfig.type().executeAction(profile, operation.summary(), filterSendDingTalk, joinPoint.getArgs(), joinPoint.getSignature().toShortString(), e, - workflowProperties.getFilterOperations().contains(operation.summary())); + + log.warn("request header server name: {}", getHeader()); + envConfig.type().executeAction(profile, + operation.summary(), + filterSendDingTalk, + joinPoint.getArgs(), + joinPoint.getSignature().toShortString(), + getHeader(), + e, + workflowProperties.getFilterOperations().contains(operation.summary())); } } } + private String getHeader() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (Objects.isNull(attributes)) { + return ""; + } + + HttpServletRequest request = attributes.getRequest(); + // 获取指定请求头的值 + String headerValue = request.getHeader(HEADER_SERVER_NAME); + return StringUtils.hasText(headerValue) ? headerValue : ""; + } + private HttpServletRequest getOriginRequest() { try { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/interceptor/RequestHeaderContextInterceptor.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/interceptor/RequestHeaderContextInterceptor.java index cfc316e8e..b37c37216 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/interceptor/RequestHeaderContextInterceptor.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/interceptor/RequestHeaderContextInterceptor.java @@ -47,11 +47,11 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (Objects.equals(HEADER_HTTP_CLIENT_VALUE, request.getHeader(HEADER_HTTP_CLIENT))) { String headerClientVersion = request.getHeader(HEADER_API_VERSION) - .replaceAll("-SNAPSHOT", "") - .replaceAll("-RELEASE", ""); + .replaceAll("-SNAPSHOT", "") + .replaceAll("-RELEASE", ""); serviceVersion = serviceVersion - .replaceAll("-SNAPSHOT", "") - .replaceAll("-RELEASE", ""); + .replaceAll("-SNAPSHOT", "") + .replaceAll("-RELEASE", ""); DefaultArtifactVersion minimumSupportedVersion = new DefaultArtifactVersion(FLOW_SERVER_VERSION_130); DefaultArtifactVersion clientVersion = new DefaultArtifactVersion(headerClientVersion); DefaultArtifactVersion serverVersion = new DefaultArtifactVersion(serviceVersion); @@ -61,15 +61,14 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor { return true; } else { - log.info("request uri 1 : {}", request.getRequestURI()); printHeader(request); - log.error(CLIENT_VERSION_SUPPORT.getMessage(), serviceVersion, headerClientVersion); throw new WorkflowEngineException(CLIENT_VERSION_SUPPORT, serviceVersion, headerClientVersion); } } // 仅 feignApi 才需要检查版本 if (!request.getRequestURI().contains("/web/") && !request.getRequestURI().contains("checkDeath") - && !StringUtils.hasText(request.getHeader(HEADER_HTTP_CLIENT))) { + && !request.getRequestURI().contains("/error") + && !StringUtils.hasText(request.getHeader(HEADER_HTTP_CLIENT))) { String serverName = request.getHeader(HEADER_SERVER_NAME); printHeader(request); log.error(MICRO_SERVER_NEED_REBUILD.getMessage(), serverName); @@ -105,7 +104,7 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor { } ExtAxProperty property = extAxProperty.get(); if (Objects.equals(property.getValue(), clientVersion.toString()) - && Objects.equals(property.getManageable().toString(), manageableStatus)) { + && Objects.equals(property.getManageable().toString(), manageableStatus)) { return; } property.setName(requestApplicationName); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/DingTalkUtils.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/DingTalkUtils.java deleted file mode 100644 index 99282c0fe..000000000 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/DingTalkUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -package cn.axzo.workflow.server.common.util; - -import cn.hutool.json.JSONUtil; -import com.dingtalk.api.DefaultDingTalkClient; -import com.dingtalk.api.DingTalkClient; -import com.dingtalk.api.request.OapiRobotSendRequest; -import com.dingtalk.api.response.OapiRobotSendResponse; -import lombok.SneakyThrows; -import org.slf4j.MDC; - -import java.util.Objects; - -import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC; - -/** - * 钉钉告警处理 - * - * @author wangli - * @since 2024/1/12 14:34 - */ -public class DingTalkUtils { - - private static final String dingtalk_robot_webhook = "https://oapi.dingtalk" + - ".com/robot/send?access_token=341ee2907f3ebc15dc495fb7771a646230058710999fec7838066c109849878e"; - - @SneakyThrows - public static void sendDingTalk(String profile, String title, Object req, Throwable throwable) { - OapiRobotSendRequest request = new OapiRobotSendRequest(); - request.setMsgtype("markdown"); - OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); - markdown.setTitle("Notice " + title + ", Env: " + profile); - markdown.setText("#### [" + profile + "]" + title + "\n" + - "> 入参: " + JSONUtil.toJsonStr(req) + "\n\n" + - "> ###### 异常信息: " + JSONUtil.toJsonStr(Objects.isNull(throwable.getCause()) ? throwable.getMessage() : - throwable.getCause().getMessage()) + " \n\n" + - "> ##### traceId: " + MDC.get(CTX_LOG_ID_MDC) + " \n"); - request.setMarkdown(markdown); - sendDingTalk(request); - } - - @SneakyThrows - public static void sendDingTalkForSlowUrl(String profile, Double time, String apiUrl, Object requestParam, Object responseBody) { - OapiRobotSendRequest request = new OapiRobotSendRequest(); - request.setMsgtype("markdown"); - OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); - markdown.setTitle("Notice 请求二方接口慢 URL, Env: " + profile); - markdown.setText("#### [" + profile + "]请求二方接口过慢\n" + - "> 接口地址: " + apiUrl + ",经过了" + time + "秒\n\n" + - "> 请求参数: " + JSONUtil.toJsonStr(requestParam) + "\n\n" + - "> ###### 结果: " + JSONUtil.toJsonStr(responseBody) + " \n"); - request.setMarkdown(markdown); - sendDingTalk(request); - } - - @SneakyThrows - public static void sendDingTalkForSlowApi(String profile, Double time, String apiUrl, String uniqueId) { - OapiRobotSendRequest request = new OapiRobotSendRequest(); - request.setMsgtype("markdown"); - OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); - markdown.setTitle("Notice API 处理耗时报告, Env: " + profile); - markdown.setText("#### [" + profile + "]API 处理耗时报告\n" + - "> 接口地址: " + apiUrl + ",经过了" + time + "秒\n\n" + - "> ###### 标识: " + uniqueId + " \n"); - request.setMarkdown(markdown); - sendDingTalk(request); - } - - @SneakyThrows - private static void sendDingTalk(OapiRobotSendRequest request) { - DingTalkClient client = new DefaultDingTalkClient(dingtalk_robot_webhook); - OapiRobotSendResponse response = client.execute(request); - } -} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RpcExternalUtil.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RpcExternalUtil.java new file mode 100644 index 000000000..36a99bede --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RpcExternalUtil.java @@ -0,0 +1,114 @@ +package cn.axzo.workflow.server.common.util; + +import cn.axzo.apollo.core.web.Result; +import cn.axzo.basics.common.util.AssertUtil; +import cn.axzo.framework.domain.ServiceException; +import cn.axzo.framework.domain.web.result.ApiListResult; +import cn.axzo.framework.domain.web.result.ApiResult; +import cn.azxo.framework.common.model.CommonResponse; +import cn.hutool.core.date.StopWatch; +import cn.hutool.core.lang.Assert; +import cn.hutool.http.HttpStatus; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * 外部rpc接口 返回 CommonResponse + * + * @author tanjie@axzo.cn + * @date 2022/5/23 11:08 + */ +@Slf4j +public class RpcExternalUtil { + + /** + * 常用的RPC请求返回值解析,如果 被请求方 返回非200会抛出异常 + */ + public static T rpcProcessor(Supplier> supplier, String operationType, Object... param) { + + return rpcProcessorMayThrow(supplier, operationType, commonResponse -> { + throw new ServiceException(commonResponse.getMsg()); + }, param); + } + + public static T rpcApolloProcessor(Supplier> supplier, String operationType, Object... param) { + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + Result result = printLatency(supplier, operationType); + log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); + Assert.notNull(result, "服务调用异常"); + Assert.isTrue(result.getCode() == 200, "服务调用异常:" + result.getMsg()); + return result.getData(); + } + + public static T rpcApiResultProcessor(Supplier> supplier, String operationType, Object... param) { + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + ApiResult result = printLatency(supplier, operationType); + log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); + Assert.notNull(result, "服务调用异常"); + Assert.isTrue(result.getCode() == 200, "服务调用异常:" + result.getMsg()); + return result.getData(); + } + + /** + * 常用的RPC请求返回值解析,如果 被请求方 返回非200会抛出异常 + * + * @param supplier ApiListResult类型的RPC接口 + * @param operationType 接口方法描述 + * @param param 接口入参 + * @return 接口返回值 + */ + public static List rpcApiListResultProcessor(Supplier> supplier, String operationType, Object... param) { + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + ApiListResult result = printLatency(supplier, operationType); + log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); + Assert.notNull(result, "服务调用异常"); + Assert.isTrue(result.isSuccess(), "服务调用异常:" + result.getMsg()); + return result.getData(); + } + + public static T rpcProcessorMayThrow(Supplier> supplier, String operationType, Consumer> throwConsumer, Object... param) { + AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空"); + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + CommonResponse result = printLatency(supplier, operationType); + log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); + + Assert.notNull(result, "服务调用异常"); + // 200自定义处理 + if (HttpStatus.HTTP_OK != result.getCode()) { + throwConsumer.accept(result); + } + return result.getData(); + } + + public static R printLatency(Supplier function, String optType) { + StopWatch stopWatch = new StopWatch(optType); + stopWatch.start(optType); + R r = function.get(); + stopWatch.stop(); + log.info(stopWatch.shortSummary(TimeUnit.MILLISECONDS)); + return r; + } + + + public static T rpcProfileProcessor(Supplier> supplier, String operationType, Object... param) { + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + StopWatch stopWatch = new StopWatch(operationType); + stopWatch.start(operationType); + CommonResponse result = supplier.get(); + stopWatch.stop(); + log.info("-Result: " + JSONUtil.toJsonStr(result)); + log.info(stopWatch.shortSummary(TimeUnit.MILLISECONDS)); + Assert.notNull(result, "服务调用异常"); + // 200自定义处理 + if (HttpStatus.HTTP_OK != result.getCode() && 404 != result.getCode()) { + throw new ServiceException(result.getMsg()); + } + + return result.getData(); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java index 0d32c68cf..03343e679 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java @@ -1,17 +1,20 @@ package cn.axzo.workflow.server.controller.delegate; import cn.axzo.framework.domain.web.result.ApiResult; +import cn.axzo.karma.client.feign.FlowSupportApi; +import cn.axzo.karma.client.model.request.PersonProfileQueryReq; +import cn.axzo.karma.client.model.response.PersonProfileResp; import cn.axzo.workflow.common.enums.ApproverScopeEnum; import cn.axzo.workflow.common.enums.CarbonCopyObjectType; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; import cn.axzo.workflow.core.deletage.BpmnTaskAssigneeSelector; import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDTO; import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeProcessor; -import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener; import cn.axzo.workflow.core.engine.model.NoticeFlowElement; -import cn.axzo.workflow.server.common.util.DingTalkUtils; +import cn.axzo.workflow.core.util.DingTalkUtils; import cn.hutool.core.lang.Assert; import cn.hutool.http.HttpStatus; import cn.hutool.json.JSONUtil; @@ -29,9 +32,14 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; +import javax.annotation.Resource; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -49,8 +57,11 @@ import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_US */ @Slf4j public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssigneeSelector, ApplicationContextAware { + @Resource + protected FlowSupportApi flowSupportApi; + @Resource + protected SupportRefreshProperties refreshProperties; private ApplicationContext applicationContext; - private EngineExecutionStartListener executionStartListener; @Override public List select(FlowElement flowElement, DelegateExecution execution, @@ -58,15 +69,15 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign List assigners = new ArrayList<>(); if (flowElement instanceof UserTask) { assigners.addAll(BpmnMetaParserHelper.getApproverScope((UserTask) flowElement) - .map(approverScopeEnum -> privateSelector(approverScopeEnum, flowElement, execution, - throwException)) - .orElseGet(Collections::emptyList)); + .map(approverScopeEnum -> privateSelector(approverScopeEnum, flowElement, execution, + throwException)) + .orElseGet(Collections::emptyList)); } else if (flowElement instanceof ServiceTask) { List customProperties = ((ServiceTask) flowElement).getCustomProperties(); if (!CollectionUtils.isEmpty(customProperties)) { String processor = CarbonCopyObjectType.valueOfType(customProperties.get(0).getName()).getProcessor(); assigners.addAll(privateSelector(ApproverScopeEnum.valueOfProcessor(processor), flowElement, - execution, throwException)); + execution, throwException)); } } else if (flowElement instanceof NoticeFlowElement) { ApproverScopeEnum processor = ((NoticeFlowElement) flowElement).getProcessor(); @@ -78,22 +89,23 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign private List privateSelector(ApproverScopeEnum processorType, FlowElement flowElement, DelegateExecution execution, Boolean throwException) { ApproverScopeProcessor processor = applicationContext.getBean(processorType.getProcessor(), - ApproverScopeProcessor.class); + ApproverScopeProcessor.class); ApproverScopeDTO scopeDto = processor.build(flowElement, execution); if (CollectionUtils.isEmpty(scopeDto.getOrgScopes()) - && CollectionUtils.isEmpty(scopeDto.getWorkerTeamScopes())) { + && CollectionUtils.isEmpty(scopeDto.getWorkerTeamScopes())) { if (throwException) { throw new WorkflowEngineException(ENGINE_USER_TASK_PARAM_ERROR, flowElement.getId(), - processorType.getDesc()); + processorType.getDesc()); } } try { - return invokeService(flowElement, execution, scopeDto); + return populateNameAndAvatar(invokeService(flowElement, execution, scopeDto), flowSupportApi, refreshProperties, applicationContext); } catch (Throwable t) { if (throwException) { + log.warn("执行查询候选审批人时发现异常, 审批节点:{}, 异常信息:{}", flowElement.getId(), t.getMessage()); if (!(t instanceof WorkflowEngineException)) { throw new WorkflowEngineException(ENGINE_USER_TASK_CALC_ERROR, flowElement.getId(), - this.getType(), t.getMessage()); + this.getType(), t.getMessage()); } throw t; } else { @@ -108,23 +120,29 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign } protected final T parseApiResult(Supplier> supplier, String operatorDesc, - String extInfo, Object... param) { + String extInfo, Object... param) { + return parseApiResult(supplier, operatorDesc, extInfo, refreshProperties, applicationContext, param); + } + + public static T parseApiResult(Supplier> supplier, String operatorDesc, + String extInfo, SupportRefreshProperties refreshProperties, + ApplicationContext context, Object... param) { StopWatch stopWatch = new StopWatch(operatorDesc); log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param)); stopWatch.start(); ApiResult result = supplier.get(); stopWatch.stop(); log.info("{}-Cost:{}, Result: {}", operatorDesc, - "API StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " 's", - JSONUtil.toJsonStr(result)); + "API StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " 's", + JSONUtil.toJsonStr(result)); try { - if (stopWatch.getTotalTimeSeconds() > executionStartListener.getApiTimeout()) { - DingTalkUtils.sendDingTalkForSlowUrl(applicationContext.getEnvironment() - .getProperty("spring.profiles.active"), - stopWatch.getTotalTimeSeconds(), - extInfo, - param, - result); + if (stopWatch.getTotalTimeSeconds() > refreshProperties.getApiTimeout()) { + DingTalkUtils.sendDingTalkForSlowUrl(context.getEnvironment() + .getProperty("spring.profiles.active"), + stopWatch.getTotalTimeSeconds(), + extInfo, + param, + result); } } catch (Exception e) { // ignore @@ -143,26 +161,26 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign try { if (flowElement instanceof UserTask) { return BpmnMetaParserHelper.getApproverSpecifyValue((UserTask) flowElement) - .map(value -> JSON.parseArray(value, String.class).stream().map(JSON::parseObject) - .map(i -> i.getString("value")) - .collect(Collectors.toList())) - .orElse(Collections.emptyList()); + .map(value -> JSON.parseArray(value, String.class).stream().map(JSON::parseObject) + .map(i -> i.getString("value")) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); } else if (flowElement instanceof ServiceTask) { List customProperties = ((ServiceTask) flowElement).getCustomProperties(); if (CollectionUtils.isEmpty(customProperties)) { return Collections.emptyList(); } return JSON.parseArray(customProperties.get(0).getSimpleValue(), String.class) - .stream().map(JSON::parseObject) - .map(i -> i.getString("value")) - .collect(Collectors.toList()); + .stream().map(JSON::parseObject) + .map(i -> i.getString("value")) + .collect(Collectors.toList()); } else if (flowElement instanceof NoticeFlowElement) { String customValues = ((NoticeFlowElement) flowElement).getCustomValues(); if (StringUtils.hasText(customValues)) { return JSON.parseArray(customValues, String.class) - .stream().map(JSON::parseObject) - .map(i -> i.getString("value")) - .collect(Collectors.toList()); + .stream().map(JSON::parseObject) + .map(i -> i.getString("value")) + .collect(Collectors.toList()); } return Collections.emptyList(); } else { @@ -170,13 +188,48 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign } } catch (Exception e) { throw new WorkflowEngineException(CONVERTOR_META_DATA_FORMAT_ERROR, "AbstractBpmnTaskAssigneeSelector" + - "#getTypes", e.getMessage()); + "#getTypes", e.getMessage()); } } + public static List populateNameAndAvatar(List assigners, + FlowSupportApi flowSupportApi, + SupportRefreshProperties refreshProperties, + ApplicationContext context) { + if (CollectionUtils.isEmpty(assigners)) { + return assigners; + } + List personIds = assigners.stream() + .map(BpmnTaskDelegateAssigner::getPersonId) + .filter(e-> Objects.nonNull(e) && StringUtils.hasText(e) && !Objects.equals("null", e)) + .map(Long::parseLong) + .distinct().collect(Collectors.toList()); + if(CollectionUtils.isEmpty(personIds)) { + return assigners; + } + Map personProfileMap = parseApiResult(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personIds(personIds).build()), + "根据 PersonId 查询自然人档案", "cn.axzo.karma.client.feign.FlowSupportApi.listPersons", refreshProperties, context, personIds) + .stream().collect(Collectors.toMap(PersonProfileResp::getId, Function.identity(), (s, t) -> s)); + assigners.forEach(assigner -> { + if (!StringUtils.hasText(assigner.getPersonId()) || Objects.equals("null", assigner.getPersonId())) { + return; + } + long personId = Long.parseLong(Optional.ofNullable(assigner.getPersonId()).orElse("-1")); + if (personProfileMap.containsKey(personId)) { + log.warn("查询到的人员信息:{}", JSON.toJSONString(personProfileMap.getOrDefault(personId, null))); + assigner.setAvatar(personProfileMap.getOrDefault(personId, new PersonProfileResp()).getAvatarUrl()); + assigner.setAssignerName(personProfileMap.getOrDefault(personId, new PersonProfileResp()).getRealName()); + + } else { + log.warn("未找到对应 person:{} 的数据", personId); + } + }); + return assigners; + } + @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.applicationContext = context; - executionStartListener = applicationContext.getBean(EngineExecutionStartListener.class); } + } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedFixedPersonTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedFixedPersonTaskAssigneeSelector.java index 1934a672e..2af518941 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedFixedPersonTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedFixedPersonTaskAssigneeSelector.java @@ -1,16 +1,29 @@ package cn.axzo.workflow.server.controller.delegate; +import cn.axzo.maokai.api.client.OrganizationalNodeUserApi; +import cn.axzo.maokai.api.vo.request.OrganizationalNodeUserSearchReq; +import cn.axzo.maokai.api.vo.response.OrganizationalNodeUserVO; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; import com.alibaba.fastjson.JSON; +import org.apache.commons.collections4.ListUtils; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import javax.annotation.Resource; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR; /** * 基于"固定人员"查询审批人 @@ -20,6 +33,9 @@ import java.util.List; */ @Component public class BasedFixedPersonTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { + @Resource + private OrganizationalNodeUserApi organizationalNodeUserApi; + @Override public String getType() { return ApproverSpecifyEnum.fixedPerson.getType(); @@ -38,8 +54,43 @@ public class BasedFixedPersonTaskAssigneeSelector extends AbstractBpmnTaskAssign return super.select(flowElement, execution, throwException); } BpmnMetaParserHelper.getApproverSpecifyValue((UserTask) flowElement) - .ifPresent(s -> assigners.addAll(JSON.parseArray(s, BpmnTaskDelegateAssigner.class))); - return assigners; + .ifPresent(s -> assigners.addAll(JSON.parseArray(s, BpmnTaskDelegateAssigner.class))); + + Set workspaceIds = assigners.stream() + .map(BpmnTaskDelegateAssigner::getTenantId) + .filter(StringUtils::hasText) + .map(Long::parseLong) + .collect(Collectors.toSet()); + + List personIds = assigners.stream() + .map(BpmnTaskDelegateAssigner::getPersonId) + .filter(StringUtils::hasText) + .map(Long::parseLong) + .collect(Collectors.toList()); + List onlineUsers = new ArrayList<>(); + try { + OrganizationalNodeUserSearchReq searchReq = new OrganizationalNodeUserSearchReq(); + searchReq.setWorkspaceIds(workspaceIds); + searchReq.setPersonIdList(personIds); + onlineUsers = parseApiResult(() -> organizationalNodeUserApi.list(searchReq), "查询指定人员是否在职", + "cn.axzo.maokai.api.client.OrganizationalNodeUserApi#list", searchReq); + } catch (Exception e) { + if (throwException) { + throw new WorkflowEngineException(ENGINE_USER_TASK_CALC_ERROR, flowElement.getId(), + this.getType(), e.getMessage()); + } else { + return Collections.emptyList(); + } + } + + // 只要有在职的人,不会走审批人为空 + if (ListUtils.emptyIfNull(onlineUsers).stream().filter(i -> Objects.equals(i.getIsDelete(), 0L)) + .anyMatch(u -> assigners.stream().anyMatch(i -> Objects.equals(i.getPersonId(), String.valueOf(u.getPersonId())) + && Objects.equals(i.getOuId(), String.valueOf(u.getOrganizationalUnitId()))))) { + return assigners; + } + + return Collections.emptyList(); } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedIdentityTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedIdentityTaskAssigneeSelector.java index 0e2317c09..918c871ba 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedIdentityTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedIdentityTaskAssigneeSelector.java @@ -1,6 +1,5 @@ package cn.axzo.workflow.server.controller.delegate; -import cn.axzo.karma.client.feign.FlowSupportApi; import cn.axzo.karma.client.model.request.ListFlowTaskAssignerReq; import cn.axzo.karma.client.model.response.FlowTaskAssignerResp; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; @@ -12,7 +11,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.ListUtils; import org.flowable.bpmn.model.FlowElement; import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; @@ -28,9 +26,6 @@ import java.util.stream.Collectors; @Component public class BasedIdentityTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { - @Autowired - private FlowSupportApi flowSupportApi; - @Override public String getType() { return ApproverSpecifyEnum.identity.getType(); @@ -59,11 +54,11 @@ public class BasedIdentityTaskAssigneeSelector extends AbstractBpmnTaskAssigneeS .collect(Collectors.toList())) .identityCodes(super.getTypes(flowElement)) .build(); - + req.setProcInstId(execution.getProcessInstanceId()); List flowTaskAssigners = parseApiResult(() -> flowSupportApi.listTaskAssignerByIdentity(req), "审批节点: " + flowElement.getId() + ", 通过身份查询审批人", - "cn.axzo.karma.client.feign.FlowSupportApi.listTaskAssignerByIdentity", req); + "cn.axzo.karma.client.feign.FlowSupportApi#listTaskAssignerByIdentity", req); if (CollUtil.isEmpty(flowTaskAssigners)) { return super.invokeService(flowElement, execution, scopeDto); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderTaskAssigneeSelector.java index 66b0835d6..3d72630af 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderTaskAssigneeSelector.java @@ -68,11 +68,11 @@ public class BasedInitiatorLeaderTaskAssigneeSelector extends AbstractBpmnTaskAs .identityId(Long.valueOf(initiator.getAssignee())) .identityType(Integer.valueOf(initiator.getAssigneeType())).build()) .build(); - + req.setProcInstId(execution.getProcessInstanceId()); List flowTaskAssigners = parseApiResult(() -> organizationalNodeUserApi.listFlowTaskAssigner(req), "审批节点: " + flowElement.getId() + ", 通过发起人主管查询审批人", - "cn.axzo.maokai.api.client.OrganizationalNodeUserApi.listFlowTaskAssigner", req); + "cn.axzo.maokai.api.client.OrganizationalNodeUserApi#listFlowTaskAssigner", req); if (CollUtil.isEmpty(flowTaskAssigners)) { return super.invokeService(flowElement, execution, scopeDto); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedPositionTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedPositionTaskAssigneeSelector.java index cadd12405..b799530d4 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedPositionTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedPositionTaskAssigneeSelector.java @@ -60,11 +60,11 @@ public class BasedPositionTaskAssigneeSelector extends AbstractBpmnTaskAssigneeS List workerTeamScopes = ListUtils.emptyIfNull(scopeDto.getWorkerTeamScopes()).stream() .map(e -> BeanUtil.copyProperties(e, FlowTaskAssignerReq.OrgScope.class)).collect(Collectors.toList()); req.getOrgScopes().addAll(workerTeamScopes); - + req.setProcInstId(execution.getProcessInstanceId()); List flowTaskAssigners = parseApiResult(() -> organizationalNodeUserApi.listFlowTaskAssigner(req), "审批节点: " + flowElement.getId() + ", 通过岗位查询审批人", - "cn.axzo.maokai.api.client.OrganizationalNodeUserApi.listFlowTaskAssigner", req); + "cn.axzo.maokai.api.client.OrganizationalNodeUserApi#listFlowTaskAssigner", req); if (CollUtil.isEmpty(flowTaskAssigners)) { return super.invokeService(flowElement, execution, scopeDto); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedRoleTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedRoleTaskAssigneeSelector.java index 8e3968943..d95e677fd 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedRoleTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedRoleTaskAssigneeSelector.java @@ -1,24 +1,32 @@ package cn.axzo.workflow.server.controller.delegate; -import cn.axzo.tyr.client.feign.TyrSaasRoleUserApi; -import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserDTO; -import cn.axzo.tyr.client.model.roleuser.req.RoleUserParam; +import cn.axzo.karma.client.feign.FlowSupportApi; +import cn.axzo.karma.client.model.request.ListFlowTaskAssignerReq; +import cn.axzo.karma.client.model.response.FlowTaskAssignerResp; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; -import cn.axzo.workflow.common.model.dto.CooperationOrgDTO.OrgScope; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; +import cn.axzo.workflow.common.model.response.category.CategoryItemVO; import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDTO; +import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; +import cn.axzo.workflow.core.service.CategoryService; import cn.hutool.core.collection.CollUtil; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.ListUtils; import org.flowable.bpmn.model.FlowElement; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import java.util.Collections; import java.util.List; -import java.util.Set; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; +import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; + /** * 基于"角色"查询审批人 * @@ -30,7 +38,10 @@ import java.util.stream.Collectors; public class BasedRoleTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { @Autowired - private TyrSaasRoleUserApi tyrSaasRoleUserApi; + private CategoryService categoryService; + + @Autowired + private BpmnProcessDefinitionService bpmnProcessDefinitionService; @Override public String getType() { @@ -51,33 +62,47 @@ public class BasedRoleTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelec @Override protected List invokeService(FlowElement flowElement, DelegateExecution execution, ApproverScopeDTO scopeDto) { - Set workspaceIdSet = ListUtils.emptyIfNull(scopeDto.getOrgScopes()).stream() - .map(OrgScope::getWorkspaceId) - .collect(Collectors.toSet()); - - - // ouIds、workspaceIds 只能传其中一个 然后代码过滤 - RoleUserParam param = RoleUserParam.builder() - .roleIds(super.getTypes(flowElement).stream().map(Long::valueOf).collect(Collectors.toSet())) - .ouIds(ListUtils.emptyIfNull(scopeDto.getOrgScopes()).stream() - .map(OrgScope::getOuId) + BpmnProcessDefinitionVO processDefinition = bpmnProcessDefinitionService.getProcessDefinition(execution.getProcessDefinitionId()); + Optional categoryItemVO = categoryService.get(BPM_MODEL_CATEGORY, processDefinition.getKey()); + Map variables = execution.getVariables(); + ListFlowTaskAssignerReq req = ListFlowTaskAssignerReq.builder() + .procInstId(execution.getProcessInstanceId()) + .procInstType(Integer.valueOf(Objects.requireNonNull(categoryItemVO.map(CategoryItemVO::getWorkspaceTypeCode).orElse(null)))) + .orgScopes(CollectionUtils.isEmpty(scopeDto.getOrgScopes()) ? Collections.emptyList() : scopeDto.getOrgScopes().stream() + .map(os -> ListFlowTaskAssignerReq.OrgScope.builder() + .ouId(os.getOuId()) + .nodeId(os.getNodeId()) + .workspaceId(os.getWorkspaceId()) + .workspaceType(os.getWorkspaceType()) + .build()) .collect(Collectors.toList())) + .workerTeamScopes(CollectionUtils.isEmpty(scopeDto.getWorkerTeamScopes()) ? Collections.emptyList() : scopeDto.getWorkerTeamScopes().stream() + .map(ot -> ListFlowTaskAssignerReq.OrgScope.builder() + .workspaceId(ot.getWorkspaceId()) + .ouId(ot.getOuId()) + .nodeId(ot.getNodeId()) + .workspaceType(ot.getWorkspaceType()) + .build()) + .collect(Collectors.toList())) + .roleIds(super.getTypes(flowElement).stream().map(Long::valueOf).collect(Collectors.toList())) + .variables(variables) .build(); - List flowTaskAssigners = parseApiResult(() -> tyrSaasRoleUserApi.roleUserList(param), + + req.setProcInstId(execution.getProcessInstanceId()); + List flowTaskAssigners = parseApiResult(() -> flowSupportApi.listTaskAssignerByRoles(req), "审批节点: " + flowElement.getId() + ", 通过角色查询审批人", - "cn.axzo.tyr.client.feign.TyrSaasRoleUserApi.roleUserList", param) - .stream().filter(f -> workspaceIdSet.contains(f.getWorkspaceId())) - .collect(Collectors.toList()); + "cn.axzo.karma.client.feign.FlowSupportApi#listTaskAssignerByRoles", req); if (CollUtil.isEmpty(flowTaskAssigners)) { return super.invokeService(flowElement, execution, scopeDto); } - return flowTaskAssigners.stream().map(u -> BpmnTaskDelegateAssigner.builder() - .assignee(String.valueOf(u.getIdentityId())) - .assigneeType(String.valueOf(u.getIdentityType())) - .tenantId(String.valueOf(u.getWorkspaceId())) + return flowTaskAssigners.stream() + .map(u -> BpmnTaskDelegateAssigner.builder() + .assignee(String.valueOf(u.getAssignee())) + .assigneeType(String.valueOf(u.getAssigneeType())) + .tenantId(String.valueOf(u.getTenantId())) .ouId(String.valueOf(u.getOuId())) - .personId(String.valueOf(u.getNaturalPersonId())) + .personId(String.valueOf(u.getPersonId())) .build()) .collect(Collectors.toList()); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/TransferToAdminTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/TransferToAdminTaskAssigneeSelector.java index e06411435..75ece3542 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/TransferToAdminTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/TransferToAdminTaskAssigneeSelector.java @@ -1,28 +1,42 @@ package cn.axzo.workflow.server.controller.delegate; -import cn.axzo.karma.client.feign.FlowSupportApi; import cn.axzo.karma.client.model.request.ListFlowTaskAssignerReq; import cn.axzo.karma.client.model.response.FlowTaskAssignerResp; import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum; import cn.axzo.workflow.common.enums.ApproverScopeEnum; +import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDTO; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.ListUtils; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_WORKSPACE_TYPE; + /** * 基于"转交给管理员"查询审批人 * @@ -33,8 +47,8 @@ import java.util.stream.Collectors; @Component public class TransferToAdminTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { - @Autowired - private FlowSupportApi flowSupportApi; + @Resource + private SupportRefreshProperties supportRefreshProperties; @Override public String getType() { @@ -56,6 +70,15 @@ public class TransferToAdminTaskAssigneeSelector extends AbstractBpmnTaskAssigne protected List invokeService(FlowElement flowElement, DelegateExecution execution, ApproverScopeDTO scopeDto) { + if(supportRefreshProperties.getUseNewToAdminApi()) { + return invokeNewQuery(flowElement, execution, scopeDto); + } else { + return invokeOldQuery(flowElement, execution, scopeDto); + } + + } + + private List invokeOldQuery(FlowElement flowElement, DelegateExecution execution, ApproverScopeDTO scopeDto) { ListFlowTaskAssignerReq.ListFlowTaskAssignerReqBuilder builder = ListFlowTaskAssignerReq.builder(); if (!CollectionUtils.isEmpty(scopeDto.getOrgScopes())) { builder.orgScopes(ListUtils.emptyIfNull(scopeDto.getOrgScopes()).stream() @@ -85,4 +108,73 @@ public class TransferToAdminTaskAssigneeSelector extends AbstractBpmnTaskAssigne } return BeanUtil.copyToList(flowTaskAssigners, BpmnTaskDelegateAssigner.class); } + + private List invokeNewQuery(FlowElement flowElement, DelegateExecution execution, ApproverScopeDTO scopeDto) { + Optional approverScope = BpmnMetaParserHelper.getApproverScope((UserTask) flowElement); + Optional optSpecify = BpmnMetaParserHelper.getApproverSpecify((UserTask) flowElement); + // 如果是项目部,且审批人指定的配法不是岗位或角色,则默认直接返回空集合,走转交管理员后为空的最终兜底逻辑 + if (optSpecify.isPresent() && approverScope.isPresent() + && Objects.equals(ApproverScopeEnum.projectWorkspace, approverScope.get()) + && !Objects.equals(ApproverSpecifyEnum.position, optSpecify.get()) + && !Objects.equals(ApproverSpecifyEnum.role, optSpecify.get())) { + return Collections.emptyList(); + } + + ListFlowTaskAssignerReq.ListFlowTaskAssignerReqBuilder builder = ListFlowTaskAssignerReq.builder(); + if (!CollectionUtils.isEmpty(scopeDto.getOrgScopes())) { + builder.orgScopes(ListUtils.emptyIfNull(scopeDto.getOrgScopes()).stream() + .map(e -> BeanUtil.copyProperties(e, ListFlowTaskAssignerReq.OrgScope.class)) + .collect(Collectors.toList())); + } + if (!CollectionUtils.isEmpty(scopeDto.getWorkerTeamScopes())) { + builder.workerTeamScopes(ListUtils.emptyIfNull(scopeDto.getOrgScopes()).stream() + .map(w -> BeanUtil.copyProperties(w, ListFlowTaskAssignerReq.OrgScope.class)) + .collect(Collectors.toList())); + } + ListFlowTaskAssignerReq req = builder.workspaceAdmin(false).build(); + req.setProcInstId(execution.getProcessInstanceId()); + if(Objects.equals(ApproverScopeEnum.entWorkspace, approverScope.get()) && Objects.equals(ApproverSpecifyEnum.fixedPerson,optSpecify.get())) { + req.setCooperateTypes(Sets.newHashSet(1,2,3,4,5,6,7,8,9,11,30)); + } else { + req.setCooperateTypes(getCooperationTypes(flowElement)); + } + // 发起人主管找其超管时,需要将发起人的数据包装进 orgScope + if (Objects.equals(ApproverSpecifyEnum.initiatorLeader, optSpecify.get())) { + BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(execution.getVariable(INTERNAL_INITIATOR, String.class)); + Integer workspaceType = execution.getVariable(INTERNAL_PROCESS_WORKSPACE_TYPE, Integer.class); + req.setOrgScopes(Lists.newArrayList(new ListFlowTaskAssignerReq.OrgScope(workspaceType, Long.parseLong(initiator.getTenantId()), Long.parseLong(initiator.getOuId()), null))); + } + approverScope.ifPresent(i -> { + if (Objects.equals(i, ApproverScopeEnum.projectWorkspace)) { + req.setWorkspaceAdmin(true); + } + }); + + List flowTaskAssigners = + parseApiResult(() -> flowSupportApi.listTaskAssignerAdminV2(req), + "审批节点: " + flowElement.getId() + ", 通过管理员查询审批人", + "cn.axzo.karma.client.feign.FlowSupportApi#listTaskAssignerAdminV2", + req); + + if (CollUtil.isEmpty(flowTaskAssigners)) { + return super.invokeService(flowElement, execution, scopeDto); + } + return BeanUtil.copyToList(flowTaskAssigners, BpmnTaskDelegateAssigner.class); + } + + public Set getCooperationTypes(FlowElement flowElement) { + return BpmnMetaParserHelper.getApproverSpecifyValue((UserTask) flowElement) + .map(value -> JSON.parseArray(value, String.class) + .stream().map(JSON::parseObject) + .map(i -> i.getString("type")) + .filter(StringUtils::hasText) + .collect(Collectors.toList()) + .stream() + .flatMap(s -> Arrays.stream(s.split(","))) + .map(Integer::parseInt) + .collect(Collectors.toSet()) + ) + .orElse(Collections.emptySet()); + } + } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEventListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEvent_100_Listener.java similarity index 98% rename from workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEventListener.java rename to workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEvent_100_Listener.java index c44e396f7..5812c80db 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEventListener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEvent_100_Listener.java @@ -57,7 +57,7 @@ import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACT @Slf4j @Component @Scope("prototype") -public class RocketMqBpmActivityEventListener extends AbstractBpmnEventListener implements BpmnActivityEventListener, Ordered { +public class RocketMqBpmActivityEvent_100_Listener extends AbstractBpmnEventListener implements BpmnActivityEventListener, Ordered { @Resource private RuntimeService runtimeService; @Resource @@ -229,7 +229,7 @@ public class RocketMqBpmActivityEventListener extends AbstractBpmnEventListener< @Override public int getOrder() { - return Integer.MIN_VALUE; + return Integer.MIN_VALUE + 100; } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/error/ErrorReporterEventListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/error/ErrorReporterEventListener.java index aff371d14..f77d9bc8e 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/error/ErrorReporterEventListener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/error/ErrorReporterEventListener.java @@ -51,7 +51,7 @@ public class ErrorReporterEventListener implements BpmnAsyncJobEventListener { reporterType = ReporterType.ONLY_LOG; } if (job.getRetries() <= 1) { - reporterType.executeAction(profile, "异步任务执行异常, 重试 3 次后仍未成功", sendDingTalk, new Object[]{job}, "act_ru_job", throwable, false); + reporterType.executeAction(profile, "异步任务执行异常, 重试 3 次后仍未成功", sendDingTalk, new Object[]{job}, "act_ru_job", "", throwable, false); } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_102_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_102_Listener.java index d56610415..0bae0dea6 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_102_Listener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_102_Listener.java @@ -3,16 +3,19 @@ package cn.axzo.workflow.server.controller.listener.task; import cn.axzo.framework.rocketmq.Event; import cn.axzo.framework.rocketmq.EventProducer; import cn.axzo.workflow.common.enums.ProcessTaskEventEnum; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.response.mq.ProcessTaskDTO; import cn.axzo.workflow.core.common.context.TaskOperationContext; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; import cn.axzo.workflow.core.listener.BpmnTaskEventListener; +import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.Process; import org.flowable.engine.RepositoryService; import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; import org.flowable.engine.repository.Deployment; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.service.delegate.DelegateTask; @@ -24,14 +27,17 @@ import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_PROCESS_DEFINITION_KEY; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ASSIGNEE_SKIP_FLAT; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ATTACHMENTS_VAR_NAME; import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION; import static cn.axzo.workflow.common.enums.ProcessTaskEventEnum.PROCESS_TASK_ASSIGNED; import static cn.axzo.workflow.common.enums.ProcessTaskEventEnum.PROCESS_TASK_COMPLETED; @@ -59,6 +65,10 @@ public class RocketMqBpmnTaskEvent_102_Listener extends AbstractBpmnEventListene @Resource private RuntimeService runtimeService; @Resource + private TaskService taskService; + @Resource + private BpmnHistoricAttachmentConverter attachmentConverter; + @Resource private RepositoryService repositoryService; @Value("${sendMq:true}") private Boolean sendMQ; @@ -100,35 +110,43 @@ public class RocketMqBpmnTaskEvent_102_Listener extends AbstractBpmnEventListene private Deployment getDeployment(String processInstanceId) { ProcessInstance processInstance = - runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); return repositoryService.createDeploymentQuery().deploymentId(processInstance.getDeploymentId()).singleResult(); } public ProcessTaskDTO build(DelegateTask delegateTask, ProcessTaskEventEnum type) { Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(delegateTask.getProcessDefinitionId()).getMainProcess()); + //1.获取当前的流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(delegateTask.getProcessInstanceId()).singleResult(); String category = getDeployment(delegateTask.getProcessInstanceId()).getCategory(); ProcessTaskDTO dto = new ProcessTaskDTO() - .setType(type) - .setCategory(category) - .setProcessDefinitionKey(category) - .setProcessTaskId(delegateTask.getId()) - .setProcessInstanceId(delegateTask.getProcessInstanceId()) - .setCurrentElementKey(delegateTask.getTaskDefinitionKey()) - .setCurrentElementName(delegateTask.getName()) - .setProcessDefinitionId(delegateTask.getProcessDefinitionId()) - .setInitiator(BpmnTaskDelegateAssigner.toObjectCompatible(delegateTask.getVariable(INTERNAL_INITIATOR))) - .setApprover(BpmnTaskDelegateAssigner.toObjectCompatible( - delegateTask.getVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId()))) - .setVariables(delegateTask.getVariables()) - .setStartTime(delegateTask.getCreateTime()) - .setTenantId(delegateTask.getTenantId()); + .setType(type) + .setCategory(category) + .setProcessDefinitionKey(category) + .setProcessTaskId(delegateTask.getId()) + .setProcessInstanceId(delegateTask.getProcessInstanceId()) + .setCurrentElementKey(delegateTask.getTaskDefinitionKey()) + .setCurrentElementName(delegateTask.getName()) + .setProcessDefinitionId(delegateTask.getProcessDefinitionId()) + .setInitiator(BpmnTaskDelegateAssigner.toObjectCompatible(delegateTask.getVariable(INTERNAL_INITIATOR))) + .setApprover(BpmnTaskDelegateAssigner.toObjectCompatible( + delegateTask.getVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId()))) + .setVariables(delegateTask.getVariables()) + .setStartTime(delegateTask.getCreateTime()) + .setTenantId(delegateTask.getTenantId()) + .setBusinessKey(processInstance.getBusinessKey()) + .setAdvice(delegateTask.getTransientVariableLocal(COMMENT_TYPE_ADVICE) == null ? null : (String) delegateTask.getTransientVariableLocal(COMMENT_TYPE_ADVICE)); BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); String version = (String) runtimeService.getVariable(delegateTask.getProcessInstanceId(), - WORKFLOW_ENGINE_VERSION); + WORKFLOW_ENGINE_VERSION); if (Objects.isNull(version)) { version = FLOW_SERVER_VERSION_121; } dto.setWorkflowEngineVersion(version); + Object attachmentObj = delegateTask.getTransientVariableLocal(TASK_ATTACHMENTS_VAR_NAME); + if (attachmentObj != null) { + dto.setAttachments((List) attachmentObj); + } return dto; } @@ -144,12 +162,12 @@ public class RocketMqBpmnTaskEvent_102_Listener extends AbstractBpmnEventListene header.put(MQ_OWNERSHIP_PROCESS_DEFINITION_KEY, dto.getProcessDefinitionKey()); } eventProducer.send(Event.builder() - .shardingKey(dto.getProcessInstanceId()) - .eventCode(eventEnum.getEventCode()) - .targetId(dto.getProcessInstanceId()) - .targetType(dto.getProcessDefinitionKey()) - .data(dto) - .build(), header); + .shardingKey(dto.getProcessInstanceId()) + .eventCode(eventEnum.getEventCode()) + .targetId(dto.getProcessInstanceId()) + .targetType(dto.getProcessDefinitionKey()) + .data(dto) + .build(), header); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/service/impl/CheckApproverServiceImpl.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/service/impl/CheckApproverServiceImpl.java index 62db9bd4a..983907017 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/service/impl/CheckApproverServiceImpl.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/service/impl/CheckApproverServiceImpl.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -57,8 +58,8 @@ public class CheckApproverServiceImpl implements CheckApproverService { //业务节点,指定业务审批人,或者业务设置了审批人,也要参与自动过审,node_type为NODE_BUSINESS,节点元素类型为UserTask //业务节点审批人为空,或者人员未指定,此时不进行自动过审操作,需要再业务指定审批人后,再做自动过审动作 if (!(Objects.equals(currentNodeType, NODE_TASK) || Objects.equals(currentNodeType, NODE_BUSINESS)) || - !StringUtils.hasText(delegateTask.getAssignee()) || - delegateTask.getAssignee().equals(NO_ASSIGNEE)) { + !StringUtils.hasText(delegateTask.getAssignee()) || + delegateTask.getAssignee().equals(NO_ASSIGNEE)) { return exists.get(); } Optional optConfig = BpmnMetaParserHelper.getButtonConfig(mainProcess, delegateTask.getTaskDefinitionKey()); @@ -71,49 +72,60 @@ public class CheckApproverServiceImpl implements CheckApproverService { return exists.get(); } Optional agreeButton = currentButtons.stream() - .filter(button -> button.getType().equals("SYSTEM") //系统按钮 - && button.getChecked() != null && button.getChecked() //选中 - && (button.getDisabled() == null || !button.getDisabled()) //没用禁用 - && button.getBtnKey().equals(BpmnButtonEnum.BPMN_APPROVE.getBtnKey())) //类型为同意 - .findFirst(); + .filter(button -> button.getType().equals("SYSTEM") //系统按钮 + && button.getChecked() != null && button.getChecked() //选中 + && (button.getDisabled() == null || !button.getDisabled()) //没用禁用 + && button.getBtnKey().equals(BpmnButtonEnum.BPMN_APPROVE.getBtnKey())) //类型为同意 + .findFirst(); //不存在同意按钮 if (!agreeButton.isPresent()) { return exists.get(); } ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); processEngineConfiguration.getActivityInstanceEntityManager() - .findActivityInstancesByProcessInstanceId(delegateTask.getProcessInstanceId(), false) - .stream() - .filter(i -> !Objects.equals(i.getActivityId(), userTask.getId())) - .filter(i -> !Objects.equals(i.getActivityType(), "exclusiveGateway")) - .filter(i -> !Objects.equals(i.getActivityType(), "sequenceFlow")) - .max(Comparator.comparing(ActivityInstanceEntity::getStartTime)) - .ifPresent(i -> { - // 与发起人比对 - if (Objects.equals(NODE_STARTER.getType(), i.getActivityId())) { - BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(delegateTask.getVariable(INTERNAL_INITIATOR)); - if (Objects.nonNull(initiator) && initiator.comparePersonIdToOther(delegateTask.getAssignee())) { - exists.compareAndSet(false, true); - } - } else { - FlowElement flowElement = mainProcess.getFlowElement(i.getActivityId()); - BpmnMetaParserHelper.getNodeType(flowElement).ifPresent(j -> { - //上一节点如果是业务节点,但是是人员审批,也需要加入到自动过审 - if (Objects.equals(NODE_TASK, j) || (Objects.equals(NODE_BUSINESS, j) && flowElement.getClass().isAssignableFrom(UserTask.class))) { - ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); - searchDTO.setProcessInstanceId(delegateTask.getProcessInstanceId()); - searchDTO.setTaskDefinitionKey(i.getActivityId()); - taskOperationContext.getExtAxHiTaskInsts(() -> extAxHiTaskInstService.queryList(searchDTO)) - .stream().filter(e -> Objects.equals(e.getStatus(), APPROVED.getStatus())) - .map(ExtAxHiTaskInst::getAssignee) - .filter(Objects::nonNull) - .filter(StringUtils::hasText) - .filter(k -> specialApproverComparison(k, delegateTask.getAssignee())) - .findAny().ifPresent(k -> exists.compareAndSet(false, true)); - } - }); + .findActivityInstancesByProcessInstanceId(delegateTask.getProcessInstanceId(), false) + .stream() + .filter(i -> !Objects.equals(i.getActivityId(), userTask.getId())) + .filter(i -> !Objects.equals(i.getActivityType(), "exclusiveGateway")) + .filter(i -> !Objects.equals(i.getActivityType(), "sequenceFlow")) + .max(Comparator.comparing(ActivityInstanceEntity::getStartTime)) + .ifPresent(i -> { + // 与发起人比对 + if (Objects.equals(NODE_STARTER.getType(), i.getActivityId())) { + BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(delegateTask.getVariable(INTERNAL_INITIATOR)); + if (Objects.nonNull(initiator) && initiator.comparePersonIdToOther(delegateTask.getAssignee())) { + exists.compareAndSet(false, true); } - }); + } else { + FlowElement flowElement = mainProcess.getFlowElement(i.getActivityId()); + BpmnMetaParserHelper.getNodeType(flowElement).ifPresent(j -> { + //上一节点如果是业务节点,但是是人员审批,也需要加入到自动过审 + if (Objects.equals(NODE_TASK, j) || (Objects.equals(NODE_BUSINESS, j) && flowElement.getClass().isAssignableFrom(UserTask.class))) { + ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); + searchDTO.setProcessInstanceId(delegateTask.getProcessInstanceId()); + List extAxHiTaskInsts = extAxHiTaskInstService.queryList(searchDTO); + extAxHiTaskInsts.sort(Comparator.comparing(ExtAxHiTaskInst::getCreateAt)); + List previousTasks = new ArrayList<>(); + for (int k = extAxHiTaskInsts.size() - 1; k > 0; k--) { + ExtAxHiTaskInst extAxHiTaskInst = extAxHiTaskInsts.get(k); + if (!extAxHiTaskInst.getTaskDefinitionKey().equals(i.getActivityId()) && !CollectionUtils.isEmpty(previousTasks)) { + break; + } + if (extAxHiTaskInst.getTaskDefinitionKey().equals(i.getActivityId())) { + previousTasks.add(extAxHiTaskInst); + } + } + previousTasks.stream() + .filter(e -> Objects.equals(e.getStatus(), APPROVED.getStatus())) + .map(ExtAxHiTaskInst::getAssignee) + .filter(Objects::nonNull) + .filter(StringUtils::hasText) + .filter(k -> specialApproverComparison(k, delegateTask.getAssignee())) + .findAny().ifPresent(k -> exists.compareAndSet(false, true)); + } + }); + } + }); return exists.get(); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/BasicPopulateAvatarController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/BasicPopulateAvatarController.java new file mode 100644 index 000000000..380679a83 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/BasicPopulateAvatarController.java @@ -0,0 +1,59 @@ +package cn.axzo.workflow.server.controller.web; + +import cn.axzo.karma.client.feign.FlowSupportApi; +import cn.axzo.karma.client.model.request.PersonProfileQueryReq; +import cn.axzo.karma.client.model.response.PersonProfileResp; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.server.common.util.RpcExternalUtil; +import cn.axzo.workflow.server.controller.delegate.AbstractBpmnTaskAssigneeSelector; +import com.google.common.collect.Lists; +import org.springframework.context.ApplicationContext; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.server.controller.delegate.AbstractBpmnTaskAssigneeSelector.populateNameAndAvatar; + +/** + * 公共的获取 BpmnTask + * + * @author wangli + * @since 2024-09-09 16:51 + */ +public abstract class BasicPopulateAvatarController { + + @Resource + protected FlowSupportApi flowSupportApi; + @Resource + private SupportRefreshProperties refreshProperties; + @Resource + private ApplicationContext applicationContext; + + /** + * 为一个人填充头像 + * + * @param assigner + */ + protected final void populateUsersAvatar(BpmnTaskDelegateAssigner assigner) { + if (Objects.isNull(assigner)) { + return; + } + populateUsersAvatar(Lists.newArrayList(assigner)); + } + + /** + * 为一个集合的人填充头像 + * + * @param assigners + */ + protected final void populateUsersAvatar(List assigners) { + populateNameAndAvatar(assigners, flowSupportApi, refreshProperties, applicationContext); + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java index a3fe66874..c66f1934f 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/TestController.java @@ -1,13 +1,8 @@ package cn.axzo.workflow.server.controller.web; import cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi; -import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; -import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; -import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; import cn.axzo.workflow.core.service.BpmnProcessInstanceService; @@ -30,10 +25,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.InputStream; -import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -73,8 +66,8 @@ public class TestController { @RepeatSubmit @GetMapping("/test") - public void test(@RequestParam String processInstanceId) { - List flowElements = forecastService.performProcessForecasting(processInstanceId, null); + public void test(@RequestParam String processInstanceId, @RequestParam(required = false) String taskDefinitionKey, @RequestParam(required = false) Boolean containSelf) { + List flowElements = forecastService.performProcessForecasting(processInstanceId, null, taskDefinitionKey, containSelf); System.out.println("flowElements = " + flowElements); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessActivityController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessActivityController.java index 38f08a9eb..1c4446237 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessActivityController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessActivityController.java @@ -4,12 +4,15 @@ import cn.axzo.workflow.client.feign.bpmn.ProcessActivityApi; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.service.BpmnProcessActivityService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; +import cn.axzo.workflow.server.controller.web.BasicPopulateAvatarController; import cn.azxo.framework.common.model.CommonResponse; +import com.alibaba.fastjson.JSON; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; @@ -27,6 +30,7 @@ import java.util.List; import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_BIZ_SET_ASSIGNEE_HAS_REPEAT; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.removeDuplicateByPersonId; +import static cn.azxo.framework.common.model.CommonResponse.success; /** * 流程活动相关控制器 @@ -39,44 +43,40 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.removeDup @RestController @ErrorReporter @Validated -public class BpmnProcessActivityController implements ProcessActivityApi { +public class BpmnProcessActivityController extends BasicPopulateAvatarController implements ProcessActivityApi { @Resource private BpmnProcessActivityService bpmnProcessActivityService; /** - * 业务节点唤醒 - * - * @param triggerId 触发 ID,数据来源于事件 - * @return - */ - @Operation(summary = "业务节点唤醒") - @GetMapping("/v2/trigger") - @RepeatSubmit - public CommonResponse trigger(@RequestParam @NotBlank(message = "触发 ID 不能为空") String triggerId, - @RequestParam(required = false, defaultValue = "false") Boolean async) { - log.info("业务节点唤醒 trigger ===>>>参数:{}, {}", triggerId, async); - if (async != null && async) { - bpmnProcessActivityService.triggerAsync(triggerId); - } else { - bpmnProcessActivityService.trigger(triggerId); - } - return CommonResponse.success(true); - } - - /** - * old/trigger 地址是 1.3.3 版本以前的接口,在接入方未完全升级前,都需要保留 + * 业务节点唤醒 旧版本使用的接口 * * @param triggerId * @return */ @Operation(summary = "业务节点唤醒") - @GetMapping({"/trigger", "/old/trigger"}) + @GetMapping("/trigger") @Override @RepeatSubmit public CommonResponse trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId) { log.info("业务节点唤醒 trigger2 ===>>>参数:{}", triggerId); - return trigger(triggerId, true); + return trigger(new BpmnActivityTriggerDTO(triggerId, true)); + } + + /** + * 业务节点唤醒 + * + * @param dto + * @return + */ + @Operation(summary = "业务节点唤醒") + @PostMapping("/trigger") + @Override + @RepeatSubmit + public CommonResponse trigger(@Validated @RequestBody BpmnActivityTriggerDTO dto) { + log.info("业务节点唤醒 trigger ===>>>参数:{}", JSON.toJSONString(dto)); + bpmnProcessActivityService.trigger(dto); + return success(true); } /** @@ -92,7 +92,7 @@ public class BpmnProcessActivityController implements ProcessActivityApi { @Override @RepeatSubmit public CommonResponse setAssignee(@Validated @RequestBody BpmnActivitySetAssigneeDTO dto) { - log.info("业务节点设置审批人 setAssignee ===>>>参数:{}", dto); + log.info("业务节点设置审批人 setAssignee ===>>>参数:{}", JSON.toJSONString(dto)); if (!dto.getServerSideDeduplication()) { List personIds = new ArrayList<>(); for (BpmnTaskDelegateAssigner assigner : dto.getAssigners()) { @@ -104,12 +104,12 @@ public class BpmnProcessActivityController implements ProcessActivityApi { } else { dto.setAssigners(removeDuplicateByPersonId(dto.getAssigners())); } - if (dto.getAsync() != null && dto.getAsync()) { - bpmnProcessActivityService.setAssigneeAsync(dto); - } else { - bpmnProcessActivityService.setAssignee(dto); - } - return CommonResponse.success(true); + + //填充头像 + populateUsersAvatar(dto.getAssigners()); + + bpmnProcessActivityService.setAssignee(dto); + return success(true); } /** diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java index 173adfd8b..ade90a7d7 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java @@ -1,5 +1,9 @@ package cn.axzo.workflow.server.controller.web.bpmn; +import cn.axzo.karma.client.feign.FlowSupportApi; +import cn.axzo.oss.http.api.ServerFileServiceApi; +import cn.axzo.oss.http.model.ApiSignUrlDownloadRequest; +import cn.axzo.oss.http.model.ApiSignUrlDownloadResponse; import cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO; @@ -8,26 +12,33 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCar import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.HistoricProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceLogVO; import cn.axzo.workflow.core.service.BpmnProcessInstanceService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; +import cn.axzo.workflow.server.common.util.RpcExternalUtil; +import cn.axzo.workflow.server.controller.web.BasicPopulateAvatarController; import cn.azxo.framework.common.model.CommonResponse; import cn.hutool.json.JSONUtil; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; import org.flowable.engine.history.HistoricProcessInstance; -import org.springframework.cache.annotation.CachePut; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -44,6 +55,8 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import static cn.azxo.framework.common.model.CommonResponse.success; @@ -55,10 +68,14 @@ import static cn.azxo.framework.common.model.CommonResponse.success; @RestController @ErrorReporter @Validated -public class BpmnProcessInstanceController implements ProcessInstanceApi { +public class BpmnProcessInstanceController extends BasicPopulateAvatarController implements ProcessInstanceApi { @Resource private BpmnProcessInstanceService bpmnProcessInstanceService; + @Resource + private FlowSupportApi flowSupportApi; + @Resource + private ServerFileServiceApi serverFileServiceApi; /** * 超管查询所有流程实例 @@ -94,6 +111,8 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @RepeatSubmit public CommonResponse createProcessInstance(@Validated @RequestBody BpmnProcessInstanceCreateDTO dto) { log.info("发起审核createProcessInstance===>>>参数:{}", JSONUtil.toJsonStr(dto)); + // 填充头像 + populateUsersAvatar(dto.getInitiator()); return success(bpmnProcessInstanceService.createProcessInstance(dto)); } @@ -124,6 +143,7 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @RepeatSubmit public CommonResponse cancelProcessInstance(@Validated @RequestBody BpmnProcessInstanceCancelDTO dto) { log.info("撤回审核cancelProcessInstant===>>>参数:{}", JSONUtil.toJsonStr(dto)); + populateUsersAvatar(dto.getInitiator()); return success(bpmnProcessInstanceService.cancelProcessInstance(dto)); } @@ -170,6 +190,7 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @Override public CommonResponse carbonCopyProcessInstance(@Validated @RequestBody BpmnProcessInstanceCarbonCopyDTO dto) { log.info("抄送流程实例carbonCopyProcessInstance===>>>参数:{}", JSONUtil.toJsonStr(dto)); + populateUsersAvatar(dto.getCopyAssigners()); return success(bpmnProcessInstanceService.carbonCopyProcessInstance(dto)); } @@ -272,7 +293,7 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { public CommonResponse> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable String tenantId) { HistoricProcessInstance processInstance = bpmnProcessInstanceService.getProcessInstance(processInstanceId, - tenantId, true); + tenantId, true); return success(processInstance.getProcessVariables()); } @@ -305,10 +326,48 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { * @return true 是在当前流程实例中,存在指定的审批人 */ @Operation(summary = "校验指定流程实例下,是否存在指定的审批人") - @PostMapping("/api/process/instance/check/approver") + @PostMapping("/check/approver") @Override public CommonResponse checkInstanceApprover(@Validated @RequestBody BpmnProcessInstanceCheckApproverDTO dto) { log.info("校验指定流程实例下,是否存在指定的审批人 checkInstanceApprover===>>>参数:{}", dto); return success(bpmnProcessInstanceService.checkInstanceApprover(dto)); } + + /** + * 获取指定流程实例的日志 + * + * @param dto + * @return + */ + @Operation(summary = "获取指定流程实例的日志") + @PostMapping("/logs") + @Override + public CommonResponse getProcessInstanceLogs(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto) { + log.info("获取指定流程实例的日志 getProcessInstanceLog===>>>参数:{}", JSONUtil.toJsonStr(dto)); + BpmnProcessInstanceLogVO log = bpmnProcessInstanceService.getProcessInstanceLog(dto); + parseSignatureUrl(log); + return success(log); + } + + private void parseSignatureUrl(BpmnProcessInstanceLogVO log) { + List signUrls = log.getTaskDetails().stream() + .filter(i -> StringUtils.hasText(i.getTaskId())) + .map(BpmnTaskInstanceLogVO::getSignatureUrl) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(signUrls)) { + Map ossUrlMap = ListUtils.emptyIfNull(getSignPrivateUrl(signUrls)).stream() + .collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, ApiSignUrlDownloadResponse::getSignUrl, (s, t) -> s)); + log.getTaskDetails().stream() + .filter(i -> StringUtils.hasText(i.getTaskId())) + .forEach(i -> i.setSignatureUrl(ossUrlMap.getOrDefault(i.getSignatureUrl(), null))); + } + } + + private List getSignPrivateUrl(List signUrls) { + ApiSignUrlDownloadRequest request = new ApiSignUrlDownloadRequest(); + request.setFileKeys(signUrls); + return RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(request), "批量获取手写签私有访问地址", request); + } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessJobController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessJobController.java index 666101425..5cf38b0f6 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessJobController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessJobController.java @@ -5,8 +5,8 @@ import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.service.BpmnProcessJobService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.azxo.framework.common.model.CommonResponse; -import com.alibaba.nacos.common.utils.StringUtils; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -45,4 +45,22 @@ public class BpmnProcessJobController implements ProcessJobApi { } return success(); } + + @Override + @GetMapping("/dead-letter/exception/stacktrace") + public String getDeadLetterJobExceptionStacktrace(@RequestParam String procInstId) { + if (StringUtils.isBlank(procInstId)) { + return ""; + } + return bpmnProcessJobService.getDeadLetterJobExceptionStacktrace(procInstId); + } + + @Override + @GetMapping("/dead-letter/exception/stacktrace/byId") + public String getDeadLetterJobExceptionStacktraceByJobId(@RequestParam String jobId) { + if (StringUtils.isBlank(jobId)) { + return ""; + } + return bpmnProcessJobService.getDeadLetterJobExceptionStacktraceByJobId(jobId); + } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java index 14076241c..a19b53079 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java @@ -1,14 +1,20 @@ package cn.axzo.workflow.server.controller.web.bpmn; +import cn.axzo.karma.client.feign.FlowSupportApi; +import cn.axzo.karma.client.model.request.PersonProfileQueryReq; +import cn.axzo.karma.client.model.response.PersonProfileResp; import cn.axzo.workflow.client.feign.bpmn.ProcessTaskApi; import cn.axzo.workflow.common.enums.AttachmentTypeEnum; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskPageSearchDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskRemindDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; @@ -22,6 +28,8 @@ import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO; import cn.axzo.workflow.core.service.BpmnProcessTaskService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; +import cn.axzo.workflow.server.common.util.RpcExternalUtil; +import cn.axzo.workflow.server.controller.web.BasicPopulateAvatarController; import cn.azxo.framework.common.model.CommonResponse; import com.alibaba.fastjson.JSON; import io.swagger.v3.oas.annotations.Operation; @@ -44,6 +52,8 @@ import javax.validation.constraints.NotEmpty; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; import static cn.azxo.framework.common.model.CommonResponse.success; @@ -55,12 +65,11 @@ import static cn.azxo.framework.common.model.CommonResponse.success; @RestController @ErrorReporter @Validated -public class BpmnProcessTaskController implements ProcessTaskApi { +public class BpmnProcessTaskController extends BasicPopulateAvatarController implements ProcessTaskApi { @Resource private BpmnProcessTaskService bpmnProcessTaskService; - /** * 待审核列表 */ @@ -102,6 +111,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { tempAttachments.add(signature); dto.setAttachmentList(tempAttachments); } + populateUsersAvatar(dto.getApprover()); bpmnProcessTaskService.approveTask(dto); return success(true); } @@ -115,9 +125,38 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @RepeatSubmit public CommonResponse batchApproveTask(@Validated @RequestBody List dtos) { log.info("批量同意 approveTaskList===>>>参数:{}", JSON.toJSONString(dtos)); + List assigners = dtos.stream().map(BpmnTaskAuditDTO::getApprover).collect(Collectors.toList()); + populateUsersAvatar(assigners); return success(bpmnProcessTaskService.batchApproveTask(dtos)); } + /** + * 获取当前节点可回退节点选项列表 + * @param taskId 当前任务id + * @return 可选回退节点列表 + */ + @Operation(summary = "获取当前节点可回退节点选项列表") + @GetMapping("/back/optional/nodes") + @Override + public CommonResponse> getBackOptionalNodes(@RequestParam @NotBlank(message = "任务id不能为空") String taskId) { + List approveOptionalNodes = bpmnProcessTaskService.getBackOptionalNodes(taskId); + return success(approveOptionalNodes); + } + + /** + * 回退到指定节点 + */ + @Operation(summary = "回退任务到指定节点") + @PostMapping("/back") + @RepeatSubmit + @Override + public CommonResponse backTask(@Validated @RequestBody BpmnTaskBackAuditDTO dto) { + log.info("回退 backTask===>>>参数:{}", JSON.toJSONString(dto)); + populateUsersAvatar(dto.getApprover()); + bpmnProcessTaskService.backTask(dto); + return success(true); + } + /** * 驳回 */ @@ -127,6 +166,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @RepeatSubmit public CommonResponse rejectTask(@Validated @RequestBody BpmnTaskAuditDTO dto) { log.info("驳回 rejectTask===>>>参数:{}", JSON.toJSONString(dto)); + populateUsersAvatar(dto.getApprover()); bpmnProcessTaskService.rejectTask(dto); return success(true); } @@ -140,6 +180,8 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @RepeatSubmit public CommonResponse batchRejectTask(@Validated @RequestBody List dtos) { log.info("批量驳回 batchRejectTask===>>>参数:{}", JSON.toJSONString(dtos)); + List assigners = dtos.stream().map(BpmnTaskAuditDTO::getApprover).collect(Collectors.toList()); + populateUsersAvatar(assigners); return success(bpmnProcessTaskService.batchRejectTask(dtos)); } @@ -152,6 +194,8 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @RepeatSubmit public CommonResponse transferTask(@Validated @RequestBody BpmnTaskTransferDTO dto) { log.info("转交任务 transferTask===>>>参数:{}", JSON.toJSONString(dto)); + // 填充头像 + populateUsersAvatar(dto.getTargetAssigner()); bpmnProcessTaskService.transferTask(dto); return success(true); } @@ -162,6 +206,8 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @RepeatSubmit public CommonResponse batchTransferTask(@Validated @RequestBody List dtos) { log.info("批量转交任务 batchTransferTask===>>>参数:{}", JSON.toJSONString(dtos)); + List assigners = dtos.stream().map(BpmnTaskTransferDTO::getOriginAssigner).collect(Collectors.toList()); + populateUsersAvatar(assigners); return success(bpmnProcessTaskService.batchTransferTask(dtos)); } @@ -172,9 +218,10 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @PostMapping("/comment") @Override @RepeatSubmit - public CommonResponse commentTask(@Validated @RequestBody BpmnTaskCommentDTO commentDTO) { - log.info("评论流程实例 commentTask===>>>参数:{}", commentDTO); - bpmnProcessTaskService.commentTask(commentDTO); + public CommonResponse commentTask(@Validated @RequestBody BpmnTaskCommentDTO dto) { + log.info("评论流程实例 commentTask===>>>参数:{}", JSON.toJSONString(dto)); + populateUsersAvatar(dto.getOperator()); + bpmnProcessTaskService.commentTask(dto); return success(true); } @@ -186,9 +233,12 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @Override @PostMapping("/countersign") @RepeatSubmit - public CommonResponse countersignTask(@Validated @RequestBody BpmnTaskCountersignDTO countersignDTO) { - log.info("加签任务 countersignTask===>>>参数:{}", JSON.toJSONString(countersignDTO)); - bpmnProcessTaskService.countersignTask(countersignDTO); + public CommonResponse countersignTask(@Validated @RequestBody BpmnTaskCountersignDTO dto) { + log.info("加签任务 countersignTask===>>>参数:{}", JSON.toJSONString(dto)); + // 填充头像 + populateUsersAvatar(dto.getOriginAssigner()); + populateUsersAvatar(dto.getTargetAssignerList()); + bpmnProcessTaskService.countersignTask(dto); return success(true); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/VersionUpgradeInitializer.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/VersionUpgradeInitializer.java index 3100fa923..e8bcc5a18 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/VersionUpgradeInitializer.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/VersionUpgradeInitializer.java @@ -72,8 +72,8 @@ public class VersionUpgradeInitializer implements ApplicationRunner { .map(DefaultArtifactVersion::new) .sorted() .forEach(upgradeVersion -> { - log.info("数据库升级版本比较: upgradeVersion: {}, inDbVersion:{} ", upgradeVersion, dbVersion); if (upgradeVersion.compareTo(dbVersion) > 0) { + log.info("数据库升级版本比较: upgradeVersion: {}, inDbVersion:{} ", upgradeVersion, dbVersion); newVersions.add(upgradeVersion); String fileName = FILE_PREFIX + upgradeVersion + ".sql"; try { diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterBroadcastMQConfiguration.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterBroadcastMQConfiguration.java index ba89219ea..0d3b029a8 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterBroadcastMQConfiguration.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterBroadcastMQConfiguration.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -44,6 +45,7 @@ import static cn.axzo.workflow.starter.StarterRPCInvokeMQConfiguration.DEFAULT_E * @since 2024/6/5 17:39 */ @Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "rocketmq.name-server") public class StarterBroadcastMQConfiguration { private final Logger log = LoggerFactory.getLogger(StarterBroadcastMQConfiguration.class); public static final String BROADCAST_EVENT_HANDLER_REPOSITORY_BEAN_NAME = "broadcastEventHandlerRepository"; @@ -82,12 +84,13 @@ public class StarterBroadcastMQConfiguration { } @Component + @ConditionalOnProperty(name = "rocketmq.name-server") @Conditional(NonContainerEnvironmentCondition.class) @RocketMQMessageListener(topic = DEFAULT_EVENT + "${spring.profiles.active}", - consumerGroup = "GID_${spring.application.name}_workflow_engine_${GID_SEGMENT}_consumer", - consumeMode = ConsumeMode.ORDERLY, - maxReconsumeTimes = 0, - nameServer = "${rocketmq.name-server}" + consumerGroup = "GID_${spring.application.name}_workflow_engine_${GID_SEGMENT}_consumer", + consumeMode = ConsumeMode.ORDERLY, + maxReconsumeTimes = 0, + nameServer = "${rocketmq.name-server}" ) public static class WorkflowEngineBroadcastConsumer extends BaseListener implements RocketMQListener, InitializingBean { private final Logger log = LoggerFactory.getLogger(WorkflowEngineBroadcastConsumer.class); @@ -119,9 +122,9 @@ public class StarterBroadcastMQConfiguration { @Override public void afterPropertiesSet() { this.filters = ImmutableList.of( - new InnerFilterMQOwnerShip(starterProperties, applicationName), - new InnerFilterDefinitionKey(starterProperties), - new InnerFilterExtension(businessFilterProvider)); + new InnerFilterMQOwnerShip(starterProperties, applicationName), + new InnerFilterDefinitionKey(starterProperties), + new InnerFilterExtension(businessFilterProvider)); } } diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterFeignClientConfiguration.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterFeignClientConfiguration.java index 0c6b6e763..3051b32c6 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterFeignClientConfiguration.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterFeignClientConfiguration.java @@ -2,8 +2,10 @@ package cn.axzo.workflow.starter; import cn.axzo.workflow.starter.api.WorkflowCoreService; import cn.axzo.workflow.starter.api.WorkflowManageService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignClientFactoryBean; import org.springframework.context.annotation.Configuration; /** @@ -16,13 +18,16 @@ import org.springframework.context.annotation.Configuration; public class StarterFeignClientConfiguration { @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(FeignClientFactoryBean.class) @EnableFeignClients(clients = WorkflowCoreService.class) public static class WorkflowCoreServiceClient { } @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "workflow.engine.starter", value = "manageable", havingValue = "true") + @ConditionalOnClass(FeignClientFactoryBean.class) @EnableFeignClients(clients = WorkflowManageService.class) public static class WorkflowManageServiceClient { } + } diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterRPCInvokeMQConfiguration.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterRPCInvokeMQConfiguration.java index 605bbc0b0..8c74d24a1 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterRPCInvokeMQConfiguration.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterRPCInvokeMQConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -49,6 +50,7 @@ import static cn.axzo.framework.rocketmq.RocketMQEventProducer.MQ_MESSAGE_ID; * @since 2024/5/30 14:05 */ @Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "rocketmq.name-server") public class StarterRPCInvokeMQConfiguration { private final Logger log = LoggerFactory.getLogger(StarterRPCInvokeMQConfiguration.class); public static final String WORKFLOW_ENGINE_STARTER_EVENT_PRODUCER_BEAN_NAME = "workflowEngineStarterEventProducer"; @@ -72,7 +74,7 @@ public class StarterRPCInvokeMQConfiguration { * @return */ @Bean(WORKFLOW_ENGINE_STARTER_EVENT_PRODUCER_BEAN_NAME) - public EventProducer workflowEngineStarterEventProducer(RocketMQTemplate rocketMQTemplate) { + public RpcInvokeEventProducer workflowEngineStarterEventProducer(RocketMQTemplate rocketMQTemplate) { return new RpcInvokeEventProducer(rocketMQTemplate, DEFAULT_MODULE, applicationName + MODULE_NAME_SUFFIX, @@ -169,6 +171,7 @@ public class StarterRPCInvokeMQConfiguration { } @Component + @ConditionalOnProperty(name = "rocketmq.name-server") @Conditional(NonContainerEnvironmentCondition.class) @RocketMQMessageListener(topic = DEFAULT_EVENT + "${spring.profiles.active}", consumerGroup = "GID_${spring.application.name}_workflow_engine_starter_${GID_SEGMENT}_consumer", diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java index 8139b4682..4606476f1 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java @@ -25,14 +25,17 @@ import cn.axzo.workflow.starter.mq.broadcast.consumer.InnerTaskEventListener; import cn.axzo.workflow.starter.mq.broadcast.consumer.InnerWorkflowListener; import cn.axzo.workflow.starter.mq.monitor.WorkflowEngineStarterDefaultMQMonitor; import cn.axzo.workflow.starter.mq.monitor.console.WorkflowEngineStarterMQMonitorController; +import cn.axzo.workflow.starter.selector.MetaFeignClientEnableSelector; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.remoting.RPCHook; +import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -51,7 +54,7 @@ import java.util.List; */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(WorkflowEngineStarterProperties.class) -@Import({StarterFeignClientConfiguration.class, StarterBroadcastMQConfiguration.class, StarterRPCInvokeMQConfiguration.class}) +@Import({StarterFeignClientConfiguration.class, MetaFeignClientEnableSelector.class, StarterRPCInvokeMQConfiguration.class, StarterBroadcastMQConfiguration.class}) public class WorkflowEngineStarterAutoConfiguration { private final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterAutoConfiguration.class); @@ -117,6 +120,7 @@ public class WorkflowEngineStarterAutoConfiguration { } @Bean(destroyMethod = "shutdown", name = "defaultMQAdminExt") + @ConditionalOnBean(RocketMQTemplate.class) @ConditionalOnProperty(prefix = "workflow.engine.starter", value = "enable-dlq-monitor", havingValue = "true", matchIfMissing = true) public DefaultMQAdminExt defaultMQAdminExt(Environment environment) { String namesrvAddress = environment.getProperty("rocketmq.name-server"); @@ -141,6 +145,7 @@ public class WorkflowEngineStarterAutoConfiguration { } @Bean + @ConditionalOnBean(RocketMQTemplate.class) @ConditionalOnProperty(prefix = "workflow.engine.starter", value = "enable-dlq-monitor", havingValue = "true", matchIfMissing = true) public WorkflowEngineStarterDefaultMQMonitor workflowEngineStarterDefaultMQMonitor(ObjectProvider mqAdminExtObjectProvider, ObjectProvider broadcastDLQProcessorObjectProvider, @@ -149,4 +154,5 @@ public class WorkflowEngineStarterAutoConfiguration { Environment environment) { return new WorkflowEngineStarterDefaultMQMonitor(mqAdminExtObjectProvider, broadcastDLQProcessorObjectProvider, rpcDLQProcessorObjectProvider, workflowEngineStarterProperties, environment); } + } diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterProperties.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterProperties.java index 584d75484..cbe1ff22e 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterProperties.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterProperties.java @@ -18,6 +18,14 @@ import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC; */ @ConfigurationProperties(prefix = "workflow.engine.starter") public class WorkflowEngineStarterProperties { + /** + * 是否开启原始 ProcessInstanceApi、ProcessTaskApi 等类似的 FeignClient,具体可用的 API 参考 cn.axzo.workflow.client.feign 包 + *

+ * 默认 false,优先 starter 提供的 coreService、manageServer。 + * 为 true 时,会为当前应用注册原始 xxxApi FeignClient + */ + private Boolean metaFeign = false; + /** * 特殊用途,不建议接入方使用 */ @@ -84,6 +92,14 @@ public class WorkflowEngineStarterProperties { */ private Boolean alert = false; + public Boolean getMetaFeign() { + return metaFeign; + } + + public void setMetaFeign(Boolean metaFeign) { + this.metaFeign = metaFeign; + } + public Boolean getManageable() { return manageable; } diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java index beb6e0597..bb5f7f8c1 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java @@ -1,53 +1,92 @@ package cn.axzo.workflow.starter.api; -import cn.axzo.workflow.common.annotation.InvokeMode; -import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; -import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO; -import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCarbonCopyDTO; -import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; -import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; -import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; -import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; -import cn.axzo.workflow.common.util.ThreadUtil; import cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterFeignConfiguration; +import cn.axzo.workflow.common.util.ThreadUtil; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; +import cn.azxo.framework.common.model.CommonResponse; import io.swagger.v3.oas.annotations.Operation; -import org.springframework.cloud.openfeign.FeignClient; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; - -import javax.annotation.Nullable; import javax.validation.constraints.NotBlank; +import cn.axzo.workflow.common.annotation.InvokeMode; +import cn.axzo.workflow.common.annotation.Manageable; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCarbonCopyDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; +import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PutMapping; +import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; import java.util.List; import java.util.Map; - -import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC; -import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskPageSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskRemindDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceGroupVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO; +import javax.validation.constraints.NotEmpty; /** - * Workflow Engine Starter Core Service
该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口 + * Workflow Engine Starter Core Service + *

+ * 该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口 + *

+ * Auto generation by workflow engine, It cannot be manually modified */ -@FeignClient(name = "workflow-engine-starter-core", url = "${axzo.service.workflow-engine:workflow-engine:8080}", configuration = WorkflowEngineStarterFeignConfiguration.class) +@org.springframework.cloud.openfeign.FeignClient(name = "workflow-engine-starter-core", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = WorkflowEngineStarterFeignConfiguration.class) public interface WorkflowCoreService { /** - * 业务节点唤醒 + * 业务节点唤醒, 该节点废弃,请换成 {@link ProcessActivityApi#trigger(cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO)} 接口 *

* 当模型中使用了“业务节点”,且设置了“不设置审批人”模式,则当业务监听到 PROCESS_ACTIVITY_START 事件时,可通过该接口推动流程继续运行 */ + @Deprecated @GetMapping("/api/process/activity/trigger") Boolean trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId); + /** + * 业务节点唤醒 + * + * @param dto + * @return + */ + @PostMapping("/api/process/activity/trigger") + Boolean trigger(@Validated @RequestBody BpmnActivityTriggerDTO dto); + /** * 业务节点设置审批人, 不支持重复设置 *

@@ -148,6 +187,17 @@ public interface WorkflowCoreService { @InvokeMode(SYNC) Map getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + /** + * 获取指定流程的日志 + * + * @param dto + * @return + */ + @Operation(summary = "获取指定流程的日志") + @PostMapping("/api/process/instance/logs") + @InvokeMode(SYNC) + BpmnProcessInstanceLogVO getProcessInstanceLogs(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto); + /** * 同意 * @@ -173,6 +223,24 @@ public interface WorkflowCoreService { @PostMapping("/api/process/task/batch/approve") BatchOperationResultVO batchApproveTask(@Validated @RequestBody List dtos); + /** + * 获取当前节点可回退节点选项列表 + * @param taskId 当前任务id + * @return 可以回退节点列表 + */ + @Operation(summary = "获取当前节点可回退节点选项列表") + @GetMapping("/api/process/task/back/optional/nodes") + List getBackOptionalNodes(@RequestParam @NotBlank(message = "任务id不能为空") String taskId); + + /** + * 回退到指定节点 + * @param dto + * @return + */ + @Operation(summary = "回退") + @PostMapping("/api/process/task/back") + Boolean backTask(@Validated @RequestBody BpmnTaskBackAuditDTO dto); + /** * 驳回 * diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java index 5b3f86a23..b076bd213 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java @@ -1,70 +1,92 @@ package cn.axzo.workflow.starter.api; +import cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterFeignConfiguration; +import cn.axzo.workflow.common.util.ThreadUtil; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; +import cn.axzo.workflow.client.config.CommonFeignConfiguration; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.Manageable; import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; -import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable; -import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; -import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO; -import cn.axzo.workflow.common.model.request.bpmn.definition.BpmnProcessDefinitionUpdateDTO; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import java.util.List; import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO; import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelUpdateDTO; -import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessDefinitionPageDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCarbonCopyDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskPageSearchDTO; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskRemindDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; +import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; +import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import com.fasterxml.jackson.databind.node.ObjectNode; +import javax.annotation.Nullable; +import java.util.Map; import cn.axzo.workflow.common.model.request.category.CategoryConfigCreateDTO; import cn.axzo.workflow.common.model.request.category.CategoryConfigSearchDTO; import cn.axzo.workflow.common.model.request.category.CategoryCreateDTO; import cn.axzo.workflow.common.model.request.category.CategorySearchDTO; import cn.axzo.workflow.common.model.request.category.CategoryUpdateDTO; -import cn.axzo.workflow.common.model.response.BpmPageResult; -import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelDetailVO; -import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO; -import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; -import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO; -import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO; -import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO; +import cn.axzo.workflow.common.model.response.category.CategoryItemVO; +import org.springframework.web.bind.annotation.PathVariable; +import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskPageSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskRemindDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceGroupVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO; -import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO; -import cn.axzo.workflow.common.model.response.category.CategoryItemVO; -import cn.axzo.workflow.common.util.ThreadUtil; -import cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterFeignConfiguration; -import com.fasterxml.jackson.databind.node.ObjectNode; -import io.swagger.v3.oas.annotations.Operation; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; - -import javax.annotation.Nullable; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import java.util.List; -import java.util.Map; - -import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC; -import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; +import cn.axzo.workflow.common.model.request.bpmn.definition.BpmnProcessDefinitionUpdateDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessDefinitionPageDTO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; /** - * Workflow Engine Starter Management Service
该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口 + * Workflow Engine Starter Management Service + *

+ * 该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口 + *

+ * Auto generation by workflow engine, It cannot be manually modified */ -@FeignClient(name = "workflow-engine-starter-manage", url = "${axzo.service.workflow-engine:workflow-engine:8080}", configuration = WorkflowEngineStarterFeignConfiguration.class) +@org.springframework.cloud.openfeign.FeignClient(name = "workflow-engine-starter-manage", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = WorkflowEngineStarterFeignConfiguration.class) public interface WorkflowManageService { /** @@ -218,26 +240,6 @@ public interface WorkflowManageService { @InvokeMode(SYNC) List getModelTenantIds(); - /** - * 该功能应该利用引擎的 TimerBoundaryEvent 来实现,但为了简便,先利用引擎的任务调度来实现 - * - * @return - */ - @PostMapping("/api/process/activity/timeout/trigger") - @Manageable - @Operation(summary = "设置指定业务节点定时继续往下执行") - Boolean setTimeoutTrigger(@Validated @RequestBody BpmnActivityTimeoutTriggerDTO dto); - - /** - * 为指定业务节点设置定时回调 - * - * @return - */ - @Manageable - @PostMapping("/api/process/activity/timeout/callback") - @Operation(summary = "设置指定业务节点定时回调") - Boolean setTimeoutCallback(@Validated @RequestBody BpmnActivityTimeoutCallbackDTO dto); - /** * 创建审批流程并带上表单 * @@ -355,6 +357,24 @@ public interface WorkflowManageService { @Manageable Void executeDeadLetterJobAction(@RequestParam(required = false) String jobId, @RequestParam(required = false) String procInstId); + /** + * 查询死信消息数据 + * @param procInstId 流程实例id + * @return + */ + @GetMapping("/dead-letter/exception/stacktrace") + @Manageable + String getDeadLetterJobExceptionStacktrace(@RequestParam String procInstId); + + /** + * 查询死信消息数据 + * @param jobId 死信job的id + * @return + */ + @GetMapping("/dead-letter/exception/stacktrace/byId") + @Manageable + String getDeadLetterJobExceptionStacktraceByJobId(@RequestParam String jobId); + /** * 获取指定业务分类 * diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/ComplexInvokeClient.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/ComplexInvokeClient.java index 5d1f90f62..81e7c81d2 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/ComplexInvokeClient.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/ComplexInvokeClient.java @@ -1,6 +1,5 @@ package cn.axzo.workflow.starter.feign.ext; -import cn.axzo.framework.rocketmq.EventProducer; import cn.axzo.workflow.common.enums.RpcInvokeModeEnum; import cn.axzo.workflow.common.model.response.mq.WorkflowEngineStarterRpcInvokeDTO; import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; @@ -15,6 +14,8 @@ import feign.Response; import lombok.SneakyThrows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.http.HttpStatus; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.DeleteMapping; @@ -49,6 +50,7 @@ import java.util.regex.Pattern; import static cn.axzo.workflow.common.constant.StarterConstants.STARTER_INVOKE_MODE; import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; import static cn.axzo.workflow.common.enums.WorkflowEngineEventEnum.WORKFLOW_ENGINE_STARTER; +import static cn.axzo.workflow.starter.StarterRPCInvokeMQConfiguration.WORKFLOW_ENGINE_STARTER_EVENT_PRODUCER_BEAN_NAME; import static java.nio.charset.StandardCharsets.UTF_8; /** @@ -63,14 +65,14 @@ public class ComplexInvokeClient implements Client { private final Logger log = LoggerFactory.getLogger(ComplexInvokeClient.class); private final WorkflowEngineStarterProperties starterProperties; - private final RpcInvokeEventProducer eventProducer; + private final BeanFactory beanFactory; private final Client feignClient; public ComplexInvokeClient(WorkflowEngineStarterProperties starterProperties, - EventProducer eventProducer, + BeanFactory beanFactory, Client feignClient) { this.starterProperties = starterProperties; - this.eventProducer = (RpcInvokeEventProducer) eventProducer; + this.beanFactory = beanFactory; //(RpcInvokeEventProducer) eventProducer; this.feignClient = feignClient; } @@ -78,40 +80,45 @@ public class ComplexInvokeClient implements Client { public Response execute(Request request, Request.Options options) throws IOException { log.debug("ComplexInvokeClient execute... Url: {}", request.url()); RpcInvokeModeEnum currentInvokeModeEnum = getInvokeMode(request); - Map> headers = request.headers(); - headers.forEach((k, v) -> log.debug("ComplexInvokeClient Header: {} = {}", k, v)); - log.debug("[{}] invoke url: {}", currentInvokeModeEnum, request.url()); if (Objects.equals(SYNC, currentInvokeModeEnum)) { return feignClient.execute(request, options); } - asyncInvoke(request); - - return Response.builder() - .status(HttpStatus.OK.value()) - .reason(HttpStatus.OK.getReasonPhrase()) - .headers(headers) - .request(request) - .body(body) - .build(); + return asyncInvoke(request, options); } /** * 发送 RPC 调用动作的 MQ 事件 * * @param request + * @param options */ - private void asyncInvoke(Request request) { - WorkflowEngineStarterRpcInvokeDTO event = new WorkflowEngineStarterRpcInvokeDTO(); - MethodMetadata metadata = request.requestTemplate().methodMetadata(); - event.setClassName(metadata.targetType().getName()); - event.setMethodName(metadata.method().getName()); + private Response asyncInvoke(Request request, Request.Options options) throws IOException { + RpcInvokeEventProducer producer = null; + try { + producer = beanFactory.getBean(WORKFLOW_ENGINE_STARTER_EVENT_PRODUCER_BEAN_NAME, RpcInvokeEventProducer.class); + WorkflowEngineStarterRpcInvokeDTO event = new WorkflowEngineStarterRpcInvokeDTO(); + MethodMetadata metadata = request.requestTemplate().methodMetadata(); + event.setClassName(metadata.targetType().getName()); + event.setMethodName(metadata.method().getName()); - List args = new ArrayList<>(); - event.setParameters(args); - buildArgs(request, metadata, args); - log.debug("[async-invoke] sourceEvent: {}", JSON.toJSONString(event)); - eventProducer.send(WORKFLOW_ENGINE_STARTER, event); + List args = new ArrayList<>(); + event.setParameters(args); + buildArgs(request, metadata, args); + log.debug("[async-invoke] sourceEvent: {}", JSON.toJSONString(event)); + producer.send(WORKFLOW_ENGINE_STARTER, event); + Map> headers = request.headers(); + headers.forEach((k, v) -> log.debug("ComplexInvokeClient Header: {} = {}", k, v)); + return Response.builder() + .status(HttpStatus.OK.value()) + .reason(HttpStatus.OK.getReasonPhrase()) + .headers(headers) + .request(request) + .body(body) + .build(); + } catch (BeansException e) { + return feignClient.execute(request, options); + } } @SneakyThrows @@ -214,7 +221,31 @@ public class ComplexInvokeClient implements Client { } static Response.Body body = new Response.Body() { - final ByteArrayInputStream inputStream = new ByteArrayInputStream(JSON.toJSONString(CommonResponse.success("Send MQ Success")).getBytes(UTF_8)); + //Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `cn.azxo.framework.common.model.CommonResponse` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('Send MQ Success'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `cn.azxo.framework.common.model.CommonResponse` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('Send MQ Success') + // at [Source: (ByteArrayInputStream); line: 1, column: 1] + // at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:389) + // at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:342) + // at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:105) + // at org.springframework.cloud.openfeign.support.SpringDecoder.decode(SpringDecoder.java:57) + // at org.springframework.cloud.openfeign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:61) + // at cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterDecoder.convert(WorkflowEngineStarterDecoder.java:74) + // at cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterDecoder.decode(WorkflowEngineStarterDecoder.java:42) + // at feign.AsyncResponseHandler.decode(AsyncResponseHandler.java:115) + // at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:87) + // at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) + // at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89) + // at cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterInvocationHandler.invoke(WorkflowEngineStarterInvocationHandler.java:59) + // at com.sun.proxy.$Proxy231.approveTask(Unknown Source) + // at cn.axzo.yoke.server.organizational.service.impl.UnitRegisterServiceImpl.approvedPassUpdateProcess(UnitRegisterServiceImpl.java:334) + // at cn.axzo.yoke.server.organizational.service.impl.UnitRegisterServiceImpl.approvedOuRegister(UnitRegisterServiceImpl.java:276) + // at cn.axzo.yoke.server.organizational.service.impl.UnitRegisterServiceImpl$$FastClassBySpringCGLIB$$6f84f39d.invoke() + // at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) + // at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) + // at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + // at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) + // 只设置为字符串会报错,恢复为设置成CommonResponse + final ByteArrayInputStream inputStream = new ByteArrayInputStream(JSON.toJSONString(CommonResponse.success(HttpStatus.OK.value(), null, null)) + .getBytes(UTF_8)); @Override public Integer length() { diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterFeignConfiguration.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterFeignConfiguration.java index 9bf6c068a..1d59f0371 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterFeignConfiguration.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterFeignConfiguration.java @@ -1,6 +1,5 @@ package cn.axzo.workflow.starter.feign.ext; -import cn.axzo.framework.rocketmq.EventProducer; import cn.axzo.workflow.common.enums.RpcInvokeModeEnum; import cn.axzo.workflow.common.util.ThreadUtil; import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; @@ -14,6 +13,7 @@ import feign.codec.Decoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; @@ -55,9 +55,9 @@ public class WorkflowEngineStarterFeignConfiguration { @Bean public Client complexInvokeClient(WorkflowEngineStarterProperties starterProperties, - @Qualifier("workflowEngineStarterEventProducer") EventProducer eventProducer, + BeanFactory beanFactory, Client feignClient) { - return new ComplexInvokeClient(starterProperties, eventProducer, feignClient); + return new ComplexInvokeClient(starterProperties, beanFactory, feignClient); } @Bean diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterInvocationHandler.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterInvocationHandler.java index d8e8e2a96..bcc0d8e43 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterInvocationHandler.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterInvocationHandler.java @@ -72,7 +72,7 @@ class WorkflowEngineStarterInvocationHandler implements InvocationHandler { private void parseInvokeModelAnnotation(Method method) { InvokeMode annotation = AnnotationUtils.getAnnotation(method, InvokeMode.class); - if (Objects.nonNull(annotation)) { + if (Objects.nonNull(annotation) && Objects.isNull(ThreadUtil.get())) { ThreadUtil.set(annotation.value()); } } diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/WorkflowEngineStarterDefaultMQMonitor.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/WorkflowEngineStarterDefaultMQMonitor.java index 578f05d3a..e3d9d2c27 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/WorkflowEngineStarterDefaultMQMonitor.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/WorkflowEngineStarterDefaultMQMonitor.java @@ -112,7 +112,7 @@ public class WorkflowEngineStarterDefaultMQMonitor implements SmartLifecycle { } }); } catch (Exception e) { - log.warn("monitor Broadcast DLQ error: {}", e.getMessage(), e); + log.warn("该异常信息是正常的,不影响服务启动!"); } } diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/selector/MetaFeignClientEnableSelector.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/selector/MetaFeignClientEnableSelector.java new file mode 100644 index 000000000..627a11d7b --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/selector/MetaFeignClientEnableSelector.java @@ -0,0 +1,316 @@ +package cn.axzo.workflow.starter.selector; + +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanExpressionResolver; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.cloud.openfeign.FeignClientFactoryBean; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +/** + * 原生 FeignClient 的注册 + * + * @author wangli + * @since 2024-09-10 09:56 + */ +@Slf4j +public class MetaFeignClientEnableSelector implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { + + private ResourceLoader resourceLoader; + + private Environment environment; + + static void validateFallback(final Class clazz) { + Assert.isTrue(!clazz.isInterface(), "Fallback class must implement the interface annotated by @FeignClient"); + } + + static void validateFallbackFactory(final Class clazz) { + Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances " + + "of fallback classes that implement the interface annotated by @FeignClient"); + } + + static String getName(String name) { + if (!StringUtils.hasText(name)) { + return ""; + } + + String host = null; + try { + String url; + if (!name.startsWith("http://") && !name.startsWith("https://")) { + url = "http://" + name; + } + else { + url = name; + } + host = new URI(url).getHost(); + + } + catch (URISyntaxException e) { + } + Assert.state(host != null, "Service id not legal hostname (" + name + ")"); + return name; + } + + static String getUrl(String url) { + if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) { + if (!url.contains("://")) { + url = "http://" + url; + } + try { + new URL(url); + } + catch (MalformedURLException e) { + throw new IllegalArgumentException(url + " is malformed", e); + } + } + return url; + } + + static String getPath(String path) { + if (StringUtils.hasText(path)) { + path = path.trim(); + if (!path.startsWith("/")) { + path = "/" + path; + } + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + } + return path; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + Boolean metaFeignEnabled = environment.getProperty("workflow.engine.starter.meta-feign", Boolean.class); + if(!Boolean.TRUE.equals(metaFeignEnabled)){ + return; + } + LinkedHashSet candidateComponents = new LinkedHashSet<>(); + ClassPathScanningCandidateComponentProvider scanner = getScanner(); + scanner.setResourceLoader(this.resourceLoader); + scanner.addIncludeFilter(new AnnotationTypeFilter(WorkflowEngineFeignClient.class)); + candidateComponents.addAll(scanner.findCandidateComponents("cn.axzo.workflow.client.feign")); + + for (BeanDefinition candidateComponent : candidateComponents) { + if (candidateComponent instanceof AnnotatedBeanDefinition) { + // verify annotated class is an interface + AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; + AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); + Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); + + Map attributes = new HashMap<>(); + attributes.put("configuration", new Class[]{}); + attributes.put("contextId", ""); + attributes.put("decode404", false); + attributes.put("fallback", void.class); + attributes.put("fallbackFactory", void.class); + attributes.put("name", "workflow-engine"); + attributes.put("path", ""); + attributes.put("primary", true); + attributes.put("qualifier", ""); + attributes.put("qualifiers", new String[]{}); + String workflowEngineUrl = environment.getProperty("axzo.service.workflow-engine", String.class); + if(!StringUtils.hasText(workflowEngineUrl)) { + workflowEngineUrl = "http://workflow-engine:8080"; + } + attributes.put("url", workflowEngineUrl); + attributes.put("value", "workflow-engine"); + + registerFeignClient(registry, annotationMetadata, attributes); + } + } + } + + private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, + Map attributes) { + String className = annotationMetadata.getClassName(); + Class clazz = ClassUtils.resolveClassName(className, null); + ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory + ? (ConfigurableBeanFactory) registry : null; + String contextId = getContextId(beanFactory, attributes); + String name = getName(attributes); + FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(); + factoryBean.setBeanFactory(beanFactory); + factoryBean.setName(name); + factoryBean.setContextId(contextId); + factoryBean.setType(clazz); + BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> { + factoryBean.setUrl(getUrl(beanFactory, attributes)); + factoryBean.setPath(getPath(beanFactory, attributes)); + factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404")))); + Object fallback = attributes.get("fallback"); + if (fallback != null) { + factoryBean.setFallback(fallback instanceof Class ? (Class) fallback + : ClassUtils.resolveClassName(fallback.toString(), null)); + } + Object fallbackFactory = attributes.get("fallbackFactory"); + if (fallbackFactory != null) { + factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class) fallbackFactory + : ClassUtils.resolveClassName(fallbackFactory.toString(), null)); + } + return factoryBean.getObject(); + }); + definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + definition.setLazyInit(true); + validate(attributes); + + AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); + beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className); + beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean); + + // has a default, won't be null + boolean primary = (Boolean) attributes.get("primary"); + + beanDefinition.setPrimary(primary); + + String[] qualifiers = getQualifiers(attributes); + if (ObjectUtils.isEmpty(qualifiers)) { + qualifiers = new String[] { contextId + "FeignClient" }; + } + + BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers); + BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); + } + + protected ClassPathScanningCandidateComponentProvider getScanner() { + return new ClassPathScanningCandidateComponentProvider(false, this.environment) { + @Override + protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { + boolean isCandidate = false; + if (beanDefinition.getMetadata().isIndependent()) { + if (!beanDefinition.getMetadata().isAnnotation()) { + isCandidate = true; + } + } + return isCandidate; + } + }; + } + + private void validate(Map attributes) { + AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes); + // This blows up if an aliased property is overspecified + // FIXME annotation.getAliasedString("name", FeignClient.class, null); + validateFallback(annotation.getClass("fallback")); + validateFallbackFactory(annotation.getClass("fallbackFactory")); + } + + private String getContextId(ConfigurableBeanFactory beanFactory, Map attributes) { + String contextId = (String) attributes.get("contextId"); + if (!StringUtils.hasText(contextId)) { + return getName(attributes); + } + + contextId = resolve(beanFactory, contextId); + return getName(contextId); + } + + private String getUrl(ConfigurableBeanFactory beanFactory, Map attributes) { + String url = resolve(beanFactory, (String) attributes.get("url")); + return getUrl(url); + } + + private String getPath(ConfigurableBeanFactory beanFactory, Map attributes) { + String path = resolve(beanFactory, (String) attributes.get("path")); + return getPath(path); + } + + /* for testing */ String getName(Map attributes) { + return getName(null, attributes); + } + + private String getQualifier(Map client) { + if (client == null) { + return null; + } + String qualifier = (String) client.get("qualifier"); + if (StringUtils.hasText(qualifier)) { + return qualifier; + } + return null; + } + + private String[] getQualifiers(Map client) { + if (client == null) { + return null; + } + List qualifierList = new ArrayList<>(Arrays.asList((String[]) client.get("qualifiers"))); + qualifierList.removeIf(qualifier -> !StringUtils.hasText(qualifier)); + if (qualifierList.isEmpty() && getQualifier(client) != null) { + qualifierList = Collections.singletonList(getQualifier(client)); + } + return !qualifierList.isEmpty() ? qualifierList.toArray(new String[0]) : null; + } + + String getName(ConfigurableBeanFactory beanFactory, Map attributes) { + String name = (String) attributes.get("serviceId"); + if (!StringUtils.hasText(name)) { + name = (String) attributes.get("name"); + } + if (!StringUtils.hasText(name)) { + name = (String) attributes.get("value"); + } + name = resolve(beanFactory, name); + return getName(name); + } + + private String resolve(ConfigurableBeanFactory beanFactory, String value) { + if (StringUtils.hasText(value)) { + if (beanFactory == null) { + return this.environment.resolvePlaceholders(value); + } + BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver(); + String resolved = beanFactory.resolveEmbeddedValue(value); + if (resolver == null) { + return resolved; + } + return String.valueOf(resolver.evaluate(resolved, new BeanExpressionContext(beanFactory, null))); + } + return value; + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/resources/META-INF/application.yml.demo b/workflow-engine-spring-boot-starter/src/main/resources/META-INF/application.yml.demo index 9e64ea2a5..0af4c06d5 100644 --- a/workflow-engine-spring-boot-starter/src/main/resources/META-INF/application.yml.demo +++ b/workflow-engine-spring-boot-starter/src/main/resources/META-INF/application.yml.demo @@ -1,6 +1,7 @@ workflow: engine: starter: + meta-feign: false # 是否开启原始 ProcessInstanceApi、ProcessTaskApi 等类似的 FeignClient,默认 false,优先 starter 提供的 coreService、manageServer。为 true 时,会为当前应用注册原始 xxxApi FeignClient invoke-mode: async # 调用 workflowCoreService 中方法的方式,可选值:sync、async join-container-group: false # 本地开发机启动时,是否将 MQ 消费者加入到集群中,默认不加入,并默认生成 GID_${spring.application.name}_workflow_engine_${spring.profiles.active}_debugging_consumer 的消费者组,该参数只对非容器环境生效 manageable: false # 是否可管理,默认 false, 开启后 Spring 容器中将多一个 WorkflowManageService 的 Bean,可调用受限访问接口 diff --git a/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/CoreServiceCodeGeneration.java b/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/CoreServiceCodeGeneration.java index bb486ff9e..685bd5110 100644 --- a/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/CoreServiceCodeGeneration.java +++ b/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/CoreServiceCodeGeneration.java @@ -8,7 +8,6 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.BodyDeclaration; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.comments.BlockComment; import com.github.javaparser.ast.expr.ClassExpr; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; @@ -88,15 +87,15 @@ public class CoreServiceCodeGeneration { private static void addDefaultMethods(ClassOrInterfaceDeclaration interfaceDeclaration) { MethodDeclaration sync = createDefaultMethod("sync", interfaceDeclaration); sync.setJavadocComment("强制使用‘同步’模式调用该方法,请在调用真实方法前调用该方法\r\n" + - "

\r\n" +
-                "  workflowCoreService.sync().createProcessInstance();\r\n" +
-                "
"); + "
\r\n" +
+            "  workflowCoreService.sync().createProcessInstance();\r\n" +
+            "
"); MethodDeclaration async = createDefaultMethod("async", interfaceDeclaration); sync.setJavadocComment("强制使用‘异步’模式调用该方法,请在调用真实方法前调用该方法\r\n" + - "
\r\n" +
-                "  workflowCoreService.async().createProcessInstance();\r\n" +
-                "
"); + "
\r\n" +
+            "  workflowCoreService.async().createProcessInstance();\r\n" +
+            "
"); } private static MethodDeclaration createDefaultMethod(String methodName, ClassOrInterfaceDeclaration interfaceDeclaration) { @@ -168,12 +167,13 @@ public class CoreServiceCodeGeneration { private static ClassOrInterfaceDeclaration setCommon(CompilationUnit cu, String newClassName) { ClassOrInterfaceDeclaration classOrInterfaceDeclaration = cu.addInterface(newClassName).setPublic(true); - classOrInterfaceDeclaration.setJavadocComment("Workflow Engine Starter Core Service
" + - "该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口"); - classOrInterfaceDeclaration.addAndGetAnnotation("FeignClient") - .addPair("name", new StringLiteralExpr("workflow-engine-starter-core")) - .addPair("url", new StringLiteralExpr("${axzo.service.workflow-engine:workflow-engine:8080}")) - .addPair("configuration", new ClassExpr(new ClassOrInterfaceType("WorkflowEngineStarterFeignConfiguration"))); + classOrInterfaceDeclaration.setJavadocComment("Workflow Engine Starter Core Service\r\n

\r\n" + + "该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口\r\n

\r\n" + + "Auto generation by workflow engine, It cannot be manually modified"); + classOrInterfaceDeclaration.addAndGetAnnotation("org.springframework.cloud.openfeign.FeignClient") + .addPair("name", new StringLiteralExpr("workflow-engine-starter-core")) + .addPair("url", new StringLiteralExpr("${axzo.service.workflow-engine:http://workflow-engine:8080}")) + .addPair("configuration", new ClassExpr(new ClassOrInterfaceType("WorkflowEngineStarterFeignConfiguration"))); cu.addImport("cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterFeignConfiguration", false, false); cu.addImport("cn.axzo.workflow.common.util.ThreadUtil", false, false); cu.addImport("cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC", true, false); diff --git a/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/ManageServiceCodeGeneration.java b/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/ManageServiceCodeGeneration.java index c5ceff26b..88223f134 100644 --- a/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/ManageServiceCodeGeneration.java +++ b/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/ManageServiceCodeGeneration.java @@ -66,15 +66,15 @@ public class ManageServiceCodeGeneration { private static void addDefaultMethods(ClassOrInterfaceDeclaration interfaceDeclaration) { MethodDeclaration sync = createDefaultMethod("sync", interfaceDeclaration); sync.setJavadocComment("强制使用‘同步’模式调用该方法,请在调用真实方法前调用该方法\r\n" + - "

\r\n" +
-                "  workflowManageService.sync().getTenantIds();\r\n" +
-                "
"); + "
\r\n" +
+            "  workflowManageService.sync().getTenantIds();\r\n" +
+            "
"); MethodDeclaration async = createDefaultMethod("async", interfaceDeclaration); async.setJavadocComment("强制使用‘异步’模式调用该方法,请在调用真实方法前调用该方法\r\n" + - "
\r\n" +
-                "  workflowManageService.async().getTenantIds();\r\n" +
-                "
"); + "
\r\n" +
+            "  workflowManageService.async().getTenantIds();\r\n" +
+            "
"); } private static MethodDeclaration createDefaultMethod(String methodName, ClassOrInterfaceDeclaration interfaceDeclaration) { @@ -158,12 +158,13 @@ public class ManageServiceCodeGeneration { private static ClassOrInterfaceDeclaration setCommon(CompilationUnit cu, String newClassName) { ClassOrInterfaceDeclaration classOrInterfaceDeclaration = cu.addInterface(newClassName).setPublic(true); - classOrInterfaceDeclaration.setJavadocComment("Workflow Engine Starter Management Service
" + - "该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口"); - classOrInterfaceDeclaration.addAndGetAnnotation("FeignClient") - .addPair("name", new StringLiteralExpr("workflow-engine-starter-manage")) - .addPair("url", new StringLiteralExpr("${axzo.service.workflow-engine:workflow-engine:8080}")) - .addPair("configuration", new ClassExpr(new ClassOrInterfaceType("WorkflowEngineStarterFeignConfiguration"))); + classOrInterfaceDeclaration.setJavadocComment("Workflow Engine Starter Management Service\r\n

\r\n" + + "该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口\r\n

\r\n" + + "Auto generation by workflow engine, It cannot be manually modified"); + classOrInterfaceDeclaration.addAndGetAnnotation("org.springframework.cloud.openfeign.FeignClient") + .addPair("name", new StringLiteralExpr("workflow-engine-starter-manage")) + .addPair("url", new StringLiteralExpr("${axzo.service.workflow-engine:http://workflow-engine:8080}")) + .addPair("configuration", new ClassExpr(new ClassOrInterfaceType("WorkflowEngineStarterFeignConfiguration"))); cu.addImport("cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterFeignConfiguration", false, false); cu.addImport("cn.axzo.workflow.common.util.ThreadUtil", false, false); cu.addImport("cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC", true, false);