diff --git a/.gitignore b/.gitignore index a66b8e2f1..d217b08a8 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ target/ *.iws *.iml *.ipr +WorkflowCoreService.java +WorkflowManageService.java ### NetBeans ### /nbproject/private/ @@ -36,4 +38,5 @@ application-local.yml *.log rebel.xml -.flattened-pom.xml \ No newline at end of file +.flattened-pom.xml +.DS_Store \ No newline at end of file diff --git a/Changelog.md b/Changelog.md index 486d24314..708c2392f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,48 @@ # Change logs +### 1.5.1-SNAPSHOT + +> - 集成表单引擎 +> - 新增表单组件文本框和附件的支撑 + +### 1.5.0-SNAPSHOT + +> - 新增 ElasticSearch 组件支撑 +> - 提供 ES 相关的搜索查询功能 + +### 1.4.1-SNAPSHOT + +> - 配合 REQ-2616 完成工程完结需求 +> - 新增业务节点定时回调功能 + +### 1.4.0-SNAPSHOT + +> - 新增 workflow-engine-spring-boot-starter 组件,大幅简化业务方接入使用的难度。 +> - 在 Starter 中新增 MQ 监控功能,避免死信依赖运维。 +> - 优化发送待办时,提前判断是否有按钮,以及能否支持批量处理。 +> - 修复同一个实例下,多个人同时操作任务,可能导致数据库的 FK 异常等问题。 +> - 修复其他的一些小问题 + +### 1.3.3-SNAPSHOT + +> - 支持“政务”类型的模型管理。 +> - 支持模型批量控制和手写签名的控制。 +> - 新增转交、加签、中止功能的异步支持。 +> - 新增批量转交和中止的功能支持。 +> - 优化引擎内部异步任务携带真实的 traceId。 + +### 1.3.2-SNAPSHOT + +> - 新增创建流程、同意、驳回三个功能的异步动作支持。 +> - 优化引擎内部事件处理效率。 +> - 优化流程实例推测功能。 + +### 1.3.1-SNAPSHOT + +> - 新增相邻两个审批节点中,存在有相同的审批人时,切前一个审批节点的人已经正常通过,则自动过审功能。 +> - 同意审批节点,支持手写签名。 +> - 新增抄送功能。 +> - 优化引擎前后端模型协议转换逻辑。 +> - 优化引擎内部事件处理逻辑。 ### 1.3.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index b9ca48e97..fd3624845 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -12,12 +13,13 @@ workflow-engine pom ${revision} - workflow-engine + Workflow Engine - 1.3.1-SNAPSHOT + 1.5.3-SNAPSHOT 2.0.0-SNAPSHOT 2.0.0-SNAPSHOT + 1.0.0-SNAPSHOT 11.8 1.18.22 1.4.2.Final @@ -25,6 +27,10 @@ 2.0.0 3.7.1 3.2.5 + 3.26.0 + 7.10.2 + 2.0.0 + 2.5.0 @@ -43,6 +49,12 @@ ${axzo-dependencies.version} pom import + + + com.xuxueli + xxl-job-core + + io.github.openfeign @@ -54,21 +66,41 @@ workflow-engine-api ${project.version} + + cn.axzo.workflow + workflow-engine-axzo-ext + ${project.version} + ${project.groupId} workflow-engine-common ${project.version} + + ${project.groupId} + workflow-engine-form + ${project.version} + ${project.groupId} workflow-engine-core ${project.version} + + ${project.groupId} + workflow-engine-elasticsearch + ${project.version} + ${project.groupId} workflow-engine-server ${project.version} + + cn.axzo.workflow + workflow-engine-spring-boot-starter + ${project.version} + cn.axzo.maokai maokai-api @@ -99,6 +131,53 @@ maven-artifact ${apache-maven.version} + + com.github.javaparser + javaparser-core + ${javaparse.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + ${elasticsearch.version} + + + + org.elasticsearch + elasticsearch + ${elasticsearch.version} + + + + org.dromara.easy-es + easy-es-boot-starter + ${easy-es.version} + + + org.dromara.easy-es + easy-es-annotation + ${easy-es.version} + + + cn.axzo + riven-api + ${axzo-dependencies.version} + + + cn.axzo.org + org-api + ${axzo-dependencies.org.version} + + + com.xuxueli + xxl-job-core + ${xxl-job.version} + + + cn.axzo.nanopart + doc-api + ${axzo-dependencies.version} + @@ -120,6 +199,11 @@ mapstruct-processor ${mapstruct.version} + + org.springframework.boot + spring-boot-configuration-processor + ${spring.boot.version} + @@ -135,9 +219,13 @@ workflow-engine-api + workflow-engine-axzo-ext workflow-engine-common + workflow-engine-form workflow-engine-core + workflow-engine-elasticsearch workflow-engine-server - + workflow-engine-support + workflow-engine-spring-boot-starter diff --git a/workflow-engine-api/pom.xml b/workflow-engine-api/pom.xml index ae99c41a8..11ba8fffc 100644 --- a/workflow-engine-api/pom.xml +++ b/workflow-engine-api/pom.xml @@ -11,7 +11,7 @@ workflow-engine-api jar - workflow-engine-api + Workflow Engine Api 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/config/CommonFeignConfiguration.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/CommonFeignConfiguration.java new file mode 100644 index 000000000..3db5067c4 --- /dev/null +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/CommonFeignConfiguration.java @@ -0,0 +1,18 @@ +package cn.axzo.workflow.client.config; + +import feign.Retryer; +import org.springframework.context.annotation.Bean; + +/** + * 开启 Feign 重试策略 + * + * @author wangli + * @since 2024/6/5 13:50 + */ +public class CommonFeignConfiguration { + + @Bean + public Retryer defaultRetryer() { + return new Retryer.Default(); + } +} diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/WorkflowEngineClientAutoConfiguration.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/WorkflowEngineClientAutoConfiguration.java index abfde80e5..37beb9211 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/WorkflowEngineClientAutoConfiguration.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/WorkflowEngineClientAutoConfiguration.java @@ -2,7 +2,6 @@ package cn.axzo.workflow.client.config; import feign.RequestInterceptor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -14,7 +13,6 @@ import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -32,7 +30,7 @@ import java.util.regex.Pattern; * @Date: 2022/9/17 * @Description: */ -@EnableFeignClients(basePackages = {"cn.axzo.workflow.client.feign"}) +//@EnableFeignClients(basePackages = {"cn.axzo.workflow.client.feign"}) @Configuration(proxyBeanMethods = false) @Slf4j public class WorkflowEngineClientAutoConfiguration { @@ -43,7 +41,7 @@ public class WorkflowEngineClientAutoConfiguration { private static final Pattern POM_VERSION = Pattern.compile(".*(\\S+).*", Pattern.DOTALL); - @Bean + @Bean("serviceVersion") public String serviceVersion() { Map env = System.getenv(); if (env != null) { @@ -63,9 +61,12 @@ public class WorkflowEngineClientAutoConfiguration { log.error("get version error: {}", e.getMessage(), e); } - String serviceVersion = Objects.isNull(version) ? "1.2.0-SNAPSHOT" : version; + String serviceVersion = Objects.isNull(version) ? "1.2.0" : version; log.info("client current version: {}", serviceVersion); - return serviceVersion; + return serviceVersion + .replaceAll("-SNAPSHOT", "") + .replaceAll("-RELEASE", "") + .trim(); } private String getVersionFromPod(URL location) throws URISyntaxException { @@ -82,7 +83,7 @@ public class WorkflowEngineClientAutoConfiguration { Map env = new HashMap<>(); env.put("create", "true"); try { - FileSystem zipfs = FileSystems.newFileSystem(location.toURI(), env); + FileSystems.newFileSystem(location.toURI(), env); } catch (Exception e1) { log.error("linux env create new FS error: {}", e1.getMessage()); } @@ -100,7 +101,7 @@ public class WorkflowEngineClientAutoConfiguration { Map env = new HashMap<>(); env.put("create", "true"); try { - FileSystem zipfs = FileSystems.newFileSystem(location.toURI(), env); + FileSystems.newFileSystem(location.toURI(), env); } catch (Exception e) { log.error("linux env create new FS error: {}", e.getMessage()); } diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/WorkflowRequestInterceptor.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/WorkflowRequestInterceptor.java index a5c831343..cfdfd3958 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/WorkflowRequestInterceptor.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/config/WorkflowRequestInterceptor.java @@ -24,6 +24,7 @@ public class WorkflowRequestInterceptor implements RequestInterceptor { public static final String HEADER_HTTP_CLIENT_VALUE = "WorkflowEngine-Feign"; public static final String HEADER_API_VERSION = "Service-Version"; public static final String HEADER_SERVER_NAME = "X-SERVER-NAME"; + public static final String HEADER_W_E = "WE"; @Override 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 3fc94102e..1cbca027b 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,8 +1,14 @@ package cn.axzo.workflow.client.feign.bpmn; +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.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 org.springframework.cloud.openfeign.FeignClient; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -11,27 +17,69 @@ import org.springframework.web.bind.annotation.RequestParam; import javax.validation.constraints.NotBlank; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + /** * 流程活动的 API * * @author wangli * @since 2023/11/17 16:28 */ -@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}") +//@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") + @InvokeMode(SYNC) CommonResponse trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId); /** - * 业务节点设置审批人 + * 业务节点唤醒 + * + * @param dto + * @return + */ + @PostMapping("/api/process/activity/trigger") + CommonResponse trigger(@Validated @RequestBody BpmnActivityTriggerDTO dto); + + + /** + * 业务节点设置审批人, 不支持重复设置 + *

+ * 当模型中使用了“业务节点”,且设置了“业务指定审批人”模式,则当业务监听到 PROCESS_ACTIVITY_WAIT_ASSIGNEE 事件时,可通过该接口设置动态设置审批人 + *

+ * 注意:如果调用接口时,传入的审批人集合为空,流程引擎将对该审批流程实例自动中止。 * * @param dto * @return */ @PostMapping("/api/process/activity/assignee/set") + @Operation(summary = "业务节点设置审批人,不支持重复调用设置审批人,需一次性传入所有审批人") CommonResponse setAssignee(@Validated @RequestBody BpmnActivitySetAssigneeDTO dto); + + /** + * 该功能应该利用引擎的 TimerBoundaryEvent 来实现,但为了简便,先利用引擎的任务调度来实现 + * + * @return + */ + @PostMapping("/api/process/activity/timeout/trigger") + @Manageable + @Operation(summary = "设置指定业务节点定时继续往下执行") + CommonResponse setTimeoutTrigger(@Validated @RequestBody BpmnActivityTimeoutTriggerDTO dto); + + /** + * 为指定业务节点设置定时回调 + * + * @return + */ + @Manageable + @PostMapping("/api/process/activity/timeout/callback") + @Operation(summary = "设置指定业务节点定时回调") + CommonResponse setTimeoutCallback(@Validated @RequestBody BpmnActivityTimeoutCallbackDTO 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 a91122a15..6b196947d 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,12 +1,14 @@ package cn.axzo.workflow.client.feign.bpmn; +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.definition.BpmnProcessDefinitionUpdateDTO; 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.process.BpmnProcessDefinitionVO; import cn.azxo.framework.common.model.CommonResponse; -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.PutMapping; @@ -16,13 +18,18 @@ import org.springframework.web.bind.annotation.RequestParam; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + /** * 流程定义 API * * @author wangli * @since 2023/9/21 16:25 */ -@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}") +//@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient +@Manageable public interface ProcessDefinitionApi { @@ -30,6 +37,7 @@ public interface ProcessDefinitionApi { * 获取活跃的流程定义分页 */ @GetMapping("/api/process/definition/page") + @InvokeMode(SYNC) CommonResponse> getProcessDefinitionPage(@Validated @RequestBody BpmnProcessDefinitionPageDTO dto); /** @@ -39,6 +47,7 @@ public interface ProcessDefinitionApi { * @return */ @PutMapping("/api/process/definition/update") + @InvokeMode(SYNC) CommonResponse updateProcessDefinition(@Validated @RequestBody BpmnProcessDefinitionUpdateDTO dto); /** @@ -48,6 +57,7 @@ public interface ProcessDefinitionApi { * @return 流程定义 */ @GetMapping("/api/process/definition/get") + @InvokeMode(SYNC) CommonResponse getProcessDefinition(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId); /** @@ -57,6 +67,7 @@ public interface ProcessDefinitionApi { * @return 流程定义 */ @GetMapping("/api/process/definition/getByDeploymentId") + @InvokeMode(SYNC) CommonResponse getProcessDefinitionByDeploymentId( @NotBlank(message = "流程部署 ID 不能为空") @RequestParam String deploymentId); @@ -68,7 +79,9 @@ public interface ProcessDefinitionApi { * @return 流程定义 */ @GetMapping("/api/process/definition/active/getByKey") - CommonResponse getActiveProcessDefinitionByKey(@NotBlank(message = "模型定义KEY不能为空") @RequestParam String key); + @InvokeMode(SYNC) + CommonResponse getActiveProcessDefinitionByKey(@NotBlank(message = "模型定义KEY不能为空") @RequestParam String key, + @RequestParam(required = false, defaultValue = NO_TENANT_ID) String tenantId); /** @@ -78,6 +91,7 @@ public interface ProcessDefinitionApi { * {@see SuspensionState} */ @PutMapping("/api/process/definition/state/update") + @InvokeMode(SYNC) CommonResponse getActiveProcessDefinitionByKey(@NotBlank(message = "流程定义ID不能为空") @RequestParam String processDefinitionId, @NotNull(message = "状态不能为空") @RequestParam Integer state); @@ -87,6 +101,7 @@ public interface ProcessDefinitionApi { * @return 流程定义ID */ @GetMapping("/api/process/definition/active/getByCategory") + @InvokeMode(SYNC) CommonResponse getActiveProcessDefinitionId(@RequestParam(required = false) String tenantId, @NotBlank(message = "分类不能为空") @RequestParam(required = false) String category); @@ -96,8 +111,9 @@ public interface ProcessDefinitionApi { * @return 流程定义ID */ @GetMapping("/api/process/definition/active/json/model") + @InvokeMode(SYNC) CommonResponse getActiveProcessDefinitionJsonModel(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId, - @NotBlank(message = "分类不能为空") @RequestParam(required = false) String category, + @NotBlank(message = "分类不能为空") @RequestParam(required = false) String key, @RequestParam(required = false) String tenantId); /** @@ -107,7 +123,8 @@ public interface ProcessDefinitionApi { * @param cascade 是否级联参数定义对应的流程实例及 job 等管理内容 * @return */ - @GetMapping("/api/process/definition/detele") + @GetMapping("/api/process/definition/delete") + @InvokeMode(SYNC) CommonResponse delete(@NotBlank(message = "流程定义部署 ID 不能为空") String deploymentId, @RequestParam(required = false) Boolean cascade); } 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 91b466260..cd9ee5cbb 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,23 +1,41 @@ package cn.axzo.workflow.client.feign.bpmn; -import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; +import cn.axzo.workflow.common.annotation.InvokeMode; +import cn.axzo.workflow.common.annotation.Manageable; +import cn.axzo.workflow.common.model.dto.SignFileDTO; +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +import cn.axzo.workflow.common.model.request.bpmn.log.LogApproveSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BeforeProcessInstanceCreateDTO; 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.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; 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.ExtProcessLogVO; +import cn.axzo.workflow.common.model.response.bpmn.process.NodesByModelVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.process.doc.DocPendingVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo; 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; @@ -30,6 +48,9 @@ import javax.annotation.Nullable; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; +import java.util.Map; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; /** * 流程实例 API @@ -37,21 +58,20 @@ import java.util.List; * @author wangli * @since 2023/9/21 16:26 */ -@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}") +//@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient public interface ProcessInstanceApi { + /** - * 查询所有的审批流 + * 创建流程前的节点列表 + * 用于发起人自选 * * @return */ - @PostMapping("/api/process/instance/page/all") - CommonResponse> getAllProcessInstancePage(@Validated @RequestBody BpmnProcessInstanceAdminPageReqVO dto); - - /** - * 我发起的审批列表 - */ - @PostMapping("/api/process/instance/page/my") - CommonResponse> getMyProcessInstancePage(@Validated @RequestBody BpmnProcessInstanceMyPageReqVO dto); + @Operation(summary = "创建审批流程前,返回模型节点列表以及节点能否设置审批人") + @PostMapping("/api/process/instance/create/before") + @InvokeMode(SYNC) + CommonResponse> nodesBeforeCreateProcessInstance(@Validated @RequestBody BeforeProcessInstanceCreateDTO dto); /** * 创建审批流程 @@ -64,18 +84,11 @@ public interface ProcessInstanceApi { * * @param dto {@link BpmnProcessInstanceCreateDTO} */ + @Operation(summary = "创建审批流程, MQ 触发规则:1. 当前流程实例会依次触发 process-instance-created 和 process-instance-started 事件,2. 第一个审批任务会依次触发 process-task-assigned 和 process-task-created 事件") @PostMapping("/api/process/instance/create") + @InvokeMode(SYNC) CommonResponse createProcessInstance(@Validated @RequestBody BpmnProcessInstanceCreateDTO dto); - /** - * 创建审批流程并带上表单 - * - * @param dto - * @return - */ - @PostMapping("/api/process/instance/form/create") - CommonResponse createProcessInstanceWith(@Validated @RequestBody BpmnProcessInstanceCreateWithFormDTO dto); - /** * 发起人主动撤回审核 * @@ -88,35 +101,89 @@ public interface ProcessInstanceApi { * @param dto {@link BpmnProcessInstanceCancelDTO} * @return */ + @Operation(summary = "发起人主动撤回审核,MQ 触发规则:1. 当前流程实例中现存的任务会依次触发 process-task-deleted 事件,2. 当前流程实例会触发 process-instance-cancelled 事件") @DeleteMapping("/api/process/instance/cancel") CommonResponse cancelProcessInstance(@Validated @RequestBody BpmnProcessInstanceCancelDTO dto); + @DeleteMapping("/api/process/instance/super/cancel") + @Manageable + CommonResponse superCancelProcessInstance(@Validated @RequestBody SuperBpmnProcessInstanceCancelDTO dto); + /** * 中止流程实例 * * @param dto * @return */ + @Operation(summary = "中止流程实例") @DeleteMapping("/api/process/instance/abort") CommonResponse abortProcessInstance(@Validated @RequestBody BpmnProcessInstanceAbortDTO dto); /** - * 抄送流程实例 + * 批量中止流程实例 + * + * @param dtos + * @return + */ + @Operation(summary = "批量中止流程实例") + @DeleteMapping("/api/process/instance/batch/abort") + CommonResponse batchAbortProcessInstance(@Validated @RequestBody List dtos); + + /** + * 抄送流程实例(未实现) * * @param dto * @return */ + @Operation(summary = "抄送流程实例") @PostMapping("/api/process/instance/carbon-copy") + @Deprecated CommonResponse carbonCopyProcessInstance(@Validated @RequestBody BpmnProcessInstanceCarbonCopyDTO dto); + /** * 获得流程实例 * * @param dto {@link BpmnProcessInstanceQueryDTO} 可根据 Id,BusinessKey进行查询 * @return 流程实例, 租户Id不必传 */ + @Operation(summary = "获得流程实例") @GetMapping("/api/process/instance/get") + @InvokeMode(SYNC) CommonResponse getProcessInstanceVO(@Validated @RequestBody BpmnProcessInstanceQueryDTO dto); + /** + * 获取指定流程实例的流程变量 + * + * @param processInstanceId + * @param tenantId + * @return + */ + @Operation(summary = "获取指定流程实例的流程变量") + @GetMapping("/api/process/instance/cooperation-org") + @InvokeMode(SYNC) + CommonResponse> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, + @Nullable @RequestParam(required = false) String tenantId); + + /** + * 查询所有的审批流 + * + * @return + */ + @Operation(summary = "查询所有的审批流") + @PostMapping("/api/process/instance/page/all") + @Manageable + @InvokeMode(SYNC) + CommonResponse> getAllProcessInstancePage(@Validated @RequestBody BpmnProcessInstanceAdminPageReqVO dto); + + /** + * 我发起的审批列表 + */ + @Operation(summary = "我发起的审批列表") + @PostMapping("/api/process/instance/page/my") + @Manageable + @InvokeMode(SYNC) + CommonResponse> getMyProcessInstancePage(@Validated @RequestBody BpmnProcessInstanceMyPageReqVO dto); + /** * 更新流程定义的状态 * @@ -125,6 +192,8 @@ public interface ProcessInstanceApi { */ @Operation(summary = "更新指定流程定义的版本的状态, 处于 suspended 状态的流程模型将不能再发起实例") @PutMapping("/api/process/instance/status/update") + @Manageable + @InvokeMode(SYNC) CommonResponse updateProcessStatus(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId, @NotNull(message = "状态不能为空") @RequestParam Integer status); @@ -135,7 +204,10 @@ public interface ProcessInstanceApi { * @param tenantId * @return */ + @Operation(summary = "获取审批流程实例的运行图") @GetMapping("/api/process/instance/graphical") + @Manageable + @InvokeMode(SYNC) CommonResponse processInstanceGraphical(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); @@ -144,20 +216,28 @@ public interface ProcessInstanceApi { * * @return */ + @Operation(summary = "推断指定流程实例的所有节点执行顺序") @GetMapping("/api/process/instance/node/forecasting") + @Manageable + @InvokeMode(SYNC) CommonResponse> processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); /** - * 获取指定流程实例的协作单位 + * 推断指定流程实例的过滤掉部分节点执行顺序 * - * @param processInstanceId - * @param tenantId + * @param allNode 如果为真时,相当于调用 {@link ProcessInstanceApi#processInstanceNodeForecast(String, String)} 方法,切会直接丢弃 nodeDefinitionKeys 参数 + * 如果为假时,才结合 nodeDefinitionKeys 过滤掉传入的节点 * @return */ - @GetMapping("/api/process/instance/cooperation-org") - CommonResponse getCooperationOrg(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, - @Nullable @RequestParam(required = false) String tenantId); + @Operation(summary = "推断指定流程实例的过滤掉部分节点执行顺序") + @GetMapping("/api/process/instance/node/filter/forecasting") + @Manageable + @InvokeMode(SYNC) + CommonResponse> processInstanceFilterNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId, + @Nullable @RequestParam(required = false) String tenantId, + @RequestParam(required = false, defaultValue = "false") Boolean allNode, + @Nullable @RequestParam(required = false) List nodeDefinitionKeys); /** * 查询实例的租户集合 @@ -166,5 +246,101 @@ public interface ProcessInstanceApi { */ @Operation(summary = "查询实例的租户集合") @GetMapping("/api/process/instance/tenant/ids") + @Manageable + @InvokeMode(SYNC) CommonResponse> getTenantIds(); + + /** + * 校验指定流程实例下,是否存在指定的审批人正处理待审批 + * + * @return true 是在当前流程实例中,存在指定的审批人 + */ + @Operation(summary = "校验指定流程实例下,是否存在指定的审批人") + @PostMapping("/api/process/instance/check/approver") + @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); + + /** + * 根据任务id查询任务状态,按钮详情 + * + * @param taskButtonsSearchDTO 请求参数 + * @return + */ + @Operation(summary = "根据任务id查询任务状态,按钮详情") + @PostMapping("/api/process/instance/task/buttons/find") + @InvokeMode(SYNC) + CommonResponse findProcessSingleTaskButtons(@Validated @RequestBody BpmnTaskButtonSearchDTO taskButtonsSearchDTO); + + /** + * 更新指定流程表单最后一次编辑的内容 + * + * @param dto + * @return + */ + @Operation(summary = "更新指定流程表单最后一次编辑的内容") + @PostMapping("/api/process/instance/form/variable/update") + @InvokeMode(SYNC) + CommonResponse updateInstanceFormVariables(@Validated @RequestBody FormVariablesUpdateDTO dto); + + /** + * 签署业务流程实例在审批待办中查询使用的文档列表 + * + * @return + */ + @Operation(summary = "签署业务流程实例在审批待办中查询使用的文档列表") + @PostMapping("/api/process/instance/select/doc/list") + @InvokeMode(SYNC) + CommonResponse> processInstanceSelectDocs(@Validated @RequestBody ProcessDocQueryDTO dto); + + /** + * 获取审批人阅读状态 + * + * @return + */ + @Operation(summary = "获取审批人阅读状态") + @PostMapping("/api/process/instance/approver/read/status") + @InvokeMode(SYNC) + CommonResponse> approverReadStatus(@Validated @RequestBody ApproverReadStatusDTO dto); + + /** + * 修改审批人关联文档阅读状态 + */ + @Operation(summary = "修改审批人关联文档阅读状态") + @PostMapping("/api/process/instance/approver/read/status/change") + @InvokeMode(SYNC) + CommonResponse approveReadStatusChange(@Validated @RequestBody ChangeApproverReadStatusDTO dto); + + /** + * 获取签署业务流程最后替换的文档 fileKey 集合 + * + * @return + */ + @Operation(summary = "获取签署业务流程最后替换的文档 fileKey 集合") + @GetMapping("/api/process/instance/final/docs") + @InvokeMode(SYNC) + CommonResponse> getProcessInstanceFinalDocs(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId); + + /** + * 查询 ExtAxProcessLog 表中审批人的冗余信息 + * + * @param dto + * @return + */ + @Operation(summary = "查询 ExtAxProcessLog 表中审批人的冗余信息") + @GetMapping("/api/process/instance/log/approve/ext") + @Manageable + @InvokeMode(SYNC) + CommonResponse> getProcessLogByInstanceIdAndPersonId(@Validated @RequestBody LogApproveSearchDTO 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 new file mode 100644 index 000000000..5e6da7692 --- /dev/null +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessJobApi.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.client.feign.bpmn; + +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; +import cn.axzo.workflow.common.annotation.Manageable; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +//@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient +public interface ProcessJobApi { + + /** + * 将死信队列中的任务转移到正常 JOB 队列中 + *

+ * 入参是二选一:当只有 jobId 时,仅将指定的 JOB 转移到正常的队列中; + * 而传入的是具体的实例 ID,那么会将这个流程下的所有在死信队列中的任务都转移到正常的队列中 + * + * @param jobId 具体的 JOB ID + * @param procInstId 具体的实例 ID + * @return + */ + @GetMapping("/api/process/job/dead-letter/resume") + @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 29d7f5878..02ad5f6ee 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,14 +1,30 @@ package cn.axzo.workflow.client.feign.bpmn; +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.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.model.doc.DocByIdDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocResetDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocTenantQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.RestPrintTemplateConfigDTO; 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.model.doc.DocBaseVO; +import cn.axzo.workflow.common.model.response.print.PrintModelDTO; 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; @@ -21,13 +37,17 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + /** * 流程模型 API * * @author wangli * @since 2023/9/21 15:47 */ -@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}") +//@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient +@Manageable public interface ProcessModelApi { /** @@ -38,6 +58,7 @@ public interface ProcessModelApi { */ @Operation(summary = "流程模型列表") @GetMapping("/api/process/model/page") + @InvokeMode(SYNC) CommonResponse> page(@Validated @RequestBody BpmnModelSearchDTO dto); /** @@ -46,6 +67,7 @@ public interface ProcessModelApi { */ @Operation(summary = "创建流程模型") @PostMapping("/api/process/model/create") + @InvokeMode(SYNC) CommonResponse create(@Validated @RequestBody BpmnModelCreateDTO dto); /** @@ -53,6 +75,7 @@ public interface ProcessModelApi { */ @Operation(summary = "通过模型ID查询指定流程模型") @GetMapping("/api/process/model/get") + @InvokeMode(SYNC) CommonResponse getById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId, @RequestParam(required = false) String tenantId); @@ -61,6 +84,7 @@ public interface ProcessModelApi { */ @Operation(summary = "通过模型KEY查询指定流程模型") @GetMapping("/api/process/model/getByKey") + @InvokeMode(SYNC) CommonResponse getByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam(required = false) String processModelKey, @NotBlank(message = "租户不能为空") @RequestParam(required = false) String tenantId); @@ -73,6 +97,7 @@ public interface ProcessModelApi { */ @Operation(summary = "获取指定模型的扩展属性") @GetMapping("/api/process/model/ext") + @InvokeMode(SYNC) CommonResponse getModelExt(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId); /** @@ -80,6 +105,7 @@ public interface ProcessModelApi { */ @Operation(summary = "更新流程模型") @PutMapping("/api/process/model/update") + @InvokeMode(SYNC) CommonResponse update(@RequestBody BpmnModelUpdateDTO dto); @@ -90,6 +116,7 @@ public interface ProcessModelApi { */ @Operation(summary = "通过模型 ID 部署流程模型") @PostMapping("/api/process/model/deploy") + @InvokeMode(SYNC) CommonResponse deployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId, @RequestParam(required = false, defaultValue = "") String modelTenantId, @RequestParam(required = false) String operator); @@ -101,6 +128,7 @@ public interface ProcessModelApi { */ @Operation(summary = "通过模型 KEY 部署流程模型") @PostMapping("/api/process/model/deployByKey") + @InvokeMode(SYNC) CommonResponse deployByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam(required = false) String processModelKey, @NotBlank(message = "租户不能为空") @RequestParam(required = false) String modelTenantId, @RequestParam(required = false) String operator); @@ -115,6 +143,7 @@ public interface ProcessModelApi { */ @Operation(summary = "通过模型 ID 取消部署流程模型") @PostMapping("/api/process/model/undeploy") + @InvokeMode(SYNC) CommonResponse unDeployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId, @RequestParam(required = false, defaultValue = "") String tenantId, @RequestParam(required = false) String operator); @@ -124,6 +153,7 @@ public interface ProcessModelApi { */ @Operation(summary = "删除指定模型 ID 的流程模型") @DeleteMapping("/api/process/model/delete") + @InvokeMode(SYNC) CommonResponse deleteById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam String processModelId, @RequestParam(required = false, defaultValue = "") String tenantId); @@ -136,6 +166,7 @@ public interface ProcessModelApi { */ @Operation(summary = "删除指定模型 KEY 的流程模型") @DeleteMapping("/api/process/model/deleteByKey") + @InvokeMode(SYNC) CommonResponse deleteByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam String processModelKey, @RequestParam(required = false, defaultValue = "") String tenantId); @@ -149,9 +180,25 @@ public interface ProcessModelApi { */ @Operation(summary = "修改模型状态") @PostMapping("/api/process/model/changeStatus") - CommonResponse changeStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId, - @NotNull(message = "状态不能为空") @RequestParam Integer status, - @RequestParam(required = false) String operator); + @InvokeMode(SYNC) + CommonResponse changeStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId, + @NotNull(message = "状态不能为空") @RequestParam Integer status, + @RequestParam(required = false) String operator); + + /** + * 修改模型打印开关状态 + * + * @param modelId + * @param status + * @param operator + * @return + */ + @Operation(summary = "修改模型打印开关状态") + @PostMapping("/api/process/model/print/changeStatus") + @InvokeMode(SYNC) + CommonResponse changePrintStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId, + @NotNull(message = "状态不能为空") @RequestParam Integer status, + @RequestParam(required = false) String operator); /** * 查询流程模型使用的分类列表 @@ -160,6 +207,7 @@ public interface ProcessModelApi { */ @Operation(summary = "查询流程模型使用的分类列表") @GetMapping("/api/process/model/category/ids") + @InvokeMode(SYNC) CommonResponse> getModelCategoryList(); /** @@ -169,5 +217,176 @@ public interface ProcessModelApi { */ @Operation(summary = "查询模型的租户集合") @GetMapping("/api/process/model/tenant/ids") - CommonResponse> getTenantIds(); + @InvokeMode(SYNC) + CommonResponse> getModelTenantIds(); + + /** + * 打印模板配置内容更新保存 + * + * @param dto + * @return + */ + @Operation(summary = "打印模板配置内容更新保存") + @PostMapping("/api/process/model/print/template/upsert") + @InvokeMode(SYNC) + CommonResponse printTemplateConfig(@Validated @RequestBody PrintTemplateConfigUpsertDTO dto); + + /** + * 获取打印模板配置内容 + * + * @param dto + * @return + */ + @Operation(summary = "获取打印模板配置内容") + @PostMapping("/api/process/model/print/template/config/query") + @InvokeMode(SYNC) + CommonResponse getPrintTemplateConfig(@Validated @RequestBody PrintTemplateConfigQueryDTO dto); + + /** + * 代运营充值的打印模板 + * + * @param dto + * @return + */ + @Operation(summary = "代运营重置打印模板") + @PostMapping(value = "/api/process/model/print/template/config/reset") + @InvokeMode(SYNC) + CommonResponse resetPrintTemplateConfig(@Validated @RequestBody RestPrintTemplateConfigDTO dto); + + /** + * 搜索文档列表 + * + * @param dto + * @return + */ + @Operation(summary = "搜索文档列表") + @PostMapping(value = "/api/process/model/doc/page") + @InvokeMode(SYNC) + CommonResponse> docPage(@Validated @RequestBody DocSearchDTO dto); + + /** + * 获取指定 docIds 文档列表 + * + * @return + */ + @Operation(summary = "获取指定 docIds 文档列表") + @PostMapping(value = "/api/process/model/doc/ids") + @InvokeMode(SYNC) + CommonResponse> docByIds(@Validated @RequestBody DocByIdDTO dto); + + /** + * 获取指定模板的原始文档列表 + * + * @param dto + * @return + */ + @Operation(summary = "根据业务 ID 获取模型文档列表,自动适配公共模板和代运营") + @PostMapping(value = "/api/process/model/doc/list") + @InvokeMode(SYNC) + CommonResponse> docList(@Validated @RequestBody DocQueryDTO dto); + + /** + * 获取关联 HiPrint 类型文档模板内容 + * + * @param fileRelationId + * @return + */ + @Operation(summary = "获取关联 HiPrint 类型文档模板内容") + @PostMapping(value = "/api/process/model/hi-print/content/get") + @InvokeMode(SYNC) + CommonResponse getHiPrintContent(@RequestParam String fileRelationId); + + /** + * 添加关联文档 + * + * @return + */ + @Operation(summary = "添加关联文档") + @PutMapping(value = "/api/process/model/doc/create") + @InvokeMode(SYNC) + CommonResponse createDoc(@Validated @RequestBody DocCreateDTO dto); + + /** + * 修改关联文档 + * + * @return + */ + @Operation(summary = "修改关联文档") + @PostMapping(value = "/api/process/model/doc/update") + @InvokeMode(SYNC) + CommonResponse updateDoc(@Validated @RequestBody DocUpdateDTO dto); + + /** + * 克隆关联文档 + * + * @param docId + * @return + */ + @Operation(summary = "克隆关联文档") + @PostMapping(value = "/api/process/model/doc/clone") + @InvokeMode(SYNC) + CommonResponse cloneDoc(@RequestParam("id") Long docId); + + /** + * 删除关联文档 + * + * @return + */ + @Operation(summary = "删除指定文档") + @DeleteMapping(value = "/api/process/model/doc/delete") + @InvokeMode(SYNC) + CommonResponse deleteDoc(@RequestParam("id") Long docId); + + /** + * 关联文档配置排序 + * + * @param dto + * @return + */ + @Operation(summary = "关联文档配置排序") + @PostMapping(value = "/api/process/model/doc/order") + @InvokeMode(SYNC) + CommonResponse orderDoc(@Validated @RequestBody DocOrderDTO dto); + + /** + * 重置关联文档 + * + * @param dto + * @return + */ + @Operation(summary = "重置关联文档配置") + @PostMapping(value = "/api/process/model/doc/reset") + @InvokeMode(SYNC) + CommonResponse resetDoc(@Validated @RequestBody DocResetDTO dto); + + /** + * 设置关联文档的停启用状态 + * + * @param dto + * @return + */ + @Operation(summary = "设置关联文档的停启用状态") + @PostMapping(value = "/api/process/model/doc/status") + @InvokeMode(SYNC) + CommonResponse statusDoc(@Validated @RequestBody DocStatusDTO dto); + + /** + * 设置关联文档的必选状态 + * + * @return + */ + @Operation(summary = "设置关联文档的必选状态") + @PostMapping(value = "/api/process/model/doc/require") + @InvokeMode(SYNC) + CommonResponse requireDoc(@Validated @RequestBody DocStatusDTO dto); + + /** + * 特殊的查询设置过关联过文档的工作台 ID 集合 + * + * @return + */ + @Operation(summary = "特殊的查询设置过关联过文档的工作台 ID 集合") + @PostMapping(value = "/api/process/model/has/docs/tenantId") + @InvokeMode(SYNC) + CommonResponse> hasFilesTenantIds(@Validated @RequestBody DocTenantQueryDTO dto); } 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 8b289ef2e..69d2c8983 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,15 +1,24 @@ package cn.axzo.workflow.client.feign.bpmn; +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.BpmnNodeBackSystemOperateDTO; +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.BpmnTaskAuditWithFormDTO; +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.BpmnTaskResetApproversDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; 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.task.BpmnHistoricTaskInstanceGroupVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO; @@ -17,7 +26,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; @@ -26,7 +34,11 @@ import org.springframework.web.bind.annotation.RequestParam; import javax.annotation.Nullable; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import java.util.List; +import java.util.Map; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; /** @@ -35,45 +47,10 @@ import java.util.List; * @author wangli * @since 2023/9/21 16:26 */ -@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}") +//@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient public interface ProcessTaskApi { - /** - * 待审核列表 - */ - @GetMapping("/api/process/task/page/todo") - CommonResponse> getTodoTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto); - - /** - * 已完成的审批列表 - */ - @GetMapping("/api/process/task/page/done") - CommonResponse> getDoneTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto); - - /** - * 获取指定流程实例的审批过程信息 - *

- * 同一层级结构 - */ - @GetMapping("/api/process/task/list/flat") - CommonResponse> getTaskListFlatByProcessInstanceId(@NotBlank(message = "流程实例 ID " + - "不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); - - /** - * 获取指定流程实例的审批过程信息 - *

- * 分组结构 - */ - @GetMapping("/api/process/task/list/group") - CommonResponse> getTaskListGroupByProcessInstanceId(@NotBlank(message = - "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); - - /** - * 获取实例正在审核的人列表 - */ - @GetMapping("/api/process/task/active/list") - CommonResponse> getActiveTasksByProcessInstanceId(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @NotBlank(message = "租户不能为空") @RequestParam String tenantId); - /** * 同意 * @@ -85,9 +62,60 @@ public interface ProcessTaskApi { * 2.2. 流程实例正常结束会触发 process-instance-completed 事件 * */ + @Operation(summary = "同意,MQ 触发规则:1. 当前审批任务会依次触发 process-task-completed 和 process-task-deleted 事件(如果有下一级审批,则会触发第 2.1 点中的事件,如果当前审核任务最后一级审批,则会触发第 2.2 点中的事件),2.1. 下一级审批任务会依次触发 process-task-assigned 和 process-task-created 事件,2.2. 流程实例正常结束会触发 process-instance-completed 事件") @PostMapping("/api/process/task/approve") CommonResponse approveTask(@Validated @RequestBody BpmnTaskAuditDTO dto); + /** + * 同意时并提交表单数据 + * + * @param dto + * @return + */ + @Operation(summary = "同意时并提交表单") + @PostMapping("/api/process/task/form/approve") + CommonResponse approveTaskWithForm(@Validated @RequestBody BpmnTaskAuditWithFormDTO dto); + + /** + * 批量同意 + * + * @param dtos + * @return + */ + @Operation(summary = "批量同意") + @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); + + /** + * 用于系统内部操作,跳转到指定节点 + * + * @param dto 请求参数 + * @return 是否成功 + */ + @Operation(summary = "系统操作回退任务到指定节点") + @PostMapping("/api/process/task/system/back") + CommonResponse systemBackTask(@Validated @RequestBody BpmnNodeBackSystemOperateDTO dto); + /** * 驳回 * @@ -97,9 +125,19 @@ public interface ProcessTaskApi { * 2. 当前流程实例会触发 process-instance-rejected 事件 * */ + @Operation(summary = "驳回,MQ 触发规则:1. 当前审批任务会触发 process-task-deleted 事件, 2. 当前流程实例会触发 process-instance-rejected 事件") @PostMapping("/api/process/task/reject") CommonResponse rejectTask(@Validated @RequestBody BpmnTaskAuditDTO dto); + /** + * 批量驳回 + * + * @param dtos 批量请求参数 + * @return + */ + @PostMapping("/api/process/task/batch/reject") + CommonResponse batchRejectTask(@Validated @RequestBody List dtos); + /** * 转交 * @@ -110,6 +148,16 @@ public interface ProcessTaskApi { @PostMapping("/api/process/task/transfer") CommonResponse transferTask(@Validated @RequestBody BpmnTaskTransferDTO dto); + /** + * 批量转交 + * + * @param dtos + * @return + */ + @Operation(summary = "批量修改审批任务的审批人") + @PostMapping("/api/process/task/batch/transfer") + CommonResponse batchTransferTask(@Validated @RequestBody List dtos); + /** * 评论 * @@ -120,16 +168,6 @@ public interface ProcessTaskApi { @PostMapping("/api/process/task/comment") CommonResponse commentTask(@Validated @RequestBody BpmnTaskCommentDTO dto); - /** - * 添加附件 - * - * @param dto - * @return - */ - @Operation(summary = "添加附件") - @PostMapping("/api/process/task/attachment") - CommonResponse addAttachment(@Validated @RequestBody BpmnTaskAttachmentDTO dto); - /** * 加签 * @@ -140,6 +178,16 @@ public interface ProcessTaskApi { @PostMapping("/api/process/task/countersign") CommonResponse countersignTask(@Validated @RequestBody BpmnTaskCountersignDTO dto); + /** + * 重置节点审批人(提级审批) + * + * @param dto + * @return + */ + @Operation(summary = "重置节点审批人(提级审批)") + @PostMapping("/api/process/task/approvers/reset") + CommonResponse resetTaskApprovers(@Validated @RequestBody BpmnTaskResetApproversDTO dto); + /** * 催办 * @@ -148,6 +196,8 @@ public interface ProcessTaskApi { */ @Operation(summary = "审批流程催办") @PostMapping("/api/process/task/remind") + @Manageable + @InvokeMode(SYNC) CommonResponse remindTask(@Validated @RequestBody BpmnTaskRemindDTO dto); /** @@ -170,6 +220,69 @@ public interface ProcessTaskApi { @PostMapping("/api/process/task/robot/complete") CommonResponse completeRobotTask(@Validated @RequestBody BpmnRobotTaskCompleteDTO dto); + /** + * 添加附件 + * + * @param dto + * @return + */ + @Operation(summary = "添加附件") + @PostMapping("/api/process/task/attachment") + @Manageable + CommonResponse addAttachment(@Validated @RequestBody BpmnTaskAttachmentDTO dto); + + /** + * 待审核列表 + */ + @Operation(summary = "待审核列表") + @GetMapping("/api/process/task/page/todo") + @Manageable + @InvokeMode(SYNC) + CommonResponse> getTodoTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto); + + /** + * 已完成的审批列表 + */ + @Operation(summary = "已完成的审批列表") + @GetMapping("/api/process/task/page/done") + @Manageable + @InvokeMode(SYNC) + CommonResponse> getDoneTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto); + + /** + * 获取指定流程实例的审批过程信息 + *

+ * 同一层级结构 + */ + @Operation(summary = "获取指定流程实例的审批过程信息") + @GetMapping("/api/process/task/list/flat") + @Manageable + @InvokeMode(SYNC) + CommonResponse> getTaskListFlatByProcessInstanceId(@NotBlank(message = "流程实例 ID " + + "不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + + /** + * 获取指定流程实例的审批过程信息 + *

+ * 分组结构 + */ + @Operation(summary = "获取指定流程实例的审批过程信息") + @GetMapping("/api/process/task/list/group") + @Manageable + @InvokeMode(SYNC) + CommonResponse> getTaskListGroupByProcessInstanceId(@NotBlank(message = + "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + + /** + * 获取实例正在审核的人列表 + */ + @Operation(summary = "获取实例正在审核的人列表") + @GetMapping("/api/process/task/active/list") + @Manageable + @InvokeMode(SYNC) + CommonResponse> getActiveTasksByProcessInstanceId(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, + @NotBlank(message = "租户不能为空") @RequestParam String tenantId); + /** * 根据实例 ID 和自然人 ID 查询对应待处理的任务 ID * @@ -177,6 +290,21 @@ public interface ProcessTaskApi { */ @Operation(summary = "根据实例 ID 和自然人 ID 查询对应待处理的任务 ID") @GetMapping("/api/process/task/find") + @Manageable + @InvokeMode(SYNC) CommonResponse findTaskIdByInstanceIdAndPersonId(@RequestParam(required = false) @NotBlank(message = "流程实例 ID 不能为空") String processInstanceId, @RequestParam(required = false) @NotBlank(message = "自然人 ID 不能为空") String personId); + + /** + * 根据实例 ID列表 和自然人 ID 查询对应待处理的任务 ID + * + * @return + */ + @Operation(summary = "根据实例 ID列表 和自然人 ID 查询对应待处理的任务 ID") + @GetMapping("/api/process/task/batch/find") + @Manageable + @InvokeMode(SYNC) + CommonResponse> findTaskIdByInstanceIdsAndPersonId(@RequestParam(required = false) @NotEmpty(message = "流程实例 ID列表 不能为空") List processInstanceIds, + @RequestParam(required = false) @NotBlank(message = "自然人 ID 不能为空") String personId); + } 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 new file mode 100644 index 000000000..88ca06197 --- /dev/null +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessVariableApi.java @@ -0,0 +1,55 @@ +package cn.axzo.workflow.client.feign.bpmn; + +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.RestBpmnProcessVariable; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import javax.validation.constraints.NotBlank; + +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 { + + /** + * 为指定流程新增变量 + */ + @PostMapping("/api/process/variable/create/{executionId}") + @InvokeMode(SYNC) + CommonResponse createVariable(@PathVariable @NotBlank(message = "流程实例 ID 不能为空") String executionId, + @RequestBody @Validated RestBpmnProcessVariable restVariable); + + /** + * 仅更新流程已存在的变量 + * + * @param executionId + * @param restVariable + * @return + */ + @PostMapping("/api/process/variable/update/{executionId}") + @InvokeMode(SYNC) + CommonResponse updateVariable(@PathVariable @NotBlank(message = "流程实例 ID 不能为空") String executionId, + @RequestBody @Validated RestBpmnProcessVariable restVariable); + + /** + * 批量删除流程变量 + */ + @DeleteMapping("/api/process/variable/delete/{executionId}") + @InvokeMode(SYNC) + CommonResponse deleteVariables(@PathVariable("executionId") String executionId, + @RequestParam String variableNames, + @RequestParam(value = "scope", required = false) String scope); +} diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/es/EsProcessInstanceApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/es/EsProcessInstanceApi.java new file mode 100644 index 000000000..2aba21149 --- /dev/null +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/es/EsProcessInstanceApi.java @@ -0,0 +1,35 @@ +package cn.axzo.workflow.client.feign.es; + +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.es.InstanceSearchReqDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.model.response.es.ProcessInstanceDocumentVO; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + +/** + * 操作 ES 的流程实例 API + * + * @author wangli + * @since 2024-10-07 21:12 + */ +@WorkflowEngineFeignClient +@Manageable +public interface EsProcessInstanceApi { + + /** + * 从 ES 中搜索符合条件的实例纬度数据 + * + * @param dto + * @return + */ + @PostMapping("/api/es/instance/search") + @InvokeMode(SYNC) + CommonResponse> searchInstanceInEs(@Validated @RequestBody InstanceSearchReqDTO dto); +} diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/FormAdminApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/FormAdminApi.java new file mode 100644 index 000000000..7556be801 --- /dev/null +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/FormAdminApi.java @@ -0,0 +1,58 @@ +package cn.axzo.workflow.client.feign.manage; + +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.form.definition.StartFormSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormSearchDTO; +import cn.axzo.workflow.common.model.response.form.FormVO; +import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + +/** + * 表达实例相关 API, 这里主要用于表单与流程的集成所需的 API。 + *

+ * 如果想单独创建表单实例,建议在 Form 包中单独提供 API。 + * + * @author wangli + * @since 2024-11-11 20:08 + */ +@WorkflowEngineFeignClient +public interface FormAdminApi { + + @PostMapping("/api/form/admin/form/page") + @InvokeMode(SYNC) + @Manageable + CommonResponse> formPage(@Validated @RequestBody FormSearchDTO dto); + + /** + * 获取指定审批业务的流程表单设置, + * @param dto + * @return + */ + @PostMapping("/api/form/admin/start/form") + @InvokeMode(SYNC) + CommonResponse getFormDefinition(@Validated @RequestBody StartFormSearchDTO dto); + + /** + * 查询指定审批实例的表单模型和数据 + *

+ * dto 中的 processInstanceId 与 taskId,至少有一个属性有值,一般建议直接使用实例 ID。 + * 当传入 taskId 时,将只查询该任务绑定的表单模型和数据。 + * + * @param dto + * @return + */ + @PostMapping("/api/form/admin/instance/render") + @InvokeMode(SYNC) + CommonResponse getFormInstance(@Validated @RequestBody FormDetailDTO dto); +} diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/PrintAdminApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/PrintAdminApi.java new file mode 100644 index 000000000..3c3e6365f --- /dev/null +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/PrintAdminApi.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.client.feign.manage; + +import cn.axzo.workflow.client.annotation.WorkflowEngineFeignClient; +import cn.axzo.workflow.common.annotation.InvokeMode; +import cn.axzo.workflow.common.annotation.Manageable; +import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintFieldQueryDTO; +import cn.azxo.framework.common.model.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import javax.validation.constraints.NotBlank; +import java.util.List; +import java.util.Map; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + +/** + * 打印相关的 API + * + * @author wangli + * @since 2025-01-16 17:35 + */ +@WorkflowEngineFeignClient +@Manageable +public interface PrintAdminApi { + @Operation(summary = "查询指定审批流程是否能打印,打印开关是否开启,是否存在打印模板") + @GetMapping("/api/print/admin/template/exists") + @InvokeMode(SYNC) + CommonResponse hasPrintTemplate(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId); + + @Operation(summary = "获取打印模板中可打印的字段") + @PostMapping("/api/print/admin/fields") + @InvokeMode(SYNC) + CommonResponse> getPrintFields(@Validated @RequestBody PrintFieldQueryDTO dto); + + @Operation(summary = "获取指定流程下用于替换打印的相关变量") + @GetMapping("/api/print/admin/field/variables") + @InvokeMode(SYNC) + CommonResponse> getPrintFieldVariables(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId); +} diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessAdminApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessAdminApi.java new file mode 100644 index 000000000..5db0d1d06 --- /dev/null +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/ProcessAdminApi.java @@ -0,0 +1,86 @@ +package cn.axzo.workflow.client.feign.manage; + +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.admin.ProcessAdminCreateDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminDeleteDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminQueryDTO; +import cn.axzo.workflow.common.model.response.admin.ProcessAdminVo; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + +@WorkflowEngineFeignClient +@Manageable +public interface ProcessAdminApi { + + /** + * 查询管理员 + * @param dto 管理员数据 + * @return 管理员id + */ + @PostMapping("/api/process/admin/query") + @InvokeMode(SYNC) + CommonResponse> queryProcessAdmins(@RequestBody ProcessAdminQueryDTO dto); + + /** + * 查询管理员 + * @param dto 管理员数据 + * @return 管理员id + */ + @PostMapping("/api/process/admin/query/count") + @InvokeMode(SYNC) + CommonResponse queryProcessAdminsCount(@RequestBody ProcessAdminQueryDTO dto); + + /** + * 添加管理员 + * @param dto 管理员数据 + * @return 管理员id + */ + @PostMapping("/api/process/admin/create") + @InvokeMode(SYNC) + CommonResponse createProcessAdmin(@RequestBody ProcessAdminCreateDTO dto); + + /** + * 批量添加管理员 + * @param dtos + * @return + */ + @PostMapping("/api/process/admin/batch/create") + @InvokeMode(SYNC) + CommonResponse batchCreateProcessAdmin(@RequestBody List dtos); + + /** + * 删除管理员 + * @param id 配置表id + * @return + */ + @DeleteMapping("/api/process/admin/delete") + @InvokeMode(SYNC) + CommonResponse deleteCommonProcessAdmin(@RequestParam Long id); + + /** + * 根据条件删除管理员 + * @param dto 删除条件 + * @return + */ + @DeleteMapping("/api/process/admin/delete/criteria") + @InvokeMode(SYNC) + CommonResponse deleteProcessAdminCriteria(@RequestBody ProcessAdminDeleteDTO dto); + + /** + * 删除管理员 + * @param ids 管理员配置id列表 + * @return + */ + @DeleteMapping("/api/process/admin/batch/delete") + @InvokeMode(SYNC) + CommonResponse batchDeleteProcessAdmin(@RequestBody List ids); +} 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 9a91739e7..b65095a8e 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,26 +1,21 @@ package cn.axzo.workflow.client.feign.manage; -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.client.annotation.WorkflowEngineFeignClient; +import cn.axzo.workflow.common.annotation.InvokeMode; +import cn.axzo.workflow.common.annotation.Manageable; +import cn.axzo.workflow.common.model.request.category.*; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; import cn.axzo.workflow.common.model.response.category.CategoryItemVO; import cn.azxo.framework.common.model.CommonResponse; -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 org.springframework.web.bind.annotation.*; import java.util.List; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.*; + /** * OMS流程业务管理API @@ -30,7 +25,9 @@ import java.util.List; * @date 2023/11/6 16:01 */ -@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}") +//@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient +@Manageable public interface ProcessCategoryApi { /** @@ -39,6 +36,7 @@ public interface ProcessCategoryApi { * @return */ @GetMapping("/api/process/category/get") + @InvokeMode(SYNC) CommonResponse get(@RequestParam Long id); /** @@ -48,6 +46,7 @@ public interface ProcessCategoryApi { * @return */ @GetMapping("/api/process/category/getByIds") + @InvokeMode(SYNC) CommonResponse> getByIds(@RequestParam List ids); /** @@ -57,6 +56,7 @@ public interface ProcessCategoryApi { * @return */ @GetMapping("/api/process/category/getByValues") + @InvokeMode(SYNC) CommonResponse> getByValues(@RequestParam List values); /** @@ -65,6 +65,7 @@ public interface ProcessCategoryApi { * @return */ @PostMapping("/api/process/category/create") + @InvokeMode(SYNC) CommonResponse create(@Validated @RequestBody CategoryCreateDTO req); /** @@ -73,6 +74,7 @@ public interface ProcessCategoryApi { * @return */ @PutMapping("/api/process/category/update") + @InvokeMode(SYNC) CommonResponse update(@Validated @RequestBody CategoryUpdateDTO dto); /** @@ -82,6 +84,7 @@ public interface ProcessCategoryApi { * @return */ @DeleteMapping("/api/process/category/delete") + @InvokeMode(SYNC) CommonResponse delete(@RequestParam Long id); /** @@ -92,6 +95,7 @@ public interface ProcessCategoryApi { * @return */ @PutMapping("/api/process/category/update/state") + @InvokeMode(SYNC) CommonResponse updateState(@RequestParam Long id, @RequestParam Boolean state); /** @@ -100,6 +104,7 @@ public interface ProcessCategoryApi { * @return */ @PostMapping("/api/process/category/list") + @InvokeMode(SYNC) CommonResponse> list(@RequestBody CategorySearchDTO dto); /** @@ -108,6 +113,7 @@ public interface ProcessCategoryApi { * @return */ @PostMapping("/api/process/category/page/search") + @InvokeMode(SYNC) CommonResponse> search(@RequestBody CategorySearchDTO dto); /** @@ -117,6 +123,7 @@ public interface ProcessCategoryApi { * @return */ @PostMapping("/api/process/category/config/create") + @InvokeMode(SYNC) CommonResponse createConfig(@RequestBody CategoryConfigCreateDTO dto); /** @@ -126,6 +133,7 @@ public interface ProcessCategoryApi { * @return */ @DeleteMapping("/api/process/category/config/delete/{id}") + @InvokeMode(SYNC) CommonResponse deleteConfig(@PathVariable Long id); /** @@ -134,6 +142,7 @@ public interface ProcessCategoryApi { * @return */ @PostMapping("/api/process/category/config/page/search") + @InvokeMode(SYNC) CommonResponse> configSearch(@RequestBody CategoryConfigSearchDTO dto); /** @@ -144,6 +153,7 @@ public interface ProcessCategoryApi { * @return */ @PostMapping("/api/process/category/config/type/update") + @InvokeMode(SYNC) CommonResponse updateCategoryConfigType(@RequestParam Long id, @RequestParam String configType); /** @@ -154,5 +164,23 @@ public interface ProcessCategoryApi { * @return true: 可以发起创建流程实例, false: 不可用 */ @GetMapping("/api/process/category/check/status") + @InvokeMode(SYNC) CommonResponse checkCategoryStatus(@RequestParam Long tenantId, @RequestParam String categoryCode); + + /** + * 查询分类对应的分组以及分组下的变量 + * @param dto 请求参数 + * @return 分组以及分组下的变量 + */ + @PostMapping("/api/process/category/group-with-vars/list") + @InvokeMode(SYNC) + CommonResponse> searchCategoryGroupAndVars(@Validated @RequestBody CategoryGroupVarSearchDto dto); + + /** + * 新增或者更新分组或者变量 + * @param dto 请求参数 + * @return 是否成功 + */ + @PostMapping("/api/process/category/group-with-vars/upsert") + CommonResponse upsertCategoryGroupAndVars(@Validated @RequestBody CategoryGroupVarUpsertDto dto); } 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 9007f814c..521386f0e 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,12 +1,16 @@ package cn.axzo.workflow.client.feign.manage; +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.BpmnButtonMetaInfo; 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 static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + /** * OMS流程业务管理API @@ -16,7 +20,9 @@ import java.util.List; * @date 2023/11/6 16:01 */ -@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}") +//@FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = CommonFeignConfiguration.class) +@WorkflowEngineFeignClient +@Manageable public interface ProcessConfigApi { /** @@ -25,6 +31,7 @@ public interface ProcessConfigApi { * @return 流程操作按钮列表 */ @GetMapping("/api/process/config/button/list") + @InvokeMode(SYNC) CommonResponse> getDefaultButtons(); diff --git a/workflow-engine-axzo-ext/pom.xml b/workflow-engine-axzo-ext/pom.xml new file mode 100644 index 000000000..f59afb25c --- /dev/null +++ b/workflow-engine-axzo-ext/pom.xml @@ -0,0 +1,39 @@ + + 4.0.0 + + workflow-engine + cn.axzo.workflow + ${revision} + + workflow-engine-axzo-ext + jar + Workflow Engine Axzo Extension + + + + cn.axzo.workflow + workflow-engine-common + ${project.version} + + + cn.axzo.basics + basics-common + + + cn.axzo.framework + axzo-processor-spring-boot-starter + provided + + + cn.axzo.framework + axzo-mybatisplus-spring-boot-starter + provided + + + cn.hutool + hutool-all + provided + + + diff --git a/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/repository/entity/ExtAxProcessAdmin.java b/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/repository/entity/ExtAxProcessAdmin.java new file mode 100644 index 000000000..d3f3332b9 --- /dev/null +++ b/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/repository/entity/ExtAxProcessAdmin.java @@ -0,0 +1,64 @@ +package cn.axzo.workflow.admin.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.enums.AdminDataSource; +import cn.axzo.workflow.common.enums.AdminRoleType; +import cn.axzo.workflow.common.enums.AdminTypeEnum; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_process_admin", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxProcessAdmin extends BaseEntity { + + private static final long serialVersionUID = 7342715107163876733L; + + /** + * 自然人id + */ + private Long personId; + + /** + * 单位id + */ + private Long organizationalUnitId; + + /** + * 工作台ID + */ + private Long workspaceId; + + /** + * 工作台类型,1-企业, 2-项目, 3-政务监管平台, 6-oms工作台,参考 WorkspaceType枚举 + */ + private Integer workspaceType; + + /** + * 管理员类型, SUPER_ADMIN-超级管理员, COMMON_ADMIN-普通管理员 + */ + private AdminTypeEnum adminType; + + /** + * 角色类型, ORGANIZATION_ADMIN-单位超管, ORG_WORKSPACE_ADMIN-项目内单位负责人, WORKSPACE_ADMIN-项目超管,OTHER-其他用户 + */ + private AdminRoleType roleType; + + /** + * 数据来源, SYSTEM_ENTRY-系统录入, USER_ENTRY-用户手动录入 + */ + private AdminDataSource dataSource; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 更新者 + */ + private Long updateBy; +} diff --git a/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/repository/mapper/ExtAxProcessAdminMapper.java b/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/repository/mapper/ExtAxProcessAdminMapper.java new file mode 100644 index 000000000..f544a3afd --- /dev/null +++ b/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/repository/mapper/ExtAxProcessAdminMapper.java @@ -0,0 +1,9 @@ +package cn.axzo.workflow.admin.repository.mapper; + +import cn.axzo.workflow.admin.repository.entity.ExtAxProcessAdmin; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ExtAxProcessAdminMapper extends BaseMapper { +} diff --git a/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/service/ExtAxProcessAdminService.java b/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/service/ExtAxProcessAdminService.java new file mode 100644 index 000000000..a523be44c --- /dev/null +++ b/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/service/ExtAxProcessAdminService.java @@ -0,0 +1,49 @@ +package cn.axzo.workflow.admin.service; + +import cn.axzo.workflow.admin.repository.entity.ExtAxProcessAdmin; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminDeleteDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminQueryDTO; + +import java.util.List; + +public interface ExtAxProcessAdminService { + + /** + * 新增管理员 + * @param processAdmin 流程配置管理员 + * @return id + */ + Long insert(ExtAxProcessAdmin processAdmin); + + /** + * 批量新增 + * @param processAdmins 配置管理员列表 + */ + void batchInsert(List processAdmins); + + /** + * 根据条件查询 + * @param queryDTO + * @return + */ + List query(ProcessAdminQueryDTO queryDTO); + + /** + * 根据条件查询数量 + * @param queryDTO + * @return + */ + Integer queryCount(ProcessAdminQueryDTO queryDTO); + + /** + * 根据条件删除管理员配置 + * @param deleteDTO 删除条件 + */ + Integer delete(ProcessAdminDeleteDTO deleteDTO); + + /** + * 批量删除管理员 + * @param ids 配置表id列表 + */ + Integer deleteCommonAdminsByIds(List ids); +} diff --git a/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/service/impl/ExtAxProcessAdminServiceImpl.java b/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/service/impl/ExtAxProcessAdminServiceImpl.java new file mode 100644 index 000000000..586e07b2d --- /dev/null +++ b/workflow-engine-axzo-ext/src/main/java/cn/axzo/workflow/admin/service/impl/ExtAxProcessAdminServiceImpl.java @@ -0,0 +1,112 @@ +package cn.axzo.workflow.admin.service.impl; + +import cn.axzo.basics.common.constant.enums.TableIsDeleteEnum; +import cn.axzo.framework.domain.ServiceException; +import cn.axzo.workflow.admin.repository.entity.ExtAxProcessAdmin; +import cn.axzo.workflow.admin.repository.mapper.ExtAxProcessAdminMapper; +import cn.axzo.workflow.admin.service.ExtAxProcessAdminService; +import cn.axzo.workflow.common.enums.AdminDataSource; +import cn.axzo.workflow.common.enums.AdminTypeEnum; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminDeleteDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminQueryDTO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@Service +public class ExtAxProcessAdminServiceImpl implements ExtAxProcessAdminService { + + @Resource + private ExtAxProcessAdminMapper extAxProcessAdminMapper; + + @Override + public Long insert(ExtAxProcessAdmin processAdmin) { + extAxProcessAdminMapper.insert(processAdmin); + return processAdmin.getId(); + } + + @Override + public void batchInsert(List processAdmins) { + if (CollectionUtils.isEmpty(processAdmins)) { + return; + } + processAdmins.forEach(this::insert); + } + + @Override + public List query(ProcessAdminQueryDTO queryDTO) { + if (queryDTO == null) { + return Collections.emptyList(); + } + return extAxProcessAdminMapper.selectList(getQueryWrapper(queryDTO)); + } + + + @Override + public Integer queryCount(ProcessAdminQueryDTO queryDTO) { + return extAxProcessAdminMapper.selectCount(getQueryWrapper(queryDTO)); + } + + @Override + public Integer delete(ProcessAdminDeleteDTO deleteDTO) { + if (deleteDTO == null) { + return 0; + } + return extAxProcessAdminMapper.delete(getDeleteWrapper(deleteDTO)); + } + + @Override + public Integer deleteCommonAdminsByIds(List ids) { + if (CollectionUtils.isEmpty(ids)) { + return 0; + } + List extAxProcessAdmins = extAxProcessAdminMapper.selectBatchIds(ids); + if (CollectionUtils.isEmpty(extAxProcessAdmins)) { + return 0; + } + validateDeleteCommonAdmins(extAxProcessAdmins); + return extAxProcessAdminMapper.deleteBatchIds(ids); + } + + private void validateDeleteCommonAdmins(List extAxProcessAdmins) { + if (CollectionUtils.isEmpty(extAxProcessAdmins)) { + return; + } + if (extAxProcessAdmins.stream().anyMatch(ad -> ad.getAdminType() == AdminTypeEnum.SUPER_ADMIN)) { + throw new ServiceException("超级管理员不允许删除"); + } + if (extAxProcessAdmins.stream().anyMatch(ad -> ad.getDataSource() == AdminDataSource.SYSTEM_ENTRY)) { + throw new ServiceException("系统数据不允许删除"); + } + } + + private LambdaQueryWrapper getQueryWrapper(ProcessAdminQueryDTO queryDTO) { + return new LambdaQueryWrapper() + .in(!CollectionUtils.isEmpty(queryDTO.getProcessAdminIds()), ExtAxProcessAdmin::getId, queryDTO.getProcessAdminIds()) + .eq(Objects.nonNull(queryDTO.getWorkspaceId()), ExtAxProcessAdmin::getWorkspaceId, queryDTO.getWorkspaceId()) + .eq(queryDTO.getAdminType() != null, ExtAxProcessAdmin::getAdminType, queryDTO.getAdminType()) + .eq(Objects.nonNull(queryDTO.getOrganizationalUnitId()), ExtAxProcessAdmin::getOrganizationalUnitId, queryDTO.getOrganizationalUnitId()) + .in(!CollectionUtils.isEmpty(queryDTO.getPersonIds()), ExtAxProcessAdmin::getPersonId, queryDTO.getPersonIds()) + .eq(Objects.nonNull(queryDTO.getDataSource()), ExtAxProcessAdmin::getDataSource, queryDTO.getDataSource()) + .eq(ExtAxProcessAdmin::getIsDelete, TableIsDeleteEnum.NORMAL.value); + } + + private LambdaQueryWrapper getDeleteWrapper(ProcessAdminDeleteDTO deleteDTO) { + return new LambdaQueryWrapper() + .in(!CollectionUtils.isEmpty(deleteDTO.getProcessAdminIds()), ExtAxProcessAdmin::getId, deleteDTO.getProcessAdminIds()) + .eq(Objects.nonNull(deleteDTO.getWorkspaceId()), ExtAxProcessAdmin::getWorkspaceId, deleteDTO.getWorkspaceId()) + .eq(deleteDTO.getAdminType() != null, ExtAxProcessAdmin::getAdminType, deleteDTO.getAdminType()) + .eq(Objects.nonNull(deleteDTO.getOrganizationalUnitId()), ExtAxProcessAdmin::getOrganizationalUnitId, deleteDTO.getOrganizationalUnitId()) + .in(!CollectionUtils.isEmpty(deleteDTO.getWorkspaceIds()), ExtAxProcessAdmin::getWorkspaceId, deleteDTO.getWorkspaceIds()) + .in(!CollectionUtils.isEmpty(deleteDTO.getOrganizationalUnitIds()), ExtAxProcessAdmin::getOrganizationalUnitId, deleteDTO.getOrganizationalUnitIds()) + .in(!CollectionUtils.isEmpty(deleteDTO.getPersonIds()), ExtAxProcessAdmin::getPersonId, deleteDTO.getPersonIds()) + .eq(Objects.nonNull(deleteDTO.getDataSource()), ExtAxProcessAdmin::getDataSource, deleteDTO.getDataSource()) + .eq(ExtAxProcessAdmin::getIsDelete, TableIsDeleteEnum.NORMAL.value); + } + +} diff --git a/workflow-engine-common/pom.xml b/workflow-engine-common/pom.xml index 943cb9df0..f02bb046b 100644 --- a/workflow-engine-common/pom.xml +++ b/workflow-engine-common/pom.xml @@ -11,7 +11,7 @@ workflow-engine-common jar - workflow-engine-common + workflow Engine Common @@ -29,6 +29,10 @@ org.projectlombok lombok + + org.dromara.easy-es + easy-es-annotation + diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/annotation/InvokeMode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/annotation/InvokeMode.java new file mode 100644 index 000000000..26a45dcf6 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/annotation/InvokeMode.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.common.annotation; + +import cn.axzo.workflow.common.enums.RpcInvokeModeEnum; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC; + +/** + * 用于标记接口的方法调用模式 + * + * @author wangli + * @since 2024/6/1 19:01 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface InvokeMode { + + /** + * 调用模式, 默认异步 + */ + RpcInvokeModeEnum value() default ASYNC; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/annotation/Manageable.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/annotation/Manageable.java new file mode 100644 index 000000000..f8d9622a1 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/annotation/Manageable.java @@ -0,0 +1,10 @@ +package cn.axzo.workflow.common.annotation; + +/** + * 控制接口是否调用受限,标记了注解的方法,表示受控,不允许暴露给客户端使用 + * + * @author wangli + * @since 2024/6/7 18:21 + */ +public @interface Manageable { +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/AsyncJobRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/AsyncJobRespCode.java new file mode 100644 index 000000000..54ee86b2f --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/AsyncJobRespCode.java @@ -0,0 +1,33 @@ +package cn.axzo.workflow.common.code; + +import cn.axzo.framework.domain.web.code.IModuleRespCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 异步任务相关响应码 + * + * @author wangli + * @since 2024/4/29 20:30 + */ +@Getter +@AllArgsConstructor +public enum AsyncJobRespCode implements IModuleRespCode { + DATA_NOT_EXISTS("001", "ExtTaskInst 数据不存在, instId: {}, taskId: {}"), + JOB_NOT_EXISTS_JOB_ID("002", "id为【{}】对应任务不存在"), + JOB_NOT_EXISTS_PROC_INST_ID("003", "流程实例id为【{}】对应任务不存在"), + RESUME_JOB_REQUEST_PARAM_ERROR("004", "恢复任务请求参数错误,jobId 和 procInstId 不能同时为空"), + ; + private final String code; + private final String message; + + @Override + public String getModuleCode() { + return "11"; + } + + @Override + public String getProjectCode() { + return "998"; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnInstanceRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnInstanceRespCode.java similarity index 64% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnInstanceRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnInstanceRespCode.java index dc3094271..bcf21af7c 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnInstanceRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnInstanceRespCode.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; @@ -25,9 +25,17 @@ public enum BpmnInstanceRespCode implements IModuleRespCode { PROCESS_INSTANCE_CANT_DELETE("010", "流程实例不能删除"), PROCESS_INSTANCE_CANT_START("011", "流程实例不能启动"), TASK_CANT_COMMENT_INSTANCE_NOT_EXISTS("012", "流程实例【{}】不存在, 不能评论"), + RUNNING_INSTANCE_ONLY_FORECAST("013", "仅运行中的实例可以推测"), + ENGINE_EXEC_EXCEPTION("014", "引擎内部异常:【{}】"), + PROCESS_TASK_NOT_EXISTS("015", "流程任务不存在或已处理"), + PROCESS_DOC_READ_PARAM_ERROR("016", "查询审批人阅读状态参数丢失自然人 ID 数据"), + PROCESS_DOC_ID_NOT_IN_MODEL("017", "当前流程中,不存在指定文档"), + PROCESS_SIGN_DATA_NOT_EXISTS("018", "签署业务审批未获取到初始模板复制数据"), + PROCESS_INSTANCE_CANT_REMIND("019", "流程实例【{}】不存在, 不能评论"), + PROCESS_EXT_LOG_PARAM_ERROR("020", "查询流程日志的审批人PersonId参数不合法"), ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -39,12 +47,4 @@ public enum BpmnInstanceRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnModelRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnModelRespCode.java new file mode 100644 index 000000000..d1881847e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnModelRespCode.java @@ -0,0 +1,47 @@ +package cn.axzo.workflow.common.code; + +import cn.axzo.framework.domain.web.code.IModuleRespCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程模型响应码 + * + * @author wangli + * @since 2023/12/14 16:25 + */ +@Getter +@AllArgsConstructor +public enum BpmnModelRespCode implements IModuleRespCode { + MODEL_KEY_EXISTS("001", "已经存在流程标识为【{}】的流程"), + MODEL_ID_NOT_EXISTS("002", "流程模型ID【{}】不存在"), + MODEL_KEY_NOT_EXISTS("003", "流程模型KEY【{}】不存在"), + MODEL_NOT_EXISTS("004", "流程模型不存在"), + BPMN_BYTES_NOT_EXISTS("005", "模型定义内容字节码不存在"), + MODEL_IS_DISABLE("006", "模型已经被停用"), + MODEL_FILE_TAG_DUPLICATE("007", "模型关联的文档业务标签重复"), + MODEL_FILE_TAG_EXISTS("008", "模型关联的文档业务标签已存在"), + MODEL_FILE_NOT_EXISTS("009", "模型关联的文档不存在或已被删除"), + MODEL_FILE_CLONE_ERROR("010", "克隆文档失败, 原因:【{}】"), + MODEL_FILE_TYPE_CLONE_NOT_SUPPORT("011", "不支持的文档类型克隆"), + MODEL_FILE_CONTENT_DATA_ERROR("012", "文档内容数据异常"), + MODEL_FILE_QUERY_ERROR("013", "文档搜索参数实例 ID 和业务 ID 不能同时为空"), + MODEL_FILE_HIPRINT_ID_INVAILD("014", "查询HiPrint文档主键参数非法"), + MODEL_FILE_REPLACE_CONTENT_ERROR("015", "替换文档变量失败,原因:【{}】"), + ; + + private final String code; + private final String message; + + @Override + public String getModuleCode() { + return "03"; + } + + @Override + public String getProjectCode() { + return "998"; + } + + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnProcessDefinitionRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnProcessDefinitionRespCode.java similarity index 80% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnProcessDefinitionRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnProcessDefinitionRespCode.java index 9166337e6..a7889dec2 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnProcessDefinitionRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnProcessDefinitionRespCode.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; @@ -21,10 +21,12 @@ public enum BpmnProcessDefinitionRespCode implements IModuleRespCode { PROCESS_DEFINITION_IS_SUSPENDED("006", "流程定义KEY【{}】处于挂起状态"), PROCESS_DEFINITION_KEY_NOT_MATCH("007", "流程定义的标识不一致, 请修改 BPMN 流程图"), PROCESS_DEFINITION_NAME_NOT_MATCH("008", "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图"), - PROCESS_DEFINITION_HAS_DIRTY_DATA("009", "流程定义KEY【{}】存在脏数据,当前模型没有流程定义内容") + PROCESS_DEFINITION_HAS_DIRTY_DATA("009", "流程定义KEY【{}】存在脏数据,当前模型没有流程定义内容"), + PROCESS_DEFINITION_IS_INVALID("010", "暂时无法发起,请先配置流程模型"), + PROCESS_DEFINITION_ID_ILLEGAL("011", "流程定义 ID 数据不合法") ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -36,12 +38,4 @@ public enum BpmnProcessDefinitionRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnTaskRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnTaskRespCode.java similarity index 67% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnTaskRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnTaskRespCode.java index 2b42a409e..5ef7f0586 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnTaskRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnTaskRespCode.java @@ -1,9 +1,11 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; import lombok.Getter; +import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVAL_ASSIGNER_LIMIT_NUMBER; + /** * 流程模型响应码 * @@ -31,10 +33,19 @@ public enum BpmnTaskRespCode implements IModuleRespCode { DUMMY_TASK_CREATED_ERROR("016", "流程实例 ID【{}】: 不存在, 不允许创建机器人节点"), TASK_HAS_BEEN_COMPLETE("017", "审批任务已被处理"), FIND_TASK_BY_PERSON_ID_ERROR("018", "流程实例中:【{}】未查找指定自然人:【{}】的待处理的流程任务!"), + PROCESS_INSTANCE_IS_NOT_EXIST("019", "Execution:{} 对应流程实例不存在,流程状态异常!"), + 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", "达到回退操作次数上限【{}】次"), + TRANSFER_TO_SELF("026", "任务不能转交给自己"), + REMIND_TASK_TOO_MANY("027", "催办任务数据异常") ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -46,13 +57,4 @@ public enum BpmnTaskRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } - } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnVariablesRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnVariablesRespCode.java new file mode 100644 index 000000000..e14b81a85 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/BpmnVariablesRespCode.java @@ -0,0 +1,40 @@ +package cn.axzo.workflow.common.code; + +import cn.axzo.framework.domain.web.code.IModuleRespCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum BpmnVariablesRespCode implements IModuleRespCode { + PROCESS_VARIABLE_NOT_EXIST("001", "流程实例【{}】不存在名称为:【{}】的变量"), + PROCESS_VARIABLE_EXIST("002", "变量【{}】已存在流程实例【{}】中"), + PROCESS_VARIABLE_SCOPE_ERROR("003", "变量【{}】的作用域为【{}】不支持"), + PROCESS_VARIABLE_NAME_MUST_BE_NOT_NULL("004", "变量的名称不能为空"), + PROCESS_VARIABLE_VARS_NOT_NULL("005", "流程变量列表参数不能为空"), + ; + + + private final String code; + private final String message; + + @Override + public String getModuleCode() { + return "12"; + } + + @Override + public String getProjectCode() { + return "998"; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/CategoryRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/CategoryRespCode.java similarity index 78% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/CategoryRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/CategoryRespCode.java index cca3ac338..ca601a73e 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/CategoryRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/CategoryRespCode.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; @@ -21,8 +21,8 @@ public enum CategoryRespCode implements IModuleRespCode { CATEGORY_CONFIG_EXISTS("006", "分类【{}】的【{}】配置中已存在重复数据"), ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -34,13 +34,5 @@ public enum CategoryRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/ConvertorRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/ConvertorRespCode.java similarity index 82% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/ConvertorRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/ConvertorRespCode.java index 84ea0fb9c..073d3e6b6 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/ConvertorRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/ConvertorRespCode.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; @@ -23,8 +23,8 @@ public enum ConvertorRespCode implements IModuleRespCode { CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR("008", "条件节点(复选)运算符【{}】暂不支持"), ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -36,13 +36,4 @@ public enum ConvertorRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } - } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FlowableEngineRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FlowableEngineRespCode.java similarity index 56% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FlowableEngineRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FlowableEngineRespCode.java index cdc9f933d..5915f3b99 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FlowableEngineRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FlowableEngineRespCode.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; @@ -16,11 +16,14 @@ public enum FlowableEngineRespCode implements IModuleRespCode { ENGINE_EXECUTION_LOST_ID_ERROR("001", "Execution 丢失"), ENGINE_USER_TASK_CALC_ERROR("002", "计算用户任务节点【nodeId:{}】的审批人发生异常, 配置类型:【{}】, 异常信息:【{}】"), ENGINE_USER_TASK_TYPE_NOT_SUPPORT("003", "审批指定方式暂不支持"), - ENGINE_USER_TASK_PARAM_ERROR("004", "构建后的查询审批人入参为空. 任务节点【nodeId:{}】, 审批人所在范围:【{}】"), + 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", "命令重试尝试【{}】次仍然失败,并出现异常, 将放弃, 错误信息:{}"), ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -32,13 +35,4 @@ public enum FlowableEngineRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } - } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FormDefinitionRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormDefinitionRespCode.java similarity index 50% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FormDefinitionRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormDefinitionRespCode.java index 4f1e9901b..fd7610380 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FormDefinitionRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormDefinitionRespCode.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; @@ -13,12 +13,11 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum FormDefinitionRespCode implements IModuleRespCode { - FORM_DEFINITION_NOT_EXISTS("001", "表单定义内容转换出错, 建议重新修改表单定义后重试"), - FORM_DEFINITION_PARSER_ERROR("002", "表单定义内容解析出错"), + FORM_DEFINITION_EXISTS("001", ""), ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -30,13 +29,4 @@ public enum FormDefinitionRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } - } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormInstanceRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormInstanceRespCode.java new file mode 100644 index 000000000..8b878fffe --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormInstanceRespCode.java @@ -0,0 +1,52 @@ +package cn.axzo.workflow.common.code; + +import cn.axzo.framework.domain.web.code.IModuleRespCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 表单实例相关响应码 + * + * @author wangli + * @since 2024-11-13 14:34 + */ +@Getter +@AllArgsConstructor +public enum FormInstanceRespCode implements IModuleRespCode { + FORM_PARAM_ERROR("001", "流程实例 ID 和任务 ID 不能都为空"), + FORM_FIELD_NOT_FOUND("002", "无法获取全量表单字段权限配置信息"), + FORM_FIELD_VALIDATOR_ERROR("003", "表单字段校验不通过"), + FORM_INSTANCE_PARSE_ERROR("004", "表单实例数据解析错误"), + FORM_INSTANCE_DATA_NOT_FOUND("005", "未找到指定实例【{}】的表单数据"), + FORM_DATA_PARSE_ERROR("006", "表单数据解析异常"), + FORM_DATA_PARSE_ERROR_BY_UPLOAD("007", "表单上传组件的数据解析异常"), + FORM_DATA_PARSE_ERROR_BY_IMAGE("008", "表单图片组件的数据解析异常"), + FORM_DATA_PARSE_ERROR_BY_CUSTOM_COMPONENT("009", "表单自定义组件的数据解析异常"), + FORM_DATA_PARSE_ERROR_BY_AMOUNT("010", "表单金额组件的数据解析异常"), + ; + + private final String code; + private final String message; + + @Override + public String getModuleCode() { + return "13"; + } + + @Override + public String getProjectCode() { + return "998"; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } + + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FormModelRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormModelRespCode.java similarity index 62% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FormModelRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormModelRespCode.java index 4650677ee..8742fc036 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/FormModelRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/FormModelRespCode.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; @@ -13,12 +13,13 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum FormModelRespCode implements IModuleRespCode { - FORM_MODEL_NOT_EXISTS("001", "表单模型不存在"), + FORM_MODEL_NOT_EXISTS("001", "表单模型不存在,请检查审批模板的字段管理Tab页"), FORM_MODEL_ID_NOT_EXISTS("002", "表单模型ID【{}】不存在"), + FORM_MODEL_EXISTS("003", "表单模型已存在"), ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -30,13 +31,4 @@ public enum FormModelRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } - } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/OtherRespCode.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/OtherRespCode.java similarity index 56% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/OtherRespCode.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/OtherRespCode.java index 9c473fb18..c8d4f93f3 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/OtherRespCode.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/code/OtherRespCode.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.code; +package cn.axzo.workflow.common.code; import cn.axzo.framework.domain.web.code.IModuleRespCode; import lombok.AllArgsConstructor; @@ -17,11 +17,15 @@ public enum OtherRespCode implements IModuleRespCode { REPEAT_SUBMIT_TIME_ERROR_TIPS("002", "重复提交间隔时间不能小于{}秒"), REPEAT_SUBMIT_ERROR_TIPS("003", "{}"), CLIENT_VERSION_SUPPORT("004", "客户端 JAR 包版本不支持,请升级到 {} 版本, 当前版本 {}"), - MICRO_SERVER_NEED_REBUILD("005", "微服务 {} 需要重新编译发布"), + MICRO_SERVER_NEED_REBUILD("005", "微服务 {} 需要重新编译发布, 如果是本地调用,请访问 WebApi 接口,在现有的接口地址前加上“web/v1/”即可"), + MESSAGE_PUSH_EVENT_BUILD_ERROR("006", "不能使用 createEvent 函数创建`发送待办`的事件, 请调用 createPendingPushEvent 函数"), + ASYNC_JOB_EXECUTION_ERROR("007", "获取指定实例 ID【{}】的锁失败"), + ILLEGAL_PARAM_ERROR("008", "非法的参数:【{}】"), + MESSAGE_IM_EVENT_BUILD_ERROR("009", "不能使用 createEvent 函数创建`IM 消息`的事件, 请调用 createIMEvent 函数"), ; - private String code; - private String message; + private final String code; + private final String message; @Override public String getModuleCode() { @@ -33,12 +37,4 @@ public enum OtherRespCode implements IModuleRespCode { return "998"; } - public void setCode(String code) { - this.code = code; - } - - public void setMessage(String message) { - this.message = message; - } - } 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 5f801a1ce..eae75fd51 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 @@ -9,7 +9,10 @@ public interface BpmnConstants { /** * 引擎自己的隐藏指令 */ + String ADSCRIPTION_TENANT_ID = "adscriptionTenantId"; String FLOWABLE_SKIP_EXPRESSION_ENABLE = "[_FLOWABLE_SKIP_EXPRESSION_ENABLED_]"; + String MQ_UNIQUE_ID = "[_MQ_UNIQUE_ID_]"; + String PROCESS_OWNERSHIP_APPLICATION = "[_PROCESS_OWNERSHIP_APPLICATION_]"; String WORKFLOW_ENGINE_VERSION = "[_WORKFLOW_ENGINE_VERSION_]"; String INTERNAL_INITIATOR = "[_INTERNAL_INITIATOR_]"; @Deprecated @@ -29,7 +32,9 @@ public interface BpmnConstants { String BIZ_ORG_RELATION = "[_BIZ_ORG_RELATION_]"; String PENDING_TEMPLATE_VARIABLE = "[_PENDING_VARIABLES]"; String INTERNAL_PROCESS_AGENT = "[_INTERNAL_PROCESS_AGENT]"; + String CREATE_INSTANCE_PARAMS = "[_CREATE_INSTANCE_PARAMS]"; String INTERNAL_PROCESS_WORKSPACE_TYPE = "[_INTERNAL_PROCESS_WORKSPACE_TYPE]"; + String INTERNAL_PROCESS_BIZ_TYPE = "[_INTERNAL_PROCESS_BIZ_TYPE]"; // 用于多实例审批时,保存计算出来的审批人 String INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO = "[_ASSIGNEE_LIST_INFO_]"; // 单任务节点, @@ -37,6 +42,9 @@ 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 INITIATOR_SPECIFY = "[_INITIATOR_SPECIFY_]"; + String SIGNATURE_COLLECTION = "[_SIGNATURE_COLLECTION_]"; String PROCESS_PREFIX = "Flowable"; @Deprecated String OLD_TASK_ASSIGNEE_SKIP_FLAT = "taskSkip"; @@ -50,15 +58,33 @@ public interface BpmnConstants { String ROBOT_ASSIGNEE_ID = NO_TENANT_ID + "|" + ROBOT_ASSIGNEE + "|" + ROBOT_ASSIGNEE_TYPE; String HIDDEN_ASSIGNEE_ID = NO_TENANT_ID + "|hidden|" + TASK_ASSIGNEE_SKIP_FLAT; String NO_ASSIGNEE = "|"; + String FLOW_MODEL_BIZ_TYPE = "modelType"; String FLOW_NODE_JSON = "jsonValue"; String FLOW_SERVER_VERSION = "serverVersion"; String FLOW_SERVER_VERSION_121 = "1.2.1"; String FLOW_SERVER_VERSION_130 = "1.3.0"; + // 1.4.2 开始启用新版本日志 + String FLOW_SERVER_VERSION_142 = "1.4.2"; + String CONFIG_SIGN = "signConfig"; String CONFIG_NOTICE = "noticeConfig"; - String TEMPLATE_NOTICE_MESSAGE_ID = "noticeMessageId"; + String CONFIG_APPROVE = "approveConfig"; + String TEMPLATE_NOTICE_MESSAGE_CONFIG = "noticeMessageConfig"; + String TEMPLATE_NOTICE_MESSAGE_EVENTS = "noticeMessageEvents"; + String TEMPLATE_NOTICE_MESSAGE_DESTINATION = "noticeMessageDestination"; + String TEMPLATE_NOTICE_MESSAGE_DESTINATION_INITIATOR = "initiator"; + String TEMPLATE_NOTICE_MESSAGE_DESTINATION_HISTORIES = "histories"; + String TEMPLATE_NOTICE_MESSAGE_DESTINATION_ROLES = "roles"; + String TEMPLATE_NOTICE_MESSAGE_DESTINATION_POSITIONS = "positions"; + String TEMPLATE_NOTICE_MESSAGE_DESTINATION_ASSIGNERS = "assigners"; String TEMPLATE_PENDING_MESSAGE_ID = "pendingMessageId"; + String TEMPLATE_SIGN_PENDING_MESSAGE_ID = "signPendingMessageId"; String TEMPLATE_CARBON_COPY_MESSAGE_ID = "carbonCopyMessageId"; String TEMPLATE_SMS_MESSAGE_ID = "smsMessageId"; + String TEMPLATE_UPGRADE_APPROVAL_CONF = "upgradeApprovalConf"; + String TEMPLATE_UPGRADE_APPROVAL_LIMIT_CONF = "limitConfig"; + String TEMPLATE_UPGRADE_APPROVAL_SPECIFY_VALUE = "specifyValue"; + String APPROVE_SUPPORT_BATCH_OPERATION = "supportBatchOperation"; + String APPROVE_USER_AGREE_SIGNATURE = "userAgreeSignature"; String CONFIG_BUTTON = "buttonConfig"; String CONFIG_CARBON_COPIES = "carbonCopyConfigs"; String CONFIG_CARBON_COPY = "carbonCopyConfig"; @@ -68,10 +94,14 @@ public interface BpmnConstants { String CONFIG_BUTTON_META = "button"; String CONFIG_FIELD = "fieldConfig"; String CONFIG_APPROVAL_METHOD = "approvalMethod"; + String CONFIG_SIGN_APPROVER_LIMIT = "signApproverLimit"; + String CONFIG_SIGN_APPROVER_ORG_LIMIT = "orgLimit"; + String CONFIG_SIGN_APPROVER_ROLE_LIMIT = "roleLimit"; String CONFIG_APPROVER_SCOPE = "approverScope"; String CONFIG_APPROVER_SPECIFY = "approverSpecify"; String CONFIG_APPROVER_MODE_TYPE = "approverModeType"; String CONFIG_APPROVER_EMPTY_HANDLE_TYPE = "approverEmptyHandleType"; + String CONFIG_ACTIVITY_SIGNATURE = "signature"; String CONFIG_FIELD_META = "field"; String CONFIG_FIELD_PERMISSION = "fieldPermission"; String CONFIG_FIELD_OPTION = "option"; @@ -80,6 +110,7 @@ public interface BpmnConstants { String CONFIG_BUTTON_TYPE_CURRENT = "current"; String CONFIG_BUTTON_TYPE_HISTORY = "history"; String CONFIG_BUTTON_TYPE_CARBON_COPY = "carbonCopy"; + String CONFIG_SIGN_TYPE = "signType"; String ELEMENT_ATTRIBUTE_NAME = "name"; String ELEMENT_ATTRIBUTE_VALUE = "value"; String ELEMENT_ATTRIBUTE_DESC = "desc"; @@ -89,11 +120,16 @@ public interface BpmnConstants { String ELEMENT_ATTRIBUTE_CHECKED = "checked"; String ELEMENT_ATTRIBUTE_DISABLED = "disabled"; String ELEMENT_ATTRIBUTE_TYPE = "type"; + String ELEMENT_ATTRIBUTE_ORG_LIMIT = "orgLimit"; + String ELEMENT_ATTRIBUTE_APPROVER_SPECIFY = "approverSpecify"; String START_EVENT_ID = "startEventNode"; String SEQUENCE_FLOW_ID = "SequenceFlowId"; String END_EVENT_ID = "endEventNode"; 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]"; + String SKIP_MQ = "skipMQ"; /** * 用于国内审批节点填写审批建议 *

@@ -106,6 +142,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"; + /** * 会签表达式 */ @@ -130,4 +168,85 @@ public interface BpmnConstants { */ Integer DISABLED = 0; + /** + * 手写签配置默认值 + */ + Boolean USER_AGREE_SIGNATURE_DEFAULT_VALUE = true; + + /** + * 批量操作配置默认值 + */ + Boolean SUPPORT_BATCH_OPERATION_DEFAULT_VALUE = false; + /** + * 用于 MQ 的 Header, 记录当前事件的归属应用 + */ + String MQ_OWNERSHIP_APPLICATION = "MQ_OWNERSHIP_APPLICATION"; + String MQ_OWNERSHIP_PROCESS_DEFINITION_KEY = "MQ_OWNERSHIP_PROCESS_DEFINITION_KEY"; + + /** + * 审批人数量限制 + */ + Integer APPROVAL_ASSIGNER_LIMIT_NUMBER = 60; + + /** + * 抄送人员数量限制 + */ + Integer CARBON_ASSIGNER_LIMIT_NUMBER = 100; + + /** + * MQ消息每批次人员数量 + */ + Integer MQ_ASSIGNER_BATCH_SIZE = 20; + + /** + * 加签显示人员数量 + */ + Integer COUNTERSIGN_ASSIGNER_SHOW_NUMBER = 2; + /** + * 重置任务审批人显示人员数量 + */ + Integer RESET_TASK_ASSIGNER_SHOW_NUMBER = 2; + + /** + * 回退操作次数上限 + */ + Integer MAX_BACKED_OPERATE_COUNT = 20; + String LATEST_SYNC_TO_ELASTICSEARCH_TIME = "latest.sync.to.elasticsearch.time"; + /** + * 固定父子文档在相同分片 + */ + String ES_FIXED_ROUTING = "workflow_engine_join_routing"; + + /** + * ouId+workspaceId 下限制人员数量为20 + */ + Integer MAX_ORG_WORKSPACE_ADMIN_COUNT = 20; + + /** + * 发起人撤回时,当前流程停留住的节点定义key + */ + String CANCEL_PROCESS_NODE_DEF_KEY_NAME = "[_CANCEL_PROCESS_NODE_DEF_KEY_]"; + // 被转交人 + String TRANSFER_TO = "[TRANSFER_TO_]"; + // 转交时的意见 + String TRANSFER_TO_ADVICE = "[TRANSFER_TO_ADVICE]"; + // 结束流程的最后一个操作人 + String CLOSE_PROCESS_ASSIGNER = "[_CLOSE_PROCESS_ASSIGNER_]"; + /** + * 签署业务发起流程实例时,重新选择的文档tag 集合 + */ + String SIGN_PROCESS_ENABLE_DOC_IDS = "[_SIGN_PROCESS_ENABLE_DOC_IDS_]"; + /** + * 签署业务,基于业务自定义变量的传入 + */ + String SIGN_VARIABLE = "[_SIGN_VARIABLES_]"; + /** + * 签署人 + */ + String SIGNATORIES = "[_SIGNATORIES_]"; + String TASK_SUBMIT_FORM_VARIABLE = "[_TASK_SUBMIT_FORM_VARIABLE_]"; + /** + * 提级审批变量标识 + */ + String SUPPORT_UPGRADE_VARIABLE = "[_SUPPORT_UPGRADE_]"; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/FormConstants.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/FormConstants.java new file mode 100644 index 000000000..5bbe9334b --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/FormConstants.java @@ -0,0 +1,29 @@ +package cn.axzo.workflow.common.constant; + +/** + * 表单引擎相关的常量 + * + * @author wangli + * @since 2024-11-05 10:20 + */ +public interface FormConstants { + + String FIELD_PROPERTY_REQUIRED = "required"; + + String FIELD_PROPERTY_EDITABLE = "editable"; + + String FIELD_PROPERTY_READONLY = "readonly"; + + String FIELD_PROPERTY_HIDDEN = "hidden"; + + String FIELD_PROPERTY_DEFAULT_VALUE= "defaultValue"; + + String FORM_FIELD_TYPE_INPUT = "input"; + String FORM_FIELD_TYPE_TEXTAREA = "textarea"; + String FORM_FIELD_TYPE_IMAGE = "image"; + String FORM_FIELD_TYPE_CUSTOM_COMPONENT = "customComponent"; + String FORM_FIELD_TYPE_TASK_ORDER = "taskOrder"; + String FORM_FIELD_TYPE_RECTIFY_ORDER = "rectifyOrder"; + String FORM_FIELD_TYPE_CHANGE_SIGNATURE_ORDER = "changeSignatureOrder"; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/LogFieldConstants.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/LogFieldConstants.java new file mode 100644 index 000000000..900b8a452 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/LogFieldConstants.java @@ -0,0 +1,6 @@ +package cn.axzo.workflow.common.constant; + +public interface LogFieldConstants { + + String X_REQUEST_ID = "x-request-id"; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/StarterConstants.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/StarterConstants.java new file mode 100644 index 000000000..66747128f --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/StarterConstants.java @@ -0,0 +1,16 @@ +package cn.axzo.workflow.common.constant; + +/** + * Starter 常量类 + * + * @author wangli + * @since 2024/5/29 11:13 + */ +public interface StarterConstants { + String STARTER_INVOKE_MODE = "WORKFLOW-ENGINE-STARTER-INVOKE-MODE"; + String DEBUGGING_MQ_SUFFIX = "_debugging"; + String K8S_POD_NAME_SPACE = "MY_POD_NAMESPACE"; + String NACOS_PROFILES_ACTIVE = "NACOS_PROFILES_ACTIVE"; + String MQ_GID_NAME_SEGMENT = "GID_SEGMENT"; + String ENABLE_MANAGEABLE = "ENABLE_MANAGEABLE"; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/TaskListenerExtConstants.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/TaskListenerExtConstants.java new file mode 100644 index 000000000..6e2d7a1ac --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/TaskListenerExtConstants.java @@ -0,0 +1,12 @@ +package cn.axzo.workflow.common.constant; + +/** + * 自定义的流程回退及转发事件常亮 + * + * @author wangli + * @since 2025-01-20 14:06 + */ +public interface TaskListenerExtConstants { + String EVENTNAME_TRANSFER = "process-task-transfer"; + String EVENTNAME_FALLBACK = "process-task-fallback"; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/VariableConstants.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/VariableConstants.java index 74b6c8515..b0ae9caa8 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/VariableConstants.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constant/VariableConstants.java @@ -24,4 +24,41 @@ public interface VariableConstants { String VAR_ACTIVITY_NAME = "activityName"; String VAR_PROCESS_RESULT = "processResult"; String VAR_OPERATOR_TYPE = "operatorType"; + String VAR_USER_AGREE_SIGNATURE = "userAgreeSignature"; + + + //=============== 打印时的变量集合中 key 的命名 ================= + String VAR_PREFIX = "业务变量"; + String PRINT_VAR_PROCESS_DEFINITION_KEY = "processDefinitionKey"; + String PRINT_VAR_PROCESS_DEFINITION_KEY_DESC = "业务名称"; + String PRINT_VAR_PROCESS_INSTANCE_ID = "processInstanceId"; + String PRINT_VAR_PROCESS_INSTANCE_ID_DESC = "审批编号"; + String PRINT_VAR_PROCESS_START_TIME = "startTime"; + String PRINT_VAR_PROCESS_START_TIME_DESC = "发起时间"; + String PRINT_VAR_PROCESS_END_TIME = "endTime"; + String PRINT_VAR_PROCESS_END_TIME_DESC = "审批结束时间"; + String PRINT_VAR_PROCESS_INITIATOR = "initiator"; + String PRINT_VAR_PROCESS_INITIATOR_DESC = "发起者"; + String PRINT_VAR_PROCESS_INITIATOR_NAME = "initiatorName"; + String PRINT_VAR_PROCESS_INITIATOR_NAME_DESC = "发起人姓名"; + String PRINT_VAR_PROCESS_INITIATOR_POSITION = "initiatorPosition"; + String PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC = "发起人岗位"; + String PRINT_VAR_PROCESS_INITIATOR_PHONE = "initiatorPhone"; + String PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC = "发起人联系方式"; + String PRINT_VAR_PROCESS_LOGS = "processLogs"; + String PRINT_VAR_PROCESS_LOGS_DESC = "审批日志"; + String PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME = "activityName"; + String PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME_DESC = "节点名称"; + String PRINT_VAR_PROCESS_LOG_APPROVER_NAME = "approverName"; + String PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC = "审批人"; + String PRINT_VAR_PROCESS_LOG_UNIT = "unit"; + String PRINT_VAR_PROCESS_LOG_UNIT_DESC = "单位"; + String PRINT_VAR_PROCESS_LOG_POSITION = "position"; + String PRINT_VAR_PROCESS_LOG_POSITION_DESC = "岗位"; + String PRINT_VAR_PROCESS_LOG_ADVICE = "advice"; + String PRINT_VAR_PROCESS_LOG_ADVICE_DESC = "审批意见"; + String PRINT_VAR_PROCESS_LOG_OPERATION_TIME = "operationTime"; + String PRINT_VAR_PROCESS_LOG_OPERATION_TIME_DESC = "审批时间"; + String PRINT_VAR_PROCESS_LOG_SIGNATURE = "signature"; + String PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC = "电子签名"; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentConstraintValidator.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentConstraintValidator.java new file mode 100644 index 000000000..fb7fd0603 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentConstraintValidator.java @@ -0,0 +1,72 @@ +package cn.axzo.workflow.common.constraint; + +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; + +/** + * 附件校验器 + * + * @author wangli + * @since 2024/4/22 15:15 + */ +public class AttachmentConstraintValidator implements ConstraintValidator> { + + private AttachmentTypeValidator[] typeConstraints; + + @Override + public void initialize(AttachmentValidator constraintAnnotation) { + this.typeConstraints = constraintAnnotation.types(); + } + + @Override + public boolean isValid(List attachments, ConstraintValidatorContext context) { + if (attachments == null || attachments.isEmpty()) { + return true; // No attachments, so no validation needed + } + boolean isValid = true; + + for (AttachmentTypeValidator typeConstraint : typeConstraints) { + AttachmentTypeEnum type = typeConstraint.type(); + int minAllowed = typeConstraint.min(); + int maxAllowed = typeConstraint.max(); + int count = getCountOfType(attachments, type); + + // Check if min or max is not limited + boolean minNotLimited = minAllowed == -1; + boolean maxNotLimited = maxAllowed == Integer.MAX_VALUE; + + if (!minNotLimited && count < minAllowed) { + addErrorMessage(context, typeConstraint.type().getDesc() + "数量最小支持" + minAllowed + "个"); + isValid = false; // Validation failed due to min limit + } + + if (!maxNotLimited && count > maxAllowed) { + addErrorMessage(context, typeConstraint.type().getDesc() + "数量最大支持" + maxAllowed + "个"); + isValid = false; // Validation failed due to max limit + } + } + + return isValid; // Validation passed + } + + // Helper method to count attachments of a specific type + private int getCountOfType(List attachments, AttachmentTypeEnum type) { + int count = 0; + for (AttachmentDTO attachment : attachments) { + if (attachment.getType().equals(type)) { + count++; + } + } + return count; + } + + private void addErrorMessage(ConstraintValidatorContext context, String message) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(message) + .addConstraintViolation(); + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentTypeValidator.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentTypeValidator.java new file mode 100644 index 000000000..949cbcc98 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentTypeValidator.java @@ -0,0 +1,41 @@ +package cn.axzo.workflow.common.constraint; + +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 工作流主要操作时, 附件的校验 + * + * @author wangli + * @since 2024/4/22 15:17 + */ +@Target({ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AttachmentTypeValidator { + + /** + * 指定的附件类型 + * + * @return + */ + AttachmentTypeEnum type(); + + /** + * 最小个数: -1 不限制 + * + * @return + */ + int min() default -1; + + /** + * 最大个数: -1 不限制 + * + * @return + */ + int max() default Integer.MAX_VALUE; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentValidator.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentValidator.java new file mode 100644 index 000000000..05730791c --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/constraint/AttachmentValidator.java @@ -0,0 +1,31 @@ +package cn.axzo.workflow.common.constraint; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 工作流主要操作时, 附件的校验 + * + * @author wangli + * @since 2024/4/22 15:17 + */ + +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = AttachmentConstraintValidator.class) +@Documented +public @interface AttachmentValidator { + + String message() default "附件校验不通过"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + AttachmentTypeValidator[] types(); +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminDataSource.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminDataSource.java new file mode 100644 index 000000000..71b281745 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminDataSource.java @@ -0,0 +1,17 @@ +package cn.axzo.workflow.common.enums; + +import lombok.Getter; + +@Getter +public enum AdminDataSource { + SYSTEM_ENTRY("systemEntry", "系统录入"), + USER_ENTRY("userEntry", "用户手动录入"); + + private final String type; + private final String desc; + + AdminDataSource(String type, String desc) { + this.type = type; + this.desc = desc; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminRoleType.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminRoleType.java new file mode 100644 index 000000000..4fa15948f --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminRoleType.java @@ -0,0 +1,19 @@ +package cn.axzo.workflow.common.enums; + +import lombok.Getter; + +@Getter +public enum AdminRoleType { + ORGANIZATION_ADMIN("organizationAdmin", "单位超管"), + ORG_WORKSPACE_ADMIN("organizationWorkspaceAdmin", "项目内单位负责人"), + WORKSPACE_ADMIN("workspaceAdmin", "项目超管"), + OTHER("other", "其他用户"); + + private final String type; + private final String desc; + + AdminRoleType(String type, String desc) { + this.type = type; + this.desc = desc; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminTypeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminTypeEnum.java new file mode 100644 index 000000000..69bae7fe2 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AdminTypeEnum.java @@ -0,0 +1,20 @@ +package cn.axzo.workflow.common.enums; + +import lombok.Getter; + +/** + * 审批管理员类型 + */ +@Getter +public enum AdminTypeEnum { + SUPER_ADMIN("super_admin", "超级管理员"), + COMMON_ADMIN("common_admin", "普通管理员"); + + private final String type; + private final String desc; + + AdminTypeEnum(String type, String desc) { + this.type = type; + this.desc = desc; + } +} 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..5b8ab0b5e 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; + /** * 审批方式枚举 * @@ -10,9 +13,12 @@ public enum ApprovalMethodEnum { human("human", "人工审批", ""), autoPassed("autoPassed", "自动通过", "[仅审批节点可能选该值]"), + autoPassed_empty("autoPassed", "自动通过", "该枚举仅日志处理使用"), autoRejection("autoRejection", "自动驳回", "[仅审批节点可能选该值]"), + autoRejection_empty("autoRejection", "自动驳回", "该枚举仅日志处理使用"), nobody("nobody", "不设置审批人", "[仅业务节点可能有该值]"), bizSpecify("bizSpecify", "业务指定审批人", "[仅业务节点可能有该值]"), + transferToAdmin("transferToAdmin", "转办给管理员", "该枚举仅日志处理使用"), ; private String type; @@ -48,4 +54,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/ApproverScopeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApproverScopeEnum.java index fb72f58ca..bde13f062 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApproverScopeEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApproverScopeEnum.java @@ -11,6 +11,7 @@ import java.util.Objects; */ public enum ApproverScopeEnum { entWorkspace("entWorkspace", "企业工作台", "entWorkspaceProcessor"), + govWorkspace("govWorkspace", "政务工作台", "govWorkspaceProcessor"), projectWorkspace("projectWorkspace", "项目工作台","projectWorkspaceProcessor"), preTaskUser("preTaskUser", "上节点审批人所在单位","preTaskUserProcessor"), preTaskSpecified("preTaskSpecified", "上节点审批人指定","preTaskUserProcessor"), diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApproverSpecifyEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApproverSpecifyEnum.java index 85dbab935..8b18cd082 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApproverSpecifyEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ApproverSpecifyEnum.java @@ -15,6 +15,9 @@ public enum ApproverSpecifyEnum { initiatorLeaderRecursion("initiatorLeaderRecursion", "发起人多级主管"), fixedPerson("fixedPerson", "固定人员"), preNodeSpecified("preNodeSpecified", "上级节点指定"), + initiatorSpecified("initiatorSpecified", "发起时指定"), + // 该枚举,目前主要为了区别签署业务与审批业务的不同配法,选择该项时,意味着该节点是由二方后端自由消费 BpmnSignApproverLimit 配置 + signerRelated("signerRelated", "签署人相关组织"), ; private String type; private String desc; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AutoApprovalTypeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AutoApprovalTypeEnum.java new file mode 100644 index 000000000..91189769d --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/AutoApprovalTypeEnum.java @@ -0,0 +1,35 @@ +package cn.axzo.workflow.common.enums; + +import lombok.Getter; +import org.springframework.util.StringUtils; + +/** + * 自动过审参数 + */ + +@Getter +public enum AutoApprovalTypeEnum { + + NO_AUTO_APPROVAL("noAutoApproval", "不自动过审"), + CONTINUOUS_NODES_AUTO_APPROVAL("continuousNodesAutoApproval", "连续节点自动过审"); + + private final String type; + private final String desc; + + AutoApprovalTypeEnum(String type, String desc) { + this.type = type; + this.desc = desc; + } + + public static AutoApprovalTypeEnum fromType(String type) { + if (!StringUtils.hasText(type)) { + return null; + } + for (AutoApprovalTypeEnum typeEnum : AutoApprovalTypeEnum.values()) { + if (typeEnum.type.equals(type)) { + return typeEnum; + } + } + return null; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnButtonEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnButtonEnum.java index 2daf65036..b8dea2f0f 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnButtonEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnButtonEnum.java @@ -1,6 +1,13 @@ package cn.axzo.workflow.common.enums; +import com.google.common.collect.Lists; + +import java.util.List; + +import static cn.axzo.workflow.common.enums.BusinessTypeEnum.APPROVAL; +import static cn.axzo.workflow.common.enums.BusinessTypeEnum.SIGN; + /** * Flowable Event Enum For RocketMQ * @@ -11,35 +18,56 @@ public enum BpmnButtonEnum { /** * 同意按钮 */ - BPMN_APPROVE(1, "BPMN_APPROVE", "同意"), + BPMN_APPROVE(1, "BPMN_APPROVE", "同意", Lists.newArrayList(APPROVAL, SIGN)), /** * 驳回按钮 */ - BPMN_REJECT(2, "BPMN_REJECT", "驳回"), + BPMN_REJECT(2, "BPMN_REJECT", "驳回", Lists.newArrayList(APPROVAL, SIGN)), + /** + * 确认签字 + */ + BPMN_CONFIRM_SIGN(3, "BPMN_CONFIRM_SIGN", "确认签字", Lists.newArrayList(SIGN)), + /** + * 查看文档 + */ + BPMN_VIEW_DOC(4, "BPMN_VIEW_DOC", "查看文档", Lists.newArrayList(SIGN)), /** * 撤回按钮 */ - BPMN_REVOCATION(3, "BPMN_REVOCATION", "撤回"), + BPMN_REVOCATION(5, "BPMN_REVOCATION", "撤回", Lists.newArrayList(APPROVAL, SIGN)), /** * 转交按钮 */ - BPMN_TRANSFER(4, "BPMN_TRANSFER", "转交"), + BPMN_TRANSFER(6, "BPMN_TRANSFER", "转交", Lists.newArrayList(APPROVAL, SIGN)), /** * 加签按钮 */ - BPMN_COUNTERSIGN(5, "BPMN_COUNTERSIGN", "加签"), + BPMN_COUNTERSIGN(7, "BPMN_COUNTERSIGN", "加签", Lists.newArrayList(APPROVAL, SIGN)), /** * 评论按钮 */ - BPMN_COMMENT(6, "BPMN_COMMENT", "评论"), + BPMN_COMMENT(8, "BPMN_COMMENT", "评论", Lists.newArrayList(APPROVAL, SIGN)), /** * 回退按钮 */ - BPMN_ROLLBACK(7, "BPMN_ROLLBACK", "回退"), + BPMN_ROLLBACK(9, "BPMN_ROLLBACK", "回退", Lists.newArrayList(APPROVAL, SIGN)), /** * 抄送按钮 */ - BPMN_COPY(8, "BPMN_COPY", "抄送"); + BPMN_COPY(10, "BPMN_COPY", "抄送", Lists.newArrayList(APPROVAL, SIGN)), + /** + * 提级审批按钮 + */ + BPMN_UPGRADE(12, "BPMN_UPGRADE", "提级审批", Lists.newArrayList(APPROVAL, SIGN)), + /** + * 催办按钮 + */ + BPMN_REMIND(11, "BPMN_REMIND", "催办", Lists.newArrayList(APPROVAL, SIGN)), + /** + * 管理员转交按钮 + */ + BPMN_ADMIN_TRANSFER(90, "BPMN_ADMIN_TRANSFER", "管理员转交", Lists.newArrayList()); + public int getOrder() { return order; @@ -53,15 +81,21 @@ public enum BpmnButtonEnum { return btnName; } + public List getSupportBizType() { + return supportBizType; + } + private final int order; private final String btnKey; private final String btnName; + private final List supportBizType; - BpmnButtonEnum(int order, String btnKey, String btnName) { + BpmnButtonEnum(int order, String btnKey, String btnName, List supportBizType) { this.order = order; this.btnKey = btnKey; this.btnName = btnName; + this.supportBizType = supportBizType; } } 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 c03147331..dfc36e971 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; @@ -14,6 +16,7 @@ public enum BpmnFlowNodeType { NODE_PARALLEL("NODE_PARALLEL", "并行节点 - 隶属于并行网关"), NODE_TASK("NODE_TASK", "审核节点"), NODE_BUSINESS("NODE_BUSINESS", "业务节点"), + NODE_SIGN("NODE_SIGN", "签署确认节点"), NODE_CARBON_COPY("NODE_CARBON_COPY", "抄送节点"), NODE_TRIGGER("NODE_TRIGGER", "触发器节点"), NODE_ROBOT("NODE_ROBOT", "机器人节点"), @@ -52,6 +55,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/BpmnNoticeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnNoticeEnum.java index d21af4fbc..562ad2c49 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnNoticeEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnNoticeEnum.java @@ -1,6 +1,6 @@ package cn.axzo.workflow.common.enums; -import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_CONFIG; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAGE_ID; @@ -12,7 +12,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAG */ public enum BpmnNoticeEnum { - notice("notice", TEMPLATE_NOTICE_MESSAGE_ID, "通知模板"), + notice("notice", TEMPLATE_NOTICE_MESSAGE_CONFIG, "通知模板"), pending("pending", TEMPLATE_PENDING_MESSAGE_ID, "待办模板"), sms("sms", TEMPLATE_SMS_MESSAGE_ID, "短信模板"), ; 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 aa29c667a..e0f2ce964 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 @@ -45,7 +45,7 @@ public enum BpmnProcessInstanceResultEnum { * @param result 结果 * @return 是否 */ - public static boolean isEndResult(Integer result) { + public static boolean isEndResult(String result) { return Arrays.asList(PROCESSING.getStatus(), APPROVED.getStatus(), REJECTED.getStatus(), CANCELLED.getStatus()).contains(result); } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessTaskResultEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessTaskResultEnum.java new file mode 100644 index 000000000..456dba759 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnProcessTaskResultEnum.java @@ -0,0 +1,29 @@ +package cn.axzo.workflow.common.enums; + +import lombok.Getter; + +@Getter +public enum BpmnProcessTaskResultEnum { + PENDING("PENDING", "待处理"), + APPROVED("APPROVED", "已同意"), + REJECTED("REJECTED", "已驳回"), + DELETED("DELETED", "已删除"), + CANCELED("CANCELED", "已撤销"), + TRANSFERRED("TRANSFERRED", "已转交"), + NONE("NONE", "没有执行动作,例如 抄送"), + ; + /** + * 结果 + */ + private final String status; + /** + * 描述 + */ + private final String desc; + + BpmnProcessTaskResultEnum(String status, String desc) { + this.status = status; + this.desc = desc; + } + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnSignType.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnSignType.java new file mode 100644 index 000000000..fe46981d7 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BpmnSignType.java @@ -0,0 +1,42 @@ +package cn.axzo.workflow.common.enums; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 签署方式 + * + * @author wangli + * @since 2025-03-25 17:00 + */ +public enum BpmnSignType { + + SINGLE("SINGLE", "指定人群,所有人共同签署一份文件"), + MULTI("MULTI", "指定人群,每人签署一份文件"), + ; + + private final String type; + private final String desc; + + BpmnSignType(String type, String desc) { + this.type = type; + this.desc = desc; + } + + public String getType() { + return type; + } + + + public String getDesc() { + return desc; + } + + public static BpmnSignType valueOfType(String type) { + return Arrays.stream(BpmnSignType.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/BusinessTypeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BusinessTypeEnum.java new file mode 100644 index 000000000..c44af52b2 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/BusinessTypeEnum.java @@ -0,0 +1,18 @@ +package cn.axzo.workflow.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public enum BusinessTypeEnum { + + SIGN("sign", "签署业务"), + + APPROVAL("approval", "审批业务"); + + private String type; + private String desc; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ButtonVisibleScopeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ButtonVisibleScopeEnum.java new file mode 100644 index 000000000..3f7dbdbbd --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ButtonVisibleScopeEnum.java @@ -0,0 +1,26 @@ +package cn.axzo.workflow.common.enums; + +import lombok.Getter; + +@Getter +public enum ButtonVisibleScopeEnum { + + INITIATOR("INITIATOR", "发起人"), + + EXECUTOR("EXECUTOR", "当前操作人"), + ; + + /** + * 结果 + */ + private final String status; + /** + * 描述 + */ + private final String desc; + + ButtonVisibleScopeEnum(String status, String desc) { + this.status = status; + this.desc = desc; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/CarbonCopyObjectType.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/CarbonCopyObjectType.java index 8ac0283a4..c4e8abee4 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/CarbonCopyObjectType.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/CarbonCopyObjectType.java @@ -15,6 +15,10 @@ public enum CarbonCopyObjectType { ent_position("ent_position", "position", "单位内指定岗位", "entWorkspaceProcessor"), ent_identity("ent_identity", "identity", "单位内指定身份", "entWorkspaceProcessor"), + //政务系统 + government_role("government_role", "role", "监管角色", "entWorkspaceProcessor"), + government_specify_user("government_specify_user", "specify_user", "监管指定人员", "entWorkspaceProcessor"), + // 项目下 project_role("project_role", "role", "项目部内指定角色", "projectWorkspaceProcessor"), project_position("project_position", "position", "项目部内指定岗位", "projectWorkspaceProcessor"), diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/DocChangeEventEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/DocChangeEventEnum.java new file mode 100644 index 000000000..c8eab6afe --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/DocChangeEventEnum.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.common.enums; + +import cn.axzo.framework.rocketmq.Event; + +/** + * 流程模型关联的文档变更 MQ 事件枚举定义 + * + * @author wangli + * @since 2025-04-07 16:46 + */ +public enum DocChangeEventEnum { + DOC_CHANGE("doc", "doc-change", "模型关联文档变更"), + ; + + private final String module; + private final String tag; + private final String desc; + private Event.EventCode eventCode; + + DocChangeEventEnum(String module, String tag, String desc) { + this.eventCode = Event.EventCode.builder() + .module(module) + .name(tag) + .build(); + this.module = module; + this.tag = tag; + this.desc = desc; + } + + public String getModule() { + return module; + } + + public String getTag() { + return tag; + } + + public String getDesc() { + return desc; + } + + public Event.EventCode getEventCode() { + return eventCode; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ElasticSearchEventEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ElasticSearchEventEnum.java new file mode 100644 index 000000000..7dc50600a --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ElasticSearchEventEnum.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.common.enums; + +import cn.axzo.framework.rocketmq.Event; + +/** + * 流程实例相关的 MQ 事件枚举定义 + * + * @author wangli + * @since 2023/9/25 11:47 + */ +public enum ElasticSearchEventEnum { + + ELASTIC_SEARCH_SYNC("elastic-search", "elastic-search-sync", "同步实例数据到ES"), + ; + private final String module; + private final String tag; + private final String desc; + private Event.EventCode eventCode; + + ElasticSearchEventEnum(String module, String tag, String desc) { + this.eventCode = Event.EventCode.builder() + .module(module) + .name(tag) + .build(); + this.module = module; + this.tag = tag; + this.desc = desc; + } + + public String getModule() { + return module; + } + + public String getTag() { + return tag; + } + + public String getDesc() { + return desc; + } + + public Event.EventCode getEventCode() { + return eventCode; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ExtModelStateFieldEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ExtModelStateFieldEnum.java new file mode 100644 index 000000000..f170a4a50 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ExtModelStateFieldEnum.java @@ -0,0 +1,11 @@ +package cn.axzo.workflow.common.enums; + +/** + * 模型扩展表的状态枚举 + * + * @author wangli + * @since 2025-01-15 09:46 + */ +public enum ExtModelStateFieldEnum { + status, printStatus +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/FileTypeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/FileTypeEnum.java new file mode 100644 index 000000000..ae3c22c02 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/FileTypeEnum.java @@ -0,0 +1,46 @@ +package cn.axzo.workflow.common.enums; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 文档类型枚举 + * + * @author wangli + * @since 2025-03-27 09:55 + */ +public enum FileTypeEnum { + WORD("word", "文本", ".docx"), + EXCEL("excel", "表格", ".xlsx"), + HIPRINT("hiprint", "智能文档", ""), + PDF("pdf", "PDF", ".pdf"), + ; + private final String type; + private final String desc; + private final String suffix; + + FileTypeEnum(String type, String desc, String suffix) { + this.type = type; + this.desc = desc; + this.suffix = suffix; + } + + public String getType() { + return type; + } + + public String getDesc() { + return desc; + } + + public String getSuffix() { + return suffix; + } + + public static FileTypeEnum valueOfType(String type) { + return Arrays.stream(FileTypeEnum.values()) + .filter(i -> Objects.equals(i.getType().toUpperCase(), type.toUpperCase())) + .findAny() + .orElse(null); + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ModelBizTypeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ModelBizTypeEnum.java new file mode 100644 index 000000000..a4d5c1bee --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ModelBizTypeEnum.java @@ -0,0 +1,39 @@ +package cn.axzo.workflow.common.enums; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 模型业务类型枚举 + * + * @author wangli + * @since 2025-03-26 11:49 + */ +public enum ModelBizTypeEnum { + + SIGN("SIGN", "签署业务"), + FLOWABLE("FLOWABLE", "审批业务"), + ; + private final String type; + private final String desc; + + ModelBizTypeEnum(String type, String desc) { + this.type = type; + this.desc = desc; + } + + public String getType() { + return type; + } + + public String getDesc() { + return desc; + } + + public static ModelBizTypeEnum valueOfType(String type) { + return Arrays.stream(ModelBizTypeEnum.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/OrderEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/OrderEnum.java new file mode 100644 index 000000000..805b79ded --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/OrderEnum.java @@ -0,0 +1,12 @@ +package cn.axzo.workflow.common.enums; + +/** + * 顺序操作枚举 + * + * @author wangli + * @since 2025-03-31 16:35 + */ +public enum OrderEnum { + + UP, DOWN +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/PrintFieldCategoryEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/PrintFieldCategoryEnum.java new file mode 100644 index 000000000..1a29b3e16 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/PrintFieldCategoryEnum.java @@ -0,0 +1,19 @@ +package cn.axzo.workflow.common.enums; + +/** + * 打印字段的类型枚举 + * + * @author wangli + * @since 2025-01-16 18:19 + */ +public enum PrintFieldCategoryEnum { + + // 表单变量 + form, + // 流程内系统变量 + system, + // 电子签名变量 + signature, + // 签署业务自定义变量 + sign +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessActivityEventEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessActivityEventEnum.java index 9017664f2..2b2595fe2 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessActivityEventEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessActivityEventEnum.java @@ -13,6 +13,7 @@ public enum ProcessActivityEventEnum { PROCESS_ACTIVITY_START("process-activity", "process-activity-start", "流程活动节点已开始"), PROCESS_ACTIVITY_WAIT_ASSIGNEE("process-activity", "process-activity-wait-assignee", "流程活动节点等待指定审批人"), PROCESS_ACTIVITY_TAKE("process-activity", "process-activity-take", "该事件类型暂时不存在"), + PROCESS_ACTIVITY_CALLBACK("process-activity", "process-activity-callback", "业务节点定时回调"), PROCESS_ACTIVITY_END("process-activity", "process-activity-end", "流程活动节点已取消"), ; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessMessagePushEventEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessMessagePushEventEnum.java index 1b65e6ac4..6d3300170 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessMessagePushEventEnum.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/ProcessMessagePushEventEnum.java @@ -13,9 +13,11 @@ public enum ProcessMessagePushEventEnum { PROCESS_PUSH_NOTICE("process-push", "process-push-notice", "站内信推送"), PROCESS_PUSH_PENDING("process-push", "process-push-pending", "待办推送"), PROCESS_PUSH_PENDING_COMPLETE("process-push", "process-push-pending-complete", "完成待办"), + PROCESS_PUSH_PENDING_ROLLBACK("process-push", "process-push-pending-rollback", "恢复待办"), PROCESS_CARBON_COPY("process-push", "process-carbon-copy", "抄送流程"), PROCESS_CARBON_COPY_COMPLETE("process-push", "process-carbon-copy-complete", "完成抄送"), PROCESS_PUSH_SMS("process-push", "process-push-sms", "短信推送"), + PROCESS_PUSH_IM("process-push", "process-push-im", "IM 推送"), ; @@ -24,12 +26,12 @@ public enum ProcessMessagePushEventEnum { private final String desc; private Event.EventCode eventCode; - ProcessMessagePushEventEnum(String model, String tag, String desc) { + ProcessMessagePushEventEnum(String module, String tag, String desc) { this.eventCode = Event.EventCode.builder() - .module(model) + .module(module) .name(tag) .build(); - this.module = model; + this.module = module; this.tag = tag; this.desc = desc; } 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..cd205223c 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 @@ -2,6 +2,9 @@ package cn.axzo.workflow.common.enums; import cn.axzo.framework.rocketmq.Event; +import static cn.axzo.workflow.common.constant.TaskListenerExtConstants.EVENTNAME_FALLBACK; +import static cn.axzo.workflow.common.constant.TaskListenerExtConstants.EVENTNAME_TRANSFER; + /** * 流程任务节点相关的 MQ 事件枚举定义 * @@ -10,8 +13,10 @@ 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_TRANSFER("process-task", EVENTNAME_TRANSFER, "流程任务已转交"), + PROCESS_TASK_FALLBACK("process-task", EVENTNAME_FALLBACK, "流程任务已回退"), PROCESS_TASK_COMPLETED("process-task", "process-task-completed", "流程任务已结束"), PROCESS_TASK_DELETED("process-task", "process-task-deleted", "流程任务已删除"), ; @@ -45,4 +50,5 @@ public enum ProcessTaskEventEnum { public Event.EventCode getEventCode() { return eventCode; } + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/RpcInvokeModeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/RpcInvokeModeEnum.java new file mode 100644 index 000000000..4a8f4c439 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/RpcInvokeModeEnum.java @@ -0,0 +1,12 @@ +package cn.axzo.workflow.common.enums; + +/** + * PRC 调用模式枚举 + * + * @author wangli + * @since 2024/5/29 10:27 + */ +public enum RpcInvokeModeEnum { + SYNC, + ASYNC, +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/SignApproverOrgLimitEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/SignApproverOrgLimitEnum.java new file mode 100644 index 000000000..e0f65da15 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/SignApproverOrgLimitEnum.java @@ -0,0 +1,52 @@ +package cn.axzo.workflow.common.enums; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 签署确认节点的审批人层级范围限制 + * + * @author wangli + * @since 2025-03-26 14:30 + */ +public enum SignApproverOrgLimitEnum { + LV_0("LV_0", "当前组织", 0), + LV_1("LV_1", "上1级组织", 1), + LV_2("LV_2", "上2级组织", 2), + LV_3("LV_3", "上3级组织", 3), + LV_4("LV_4", "上4级组织", 4), + LV_5("LV_5", "上5级组织", 5), + LV_ALL("LV_ALL", "所有组织", -1), + ; + + private final String type; + + private final String desc; + + private final Integer code; + + SignApproverOrgLimitEnum(String type, String desc, Integer code) { + this.type = type; + this.desc = desc; + this.code = code; + } + + public String getType() { + return type; + } + + public String getDesc() { + return desc; + } + + public Integer getCode() { + return code; + } + + public static SignApproverOrgLimitEnum valueOfType(String type) { + return Arrays.stream(SignApproverOrgLimitEnum.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/SignApproverRoleLimitEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/SignApproverRoleLimitEnum.java new file mode 100644 index 000000000..2323fda54 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/SignApproverRoleLimitEnum.java @@ -0,0 +1,39 @@ +package cn.axzo.workflow.common.enums; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 签署确认节点的审批人层级范围限制 + * + * @author wangli + * @since 2025-03-26 14:30 + */ +public enum SignApproverRoleLimitEnum { + LEADER("LEADER", "负责人"), + ; + + private final String type; + + private final String desc; + + SignApproverRoleLimitEnum(String type, String desc) { + this.type = type; + this.desc = desc; + } + + public String getType() { + return type; + } + + public String getDesc() { + return desc; + } + + public static SignApproverRoleLimitEnum valueOfType(String type) { + return Arrays.stream(SignApproverRoleLimitEnum.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/TimeQueryDirection.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/TimeQueryDirection.java new file mode 100644 index 000000000..114403b07 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/TimeQueryDirection.java @@ -0,0 +1,16 @@ +package cn.axzo.workflow.common.enums; + +/** + * 时间查询方向 + *

+ * 注意: 该枚举用在查询 flowable 引擎数据时, 都是包含自身时间点的. + * 例如, 使用 Before 时,也就是说在某个时间点之前,是包含"某个时间"自身的. + * + * @author wangli + * @since 2024-09-29 09:56 + */ +public enum TimeQueryDirection { + BEFORE, + AFTER, + ; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/VarTypeEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/VarTypeEnum.java new file mode 100644 index 000000000..bbfd8227f --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/VarTypeEnum.java @@ -0,0 +1,21 @@ +package cn.axzo.workflow.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * 模版上变量字段类型 + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +public enum VarTypeEnum { + + TEXT("text", "文本"), + + PICTURE("picture", "图片"); + + private String type; + private String desc; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/WorkflowEngineEventEnum.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/WorkflowEngineEventEnum.java new file mode 100644 index 000000000..b03fb9847 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/WorkflowEngineEventEnum.java @@ -0,0 +1,49 @@ +package cn.axzo.workflow.common.enums; + +import cn.axzo.framework.rocketmq.Event; + +/** + * 流程引擎自产自销事件枚举 + * + * @author wangli + * @since 2024/5/20 13:57 + */ +public enum WorkflowEngineEventEnum { + + WORKFLOW_ENGINE_SERVER("workflow-engine", "workflow-engine-server", "引擎服务端事件"), + WORKFLOW_ENGINE_STARTER("workflow-engine", "workflow-engine-starter-%s", "引擎客户端事件"), + ; + + private final String module; + private final String tag; + private final String desc; + private Event.EventCode eventCode; + + WorkflowEngineEventEnum(String module, String tag, String desc) { + this.module = module; + this.tag = tag; + this.desc = desc; + this.eventCode = Event.EventCode.builder() + .module(module) + .name(tag) + .build(); + } + + public Event.EventCode getEventCode(String appName) { + eventCode.setName(String.format(eventCode.getName(), appName)); + return eventCode; + } + + public String getModule() { + return module; + } + + public String getTag() { + return eventCode.getName(); + } + + public String getDesc() { + return desc; + } + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/WorkspaceType.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/WorkspaceType.java index bbb060cc5..da845ee8d 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/WorkspaceType.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/enums/WorkspaceType.java @@ -19,10 +19,13 @@ public enum WorkspaceType { */ ENT(1, "企业"), PROJECT(2, "项目"), - OMS(6, "oms工作台"); + GOVERNMENT(3, "政务监管平台"), + OMS(6, "oms工作台"), + UN_KNOW(0, "未知"), + ; - private Integer code; - private String desc; + private final Integer code; + private final String desc; public static WorkspaceType getType(Integer code) { return Arrays.stream(values()).filter(it -> it.getCode().equals(code)) diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/exception/WorkflowEngineException.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/exception/WorkflowEngineException.java similarity index 78% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/exception/WorkflowEngineException.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/exception/WorkflowEngineException.java index 37db2bdcc..9a5e1f918 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/exception/WorkflowEngineException.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/exception/WorkflowEngineException.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.exception; +package cn.axzo.workflow.common.exception; import cn.axzo.framework.domain.ServiceException; import cn.axzo.framework.domain.web.code.IRespCode; @@ -19,6 +19,11 @@ public class WorkflowEngineException extends ServiceException { this.code = code.getRespCode(); } + public WorkflowEngineException(IRespCode code, Throwable cause, String... params) { + super(doFormat(code.getCode(), code.getMessage(), params), cause); + this.code = code.getRespCode(); + } + @Override public String getCode() { return this.code; @@ -41,7 +46,7 @@ public class WorkflowEngineException extends ServiceException { for (l = 0; l < params.length; l++) { j = messagePattern.indexOf("{}", i); if (j == -1) { - log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params); + log.warn("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params); if (i == 0) { return messagePattern; } else { @@ -55,7 +60,7 @@ public class WorkflowEngineException extends ServiceException { } } if (messagePattern.indexOf("{}", i) != -1) { - log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params); + log.warn("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params); } sbuf.append(messagePattern.substring(i)); return sbuf.toString(); diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/NextNodePreCheckAlterDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/NextNodePreCheckAlterDTO.java new file mode 100644 index 000000000..d921ac342 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/NextNodePreCheckAlterDTO.java @@ -0,0 +1,19 @@ +package cn.axzo.workflow.common.model; + +import lombok.Data; + +/** + * 节点检测告警对象 + * + * @author wangli + * @since 2024-09-13 11:37 + */ +@Data +public class NextNodePreCheckAlterDTO { + + private String processInstanceId; + + private String activityId; + + private String errorMsg; +} 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/AmountFieldDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/AmountFieldDTO.java new file mode 100644 index 000000000..d51003baf --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/AmountFieldDTO.java @@ -0,0 +1,33 @@ +package cn.axzo.workflow.common.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 金额字段值类型 + * + * @author wangli + * @since 2025-05-16 17:21 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AmountFieldDTO implements Serializable { + private static final long serialVersionUID = -3519991080707599177L; + + /** + * 小写 + */ + private BigDecimal standardNumerals; + + /** + * 大写 + */ + private String uppercaseNumerals; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BizDocDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BizDocDTO.java new file mode 100644 index 000000000..1a110eaad --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BizDocDTO.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.common.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 模型关联文档变更事件内部传输对象 + * + * @author wangli + * @since 2025-04-07 16:53 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BizDocDTO implements Serializable { + + private String archiveName; + + private String archiveCode; + + private Boolean enabled; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BpmnFormRelationCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BpmnFormRelationCreateDTO.java new file mode 100644 index 000000000..93e0cdc53 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BpmnFormRelationCreateDTO.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.common.model.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 模型与表单的创建模型 + * + * @author wangli + * @since 2024-11-07 18:48 + */ +@Data +@Accessors(chain = true) +public class BpmnFormRelationCreateDTO { + + @ApiModelProperty(value = "业务标识") + private String key; + + @ApiModelProperty(value = "模型定义ID") + private String bpmnDefinitionId; + + @ApiModelProperty(value = "表单部署ID") + private String formDeploymentId; + + @ApiModelProperty(value = "租户 ID") + private String tenantId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BpmnFormRelationSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BpmnFormRelationSearchDTO.java new file mode 100644 index 000000000..afcf841a9 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/BpmnFormRelationSearchDTO.java @@ -0,0 +1,43 @@ +package cn.axzo.workflow.common.model.dto; + +import cn.axzo.workflow.common.model.request.BpmPageParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 搜索 + * + * @author wangli + * @date 2024/11/08 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class BpmnFormRelationSearchDTO extends BpmPageParam { + + /** + * 业务标识 + */ + @ApiModelProperty(value = "业务标识") + private String key; + + /** + * 审批模型定义 ID + */ + @ApiModelProperty(value = "审批模型定义 ID") + private String bpmnDefinitionId; + + /** + * 表单定义部署 ID + */ + @ApiModelProperty(value = "表单定义部署 ID") + private String formDeploymentId; + + /** + * 租户 ID + */ + @ApiModelProperty(value = "租户 ID") + private String tenantId = NO_TENANT_ID; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/ContactsPersonDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/ContactsPersonDTO.java new file mode 100644 index 000000000..2ff8b1e85 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/ContactsPersonDTO.java @@ -0,0 +1,69 @@ +package cn.axzo.workflow.common.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 通讯录组件返回的人员信息模型 + * + * @author wangli + * @since 2025-05-15 14:04 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ContactsPersonDTO { + /** + * xx:xx:xx + */ + private String nodeId; + + /** + * 身份 ID + */ + private Long identityId; + + /** + * 身份类型 + */ + private Integer identityType; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 自然人 ID + */ + private Long personId; + + /** + * 人员所在的节点 ID + */ + private Long orgNodeId; + + /** + * 人员所在的单位 ID + */ + private Long ouId; + + /** + * 人员所在的单位名称 + */ + private String ouName; + + /** + * 人员所在的工作台 ID + */ + private Long workspaceId; + + /** + * 人员所在的工作台类型 + */ + private Integer workspaceType; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CooperationOrgDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CooperationOrgDTO.java index d65c4d0bc..858e68eb3 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CooperationOrgDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/CooperationOrgDTO.java @@ -20,6 +20,7 @@ import java.util.List; @Accessors(chain = true) @NoArgsConstructor @AllArgsConstructor +@Builder public class CooperationOrgDTO implements Serializable { private static final long serialVersionUID = 4739924705980062997L; @@ -43,6 +44,7 @@ public class CooperationOrgDTO implements Serializable { *

* 1 - 企业 * 2 - 项目 + * 3 - 政务 * 6 - oms */ private Integer workspaceType; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/JobInfo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/JobInfo.java new file mode 100644 index 000000000..35a2bac82 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/JobInfo.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 2025-06-23 19:35 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +public class JobInfo implements Serializable { + + private static final long serialVersionUID = -6092011348559752255L; + /** + * 岗位名称 + */ + private String jobName; + + /** + * 岗位编码 + */ + private String jobCode; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/NodeInfo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/NodeInfo.java new file mode 100644 index 000000000..6f0b430f3 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/NodeInfo.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 2025-06-23 19:35 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +public class NodeInfo implements Serializable { + + private static final long serialVersionUID = -6092011348559752255L; + /** + * 岗位名称 + */ + private Long nodeId; + + /** + * 岗位编码 + */ + private String nodeName; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/OrgSnapshotInfo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/OrgSnapshotInfo.java new file mode 100644 index 000000000..dae9af37b --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/OrgSnapshotInfo.java @@ -0,0 +1,71 @@ +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; +import java.util.List; + +/** + * 组织架构中的项目快照信息 + * + * @author wangli + * @since 2025-06-23 18:31 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +public class OrgSnapshotInfo implements Serializable { + private static final long serialVersionUID = -2898438378034300663L; + + /** + * 工作台名称 + */ + private String workspaceName; + + /** + * 工作台 ID + */ + private String workspaceId; + + /** + * 工作台类型 + */ + private Integer workspaceType; + + /** + * 参建单位类型 + */ + private Integer cooperationType; + + /** + * 参建单位名称 + */ + private String ouName; + + /** + * 参建单位 ID + */ + private String ouId; + + /** + * 顶级节点 ID + */ + private String topNodeId; + + /** + * 部门快照信息集合 + */ + private List nodeInfos; + + /** + * 岗位快照信息集合 + */ + private List jobInfos; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/OrgStructureSnapshotInfo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/OrgStructureSnapshotInfo.java new file mode 100644 index 000000000..6c1e826d0 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/OrgStructureSnapshotInfo.java @@ -0,0 +1,55 @@ +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 2025-06-23 18:23 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +public class OrgStructureSnapshotInfo implements Serializable { + private static final long serialVersionUID = 4199079714585922731L; + + /** + * 审批人姓名 + */ + private String personName; + + /** + * 头像(仅为审批任务被接受那一刻的电话,不可作为对外展示数据) + */ + private String avatarUrl; + + /** + * 手机号(仅为审批任务被接受那一刻的电话,不可作为对外展示数据) + */ + private String phone; + + /** + * 审批人顶级节点 ID + */ + private Long topNodeId; + + /** + * 工作台类型 + */ + private int workspaceType; + + /** + * 组织快照信息 + */ + private OrgSnapshotInfo snapshotInfo; + +} \ No newline at end of file diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignFileDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignFileDTO.java new file mode 100644 index 000000000..852f91181 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignFileDTO.java @@ -0,0 +1,60 @@ +package cn.axzo.workflow.common.model.dto; + +import cn.axzo.workflow.common.enums.FileTypeEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 签署文件记录信息 + * + * @author wangli + * @since 2025-04-03 11:21 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SignFileDTO implements Serializable { + + private static final long serialVersionUID = -8709597975507074853L; + /** + * 文件模板主键 ID + */ + private Long id; + + /** + * 文件名称,可能会包含变量 + */ + private String fileName; + + /** + * 模板名称 + */ + private String templateName; + + /** + * 文件的标签 + */ + private String fileTag; + + /** + * 文件 code + */ + private String fileCode; + + /** + * 文件的类型 + */ + private FileTypeEnum fileType; + + /** + * 替换变量后的文件 fileKey + */ + private String fileKey; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignatureDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignatureDTO.java new file mode 100644 index 000000000..183722818 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SignatureDTO.java @@ -0,0 +1,45 @@ +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; +import java.util.List; + +/** + * 节点的电子签名数据 + * + * @author wangli + * @since 2025-01-15 20:11 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SignatureDTO implements Serializable { + private static final long serialVersionUID = 1L; + /** + * 节点 ID + */ + private String activityId; + /** + * 节点名称 + */ + private String activityName; + /** + * 签名图片数据有序集合 + */ + private List signatures; + + @Data + @Accessors(chain = true) + public static class SignDetail implements Serializable { + private static final long serialVersionUID = 1L; + private String signature; + private String advice; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SimpleDocDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SimpleDocDTO.java new file mode 100644 index 000000000..0210c2a47 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SimpleDocDTO.java @@ -0,0 +1,37 @@ +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 2025-04-08 13:59 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SimpleDocDTO implements Serializable { + /** + * 关联的文档 ID + */ + private Long id; + + /** + * 关联的文档业务标签 + */ + private String tag; + + /** + * 阅读状态: true 已读 + */ + private Boolean readStatus; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SimpleTaskDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SimpleTaskDTO.java new file mode 100644 index 000000000..0b2ea2462 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/SimpleTaskDTO.java @@ -0,0 +1,26 @@ +package cn.axzo.workflow.common.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 简单的任务模型 + * + * @author wangli + * @since 2024-12-09 14:30 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SimpleTaskDTO { + /** + * 流程实例 ID + */ + private String processInstanceId; + + /** + * 任务 ID + */ + private String taskId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/TermNodeAddTimerJobDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/TermNodeAddTimerJobDTO.java new file mode 100644 index 000000000..d075b7194 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/TermNodeAddTimerJobDTO.java @@ -0,0 +1,26 @@ +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 2025-03-19 16:33 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TermNodeAddTimerJobDTO implements Serializable { + private String processInstanceId; + private String activityId; + private String timeCycle; +} 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/dto/UploadFieldDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/UploadFieldDTO.java new file mode 100644 index 000000000..1eeede655 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/UploadFieldDTO.java @@ -0,0 +1,38 @@ +package cn.axzo.workflow.common.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 上传附件字段类型 + * + * @author wangli + * @since 2024-11-22 14:48 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class UploadFieldDTO implements Serializable { + + private static final long serialVersionUID = -1L; + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件类型 + */ + private String fileUrl; + + /** + * 文件 oss 的 key + */ + private String fileKey; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/VariableObjectDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/VariableObjectDTO.java new file mode 100644 index 000000000..4523937f7 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/VariableObjectDTO.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.common.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 从流程实例中获取变量,携带数据类型的对象 + * + * @author wangli + * @since 2025-04-09 11:46 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class VariableObjectDTO { + + public enum Type { + img, text, obj, signatureAndAdvice + } + + /** + * 变量 key + */ + private String key; + + /** + * 变量中文名 + */ + private String desc; + + /** + * 变量值 + */ + private Object value; + + /** + * 变量类型 + */ + @Builder.Default + private Type type = Type.text; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/es/DataSyncSummaryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/es/DataSyncSummaryDTO.java new file mode 100644 index 000000000..39a18fb86 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/es/DataSyncSummaryDTO.java @@ -0,0 +1,25 @@ +package cn.axzo.workflow.common.model.dto.es; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 数据同步 ES 的结果统计 + * + * @author wangli + * @since 2024-09-30 14:42 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class DataSyncSummaryDTO { + + private Long processInstanceCount; + + private Long processTaskCount; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/es/HistoricProcessInstanceSearchForEsDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/es/HistoricProcessInstanceSearchForEsDTO.java new file mode 100644 index 000000000..d0e0cc31d --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/es/HistoricProcessInstanceSearchForEsDTO.java @@ -0,0 +1,100 @@ +package cn.axzo.workflow.common.model.dto.es; + +import cn.axzo.workflow.common.enums.TimeQueryDirection; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.Date; + +/** + * 历史流程实例的搜索对象 + * + * @author wangli + * @since 2024-09-27 14:03 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class HistoricProcessInstanceSearchForEsDTO { + + /** + * 流程实例 ID + */ + private String processInstanceId; + + /** + * 业务传参关键 + */ + private String businessKey; + + /** + * 流程定义 KEY + */ + private String processDefinitionKey; + + /** + * 流程实例名称 + */ + private String processInstanceName; + + /** + * 流程实例状态 + */ + private String businessStatus; + + /** + * 租户 ID + */ + private String tenantId; + + /** + * 实例是否已结束 + * + */ + private Boolean finished; + + /** + * 开始时间 + */ + private Date startTime; + + /** + * 控制查询开始时间的方向,包含自身的时间点 + *

+ * 默认是查询开始时间之后 + */ + @Builder.Default + private TimeQueryDirection startTimeDirection = TimeQueryDirection.AFTER; + + /** + * 结束时间 + */ + private Date endTime; + + /** + * 控制查询结束时间的方向,包含自身的时间点 + *

+ * 默认是查询结束时间点之前 + */ + @Builder.Default + private TimeQueryDirection endTimeDirection = TimeQueryDirection.BEFORE; + + /** + * 是否包含流程变量 + */ + @Builder.Default + private Boolean hasVariables = false; + + /** + * 用于覆盖同步逻辑中的PageSize,一般不需要传 + */ + @Builder.Default + private Integer overPageSize = 50; + +} + diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/print/FieldAttributeDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/print/FieldAttributeDTO.java new file mode 100644 index 000000000..0c1052145 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/print/FieldAttributeDTO.java @@ -0,0 +1,31 @@ +package cn.axzo.workflow.common.model.dto.print; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 字段属性 + * + * @author wangli + * @since 2025-01-16 17:31 + */ +@Data +@ApiModel("打印时的字段的扩展属性模型") +@NoArgsConstructor +@Accessors(chain = true) +public class FieldAttributeDTO { + + /** + * 属性 code + */ + private String code; + + /** + * 属性名称 + */ + private String name; + + private String fieldFormType; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/print/PrintFieldDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/print/PrintFieldDTO.java new file mode 100644 index 000000000..078b9c544 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/print/PrintFieldDTO.java @@ -0,0 +1,46 @@ +package cn.axzo.workflow.common.model.dto.print; + +import cn.axzo.workflow.common.enums.PrintFieldCategoryEnum; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 打印时的字段模型 + * + * @author wangli + * @since 2025-01-16 17:09 + */ +@ApiModel("打印时的字段模型") +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class PrintFieldDTO { + /** + * 字段中文名称 + */ + private String name; + /** + * 字段类型(表单字段、系统字段) + */ + private PrintFieldCategoryEnum fieldCategoryType; + + /** + * 用于替换是变量名 + */ + private String code; + + /** + * 字段的表单的类型 + */ + private String fieldFormType; + + /** + * 复杂组件的扩展字段,例如“自定义组件”,“审批日志”组件等 + */ + private List attributes; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmPageParam.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmPageParam.java index 91a566e5c..640809192 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmPageParam.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmPageParam.java @@ -10,6 +10,7 @@ import java.io.Serializable; @ApiModel("分页参数") public class BpmPageParam implements Serializable { + private static final long serialVersionUID = -7002013785995706695L; private static final Integer PAGE_NO = 1; private static final Integer PAGE_SIZE = 10; @ApiModelProperty(value = "页码,从 1 开始", required = true, example = "1") 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 new file mode 100644 index 000000000..ed9af9805 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/BpmnApproveConf.java @@ -0,0 +1,42 @@ +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.experimental.Accessors; + +import javax.validation.Valid; + +@ApiModel("JSON 版本的 BPMN 协议模型中流程配置管理") +@Data +@AllArgsConstructor +@Accessors(chain = true) +public class BpmnApproveConf { + + public BpmnApproveConf() { + this.supportBatchOperation = false; + this.userAgreeSignature = false; + } + + /** + * 是否支持批量审批 + */ + @ApiModelProperty(value = "是否支持批量审批") + @Valid + private Boolean supportBatchOperation; + + /** + * 审批同意录入手写签名 + */ + @ApiModelProperty(value = "审批同意录入手写签名") + @Valid + private Boolean userAgreeSignature; + + /** + * 审批自动过审配置 + */ + @ApiModelProperty(value = "同一审批人自动过审类型,默认不自动过审,枚举类型为'NO_AUTO_APPROVAL'") + private AutoApprovalTypeEnum autoApprovalType = AutoApprovalTypeEnum.NO_AUTO_APPROVAL; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminCreateDTO.java new file mode 100644 index 000000000..3d9816623 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminCreateDTO.java @@ -0,0 +1,82 @@ +package cn.axzo.workflow.common.model.request.admin; + +import cn.axzo.workflow.common.enums.AdminDataSource; +import cn.axzo.workflow.common.enums.AdminRoleType; +import cn.axzo.workflow.common.enums.AdminTypeEnum; +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.NotNull; + +/** + * 新增管理员模型 + */ +@ApiModel("新增管理员模型") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ProcessAdminCreateDTO { + + /** + * 自然人id + */ + @ApiModelProperty(value = "自然人id") + @NotNull(message = "人员不能为空") + private Long personId; + + /** + * 单位id + */ + @ApiModelProperty(value = "单位id") + @NotNull(message = "单位id不能为空") + private Long organizationalUnitId; + + /** + * 工作台ID + */ + @ApiModelProperty(value = "工作台id") + @NotNull(message = "工作台id不能为空") + private Long workspaceId; + + /** + * 工作台类型,1-企业, 2-项目, 3-政务监管平台, 6-oms工作台,参考 WorkspaceType枚举 + */ + @ApiModelProperty(value = "工作台类型") + @NotNull(message = "工作台类型不能为空") + private Integer workspaceType; + + /** + * 管理员类型, SUPER_ADMIN-超级管理员, COMMON_ADMIN-普通管理员 + */ + @ApiModelProperty(value = "管理员类型, super_admin-超级管理员, common_admin-普通管理员") + private AdminTypeEnum adminType; + + /** + * 角色类型, ORGANIZATION_ADMIN-单位超管, ORG_WORKSPACE_ADMIN-项目内单位负责人, WORKSPACE_ADMIN-项目超管,OTHER-其他用户 + */ + @ApiModelProperty(value = "角色类型, organization_admin-单位超管, organization_workspace_admin-项目内单位负责人, workspace_admin-项目超管,other-其他用户") + private AdminRoleType roleType; + + /** + * 数据来源, SYSTEM_ENTRY-系统录入, USER_ENTRY-用户手动录入 + */ + @ApiModelProperty(value = "数据来源, system-系统录入, user-用户手动录入") + private AdminDataSource dataSource; + + /** + * 创建者 + */ + @ApiModelProperty(value = "创建者") + private Long createBy; + + /** + * 更新者 + */ + @ApiModelProperty(value = "更新者") + private Long updateBy; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminDeleteDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminDeleteDTO.java new file mode 100644 index 000000000..5479dd9a9 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminDeleteDTO.java @@ -0,0 +1,68 @@ +package cn.axzo.workflow.common.model.request.admin; + +import cn.axzo.workflow.common.enums.AdminDataSource; +import cn.axzo.workflow.common.enums.AdminTypeEnum; +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.List; + +@ApiModel("删除管理员模型") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProcessAdminDeleteDTO { + + /** + * 流程管理员配置id列表 + */ + @ApiModelProperty(value = "流程管理员配置id列表") + private List processAdminIds; + + /** + * 自然人id + */ + @ApiModelProperty(value = "自然人id列表") + private List personIds; + + /** + * 工作台ID + */ + @ApiModelProperty(value = "工作台id") + private Long workspaceId; + + /** + * 工作台id列表 + */ + @ApiModelProperty(value = "工作台id列表") + private List workspaceIds; + + /** + * 单位id + */ + @ApiModelProperty(value = "单位id") + private Long organizationalUnitId; + + /** + * 单位id列表 + */ + @ApiModelProperty(value = "单位id列表") + private List organizationalUnitIds; + + /** + * 数据来源, SYSTEM_ENTRY-系统录入, USER_ENTRY-用户手动录入 + */ + @ApiModelProperty(value = "数据来源") + private AdminDataSource dataSource; + + /** + * 管理员类型 + */ + @ApiModelProperty(value = "管理员类型") + private AdminTypeEnum adminType; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminQueryDTO.java new file mode 100644 index 000000000..85a3ebe06 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/admin/ProcessAdminQueryDTO.java @@ -0,0 +1,59 @@ +package cn.axzo.workflow.common.model.request.admin; + +import cn.axzo.workflow.common.enums.AdminDataSource; +import cn.axzo.workflow.common.enums.AdminTypeEnum; +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.List; + +/** + * 查询管理员模型 + */ +@ApiModel("查询管理员模型") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProcessAdminQueryDTO { + + /** + * 流程管理员配置id列表 + */ + @ApiModelProperty(value = "流程管理员配置id列表") + private List processAdminIds; + + /** + * 自然人id + */ + @ApiModelProperty(value = "自然人id列表") + private List personIds; + + /** + * 工作台ID + */ + @ApiModelProperty(value = "工作台id") + private Long workspaceId; + + /** + * 单位id + */ + @ApiModelProperty(value = "单位id") + private Long organizationalUnitId; + + /** + * 数据来源, system-系统录入, user-用户手动录入 + */ + @ApiModelProperty(value = "数据来源") + private AdminDataSource dataSource; + + /** + * 管理员类型 + */ + @ApiModelProperty(value = "管理员类型") + private AdminTypeEnum adminType; +} 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..99fbe54e0 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,7 +7,11 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; + +import static cn.axzo.workflow.common.enums.BpmnButtonEnum.BPMN_UPGRADE; /** * 流程定义中的按钮配置 @@ -25,25 +29,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; @@ -77,4 +81,11 @@ public class BpmnButtonConf implements Serializable { this.carbonCopy = carbonCopy; } + public void removeUpGradeButton() { + // 移除升级按钮 + this.initiator.removeIf(button -> Objects.equals(BPMN_UPGRADE.getBtnKey(), button.getType())); + this.current.removeIf(button -> Objects.equals(BPMN_UPGRADE.getBtnKey(), button.getType())); + this.history.removeIf(button -> Objects.equals(BPMN_UPGRADE.getBtnKey(), button.getType())); + this.carbonCopy.removeIf(button -> Objects.equals(BPMN_UPGRADE.getBtnKey(), button.getType())); + } } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnButtonMetaInfo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnButtonMetaInfo.java index b0fe32358..579f99a21 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnButtonMetaInfo.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnButtonMetaInfo.java @@ -1,9 +1,12 @@ package cn.axzo.workflow.common.model.request.bpmn; +import cn.axzo.workflow.common.enums.BusinessTypeEnum; import lombok.Data; import lombok.experimental.Accessors; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * 按钮元数据 @@ -15,10 +18,15 @@ import java.io.Serializable; @Accessors(chain = true) public class BpmnButtonMetaInfo implements Serializable { + public static final String BUTTON_TYPE_SYSTEM = "SYSTEM"; + public static final String BUTTON_TYPE_CUSTOM = "CUSTOM"; + + private static final long serialVersionUID = -5224316166904752829L; + /** * 按钮顺序 */ - private Integer order; + private Integer order = 0; /** * 按钮唯一标识 @@ -39,10 +47,19 @@ public class BpmnButtonMetaInfo implements Serializable { * 是否禁用勾选 */ private Boolean disabled; + /** + * 是否隐藏按钮 + */ + private Boolean hidden = false; /** * 按钮类型 SYSTEM/CUSTOM */ private String type; + /** + * 按钮支持的业务类型 + */ + private List supportBizType = new ArrayList<>(); + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonModel.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonModel.java index 7ba66fe4a..0c511be70 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonModel.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonModel.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.common.model.request.bpmn; +import cn.axzo.workflow.common.model.request.BpmnApproveConf; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -26,6 +27,11 @@ public class BpmnJsonModel implements Serializable { @ApiModelProperty(value = "流程的 Json 结构") private BpmnJsonNode node; + /** + * 签署配置, 可为空,只有签署业务才有该配置 + */ + @ApiModelProperty(value = "签署配置") + private BpmnSignConf signConf; /** * 通知管理配置 */ @@ -46,4 +52,11 @@ public class BpmnJsonModel implements Serializable { @ApiModelProperty(value = "流程字段配置") @Valid private List fieldConf; + + /** + * 审批相关高级配置 + */ + @ApiModelProperty(value = "审批相关高级配置") + @Valid + private BpmnApproveConf approveConf; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonNodeProperty.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonNodeProperty.java index 47e5b1e50..28c72e325 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonNodeProperty.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnJsonNodeProperty.java @@ -5,6 +5,7 @@ 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.enums.BpmnFlowNodeMode; +import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -33,9 +34,9 @@ public class BpmnJsonNodeProperty { //************* 审批人所在范围Start **************// /** - * 审批人所在范围: entWorkspace(企业工作台), projectWorkspace(项目工作台), preTaskUser(上节点审批人所在单位) + * 审批人所在范围: entWorkspace(企业工作台), govWorkspace(政务工作台), projectWorkspace(项目工作台), preTaskUser(上节点审批人所在单位) */ - @ApiModelProperty(value = "任务节点: 审批人所在范围", notes = "entWorkspace: 企业工作台, projectWorkspace: 项目工作台, preTaskUser:" + + @ApiModelProperty(value = "任务节点: 审批人所在范围", notes = "entWorkspace: 企业工作台, govWorkspace: 政务工作台, projectWorkspace: 项目工作台, preTaskUser:" + " 上节点审批人所在单位") private ApproverScopeEnum approverScope; //************* 审批人所在范围End **************// @@ -44,13 +45,19 @@ public class BpmnJsonNodeProperty { //************* 审批人指定Start **************// /** * 审批人指定: position(指定岗位), role(指定角色), identity(指定身份), initiatorLeader(发起人主管), initiatorLeaderRecursion(发起人多级主管), - * fixedPerson(固定人员) + * fixedPerson(固定人员) initiatorSpecified(发起人自选) signerRelated(签署人相关组织) */ @ApiModelProperty(value = "任务节点: 审批人指定", notes = "position: 指定岗位, role: 指定角色, identity: 指定身份, initiatorLeader: " + - "发起人主管, initiatorLeaderRecursion: 发起人多级主管, fixedPerson: 固定人员") + "发起人主管, initiatorLeaderRecursion: 发起人多级主管, fixedPerson: 固定人员, initiatorSpecified: 发起人自选, signerRelated: 签署人相关组织") @NotBlank(message = "审批人指定不能为空") private ApproverSpecifyEnum approverSpecify; + /** + * 签署确认节点的审批人限定规则 + */ + @ApiModelProperty(value = "签署确认节点的审批人限定规则") + private BpmnSignApproverLimit signApproverLimit; + /** * 具体的配置值 *

@@ -93,12 +100,17 @@ public class BpmnJsonNodeProperty { private String emptyApproverSpecify; //************* 审批人为空时的策略End **************// + /** + * 电子签名开关 + */ + @ApiModelProperty(value = "电子签名") + private Boolean signature; /** - * 表单字段权限, JSON 格式,按照 UI 进行自定义组装, 引擎不做任何解析, 之后的需求会让业务方前端开发来消费这里的配置 + * 提级审批配置 */ - @ApiModelProperty(value = "发起人节点/任务节点: 字段权限集合", notes = "后端不做任何解析, 前端给什么样,就返什么样") - private String fieldPermission; + @ApiModelProperty(value = "提级审批配置") + private BpmnUpgradeApprovalConf upgradeApprovalConf; /** * 按钮权限 @@ -142,8 +154,9 @@ public class BpmnJsonNodeProperty { //************* 抄送节点Start **************// /** - * 发起时使用的表单 key + * 表单字段权限配置 */ - @ApiModelProperty(value = "发起时使用的表单 key") - private String formKey; + @ApiModelProperty(value = "表单字段权限控制") + private List fieldPermission; + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeConf.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeConf.java index 0cb7b2db1..6e4e7d15c 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeConf.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeConf.java @@ -26,7 +26,7 @@ public class BpmnNoticeConf implements Serializable { */ @Valid // @NotNull(message = "消息模板不能为空") - private BpmnNoticeProperty notice; + private BpmnNoticeProperty notice = new BpmnNoticeProperty(); /** * 待办的配置 diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeProperty.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeProperty.java index 2e6b89ba1..1d79ad515 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeProperty.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeProperty.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.common.model.request.bpmn; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -7,6 +8,8 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * 站内信 @@ -21,15 +24,49 @@ import java.io.Serializable; public class BpmnNoticeProperty implements Serializable { /** - * 通知消息模板 ID + * 通知的消息模板,该属性目前是由后端进行配置,前端暂可不关心 */ - @ApiModelProperty(value = "通知消息模板 ID") - // @NotBlank(message = "通知消息模板 ID 不能为空") + @ApiModelProperty(value = "通知的消息模板", hidden = true) private String noticeMessageId; + /** + * 是否发送消息通知 + */ + @ApiModelProperty(value = "是否发送消息通知") + private Boolean sendMessage; /** - * 用于前端回显数据, 服务端不解析 + * 消息触发时机列表 */ - @ApiModelProperty(value = "用于前端回显数据, 服务端不解析") - private String viewJson; + @ApiModelProperty(value = "触发时机列表") + private List triggerEvents = new ArrayList<>(); + + /** + * 消息模版发起人配置 + */ + @ApiModelProperty(value = "消息模版发起人配置") + private BpmnNoticeReceiver initiator = new BpmnNoticeReceiver(); + + /** + * 消息历史审批配置 + */ + @ApiModelProperty(value = "消息模版历史审批人配置") + private BpmnNoticeReceiver histories = new BpmnNoticeReceiver(); + + /** + * 消息模版指定岗位配置 + */ + @ApiModelProperty(value = "消息模版指定岗位配置") + private BpmnNoticeReceiver positions = new BpmnNoticeReceiver(); + + /** + * 消息模版指定角色配置 + */ + @ApiModelProperty(value = "消息模版指定角色配置") + private BpmnNoticeReceiver roles = new BpmnNoticeReceiver(); + + /** + * 消息模板指定人员 + */ + @ApiModelProperty(value = "消息模板指定人员") + private BpmnNoticeReceiver assigners = new BpmnNoticeReceiver(); } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeReceiver.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeReceiver.java new file mode 100644 index 000000000..e15aadaaf --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnNoticeReceiver.java @@ -0,0 +1,26 @@ +package cn.axzo.workflow.common.model.request.bpmn; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@ApiModel("JSON 版本的 BPMN 协议模型中的通知管理的站内信消息接收人配置") +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class BpmnNoticeReceiver { + + /** + * 是否选中 + */ + @ApiModelProperty(value = "是否选中") + private Boolean selected = false; + + /** + * 接收人内容,发起人,历史审批人不设置该值 + */ + @ApiModelProperty(value = "接收人内容,发起人,历史审批人不设置该值") + private String viewJson; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnProcessVariable.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnProcessVariable.java new file mode 100644 index 000000000..149602ce8 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnProcessVariable.java @@ -0,0 +1,35 @@ +package cn.axzo.workflow.common.model.request.bpmn; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 流程变量模型 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class BpmnProcessVariable { + + @ApiModelProperty(value = "流程变量名称") + @NotBlank(message = "流程变量名称不能为空") + private String name; + +// @ApiModelProperty(value = "流程变量类型") +// @NotBlank(message = "流程变量类型不能为空") +// private String type; + + @ApiModelProperty(value = "流程变量值") + @NotNull(message = "流程变量值不能为空") + private Object value; + + private String valueUrl; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignApproverLimit.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignApproverLimit.java new file mode 100644 index 000000000..d49918167 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignApproverLimit.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.common.model.request.bpmn; + +import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import cn.axzo.workflow.common.enums.SignApproverRoleLimitEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * JSON 版本的 BPMN 协议模型中的确认人限定规则 + * + * @author wangli + * @since 2025-03-26 20:05 + */ +@ApiModel("JSON 版本的 BPMN 协议模型中的确认人限定规则") +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class BpmnSignApproverLimit { + + /** + * 签署确认节点下人的组织限定规则 + *

+ * LV_0("LV_0", "当前组织"), + * LV_1("LV_1", "上1级组织"), + * LV_2("LV_2", "上2级组织"), + * LV_3("LV_3", "上3级组织"), + * LV_4("LV_4", "上4级组织"), + * LV_5("LV_5", "上5级组织"), + */ + @ApiModelProperty(value = "签署确认节点下人的组织限定规则") + private SignApproverOrgLimitEnum orgLimit; + + /** + * 签署确认节点下人的角色限定规则 + *

+ * INITIATOR_SPECIFIED("INITIATOR_SPECIFIED", "发起人自选"), + * LEADER("LEADER", "负责人"), + */ + @ApiModelProperty(value = "签署确认节点下人的角色限定规则") + private SignApproverRoleLimitEnum roleLimit; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignConf.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignConf.java new file mode 100644 index 000000000..f88d150ba --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignConf.java @@ -0,0 +1,40 @@ +package cn.axzo.workflow.common.model.request.bpmn; + +import cn.axzo.workflow.common.enums.BpmnSignType; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 流程定义中的签署配置 + * + * @author wangli + * @since 2025-03-25 16:56 + */ +@ApiModel("JSON 版本的 BPMN 协议模型中的签署管理") +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class BpmnSignConf implements Serializable { + + private static final long serialVersionUID = -6015492451455020117L; + + /** + * 签署方式 + *

+ * SINGLE("SINGLE", "指定人群,所有人共同签署一份文件"), + * MULTI("MULTI", "指定人群,每人签署一份文件"), + */ + @ApiModelProperty(value = "签署方式") + private BpmnSignType signType; + + /** + * 签署业务待办模板 + */ + @ApiModelProperty(value = "签署业务待办模板") + private BpmnSignPendingProperty signPendingProperty; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignPendingProperty.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignPendingProperty.java new file mode 100644 index 000000000..89da2289b --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnSignPendingProperty.java @@ -0,0 +1,37 @@ +package cn.axzo.workflow.common.model.request.bpmn; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * JSON 版本的 BPMN 协议模型中的签署的待办 + * + * @author wangli + * @since 2025-03-25 17:09 + */ +@ApiModel("JSON 版本的 BPMN 协议模型中的签署的待办") +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class BpmnSignPendingProperty implements Serializable { + private static final long serialVersionUID = -6015492451455020117L; + + /** + * 待办消息模板 ID + */ + @ApiModelProperty(value = "待办消息模板 ID") + @NotBlank(message = "待办消息模板 ID 不能为空") + private String pendingMessageId; + + /** + * 用于前端回显数据, 服务端不解析 + */ + @ApiModelProperty(value = "用于前端回显数据, 服务端不解析") + private String viewJson; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnUpgradeApprovalConf.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnUpgradeApprovalConf.java new file mode 100644 index 000000000..cda97deaa --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/BpmnUpgradeApprovalConf.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.common.model.request.bpmn; + +import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; +import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * JSON 版本的 BPMN 协议模型中的提级审批配置模型 + * + * @author wangli + * @since 2025-06-20 11:32 + */ +@ApiModel("JSON 版本的 BPMN 协议模型中的提级审批配置模型") +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class BpmnUpgradeApprovalConf implements Serializable { + private static final long serialVersionUID = 36282987684860776L; + + /** + * 是否已开启 + */ + private Boolean enabled; + + /** + * 提级审批的审批人层级范围限制 + */ + private SignApproverOrgLimitEnum orgLimit; + + /** + * 提级审批的审批人指定方式 + */ + private ApproverSpecifyEnum approverSpecify; + + /** + * 提级审批的审批人指定具体值 + */ + private String specifyValue; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/RestBpmnProcessVariable.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/RestBpmnProcessVariable.java new file mode 100644 index 000000000..615330700 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/RestBpmnProcessVariable.java @@ -0,0 +1,33 @@ +package cn.axzo.workflow.common.model.request.bpmn; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class RestBpmnProcessVariable extends BpmnProcessVariable { + + public enum RestVariableScope { + LOCAL, GLOBAL + } + + /** + * 变量作用域, 目前支持 LOCAL(局部变量) 和 GLOBAL(全局变量) + */ + private RestVariableScope scope = RestVariableScope.GLOBAL; + + public String getVariableScope() { + String scope = null; + if (this.scope != null) { + scope = this.scope.name().toLowerCase(); + } + return scope; + } + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/activity/BpmnActivityTimeoutCallbackDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/activity/BpmnActivityTimeoutCallbackDTO.java new file mode 100644 index 000000000..93643e5f7 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/activity/BpmnActivityTimeoutCallbackDTO.java @@ -0,0 +1,41 @@ +package cn.axzo.workflow.common.model.request.bpmn.activity; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import java.util.Map; + +/** + * 为指定业务节点设置倒计时回调 + * + * @author wangli + * @since 2024-08-16 15:30 + */ +@ApiModel("为指定业务节点设置倒计时回调") +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BpmnActivityTimeoutCallbackDTO { + + /** + * 业务节点的触发 ID + */ + @NotBlank(message = "触发 ID 不能为空") + private String triggerId; + + /** + * 触发往下流转的时间点, 格式:yyyy-MM-dd HH:mm:ss + *

+ * 应该为 Date 类型,但为了确保特殊情况需要通过 pod 内直接触发 + */ + @NotBlank(message = "触发时间不能为空,格式:yyyy-MM-dd HH:mm:ss") + private String endTime; + + /** + * 用于追加或修改现有的变量值 + */ + private Map variables; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/activity/BpmnActivityTimeoutTriggerDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/activity/BpmnActivityTimeoutTriggerDTO.java new file mode 100644 index 000000000..9b1e4ec62 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/activity/BpmnActivityTimeoutTriggerDTO.java @@ -0,0 +1,41 @@ +package cn.axzo.workflow.common.model.request.bpmn.activity; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import java.util.Map; + +/** + * 为指定业务节点设置倒计时,在啥时候继续往下流转 + * + * @author wangli + * @since 2024-08-16 15:30 + */ +@ApiModel("为指定业务节点设置倒计时") +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BpmnActivityTimeoutTriggerDTO { + /** + * 业务节点的触发 ID + */ + @NotBlank(message = "触发 ID 不能为空") + private String triggerId; + + /** + * 触发往下流转的时间点, 格式:yyyy-MM-dd HH:mm:ss + *

+ * 应该为 Date 类型,但为了确保特殊情况需要通过 pod 内直接触发 + */ + @NotBlank(message = "触发时间不能为空,格式:yyyy-MM-dd HH:mm:ss") + private String endTime; + + /** + * 用于追加或修改现有的变量值(暂不支持) + */ + @Deprecated + private Map variables; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/definition/BpmnProcessDefinitionUpdateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/definition/BpmnProcessDefinitionUpdateDTO.java index 37bb0011b..63bdb547c 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/definition/BpmnProcessDefinitionUpdateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/definition/BpmnProcessDefinitionUpdateDTO.java @@ -47,6 +47,12 @@ public class BpmnProcessDefinitionUpdateDTO { @ApiModelProperty(value = "描述") private String description; + /** + * 表单 KEY + */ + @ApiModelProperty(value = "表单 KEY") + private String formKey; + /** * 模型定义内容 */ diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/log/LogApproveSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/log/LogApproveSearchDTO.java new file mode 100644 index 000000000..288946c7a --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/log/LogApproveSearchDTO.java @@ -0,0 +1,57 @@ +package cn.axzo.workflow.common.model.request.bpmn.log; + +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + * 用于查询审批日志的人的模型 + * + * @author wangli + * @since 2025-07-07 19:40 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class LogApproveSearchDTO { + + @ApiModelProperty("流程实例 ID") + @NotBlank(message = "流程实例 ID 不能为空") + private String processInstanceId; + + /** + * 审批任务 ID,如果有值,优先使用 taskId 查询 + *

+ * 如果没有值,则 personId、tenantId、ouId 一定不能为 null + */ + @ApiModelProperty("审批任务 ID") + private String taskId; + + /** + * 如果没传 taskId,那么该属性必有值 + */ + @ApiModelProperty("审批人 PersonId") + private String personId; + + /** + * 如果没传 taskId,那么该属性必有值 + */ + @ApiModelProperty("审批人 tenantId") + private String tenantId; + + /** + * 如果没传 taskId,那么该属性应该有值,如果是“工人”可以没有值 + */ + @ApiModelProperty("审批人 ouId") + private String ouId; + + @ApiModelProperty("指定状态,如果为空,默认指定审批中状态的数据") + private BpmnProcessInstanceResultEnum status; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelCreateDTO.java index b0fbf827f..e2833ef2f 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelCreateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelCreateDTO.java @@ -1,6 +1,7 @@ package cn.axzo.workflow.common.model.request.bpmn.model; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel; +import cn.axzo.workflow.common.model.request.form.FormJsonModel; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -19,6 +20,8 @@ import java.io.Serializable; @Accessors(chain = true) public class BpmnModelCreateDTO implements Serializable { + private static final long serialVersionUID = 1383240088010310865L; + @ApiModelProperty(value = "流程模型标识", example = "process_key", hidden = true) @Length(max = 255, message = "流程标识最长只支持255个字符") private String key; @@ -31,19 +34,16 @@ public class BpmnModelCreateDTO implements Serializable { @Length(max = 32, message = "流程名称最长只支持32个字符") private String name; - /** - * 自定义分类 - */ - @ApiModelProperty(value = "自定义分类", notes = "由业务自定义") - @NotBlank(message = "自定义分类不能为空") - private String category; - /** * 描述 */ @ApiModelProperty(value = "描述", notes = "存放与底层模型中 meta_info 中") private String description; + @ApiModelProperty(value = "表单模型的 Json 结构") + @Valid + private FormJsonModel formJsonModel; + @ApiModelProperty(value = "流程模型的 Json 结构") @Valid private BpmnJsonModel jsonModel; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelSearchDTO.java index 6e6cf3264..4f5bef53a 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelSearchDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelSearchDTO.java @@ -20,7 +20,7 @@ public class BpmnModelSearchDTO extends BpmPageParam { * 标识 */ @ApiModelProperty(value = "流程模型标识", example = "process1641042089407", notes = "精准匹配") - private String key; + private List keys; /** * 名称 diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelUpdateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelUpdateDTO.java index f7826dd46..7e5e7d735 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelUpdateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/BpmnModelUpdateDTO.java @@ -16,6 +16,8 @@ import lombok.experimental.Accessors; @Accessors(chain = true) public class BpmnModelUpdateDTO extends BpmnModelCreateDTO { + private static final long serialVersionUID = 3661214238064751897L; + @ApiModelProperty(value = "流程模型 ID", required = true) private String processModelId; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocByIdDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocByIdDTO.java new file mode 100644 index 000000000..c1603a14c --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocByIdDTO.java @@ -0,0 +1,27 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 获取文档 + * + * @author wangli + * @since 2025-04-16 20:18 + */ +@ApiModel("获取文档") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DocByIdDTO { + + private List ids; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocCloneDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocCloneDTO.java new file mode 100644 index 000000000..0ba48428d --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocCloneDTO.java @@ -0,0 +1,40 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 克隆文档 + * + * @author wangli + * @since 2025-04-18 11:30 + */ +@ApiModel("克隆文档") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DocCloneDTO { + /** + * 文档 ID + */ + private Long docId; + /** + * 克隆好的文档归属模型 ID + */ + private String targetModelId; + /** + * 克隆好的文档归属租户 + */ + private String targetTenantId; + + /** + * 克隆好的文档的 tag + */ + private String targetFileTag; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocCreateDTO.java new file mode 100644 index 000000000..ad264818b --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocCreateDTO.java @@ -0,0 +1,121 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import cn.axzo.workflow.common.enums.FileTypeEnum; +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 lombok.experimental.SuperBuilder; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 创建模型关联的文档 + * + * @author wangli + * @since 2025-03-27 14:50 + */ +@ApiModel("创建模型关联的文档") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class DocCreateDTO implements Serializable { + + /** + * 关联到的模型 ID + */ + @ApiModelProperty(value = "文档被关联到的模型 ID") + @NotBlank(message = "模型 ID 不能为空") + private String modelId; + + /** + * 模型对应业务 KEY + */ + @ApiModelProperty(value = "模型对应的业务 ID") + @NotBlank(message = "模型 KEY 不能为空") + private String modelKey; + /** + * 文档关联ID + *

+ * word/excel 对应 wps 的 Id, + * hp 对应是content 表的主键(前端新建时不回传) + * pdf 对应 oss 中的 fileKey + */ + @ApiModelProperty(value = "底层文件关联标识") + private String fileRelationId; + + /** + * 文件名称 + */ + @ApiModelProperty(value = "文件名称") + private String fileName; + + /** + * 模板名称 + */ + @ApiModelProperty(value = "模板名称") + private String templateName; + + /** + * 自动归档位置(本期不实现) + */ + private String location; + + /** + * 文档类型 + *

+ * WORD("word", "文本"), + * EXCEL("excel", "表格"), + * HIPRINT("hiprint", "智能文档"), + * PDF("pdf", "PDF"), + */ + @ApiModelProperty(value = "文件类型") + private FileTypeEnum fileType; + + /** + * 业务标签 + */ + @ApiModelProperty(value = "业务标签") + private String tag; + + /** + * 启用状态 + */ + @Builder.Default + @ApiModelProperty(value = "启用状态") + private Boolean status = true; + + /** + * 是否是临时文件 + */ + @Builder.Default + @ApiModelProperty(value = "是否是临时文件,默认 false") + private Boolean tempFile = false; + /** + * 是否必选 + */ + @Builder.Default + @ApiModelProperty(value = "是否必选") + private Boolean require = false; + + /** + * HiPrint文件类型,需要回传 + */ + @ApiModelProperty(value = "HiPrint文件类型,需要回传,其他类型无视该字段") + private String content; + + /** + * 租户 ID + */ + @ApiModelProperty(value = "租户 ID") + @Builder.Default + private String tenantId = NO_TENANT_ID; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocOrderDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocOrderDTO.java new file mode 100644 index 000000000..ae24e1c55 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocOrderDTO.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import cn.axzo.workflow.common.enums.OrderEnum; +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; + +/** + * 模型关联的文档排序 + * + * @author wangli + * @since 2025-03-31 16:32 + */ +@ApiModel("模型关联的文档排序") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class DocOrderDTO implements Serializable { + private static final long serialVersionUID = -4783444148609103421L; + /** + * 文档主键 ID + */ + private Long id; + + /** + * 操作顺序 + */ + private OrderEnum order; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocQueryDTO.java new file mode 100644 index 000000000..a709c868a --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocQueryDTO.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 流程关联文档搜索入参模型 + * + * @author wangli + * @since 2025-03-31 09:46 + */ +@ApiModel("流程关联文档搜索入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DocQueryDTO { + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例 ID") + private String processInstanceId; + + /** + * 流程定义 KEY(业务 ID) + */ + @ApiModelProperty(value = "流程定义 KEY(业务 ID)") + private String processDefinitionKey; + + /** + * 租户 ID,对应工作台 ID + */ + @ApiModelProperty(value = "租户 ID,对应工作台 ID") + private String tenantId; + + @ApiModelProperty(value = "是否包含临时文档") + @Builder.Default + private Boolean tempFile = false; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocResetDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocResetDTO.java new file mode 100644 index 000000000..08b571111 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocResetDTO.java @@ -0,0 +1,29 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@ApiModel("流程关联文档重置入参模型") +@Data +public class DocResetDTO { + + /** + * 业务 ID + */ + @NotBlank(message = "业务 ID 不能为空") + private String category; + + /** + * 代运营模型 ID + */ + @NotBlank(message = "模型 ID 不能为空") + private String modelId; + + /** + * 代运营的单位或租户ID + */ + @NotBlank(message = "工作台 ID 不能为空") + private String workspaceId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocSearchDTO.java new file mode 100644 index 000000000..8eece5314 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocSearchDTO.java @@ -0,0 +1,34 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import cn.axzo.workflow.common.model.request.BpmPageParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 流程关联文档搜索入参模型 + * + * @author wangli + * @since 2025-03-31 09:46 + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel("流程关联文档搜索入参模型") +@Data +public class DocSearchDTO extends BpmPageParam { + + private static final long serialVersionUID = -308388315001754954L; + /** + * 流程模型 ID + */ + @ApiModelProperty(value = "模型 ID") + private String modelId; + + @ApiModelProperty(value = "租户 ID") + private String tenantId = NO_TENANT_ID; + + @ApiModelProperty(value = "是否包含临时文档") + private Boolean tempFile = false; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocStatusDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocStatusDTO.java new file mode 100644 index 000000000..ba091cf13 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocStatusDTO.java @@ -0,0 +1,32 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 流程关联文档状态变更入参模型 + * + * @author wangli + * @since 2025-04-07 11:44 + */ +@ApiModel("流程关联文档状态变更入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DocStatusDTO { + /** + * 文档 ID + */ + @ApiModelProperty(value = "文档 ID") + private Long id; + + /** + * 状态 true 启用, false 停用 + */ + private Boolean status; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocTenantQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocTenantQueryDTO.java new file mode 100644 index 000000000..3a3cb16b7 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocTenantQueryDTO.java @@ -0,0 +1,30 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + * 查询指定业务下设置过关联文档的代运营项目集合入参 + * + * @author wangli + * @since 2025-04-10 17:57 + */ +@ApiModel("查询指定业务下设置过关联文档的代运营项目集合入参") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class DocTenantQueryDTO { + + /** + * 业务ID + */ + @NotBlank(message = "业务 ID 不能为空") + private String processDefinitionKey; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocUpdateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocUpdateDTO.java new file mode 100644 index 000000000..ef270fa99 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/model/doc/DocUpdateDTO.java @@ -0,0 +1,30 @@ +package cn.axzo.workflow.common.model.request.bpmn.model.doc; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; + +/** + * 创建模型关联的文档 + * + * @author wangli + * @since 2025-03-27 14:50 + */ +@ApiModel("创建模型关联的文档") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class DocUpdateDTO extends DocCreateDTO implements Serializable { + + @ApiModelProperty(value = "主键ID") + private Long id; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintFieldQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintFieldQueryDTO.java new file mode 100644 index 000000000..3775f63e5 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintFieldQueryDTO.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.common.model.request.bpmn.print; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + * 获取指定打印模板可打印的字段集合的查询入参模型 + * + * @author wangli + * @since 2025-01-16 17:42 + */ +@ApiModel("获取指定打印模板可打印的字段集合的查询入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PrintFieldQueryDTO { + + /** + * 模板定义 KEY + */ + @ApiModelProperty(value = "流程模板 KEY") + @NotBlank(message = "流程模板 KEY 不能为空") + private String processDefinitionKey; + + /** + * 租户 ID + */ + @ApiModelProperty(value = "租户 ID") + private String tenantId; + + /** + * 是否抛出内部异常 + */ + @ApiModelProperty(value = "是否报错内部异常") + @Builder.Default + private Boolean throwException = true; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintTemplateConfigQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintTemplateConfigQueryDTO.java new file mode 100644 index 000000000..c5e4503b6 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintTemplateConfigQueryDTO.java @@ -0,0 +1,48 @@ +package cn.axzo.workflow.common.model.request.bpmn.print; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 获取打印模板配置内容入参模型 + * + * @author wangli + * @since 2025-01-20 16:53 + */ +@ApiModel("获取打印模板配置内容入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PrintTemplateConfigQueryDTO { + + /** + * 审批模型 ID + */ + @ApiModelProperty(value = "审批模型 ID") + private String modelId; + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例 ID") + private String processInstanceId; + + /** + * 业务 ID + */ + @ApiModelProperty(value = "业务 ID") + private String processDefinitionKey; + + /** + * 租户 ID,配置 processDefinitionKey 使用 + */ + @ApiModelProperty(value = "租户 ID") + private String tenantId = NO_TENANT_ID; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintTemplateConfigUpsertDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintTemplateConfigUpsertDTO.java new file mode 100644 index 000000000..432d33d31 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/PrintTemplateConfigUpsertDTO.java @@ -0,0 +1,46 @@ +package cn.axzo.workflow.common.model.request.bpmn.print; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 打印模板配置内容入参模型 + * + * @author wangli + * @since 2025-01-20 16:44 + */ +@ApiModel("打印模板配置内容入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PrintTemplateConfigUpsertDTO { + + /** + * 审批模型 ID + */ + @ApiModelProperty("审批模型 ID") + private String modelId; + + /** + * 打印文件名称 + */ + @ApiModelProperty(value = "打印文件名称") + private String printFileName; + + /** + * 打印模板名称 + */ + @ApiModelProperty(value = "打印模板名称") + private String printTemplateName; + + /** + * 打印模板配置内容 + */ + @ApiModelProperty(value = "打印模板配置内容") + private String printTemplateConfig; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/RestPrintTemplateConfigDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/RestPrintTemplateConfigDTO.java new file mode 100644 index 000000000..d05f50c1e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/print/RestPrintTemplateConfigDTO.java @@ -0,0 +1,39 @@ +package cn.axzo.workflow.common.model.request.bpmn.print; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +/** + * 重置打印模型(代运营专用) + * + * @author wangli + * @since 2025-01-20 19:55 + */ +@ApiModel("打印模板配置内容入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RestPrintTemplateConfigDTO { + + /** + * 模板的定义 ID + */ + @ApiModelProperty(value = "模板的定义 ID") + @NotBlank(message = "模型定义 ID 不能为空") + private String processDefinitionKey; + + /** + * 代运营模型 ID + */ + @ApiModelProperty(value = "代运营模型 ID") + @NotBlank(message = "代运营模板 ID不能为空") + private String modelId; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BeforeProcessInstanceCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BeforeProcessInstanceCreateDTO.java new file mode 100644 index 000000000..f5e5a90f8 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BeforeProcessInstanceCreateDTO.java @@ -0,0 +1,68 @@ +package cn.axzo.workflow.common.model.request.bpmn.process; + +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; +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 javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +/** + * 创建审批流程前的入参模型 + * + * @author wangli + * @since 2025-01-15 10:21 + */ +@ApiModel("创建流程实例前的入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BeforeProcessInstanceCreateDTO { + /** + * 流程定义的标识 + *

+ * [对应 OMS 系统中审批业务的业务 ID] + */ + @ApiModelProperty(value = "业务 ID") + @NotEmpty(message = "流程定义的标识不能为空") + private String processDefinitionKey; + + /** + * 发起流程实例归属租户 ID + *

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

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

+ */ + @ApiModelProperty(value = "发起的审批是属于哪个租户") + @NotBlank(message = "工作台 ID 不能为空") + private String tenantId; + + /** + * 组织关系 + *

+ * 用于流程引擎计算对应节点的审批人, 例如第一个审批节点配置的是劳务分包的岗位,第二个审批节点配置的专业分包的角色, + * 那么, 组织关系就需要传入劳务分包的信息以及专业分包的信息,如果还有更多的审批节点配置,以此类推. + * 同时,该属性还会用于计算抄送人,以及消息通知的目标接收人 + */ + @ApiModelProperty(value = "组织关系") + private CooperationOrgDTO cooperationOrg; + + @ApiModelProperty(value = "发起人信息") + @Valid + private BpmnTaskDelegateAssigner initiator; + + /** + * 最后响应的结果中,过滤出需要的类型 + */ + @ApiModelProperty(value = "流程节点过滤类型") + private List filter; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnBasicProcessInstanceQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnBasicProcessInstanceQueryDTO.java index 4abf1f7f8..8b770bb76 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnBasicProcessInstanceQueryDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnBasicProcessInstanceQueryDTO.java @@ -2,7 +2,10 @@ package cn.axzo.workflow.common.model.request.bpmn.process; 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.annotation.Nullable; @@ -17,6 +20,9 @@ import javax.validation.constraints.NotBlank; @ApiModel("最基础的流程实例查询入参模型") @Data @Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnBasicProcessInstanceQueryDTO { /** * 流程实例 ID diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceAbortDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceAbortDTO.java index c1af47000..474fdf003 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceAbortDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceAbortDTO.java @@ -1,11 +1,21 @@ package cn.axzo.workflow.common.model.request.bpmn.process; +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +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.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.hibernate.validator.constraints.Length; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.List; /** * 中止流程实例的入参模型 @@ -15,6 +25,9 @@ import javax.validation.constraints.NotBlank; */ @ApiModel("中止流程实例的入参模型") @Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnProcessInstanceAbortDTO { /** @@ -36,6 +49,55 @@ public class BpmnProcessInstanceAbortDTO { */ @ApiModelProperty(value = "中止原因") @NotBlank(message = "中止原因不能为空") - @Length(max = 15, message = "中止原因长度不能超过 15 个字符") + @Length(max = 100, message = "中止原因长度不能超过 100 个字符") private String reason; + + /** + * 评论信息 + * advice 统一均为审批节点的审批意见,一般为用户在同意、驳回时填写的内容 + * 有值时,在日志中一般出现在 operationDesc 的下方 + */ + @ApiModelProperty(value = "中止意见") + private String advice; + + /** + * 附件列表 + */ + @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) + private List attachmentList; + + /** + * 中止节点的名称 + * 为空时,则默认展示为“系统中止” + */ + @ApiModelProperty(value = "中止节点名称") + private String nodeName; + + /** + * 指定中止的操作人信息 + *

+ * 只能是 axzo 平台存在的人 + */ + @ApiModelProperty(value = "指定该动作的操作人") + private BpmnTaskDelegateAssigner assigner; + + /** + * 暂不生效 + * 手写签名图片地址 + */ + @ApiModelProperty(value = "手写签名url") + @Deprecated + private String signatureUrl; + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = true; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceAdminPageReqVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceAdminPageReqVO.java index 209f1898a..09f65b23c 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceAdminPageReqVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceAdminPageReqVO.java @@ -4,6 +4,7 @@ import cn.axzo.workflow.common.model.request.BpmPageParam; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.ToString; import java.util.Date; @@ -15,11 +16,13 @@ import java.util.List; * @author wangli * @since 06/03/2024 2:35 pm */ +@EqualsAndHashCode(callSuper = true) @ApiModel("用于超管查询所有的流程实例入参模型") @Data @ToString(callSuper = true) public class BpmnProcessInstanceAdminPageReqVO extends BpmPageParam { + private static final long serialVersionUID = 5051264217867881829L; /** * 流程实例 ID */ @@ -34,9 +37,11 @@ public class BpmnProcessInstanceAdminPageReqVO extends BpmPageParam { /** * 审批业务分类 + *

+ * 底层实现统一使用 KEY 进行交互 */ @ApiModelProperty(value = "审批业务分类") - private String category; + private String key; /** * 流程状态 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 3b8338265..0296fc6b8 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 @@ -1,13 +1,23 @@ package cn.axzo.workflow.common.model.request.bpmn.process; +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +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.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.valid.group.ValidGroup; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.List; /** * 取消流程实例的入参模型 @@ -17,19 +27,23 @@ import javax.validation.constraints.NotNull; */ @ApiModel("取消流程实例的入参模型") @Data +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder public class BpmnProcessInstanceCancelDTO { /** * 流程实例的编号 */ @ApiModelProperty(value = "流程实例编号", example = "11") - @NotBlank(message = "流程实例编号不能为空") + @NotBlank(message = "流程实例编号不能为空", groups = {ValidGroup.Insert.class, ValidGroup.Update.class}) private String processInstanceId; /** * 工作台 ID */ @ApiModelProperty(value = "工作台 ID") + @NotBlank(message = "工作台不能为空", groups = ValidGroup.Insert.class) private String tenantId; /** @@ -38,12 +52,38 @@ public class BpmnProcessInstanceCancelDTO { @ApiModelProperty(value = "撤回的原因", example = "主动撤回") private String reason; + /** + * 附件列表 + */ + @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制", groups = {ValidGroup.Insert.class, ValidGroup.Update.class}) + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + , groups = {ValidGroup.Insert.class, ValidGroup.Update.class}) + private List attachmentList; + + /** + * 撤回节点的名称 + * 为空时,则默认展示为“发起人撤回” + */ + @ApiModelProperty(value = "撤回节点名称") + private String nodeName; + /** * 发起人的信息 */ @ApiModelProperty(value = "审批流程发起人信息") @Valid - @NotNull(message = "审批流程的发起人不能为空") + @NotNull(message = "审批流程的发起人不能为空", groups = {ValidGroup.Insert.class, ValidGroup.Update.class}) private BpmnTaskDelegateAssigner initiator; + + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + private Boolean async = false; + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCarbonCopyDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCarbonCopyDTO.java index 963fb2a3f..897143a40 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCarbonCopyDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCarbonCopyDTO.java @@ -1,13 +1,20 @@ package cn.axzo.workflow.common.model.request.bpmn.process; +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +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.BpmnTaskDelegateAssigner; 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.NotEmpty; +import javax.validation.constraints.Size; import java.util.List; /** @@ -18,6 +25,9 @@ import java.util.List; */ @ApiModel("抄送流程实例的入参模型") @Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnProcessInstanceCarbonCopyDTO { /** @@ -37,6 +47,12 @@ public class BpmnProcessInstanceCarbonCopyDTO { * 附件列表 */ @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) private List attachmentList; /** diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCheckApproverDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCheckApproverDTO.java new file mode 100644 index 000000000..ae496e39f --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCheckApproverDTO.java @@ -0,0 +1,42 @@ +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.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 校验指定的人员信息,是否存在指定流程实例中的当前审批人中 + * + * @author wangli + * @since 2024/4/29 15:37 + */ +@ApiModel("校验指定的人员信息,是否存在指定流程实例中的当前审批人中") +@Data +public class BpmnProcessInstanceCheckApproverDTO { + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "审批实例 ID") + @NotBlank(message = "审批实例 ID 不能为空") + private String processInstanceId; + + /** + * 参与校验的审批人 + */ + @ApiModelProperty(value = "参与校验的审批人") + @Valid + @NotNull(message = "参与校验的审批人") + private BpmnTaskDelegateAssigner approver; + + /** + * 是否只用 personId 判断 + */ + @ApiModelProperty(value = "是否只用 personId 判断") + private Boolean onlyPersonId = false; +} 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 80ae71ff2..1b57e3a1a 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 @@ -1,15 +1,22 @@ package cn.axzo.workflow.common.model.request.bpmn.process; +import cn.axzo.workflow.common.constant.BpmnConstants; import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; import cn.axzo.workflow.common.model.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.math.NumberUtils; import javax.validation.Valid; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -17,7 +24,10 @@ import java.util.Map; */ @ApiModel("创建流程实例的入参模型") @Data -public class BpmnProcessInstanceCreateDTO { +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnProcessInstanceCreateDTO extends BpmnProcessInstanceCreateWithFormDTO { /** * 废弃 @@ -28,27 +38,43 @@ public class BpmnProcessInstanceCreateDTO { /** * 流程定义的标识 *

- * [对应业务分类的 businessId] + * [对应 OMS 系统中审批业务的业务 ID] */ @NotEmpty(message = "流程定义的标识不能为空") private String processDefinitionKey; /** - * 模型归属租户 ID + * 发起流程实例归属租户 ID *

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

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

*/ - @ApiModelProperty(value = "发起的模型是属于哪个租户") - private String tenantId = ""; + @ApiModelProperty(value = "发起的审批是属于哪个租户") + @NotBlank(message = "工作台 ID 不能为空") + private String tenantId; + + /** + * 流程在发起时指定审批人 + */ + @ApiModelProperty(value = "发起时指定节点的审批人") + private Map> specifyAssignerMap; /** * 流程实例关联的变量 */ + @Builder.Default private Map variables = new HashMap<>(); + /** + * 业务管理中定义变量的入参 + */ + @Builder.Default + private Map bizCustomVariables = new HashMap<>(); + /** * 待办相关的变量,如路由参数, 模板参数等等 */ + @Builder.Default private Map pendingVariables = new HashMap<>(); /** @@ -56,6 +82,7 @@ public class BpmnProcessInstanceCreateDTO { *

* 用于流程引擎计算对应节点的审批人, 例如第一个审批节点配置的是劳务分包的岗位,第二个审批节点配置的专业分包的角色, * 那么, 组织关系就需要传入劳务分包的信息以及专业分包的信息,如果还有更多的审批节点配置,以此类推. + * 同时,该属性还会用于计算抄送人,以及消息通知的目标接收人 */ @ApiModelProperty(value = "组织关系") @NotNull(message = "组织关系不能为空") @@ -81,11 +108,28 @@ public class BpmnProcessInstanceCreateDTO { private String customProcessInstanceName; /** - * 废弃 - * 下级审批人 + * 是否异步执行,该异步是引擎的一种运行模式 */ - // @ApiModelProperty(value = "下级审批人", notes = "可为空,定义选择审批人,如果不为空,则覆盖下一级任务的审核人") - // @Deprecated - // private BpmnTaskDelegateAssigner nextApprover; + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = true; -} + /** + * 签署业务,发起时,可基于模型启用的模板,再勾选使用本次发起时选择的文档标签 + */ + @ApiModelProperty(value = "签署业务发起时,选择的文档") + private List docIds; + + /** + * 仅针对签署业务,设置审批完成后的最终签署人列表,该属性仅为透传,业务消费时,请从 MQ 广播事件中的 variables 中通过 key= {@link BpmnConstants#SIGNATORIES } 获取 + */ + @ApiModelProperty(value = "签署业务,最终签署人") + private List signatories; + + public String getTenantId() { + if (NumberUtils.isDigits(tenantId)) { + return tenantId; + } + return null; + } +} \ No newline at end of file diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateWithFormDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateWithFormDTO.java index 82d47e623..c983e8d45 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateWithFormDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceCreateWithFormDTO.java @@ -1,11 +1,11 @@ package cn.axzo.workflow.common.model.request.bpmn.process; +import cn.axzo.workflow.common.model.dto.UploadFieldDTO; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import lombok.EqualsAndHashCode; -import javax.validation.constraints.NotBlank; +import java.util.Map; /** * 创建工作流实例同时携带表单的入参模型 @@ -14,25 +14,30 @@ import javax.validation.constraints.NotBlank; * @since 2023/7/20 15:49 */ @ApiModel("创建工作流实例同时携带表单的入参模型") -@EqualsAndHashCode(callSuper = true) @Data -public class BpmnProcessInstanceCreateWithFormDTO extends BpmnProcessInstanceCreateDTO { +public class BpmnProcessInstanceCreateWithFormDTO { /** - * 表单的定义 ID + * 审批使用了表单,请一定注意传参 *

- * 参考:ACT_FO_FORM_DEFINITION 表的主键 + * 图片类型和附件类型组件:请用 @see {@link UploadFieldDTO} 对象集合传入 + *

+     *     // form_image 为表单项的 key, value 为 UploadFileDTO 对象集合,如果前端使用了组件,一般建议回传所有属性;特殊情况下可以只传 fileUrl
+     *     "form_image": [{
+     *          "fileName": "",
+     *          "fileUrl": "http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960",
+     *          "fileKey": 123
+     *      }]
+     * 
*/ - @ApiModelProperty(value = "表单定义 ID", example = "ssss") - @NotBlank(message = "表单不能为空") - private String formDefinitionId; - + @ApiModelProperty(value = "通过表单创建流程时传入的初始表单数据") + private Map startFormVariables; /** * 工作流实例集成表单后,可以通过表单 key 组装成的变量存入该变量的值,可用于后续流程的流转 *

* 一般不用设置 */ - @ApiModelProperty(value = "用于接入外部传入的信息", hidden = true) + @ApiModelProperty(value = "用于接入外部传入的信息, 类似于 Bpmn 的 BusinessKey", hidden = true) private String outcome; } 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..e0a4841d7 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceLogQueryDTO.java @@ -0,0 +1,55 @@ +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 时,同样也不会返回按钮") + @Builder.Default + 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/process/BpmnProcessInstanceMyPageReqVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceMyPageReqVO.java index dbfc7cd97..870c06726 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceMyPageReqVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceMyPageReqVO.java @@ -49,7 +49,7 @@ public class BpmnProcessInstanceMyPageReqVO extends BpmPageParam { * 业务分类 */ @ApiModelProperty(value = "自定义分类") - private String category; + private String key; /** * 发起时间 diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceQueryDTO.java index 8a445e11b..013ec1689 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceQueryDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceQueryDTO.java @@ -2,8 +2,11 @@ package cn.axzo.workflow.common.model.request.bpmn.process; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceVariablesDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceVariablesDTO.java new file mode 100644 index 000000000..80ba346d2 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/BpmnProcessInstanceVariablesDTO.java @@ -0,0 +1,24 @@ +package cn.axzo.workflow.common.model.request.bpmn.process; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取流程实例中变量集合的入参模型 + * + * @author wangli + * @since 2025-01-16 17:05 + */ +@ApiModel("获取流程实例中变量集合的入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnProcessInstanceVariablesDTO { + + private String processInstanceId; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/HistoricProcessInstanceSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/HistoricProcessInstanceSearchDTO.java index a2c0a504d..5057ae3ea 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/HistoricProcessInstanceSearchDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/HistoricProcessInstanceSearchDTO.java @@ -23,7 +23,7 @@ public class HistoricProcessInstanceSearchDTO extends BpmPageParam { @ApiModelProperty(value = "businessKey OR Name") private String searchKey; - private String category; + private String key; private String result; diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/SuperBpmnProcessInstanceCancelDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/SuperBpmnProcessInstanceCancelDTO.java new file mode 100644 index 000000000..7773be65e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/SuperBpmnProcessInstanceCancelDTO.java @@ -0,0 +1,26 @@ +package cn.axzo.workflow.common.model.request.bpmn.process; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * 取消流程实例的入参模型 + * + * @author wangli + * @since 2023/7/17 09:34 + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel("取消流程实例的入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class SuperBpmnProcessInstanceCancelDTO extends BpmnProcessInstanceCancelDTO { + + private Boolean superAdmin; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ApproverReadStatusDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ApproverReadStatusDTO.java new file mode 100644 index 000000000..3c7d1b9f5 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ApproverReadStatusDTO.java @@ -0,0 +1,40 @@ +package cn.axzo.workflow.common.model.request.bpmn.process.doc; + +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.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 签署业务流程实例,查询审批的关联文档阅读状态入参 + * + * @author wangli + * @since 2025-04-08 11:27 + */ +@ApiModel("签署业务流程实例,查询审批的关联文档阅读状态入参") +@Data +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class ApproverReadStatusDTO { + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例 ID") + @NotBlank(message = "流程实例 ID 不能为空") + private String processInstanceId; + + /** + * 访问人 + */ + @ApiModelProperty(value = "访问人") + @NotNull(message = "访问人信息不能为空") + private BpmnTaskDelegateAssigner assigner; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ChangeApproverReadStatusDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ChangeApproverReadStatusDTO.java new file mode 100644 index 000000000..3d04bbbde --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ChangeApproverReadStatusDTO.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.common.model.request.bpmn.process.doc; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import javax.validation.constraints.NotNull; + +/** + * 签署业务流程实例,更新审批的关联文档阅读状态入参 + * + * @author wangli + * @since 2025-04-08 14:50 + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel("签署业务流程实例,更新审批的关联文档阅读状态入参") +@Data +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class ChangeApproverReadStatusDTO extends ApproverReadStatusDTO { + /** + * 文档ID + */ + @ApiModelProperty(value = "文档 ID") + @NotNull(message = "文档 ID 不能为空") + private Long docId; + + @ApiModelProperty(value = "阅读状态") + @NotNull(message = "阅读状态不能为空") + private Boolean readStatus; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ProcessDocQueryDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ProcessDocQueryDTO.java new file mode 100644 index 000000000..6d6a21d06 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/process/doc/ProcessDocQueryDTO.java @@ -0,0 +1,39 @@ +package cn.axzo.workflow.common.model.request.bpmn.process.doc; + +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 javax.validation.constraints.NotBlank; + +/** + * 签署业务流程实例,在审批待办中查询文档列表的入参 + * + * @author wangli + * @since 2025-04-03 16:30 + */ +@ApiModel("签署业务流程实例,在审批待办中查询文档列表的入参") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ProcessDocQueryDTO { + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例 ID") + @NotBlank(message = "流程实例 ID 不能为空") + private String processInstanceId; + + /** + * 只有当该属性传值后,响应模型中才会返回阅读状态信息 + */ + @ApiModelProperty(value = "访问者") + private BpmnTaskDelegateAssigner assigner; + +} 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..e0995aed8 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,16 @@ 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 org.dromara.easyes.annotation.IndexField; +import org.dromara.easyes.annotation.rely.FieldType; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import java.io.Serializable; /** * 附件模型 @@ -16,11 +22,16 @@ 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 */ + @IndexField(fieldType = FieldType.KEYWORD) private String id; /** @@ -28,6 +39,7 @@ public class AttachmentDTO { */ @ApiModelProperty(value = "附件类型") @NotNull(message = "附件类型不能为空") + @IndexField(fieldType = FieldType.KEYWORD) private AttachmentTypeEnum type; /** @@ -35,12 +47,14 @@ public class AttachmentDTO { */ @ApiModelProperty(value = "文件名称不能为空") @NotBlank(message = "文件名称不能为空") + @IndexField(fieldType = FieldType.KEYWORD) private String name; /** * 文件描述 */ @ApiModelProperty(value = "文件描述") + @IndexField(exist = false) private String description; /** @@ -48,6 +62,13 @@ public class AttachmentDTO { */ @ApiModelProperty(value = "附件地址") @NotBlank(message = "附件地址不能为空") + @IndexField(fieldType = FieldType.KEYWORD) private String url; + /** + * 文件 OSS 的 fileKey + */ + @ApiModelProperty(value = "附件 oss 的 fileKey") + private String fileKey; + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BaseBpmnTaskDelegateAssigner.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BaseBpmnTaskDelegateAssigner.java new file mode 100644 index 000000000..a50fff4aa --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BaseBpmnTaskDelegateAssigner.java @@ -0,0 +1,30 @@ +package cn.axzo.workflow.common.model.request.bpmn.task; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 一些审批人模型的扩展信息 + * + * @author wangli + * @since 2025-06-28 13:17 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public abstract class BaseBpmnTaskDelegateAssigner { + + /** + * 人员标签 + * 0: 注销 1:离场 2: 离职 + */ + private Integer tag; + + /** + * 是否能提级审批 + */ + private Boolean supportUpgradeApproval; +} 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 8095efa78..baa69fe44 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 @@ -2,11 +2,13 @@ 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.Valid; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Size; import java.util.List; /** @@ -17,6 +19,9 @@ import java.util.List; */ @ApiModel("业务节点设置审批人") @Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnActivitySetAssigneeDTO { /** * PROCESS_ACTIVITY_WAIT_ASSIGNEE 事件中的触发 ID @@ -42,11 +47,12 @@ public class BpmnActivitySetAssigneeDTO { private String processInstanceId; /** - * 需要设置的审批人, 业务侧自行去重, 请根据 personId 进行去重, 如果发现有重复, 并抛出异常 + * 需要设置的审批人, 业务侧自行去重, 请根据 personId 进行去重, 如果发现有重复, 并抛出异常. + *

+ * 业务如果传入的 assigners 集合为空, 引擎则会对该流程进行自动中止处理,且驳回意见为“业务未指定审批人” */ @ApiModelProperty(value = "审批人集合信息", notes = "业务传参时,需要注意去重") - @Valid - @NotEmpty(message = "审批人不能为空") + @Size(max = 60, message = "指定审批人数量限制为60") private List assigners; /** @@ -55,6 +61,14 @@ public class BpmnActivitySetAssigneeDTO { *

* 如果业务不能接受,请自己主动去重后提交. */ + @Builder.Default private Boolean serverSideDeduplication = false; + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = false; + } 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..be10451a9 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnActivityTriggerDTO.java @@ -0,0 +1,46 @@ +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; +import java.io.Serializable; + +/** + * 推动业务节点继续执行 + * + * @author wangli + * @since 2024-09-09 13:46 + */ +@ApiModel("业务节点设置审批人") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnActivityTriggerDTO implements Serializable { + + private static final long serialVersionUID = 1759998765977414031L; + + /** + * 业务节点的触发 ID + */ + @NotBlank(message = "触发 ID 不能为空") + @ApiModelProperty(value = "触发 ID", notes = "数据来源于事件") + private String triggerId; + + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = true; + + /** + * 流程定义节点id + */ + private String activityId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnNodeBackSystemOperateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnNodeBackSystemOperateDTO.java new file mode 100644 index 000000000..c01572e98 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnNodeBackSystemOperateDTO.java @@ -0,0 +1,67 @@ +package cn.axzo.workflow.common.model.request.bpmn.task; + +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; +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.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.List; + +/** + * 审批节点退回模型 + * + */ +@ApiModel("审批节点退回模型") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +public class BpmnNodeBackSystemOperateDTO implements Serializable { + + private static final long serialVersionUID = -4160538355403179298L; + + @ApiModelProperty(value = "流程实例id", required = true) + @NotBlank(message = "流程实例id不能为空") + private String processInstanceId; + + @ApiModelProperty(value = "当前流程节点id", required = true) + @NotBlank(message = "当前节点id不能为空") + private String currentActivityId; + + @ApiModelProperty(value = "目标流程节点id", required = true) + @NotBlank(message = "目标流程节点id不能为空") + private String toActivityId; + + @ApiModelProperty(value = "可以指定任务处理", required = true) + private List targetTaskIds; + + @ApiModelProperty(value = "意见") + private String advice; + + @ApiModelProperty(value = "操作描述") + private String operationDesc; + + @ApiModelProperty(value = "操作人") + private BpmnTaskDelegateAssigner operator; + + /** + * 附件列表 + */ + @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) + private List attachmentList; + +} 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/BpmnRobotTaskCompleteDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnRobotTaskCompleteDTO.java index 2ed96b9df..905c68fe4 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnRobotTaskCompleteDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnRobotTaskCompleteDTO.java @@ -1,11 +1,19 @@ package cn.axzo.workflow.common.model.request.bpmn.task; +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +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.Valid; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.List; /** * 完成机器人节点的入参模型 @@ -15,6 +23,9 @@ import javax.validation.constraints.NotBlank; */ @ApiModel("完成机器人节点的入参模型") @Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnRobotTaskCompleteDTO { /** @@ -34,14 +45,32 @@ public class BpmnRobotTaskCompleteDTO { /** * 完成机器人节点的人信息 * - * @since 1.3.0 版本不支持传人员信息 + * @since 暂不支持传人员信息 */ @ApiModelProperty(value = "当前审核人信息", notes = "可为空,则该任务不验证用户归属") - // @Valid - // @NotNull(message = "审批人不能为空") + @Deprecated private BpmnTaskDelegateAssigner approver; @ApiModelProperty(value = "机器人节点信息") @Valid private BpmnFlowNodeDTO robotNode; + + /** + * 图片附件 + */ + @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) + private List attachmentList; + + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = false; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnRobotTaskCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnRobotTaskCreateDTO.java index e7281fafa..f84e15c7c 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnRobotTaskCreateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnRobotTaskCreateDTO.java @@ -2,7 +2,10 @@ 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.Valid; import javax.validation.constraints.NotBlank; @@ -16,6 +19,9 @@ import javax.validation.constraints.NotNull; */ @ApiModel("创建机器人节点的入参模型") @Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnRobotTaskCreateDTO { /** * 流程实例 ID @@ -27,11 +33,10 @@ public class BpmnRobotTaskCreateDTO { /** * 当前审核人信息 * - * @since 1.3.0 版本不支持传人员信息 + * @since 暂不支持传人员信息 */ @ApiModelProperty(value = "当前审核人信息", notes = "可为空,则该任务不验证用户归属", hidden = true) - // @Valid - // @NotNull(message = "审批人不能为空") + @Deprecated private BpmnTaskDelegateAssigner approver; /** @@ -41,4 +46,11 @@ public class BpmnRobotTaskCreateDTO { @Valid @NotNull(message = "机器人节点信息不能为空") private BpmnFlowNodeDTO robotNode; + + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = false; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditDTO.java index 81c5d947b..64f01c435 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditDTO.java @@ -1,22 +1,34 @@ package cn.axzo.workflow.common.model.request.bpmn.task; +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.validation.annotation.Validated; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.util.List; /** * 审批任务节点的入参模型 + * * @author wangli */ @ApiModel("审批任务节点的入参模型") @Data @Validated +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnTaskAuditDTO { @ApiModelProperty(value = "任务编号", required = true, example = "1024") @@ -33,6 +45,12 @@ public class BpmnTaskAuditDTO { * 附件列表 */ @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) private List attachmentList; /** @@ -55,4 +73,24 @@ public class BpmnTaskAuditDTO { @ApiModelProperty(value = "下级审批人信息", notes = "可为空,定义选择审批人,如果不为空,则覆盖下一级任务的审核人") // @Valid private BpmnTaskDelegateAssigner nextApprover; + + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = true; + + /** + * 指定节点类型 + */ + @ApiModelProperty(value = "指定节点类型", notes = "指定节点类型,类型不匹配抛出异常") + @Deprecated + private List nodeTypes; + + /** + * 操作描述,该属性为流程服务内部使用 + */ + @Deprecated + private String operationDesc; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditWithFormDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditWithFormDTO.java new file mode 100644 index 000000000..89c35023c --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskAuditWithFormDTO.java @@ -0,0 +1,33 @@ +package cn.axzo.workflow.common.model.request.bpmn.task; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Map; + +/** + * 审批同意时携带表单 + * + * @author wangli + * @since 2024-11-08 11:36 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@ApiModel(value = "审批任务携带表单数据的入参模型") +public class BpmnTaskAuditWithFormDTO extends BpmnTaskAuditDTO { + + /** + * 表单数据 + *

+ * 必须为全量表单数据,不能只提交部分表单数据 + */ + @ApiModelProperty(value = "表单数据") + private Map formVariables; + + /** + * 暂对不用 + */ + private String outcome; +} 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/request/bpmn/task/BpmnTaskButtonSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskButtonSearchDTO.java new file mode 100644 index 000000000..dc32d16c7 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskButtonSearchDTO.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.Data; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +@Data +@ApiModel("获取审批任务按钮,状态入参模型") +public class BpmnTaskButtonSearchDTO implements Serializable { + + private static final long serialVersionUID = -3220083731019329293L; + + @ApiModelProperty(value = "流程实例id", required = true) + @NotBlank(message = "流程实例id不能为空") + private String processInstanceId; + + @ApiModelProperty(value = "审批任务id", required = true) + @NotBlank(message = "审批任务id不能为空") + private String taskId; + + private Long initiatorPersonId; + + private Long executorPersonId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskCommentDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskCommentDTO.java index 1d8b3f843..4d5be875d 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskCommentDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskCommentDTO.java @@ -1,13 +1,20 @@ package cn.axzo.workflow.common.model.request.bpmn.task; +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +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 org.hibernate.validator.constraints.Length; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; @@ -19,6 +26,9 @@ import java.util.List; */ @ApiModel("评论的入参模型") @Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnTaskCommentDTO implements Serializable { /** @@ -54,7 +64,20 @@ public class BpmnTaskCommentDTO implements Serializable { * 附件列表 */ @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) @Valid private List attachmentList; + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = false; + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskCountersignDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskCountersignDTO.java index 00d21bbb4..9bdc7c69b 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskCountersignDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskCountersignDTO.java @@ -1,13 +1,20 @@ package cn.axzo.workflow.common.model.request.bpmn.task; +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; @@ -21,6 +28,9 @@ import java.util.List; */ @Data @Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnTaskCountersignDTO implements Serializable { private static final long serialVersionUID = -8106887960942113552L; @@ -52,6 +62,12 @@ public class BpmnTaskCountersignDTO implements Serializable { * 附件列表 */ @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) private List attachmentList; /** @@ -69,4 +85,11 @@ public class BpmnTaskCountersignDTO implements Serializable { @NotNull(message = "加签任务发起人不能为空") private BpmnTaskDelegateAssigner originAssigner; + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = true; + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskDelegateAssigner.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskDelegateAssigner.java index 8a03f6428..7607bf78a 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskDelegateAssigner.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskDelegateAssigner.java @@ -5,9 +5,13 @@ import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.math.NumberUtils; +import org.dromara.easyes.annotation.IndexField; +import org.dromara.easyes.annotation.rely.FieldType; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; @@ -32,6 +36,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.ROBOT_ASSIGNEE_ID; * * @author wangli */ +@EqualsAndHashCode(callSuper = true) @Data @Builder @NoArgsConstructor @@ -39,24 +44,28 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.ROBOT_ASSIGNEE_ID; @Accessors(chain = true) @Validated @Slf4j -public class BpmnTaskDelegateAssigner implements Serializable { +public class BpmnTaskDelegateAssigner extends BaseBpmnTaskDelegateAssigner implements Serializable { private static final long serialVersionUID = -8106887960942113552L; /** - * 审核人标识, 应该必传 + * 【废弃】审核人标识, 应该必传 *

* 枢智:用户 ID * 安心筑:身份 ID */ + @Deprecated + @IndexField(fieldType = FieldType.KEYWORD) private String assignee; /** - * 审核人标识扩展信息 + * 【废弃】审核人标识扩展信息 *

* 枢智:可不传 * 安心筑:身份 Type 应该必传 */ + @Deprecated + @IndexField(fieldType = FieldType.KEYWORD) private String assigneeType; /** @@ -65,13 +74,17 @@ public class BpmnTaskDelegateAssigner implements Serializable { * 枢智:用户姓名 * 安心筑:用户姓名 */ + @IndexField(fieldType = FieldType.KEYWORD) private String assignerName; /** * 对应安心筑特殊的PersonId *

* 仅安心筑使用, 应该必传 + *

+ *

注意:该属性在部分场景下会是非 Long 类型的值,建议业务消费时,调用{@link BpmnTaskDelegateAssigner#parsePersonId()}函数进行转换

*/ + @IndexField(fieldType = FieldType.KEYWORD) private String personId; /** @@ -80,20 +93,28 @@ public class BpmnTaskDelegateAssigner implements Serializable { * 枢智: 企业 ID * 安心筑: 工作台 ID */ + @IndexField(fieldType = FieldType.KEYWORD) private String tenantId; /** * 人所在的单位 ID * 仅安心筑使用, 工人可以没有, 其他身份一定需要 */ + @IndexField(fieldType = FieldType.KEYWORD) private String ouId; /** - * 该属性业务接入时无需关系. 尽量保证其他属性都设值. + * 该属性业务接入时无需关心. 尽量保证其他属性都设值. * 头像 */ + @IndexField(exist = false) private String avatar; + /** + * 人员对应的 nodeId,如果在审批模板中对应节点未使用流程自主查询审批人的功能(如指定岗位/身份/角色之类),则需要业务方主动传入 + */ + private String nodeId; + public final String buildAssigneeId_1_2_1() { if (StringUtils.hasLength(assigneeType)) { return tenantId + "|" + assignee + "|" + assigneeType; @@ -105,6 +126,10 @@ public class BpmnTaskDelegateAssigner implements Serializable { * @since 1.3.0 版本起, 使用 ouId + personId 作为审批人唯一标识 */ public final String buildAssigneeId() { + if (Objects.equals(personId, "system")) { + // 特殊逻辑,用于服务内部系统驳回、拒绝时的特殊处理 + return personId; + } if (StringUtils.hasLength(ouId)) { return ouId + "|" + personId; } else { @@ -112,6 +137,18 @@ public class BpmnTaskDelegateAssigner implements Serializable { } } + public BpmnTaskDelegateAssigner(String assignerName, String personId, String tenantId) { + this.assignerName = assignerName; + this.personId = personId; + this.tenantId = tenantId; + } + + public BpmnTaskDelegateAssigner(String assignee, String assigneeType, String assignerName, String personId, String tenantId) { + this.assignerName = assignerName; + this.personId = personId; + this.tenantId = tenantId; + } + public final boolean comparePersonIdToOther(BpmnTaskDelegateAssigner other) { return Objects.equals(personId, other.getPersonId()); } @@ -143,7 +180,7 @@ public class BpmnTaskDelegateAssigner implements Serializable { } } } catch (Exception e) { - log.error("审批人模型数据比对发现意外数据, assignee: " + assignee); + log.warn("审批人模型数据比对发现意外数据, assignee: {}", assignee); } return false; } @@ -169,8 +206,54 @@ public class BpmnTaskDelegateAssigner implements Serializable { .setTenantId(tenantId); } - @Override - public String toString() { + public String toJson() { return JSON.toJSONString(this); } + + + /** + * 兼容版本间的不同类型转换成审批人模型 + * + * @param request 目前存在两种类型,一种为 BpmnTaskDelegateAssigner 实例对象, 另一种为 String 类型的 JSON 数据 + * @return + */ + public static BpmnTaskDelegateAssigner toObjectCompatible(Object request) { + if (Objects.isNull(request)) { + return null; + } + if (request instanceof BpmnTaskDelegateAssigner) { + return (BpmnTaskDelegateAssigner) request; + } else if (request instanceof String) { + return toObject((String) request); + } else { + return null; + } + } + + public static BpmnTaskDelegateAssigner toObject(String jsonStr) { + return JSON.parseObject(jsonStr, BpmnTaskDelegateAssigner.class); + } + + /** + * @return + */ + public Long parsePersonId() { + if (NumberUtils.isDigits(personId)) { + return Long.parseLong(personId); + } else { + return 0L; + } + } + + public static void main(String[] args) { + BpmnTaskDelegateAssigner assigner = new BpmnTaskDelegateAssigner(); + assigner.setPersonId("system"); + System.out.println("assigner.parsePersonId() = " + assigner.parsePersonId()); + + assigner.setPersonId(""); + System.out.println("assigner.parsePersonId() = " + assigner.parsePersonId()); + + assigner.setPersonId("123"); + System.out.println("assigner.parsePersonId() = " + assigner.parsePersonId()); + } } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskRemindDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskRemindDTO.java index 7f51ba85b..806bd51b1 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskRemindDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskRemindDTO.java @@ -2,10 +2,13 @@ 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 lombok.experimental.Accessors; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; import java.util.List; /** @@ -16,8 +19,16 @@ import java.util.List; */ @ApiModel("催办功能入参模型") @Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder public class BpmnTaskRemindDTO { - + /** + * 操作催办时的终端类型,管理端cmp、工人端cm + */ + @NotBlank(message = "终端类型不能为空") + private String terminalType; /** * 审批节点唯一标识 */ @@ -27,9 +38,9 @@ public class BpmnTaskRemindDTO { /** * 催办方式 *

- * 站内信:notice 短信:sms + * IM, 如果为空,默认是 IM */ - @NotEmpty(message = "催办方式不能为空") + @ApiModelProperty(value = "催办方式", example = "im") private List remindTypes; /** @@ -40,8 +51,9 @@ public class BpmnTaskRemindDTO { private String processInstanceId; /** - * 租户 ID + * 是否异步执行 */ - @ApiModelProperty("租户 ID") - private String tenantId; + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = true; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskResetApproversDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskResetApproversDTO.java new file mode 100644 index 000000000..c8f2e9305 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskResetApproversDTO.java @@ -0,0 +1,80 @@ +package cn.axzo.workflow.common.model.request.bpmn.task; + +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.List; + +/** + * 重置指定节点下的所有审批人 + * + * @author wangli + * @since 2025-06-24 17:07 + */ +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnTaskResetApproversDTO implements Serializable { + private static final long serialVersionUID = 4570922472032531513L; + + /** + * 审批任务ID + */ + @ApiModelProperty(value = "审批任务ID") + @NotEmpty(message = "任务ID不能为空") + private String taskId; + + /** + * 加签意见 + */ + @ApiModelProperty(value = "提级意见") + private String advice; + + /** + * 附件列表 + */ + @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) + private List attachmentList; + + /** + * 任务提级给谁审批 + */ + @ApiModelProperty(value = "任务提级给谁审批") + @NotEmpty(message = "任务接收人不能为空") + private List targetAssignerList; + + /** + * 任务操作人 + */ + @ApiModelProperty(value = "任务原发起人") + @Valid + @NotNull(message = "任务发起人不能为空") + private BpmnTaskDelegateAssigner originAssigner; + + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = true; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskTransferDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskTransferDTO.java index 122dab987..dc4c0f7c8 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskTransferDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/BpmnTaskTransferDTO.java @@ -1,11 +1,19 @@ package cn.axzo.workflow.common.model.request.bpmn.task; +import cn.axzo.workflow.common.constraint.AttachmentTypeValidator; +import cn.axzo.workflow.common.constraint.AttachmentValidator; +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.Valid; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.io.Serializable; import java.util.List; /** @@ -16,7 +24,12 @@ import java.util.List; */ @ApiModel("转交审批任务的入参模型") @Data -public class BpmnTaskTransferDTO { +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnTaskTransferDTO implements Serializable { + + private static final long serialVersionUID = 8142105892475867826L; /** * 审批任务 ID @@ -35,13 +48,38 @@ public class BpmnTaskTransferDTO { * 附件列表 */ @ApiModelProperty(value = "附件列表") + @Size(max = 11, message = "附件数量超过限制") + @AttachmentValidator(types = { + @AttachmentTypeValidator(type = AttachmentTypeEnum.image, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.file, max = 5), + @AttachmentTypeValidator(type = AttachmentTypeEnum.signature, max = 1)} + ) private List attachmentList; + /** + * 审批任务原审批人 + */ @ApiModelProperty(value = "审批任务原审批人", notes = "可以为空,意义在于可以将没有指派给任何人的任务转交给其他人") @Valid private BpmnTaskDelegateAssigner originAssigner; - @ApiModelProperty(value = "审批任务转发给谁", notes = "可以为空,意义在于如果分配给某离职的人,可能需要置为空") + /** + * 审批任务转交给谁 + */ + @ApiModelProperty(value = "审批任务转交给谁", notes = "可以为空,意义在于如果分配给某离职的人,可能需要置为空") private BpmnTaskDelegateAssigner targetAssigner; + /** + * 转交描述 + */ + @ApiModelProperty(value = "转交描述", notes = "正常是'xx转交给xx',该字段值追加在后面") + private String additionalOpeDesc; + + /** + * 是否异步执行 + */ + @ApiModelProperty(value = "是否异步", notes = "异步时,只接收请求便返回数据") + @Builder.Default + private Boolean async = true; + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/ExtHiTaskSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/ExtHiTaskSearchDTO.java index e88d62a65..a16d170dc 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/ExtHiTaskSearchDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/bpmn/task/ExtHiTaskSearchDTO.java @@ -3,7 +3,12 @@ package cn.axzo.workflow.common.model.request.bpmn.task; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; 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.List; /** * 扩展的历史任务表搜索入参模型 @@ -13,6 +18,9 @@ import lombok.Data; */ @ApiModel("扩展的历史任务表搜索入参模型") @Data +@Builder +@AllArgsConstructor +@NoArgsConstructor public class ExtHiTaskSearchDTO { @ApiModelProperty("流程实例 ID") @@ -29,4 +37,7 @@ public class ExtHiTaskSearchDTO { @ApiModelProperty("任务状态") private BpmnProcessInstanceResultEnum status; + + @ApiModelProperty("需要排除的id列表") + private List excludeIds; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryConfigSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryConfigSearchDTO.java index e3d32a85f..7f6218892 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryConfigSearchDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryConfigSearchDTO.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.common.model.request.category; import cn.axzo.workflow.common.model.request.BpmPageParam; import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; @@ -21,25 +22,30 @@ public class CategoryConfigSearchDTO extends BpmPageParam { /** * 分类ID */ + @ApiModelProperty(value = "字典 ID") private Long dictId; /** * 配置类型 */ + @ApiModelProperty(value = "配置类型") private String configType; /** * 工作台 ID */ + @ApiModelProperty(value = "工作台 ID") private Long workspaceId; /** * 工作台 ID 集合 */ + @ApiModelProperty(value = "工作台 ID 集合") private List workspaceIds; /** * 操作人姓名 */ + @ApiModelProperty(value = "操作人姓名") private String operationName; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryCreateDTO.java index 038ff3840..d80e8f178 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryCreateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryCreateDTO.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.common.model.request.category; +import cn.axzo.workflow.common.enums.BusinessTypeEnum; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.hibernate.validator.constraints.Length; @@ -9,36 +10,81 @@ import javax.validation.constraints.NotBlank; @Data public class CategoryCreateDTO { + /** + * 字典标签 + */ @ApiModelProperty(value = "字典标签", example = "新业务") @Length(max = 50, message = "字典标签最长只支持255个字符") @NotBlank(message = "字典标签表示不能为空") private String label; + /** + * 字典值 + */ @ApiModelProperty(value = "字典值", example = "new_business") @Length(max = 50, message = "字典值最长只支持50个字符") @NotBlank(message = "字典值表示不能为空") private String value; + /** + * 字典类型 + */ @ApiModelProperty(value = "字典类型", example = "字典类型(用于字典分组)") private String type; + /** + * 备注 + */ @ApiModelProperty(value = "备注", example = "remark") @Length(max = 250, message = "备注最长只支持250个字符") private String remark; + /** + * 字典排序 + */ @ApiModelProperty(value = "字典排序", example = "1") private Long sort; + /** + * 状态 + */ @ApiModelProperty(value = "状态", example = "true or false") private Boolean status; + /** + * 创建人姓名 + */ @ApiModelProperty(value = "创建人姓名", example = "张三") private String operatorName; - @ApiModelProperty(value = "工作台类型值") + /** + * 工作台类型: 1企业工作台 2项目部工作台 3政务监管工作台 6OMS工作台 + */ + @ApiModelProperty(value = "工作台类型值, 1企业工作台 2项目部工作台 3政务监管工作台 6OMS工作台") private String workspaceCodeType; + /** + * 租户 + */ @ApiModelProperty(value = "租户", example = "1") private String tenantId = ""; + /** + * 业务类型 + */ + @ApiModelProperty(value = "业务类型,SIGN-签署业务,APPROVAL-审批业务") + private BusinessTypeEnum businessType; + + /** + * 图标 + */ + @ApiModelProperty(value = "图标") + private String icon; + + /** + * 是否展示在发起工作台 + */ + @ApiModelProperty(value = "是否展示在发起工作台,默认为false") + private Boolean displayInitiateMenu = false; + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryGroupVarSearchDto.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryGroupVarSearchDto.java new file mode 100644 index 000000000..e0862cc54 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryGroupVarSearchDto.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.common.model.request.category; + +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.Min; + +@ApiModel("业务分类分组和变量搜索入参模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CategoryGroupVarSearchDto { + + @Min(value = 1, message = "字典ID必须大于0") + @ApiModelProperty(value = "字典 ID") + private Long dictId; + + /** + * 所属业务code + */ + @ApiModelProperty(value = "所属业务") + private String category; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryGroupVarUpsertDto.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryGroupVarUpsertDto.java new file mode 100644 index 000000000..24fde2c59 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategoryGroupVarUpsertDto.java @@ -0,0 +1,104 @@ +package cn.axzo.workflow.common.model.request.category; + +import cn.axzo.workflow.common.enums.VarTypeEnum; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import javax.validation.Valid; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.io.Serializable; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CategoryGroupVarUpsertDto { + + @ApiModelProperty(value = "字典id") + @Min(value = 1, message = "字典id值必须大于等1") + @NotNull(message = "字典id不能为空") + private Long dictId; + + @Valid + private List groupVos; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class CategoryGroupUpsertVo implements Serializable { + private static final long serialVersionUID = 5406711143425155649L; + + /** + * id + */ + private Long id; + + /** + * 上级分组id + */ + private Long parentGroupId; + + /** + * 分组名 + */ + @NotBlank(message = "分组名称不能为空") + @Length(max = 50, message = "字典标签最长只支持50个字符") + private String groupName; + + @NotNull(message = "序号不能为空") + private Integer ordinal; + + @Valid + private List vars; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class CategoryVarUpsertVo implements Serializable { + + private static final long serialVersionUID = -1918203166603971593L; + + private Long id; + + /** + * 分组id + */ + private Long groupId; + + /** + * 变量类型,文本/图片 + */ + @NotNull(message = "变量类型不能为空") + private VarTypeEnum type; + + /** + * 变量code + */ + @NotBlank(message = "变量编码不能为空") + @Length(max = 50, message = "变量编码最长只支持50个字符") + @Pattern(regexp = "^[a-zA-Z]+$", message = "变量编码仅限英文字符") + private String code; + + /** + * 变量名称 + */ + @NotBlank(message = "变量名称不能为空") + @Length(max = 50, message = "变量名称最长只支持50个字符") + private String name; + + @NotNull(message = "序号不能为空") + private Integer ordinal; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategorySearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategorySearchDTO.java index 5a8fab133..2e2738eae 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategorySearchDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/category/CategorySearchDTO.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.common.model.request.category; +import cn.axzo.workflow.common.enums.BusinessTypeEnum; import cn.axzo.workflow.common.model.request.BpmPageParam; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -13,21 +14,48 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data public class CategorySearchDTO extends BpmPageParam { + /** + * 字典类型, bpm_model_category + */ @ApiModelProperty(value = "字典类型", example = "dict_group", notes = "精准匹配") private String dictType; + /** + * 字典标签, 对应业务名称 + */ @ApiModelProperty(value = "字典标签", example = "新业务") private String label; + /** + * 字典值, 对应业务 ID + */ @ApiModelProperty(value = "字典值", example = "new_business") private String value; + /** + * 字典状态, 0 停用, 1 启用 + */ @ApiModelProperty(value = "状态", example = "0 or 1") private Integer status; - @ApiModelProperty(value = "工作台类型值") + /** + * 工作台类型: 1企业工作台 2项目部工作台 3政务监管工作台 6OMS工作台 + */ + @ApiModelProperty(value = "工作台类型值, 1企业工作台 2项目部工作台 3政务监管工作台 6OMS工作台") private String workspaceTypeCode; + /** + * 租户 ID + */ @ApiModelProperty(value = "租户 ID", example = "1") private String tenantId = ""; + + /** + * 业务类型 + */ + @ApiModelProperty(value = "业务类型") + private BusinessTypeEnum businessType; + + @ApiModelProperty(value = "根据创建时间排序") + private String orderCreateAt; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/es/InstanceSearchReqDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/es/InstanceSearchReqDTO.java new file mode 100644 index 000000000..5ada6095f --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/es/InstanceSearchReqDTO.java @@ -0,0 +1,53 @@ +package cn.axzo.workflow.common.model.request.es; + +import cn.axzo.workflow.common.model.request.BpmPageParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import java.util.Date; +import java.util.List; + +/** + * 搜索 Es 中实例纬度的数据 + * + * @author wangli + * @since 2024-10-08 14:03 + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel("搜索 Es 中实例纬度的数据") +@Data +public class InstanceSearchReqDTO extends BpmPageParam { + + @ApiModelProperty(value = "审批人所属租户 ID") + private String tenantId; + + @ApiModelProperty(value = "审批人所属单位 ID") + private String ouId; + + @ApiModelProperty(value = "审批人自然人 ID") + private String personId; + + @ApiModelProperty(value = "审批人姓名") + private String assigneeName; + + @ApiModelProperty(value = "流程实例状态", notes = "参考 BpmnProcessInstanceResultEnum 枚举") + private String businessStatus; + + @ApiModelProperty(value = "流程实例名称") + private String processInstanceName; + + private Date beginStartTime; + private Date overStartTime; + + private Date beginEndTime; + private Date overEndTime; + + /** + * 流程实例 ID, 与其它所有的属性互斥 + */ + @ApiModelProperty(value = "流程实例 ID 集合") + private List processInstanceIds; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/es/TaskSearchReqDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/es/TaskSearchReqDTO.java new file mode 100644 index 000000000..48bd511e3 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/es/TaskSearchReqDTO.java @@ -0,0 +1,57 @@ +package cn.axzo.workflow.common.model.request.es; + +import cn.axzo.workflow.common.model.request.BpmPageParam; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 搜索 Es 中任务纬度的数据 + * + * @author wangli + * @since 2024-10-08 09:49 + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel("搜索 Es 中任务纬度的数据") +@Data +public class TaskSearchReqDTO extends BpmPageParam { + private static final long serialVersionUID = -1L; + + @ApiModelProperty(value = "流程实例 ID") + private String processInstanceId; + /** + * 搜索含有指定该租户的任务 + */ + @ApiModelProperty(value = "租户 ID") + private String assigneeTenantId; + + /** + * 搜索含有指定单位的任务 + */ + @ApiModelProperty(value = "单位 ID") + private String assigneeOuId; + + /** + * 搜索含有指定自然人的任务 + */ + @ApiModelProperty(value = "自然人 ID") + private String assigneePersonId; + + /** + * 搜索含有指定自然人名称的任务 + */ + @ApiModelProperty(value = "自然人姓名") + private String assigneeName; + + /** + * 与上面三个属性互斥 + *

+ * 成对是数据项 + */ + @ApiModelProperty(value = "审批人信息集合") + private List assigners; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormFieldPermissionConf.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormFieldPermissionConf.java new file mode 100644 index 000000000..a9881048c --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormFieldPermissionConf.java @@ -0,0 +1,60 @@ +//package cn.axzo.workflow.common.model.request.form; +// +//import io.swagger.annotations.ApiModel; +//import io.swagger.annotations.ApiModelProperty; +//import lombok.Data; +//import lombok.NoArgsConstructor; +//import lombok.experimental.Accessors; +// +//import java.io.Serializable; +//import java.util.List; +// +///** +// * 表单字段权限配置 +// *

+// * 发起人 +// * 只读 可编辑必填 可编辑非必填 隐藏 +// * 1 1 1 1 字段 1 +// * 1 0 0 1 字段 2 +// * +// * @author wangli +// * @since 2024-11-06 18:19 +// */ +//@ApiModel("JSON 版本的 BPMN 协议模型中的表单字段权限") +//@Data +//@NoArgsConstructor +//@Accessors(chain = true) +//public class FormFieldPermissionConf implements Serializable { +// private static final long serialVersionUID = 1L; +// +// /** +// * 发起人的表单字段权限 +// */ +// @ApiModelProperty(value = "发起人的表单字段权限") +// private List initiator; +// +// /** +// * 审批人的表单字段权限 +// */ +// @ApiModelProperty(value = "审批人的表单字段权限") +// private List current; +// +// /** +// * 历史审批人的表单字段权限 +// */ +// @ApiModelProperty(value = "历史审批人的表单字段权限") +// private List history; +// +// /** +// * 抄送人的表单字段权限 +// */ +// @ApiModelProperty(value = "抄送人的表单字段权限") +// private List carbonCopy; +// +// /** +// * 管理员的字段权限 +// */ +// @ApiModelProperty(value = "管理员的字段权限") +// private List admin; +// +//} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormJsonModel.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormJsonModel.java new file mode 100644 index 000000000..39f13b88e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormJsonModel.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.common.model.request.form; + +import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO; +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; +import java.io.Serializable; +import java.util.List; + +/** + * 新版本的表单定义模型 JSON 结构 + * + * @author wangli + * @since 2024-11-06 17:24 + */ +@ApiModel("新版本的表单定义模型 JSON 结构") +@Data +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +public class FormJsonModel implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "表单名称") + private String name; + + @ApiModelProperty(value = "表单模型") + @Valid + private List formFields; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormPermissionMetaInfo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormPermissionMetaInfo.java new file mode 100644 index 000000000..0a8cd715e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/FormPermissionMetaInfo.java @@ -0,0 +1,93 @@ +package cn.axzo.workflow.common.model.request.form; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 表单字段权限信息 + * + * @author wangli + * @since 2024-11-07 11:09 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FormPermissionMetaInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 字段标识 + */ + private String fieldId; + + /** + * 字段名称 + */ + private String fieldName; + + /** + * 可编辑必填 + */ + @Builder.Default + private Boolean required = false; + + /** + * 可编辑非必填 + */ + @Builder.Default + private Boolean editable = false; + + /** + * 只读 + */ + @Builder.Default + private Boolean readonly = true; + + /** + * 隐藏 + */ + @Builder.Default + private Boolean hidden = false; + /** + * 前端回显字段,后端不做任何消费逻辑 + */ + private String value; + + // 将对象的属性转换为对应的整数表示 + public int toBinary() { + int binaryValue = 0; + binaryValue |= (required ? 1 : 0) << 3; + binaryValue |= (editable ? 1 : 0) << 2; + binaryValue |= (readonly ? 1 : 0) << 1; + binaryValue |= (hidden ? 1 : 0); + return binaryValue; + } + + // 从整数表示还原出对象 + public static FormPermissionMetaInfo fromBinary(String fieldId, String fieldName, int binaryValue) { + boolean required = ((binaryValue >> 3) & 1) == 1; + boolean editable = ((binaryValue >> 2) & 1) == 1; + boolean readonly = ((binaryValue >> 1) & 1) == 1; + boolean hidden = (binaryValue & 1) == 1; + return new FormPermissionMetaInfo(fieldId, fieldName, required, editable, readonly, hidden, null); + } + + public FormPermissionMetaInfo toReadonly() { + if (required || editable || readonly) { + setRequired(false); + setEditable(false); + setReadonly(true); + setHidden(false); + } + return this; + } + + public String toBinaryString() { + return String.format("%04d", Integer.parseInt(Integer.toBinaryString(toBinary()), 10)); + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormContentSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormContentSearchDTO.java new file mode 100644 index 000000000..cac8b9336 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormContentSearchDTO.java @@ -0,0 +1,50 @@ +package cn.axzo.workflow.common.model.request.form.definition; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Map; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 表单定义内容的响应模型 + * + * @author wangli + * @since 2024-11-11 14:23 + */ +@ApiModel("表单定义于内容的响应模型") +@Data +public class FormContentSearchDTO { + + /** + * 表单 KEY + */ + @ApiModelProperty(value = "表单唯一标识") + private String key; + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例 ID", example = "we", notes = "当 key 不知道时,可以用实例 ID 代替key") + private String processInstanceId; + + /** + * 流程任务 ID + */ + @ApiModelProperty(value = "流程任务 ID") + private String taskId; + + /** + * 变量集合 + */ + @ApiModelProperty(value = "额外变量") + private Map variables; + + /** + * 租户 ID + */ + @ApiModelProperty(value = "租户 ID") + private String tenantId = NO_TENANT_ID; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionCreateDTO.java new file mode 100644 index 000000000..1276892df --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionCreateDTO.java @@ -0,0 +1,46 @@ +package cn.axzo.workflow.common.model.request.form.definition; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import java.util.List; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 表单定义的更新入参模型 + * + * @author wangli + * @since 2023/7/25 14:21 + */ +@ApiModel("表单定义的更新入参模型") +@Data +public class FormDefinitionCreateDTO { + + @ApiModelProperty(value = "表单名称") + @NotBlank(message = "表单名称不能为空") + public String name; + + @ApiModelProperty(value = "版本号") + public Integer version; + + @ApiModelProperty(value = "表单标识") + @NotBlank(message = "表单标识不能为空") + private String key; + + @ApiModelProperty(value = "表单分类") + private String category; + + private FormDefinitionDTO formDefinition; + + @ApiModelProperty(value = "描述") + private String description; + + private String tenantId = NO_TENANT_ID; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionDTO.java index 35f18c66e..2ec301d7f 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionDTO.java @@ -3,6 +3,8 @@ package cn.axzo.workflow.common.model.request.form.definition; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; import java.util.List; /** @@ -13,16 +15,26 @@ import java.util.List; */ @Data public class FormDefinitionDTO { - @ApiModelProperty(value = "表单定义的key", hidden = true) - private String key; - @ApiModelProperty(value = "表单定义的名称", hidden = true) + /** + * 表单名称 + */ + @ApiModelProperty(value = "表单定义的名称") + @NotBlank(message = "表单定义名称不能为空") private String name; - @ApiModelProperty(value = "表单定义的版本", hidden = true) - private Integer version; + @ApiModelProperty(value = "表单定义的标识") + @NotBlank(message = "表单定义标识不能为空") + private String key; + + @ApiModelProperty(value = "表单的版本") + private int version; @ApiModelProperty(value = "表单定义的具体字段项", example = "[{'': '''}]") - private List fields; + @Valid + private List fields; + + @ApiModelProperty(value = "描述") + private String description; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionSearchDTO.java new file mode 100644 index 000000000..282e83c12 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionSearchDTO.java @@ -0,0 +1,33 @@ +package cn.axzo.workflow.common.model.request.form.definition; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 查询表单定义入参模型 + * + * @author wangli + * @since 2024-11-07 18:29 + */ +@ApiModel("表单定义的搜索入参模型") +@Data +public class FormDefinitionSearchDTO { + + @ApiModelProperty(value = "表单部署 ID") + private String deploymentId; + + @ApiModelProperty(value = "表单定义 KEY") + private String key; + + @ApiModelProperty(value = "表单定义版本") + private Integer version; + + @ApiModelProperty(value = "表单归属租户") + private String tenantId = NO_TENANT_ID; + + @ApiModelProperty(value = "表单关联的流程定义部署 ID") + private String parentDeploymentId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionUpdateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionUpdateDTO.java index c632a8b3f..1f067cc4c 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionUpdateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormDefinitionUpdateDTO.java @@ -3,27 +3,24 @@ package cn.axzo.workflow.common.model.request.form.definition; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.EqualsAndHashCode; import javax.validation.constraints.NotBlank; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + /** * 表单定义的更新入参模型 * * @author wangli * @since 2023/7/25 14:21 */ +@EqualsAndHashCode(callSuper = true) @ApiModel("表单定义的更新入参模型") @Data -public class FormDefinitionUpdateDTO { +public class FormDefinitionUpdateDTO extends FormDefinitionCreateDTO{ - @ApiModelProperty("表单模型ID") - @NotBlank(message = "表单模型 ID 不能为空") - public String formModelId; - - @ApiModelProperty(value = "表单定义内容") - private FormDefinitionDTO formDefinition; - - @NotBlank(message = "租户不能为空") - private String tenantId; + @ApiModelProperty(value = "表单定义 ID") + private String formModelId; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldDTO.java new file mode 100644 index 000000000..4a07d8a8c --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldDTO.java @@ -0,0 +1,86 @@ +package cn.axzo.workflow.common.model.request.form.definition; + + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; +import java.util.Map; + +/** + * 表单定义中的字段相关属性入参模型 + *

+ * 本模型不支持变更增加属性,只能放入 params 中 + * + * @author wangli + * @since 2023/7/20 10:39 + */ +@ApiModel("表单定义中的字段相关属性入参模型") +@Data +public class FormFieldDTO { + + /** + * 字段类型, 如果是分组:FormContainer,其他表单组件:FormField + */ + @ApiModelProperty(value = "表单字段类型") + @NotBlank(message = "字段类型不能为空") + private String fieldType = "FormField"; + + /** + * 前端组件类型 + *

+ * { label: "文本", value: "input" }, + * { label: "多行文本", value: "textarea" }, + * { label: "附件", value: "upload" }, + * { label: "图片", value: "image" }, + * { label: "自定义组件", value: "customComponent" }, + * { label: "任务单", value: "taskOrder" }, + * { label: "整改单", value: "rectifyOrder" }, + * { label: "变洽签单", value: "changeSignatureOrder" }, + * { label: "通讯录", value: "contacts" }, + * { label: "金额", value: "amount" }, + */ + @ApiModelProperty(value = "前端的组件类型") + private String type; + + /** + * 表单字段组件 ID + */ + @ApiModelProperty(value = "表单字段组件ID", example = "account") + @NotBlank(message = "表单字段组件 ID 不能为空") + private String id; + + /** + * 表单字段名称 + */ + @ApiModelProperty(value = "表单字段名称", example = "账号") + private String name; + + /** + * 部分组件是多值,比如日期区间,多选, 多附件 + */ + @ApiModelProperty(value = "表单字段默认值", example = "account") + private Object value; + + /** + * 表单占位提示 + */ + @ApiModelProperty(value = "表单字段占位提示") + private String placeholder; + + /** + * 表单的扩展属性,里面会包含required/editable/readonly/hidden/defaultValue 的 key + */ + @ApiModelProperty(value = "该表单字段的其他扩展属性,期望 JSON 格式") + private Map params; + + /** + * 当 fieldType=FormContainer 时,这里的代表内部的集合 + */ + @ApiModelProperty(value = "分组内的字段,配合 fieldType=FormContainer 使用") + private List> fields; +} + + diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldExtDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldExtDTO.java new file mode 100644 index 000000000..4fe96e93c --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldExtDTO.java @@ -0,0 +1,47 @@ +package cn.axzo.workflow.common.model.request.form.definition; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 表单字段扩展对象 + *

+ * 注意: FormField 类型中其实包含了一部分本类型的字段,但为了保持统一,将弃用外面的属性,仅以这里为准 + * + * @author wangli + * @since 2024-11-12 18:33 + */ +@Data +@ApiModel("表单字段扩展对象") +public class FormFieldExtDTO { + /** + * 字段可编辑 + */ + @ApiModelProperty(value = "可编辑") + private Boolean editable; + + /** + * 字段必填 + */ + @ApiModelProperty(value = "必填") + private Boolean required; + + /** + * 字段只读 + */ + @ApiModelProperty(value = "只读") + private Boolean readonly; + + /** + * 字段隐藏 + */ + @ApiModelProperty(value = "隐藏") + private Boolean hidden; + + /** + * 字段默认值 + */ + @ApiModelProperty(value = "默认值") + private String defaultValue; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldsDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldsDTO.java deleted file mode 100644 index b59efd052..000000000 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/FormFieldsDTO.java +++ /dev/null @@ -1,51 +0,0 @@ -package cn.axzo.workflow.common.model.request.form.definition; - - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import java.util.Map; - -/** - * 表单定义中的字段相关属性入参模型 - * - * @author wangli - * @since 2023/7/20 10:39 - */ -@ApiModel("表单定义中的字段相关属性入参模型") -@Data -public class FormFieldsDTO { - - @ApiModelProperty(value = "表单字段类型", hidden = true) - @NotBlank(message = "字段类型不能为空") - private transient String fieldType = "FormField"; - - @ApiModelProperty(value = "前端的组件类型") - private String type; - - @ApiModelProperty(value = "表单字段组件ID", example = "account") - @NotBlank(message = "表单字段组件 ID 不能为空") - private String id; - - @ApiModelProperty(value = "表单字段名称", example = "账号") - private String name; - - @ApiModelProperty(value = "表单字段默认值", example = "account") - private String value; - - @ApiModelProperty(value = "表单字段是否必填,默认 false", example = "true or false") - private Boolean required = false; - - @ApiModelProperty(value = "表单字段是否只读,默认 false", example = "true or false") - private Boolean readOnly = false; - - @ApiModelProperty(value = "表单字段占位提示") - private String placeholder; - - @ApiModelProperty(value = "该表单字段的其他扩展属性,期望 JSON 格式") - private Map params; -} - - diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/StartFormSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/StartFormSearchDTO.java new file mode 100644 index 000000000..6ec0231d1 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/definition/StartFormSearchDTO.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.common.model.request.form.definition; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 获取开始表单的模型入参 + * + * @author wangli + * @since 2024-11-14 16:27 + */ +@Data +@ApiModel("获取开始表单的模型入参") +public class StartFormSearchDTO { + /** + * 业务标识 + */ + @ApiModelProperty(value = "业务标识", notes = "也等于表单唯一标识,目前新建一个审批模型,表单和审批的标识都是一样的") + @NotBlank(message = "业务标识不能为空") + private String key; + + /** + * 租户 ID + *

+ * 对应安心筑的工作台 ID + */ + @ApiModelProperty(value = "租户 ID") + @NotBlank(message = "租户 ID (工作台)不能为空") + private String tenantId = NO_TENANT_ID; + + /** + * 显示原始默认值的变量 + */ + @ApiModelProperty(value = "是否显示原始的默认值", hidden = true, notes = "如果为 true 则将默认值中的 ${} 变量不进行替换") + private Boolean showOriginDefaultValue = false; + + @ApiModelProperty(value = "是否抛出内部异常") + private Boolean throwException = true; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormDetailDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormDetailDTO.java new file mode 100644 index 000000000..985b5906e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormDetailDTO.java @@ -0,0 +1,46 @@ +package cn.axzo.workflow.common.model.request.form.instance; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 表单详情响应模型 + * + * @author wangli + * @since 2024-11-14 14:22 + */ +@ApiModel("表单详情响应模型") +@Data +public class FormDetailDTO { + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例ID") + @NotBlank(message = "流程实例 ID 不能为空") + private String processInstanceId; + + /** + * 任务 ID + */ + @ApiModelProperty(value = "任务 ID") + private String taskId; + + /** + * 访问表单的人,该属性内部主要用于计算表单字段权限 + * 支持为 null,则表单所有字段默认全部隐藏 + */ + @ApiModelProperty(value = "访问表单的人") + private BpmnTaskDelegateAssigner assigner; + + /** + * 显示原始默认值的变量 + */ + @ApiModelProperty(value = "是否显示原始的默认值", notes = "如果为 true 则将默认值中的 ${} 变量不进行替换") + private Boolean showOriginDefaultValue = false; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormInstanceSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormInstanceSearchDTO.java new file mode 100644 index 000000000..0fb0f66fb --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormInstanceSearchDTO.java @@ -0,0 +1,53 @@ +package cn.axzo.workflow.common.model.request.form.instance; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; +import java.util.Map; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 表单实例数据响应模型 + * + * @author wangli + * @since 2024-11-11 15:26 + */ +@ApiModel("表单实例数据响应模型") +@Data +public class FormInstanceSearchDTO implements Serializable { + + /** + * 表单标识=业务标识 + */ + @ApiModelProperty(value = "表单定义 ID", hidden = true) + private transient String formDefinitionId; + + /** + * 实例 ID + */ + @ApiModelProperty(value = "实例 ID") + @NotBlank(message = "流程实例不能为空") + private String processInstanceId; + + /** + * 任务 ID + */ + @ApiModelProperty(value = "任务 ID") + private String taskId; + + /** + * 用于临时变更表单内容的变量,不对数据库生效,一些特殊场景可使用 + */ + @ApiModelProperty(value = "临时变更表单内容的变量", hidden = true) + private Map variables; + + /** + * 租户 ID + */ + @ApiModelProperty(value = "租户 ID", hidden = true) + private transient String tenantId = NO_TENANT_ID; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormSearchDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormSearchDTO.java new file mode 100644 index 000000000..5341465ab --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormSearchDTO.java @@ -0,0 +1,34 @@ +package cn.axzo.workflow.common.model.request.form.instance; + +import cn.axzo.workflow.common.model.request.BpmPageParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * 表单搜索入参模型 + * + * @author wangli + * @since 2024-11-11 19:55 + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel("表单搜索入参模型") +@Data +public class FormSearchDTO extends BpmPageParam { + + /** + * 业务标识 + */ + @ApiModelProperty("业务标识") + private String key; + + /** + * 租户 ID + */ + @ApiModelProperty(value = "租户 ID") + private String tenantId = NO_TENANT_ID; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormVariablesUpdateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormVariablesUpdateDTO.java new file mode 100644 index 000000000..b35a1674b --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/instance/FormVariablesUpdateDTO.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.common.model.request.form.instance; + +import cn.axzo.workflow.common.model.dto.UploadFieldDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; +import java.util.Map; + +/** + * 更新指定流程表单最后一次操作的表单内容的入参模型 + * + * @author wangli + * @since 2025-02-08 10:04 + */ +@ApiModel("更新指定流程表单最后一次操作的表单内容") +@Data +public class FormVariablesUpdateDTO implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 流程实例 ID + */ + @ApiModelProperty(value = "流程实例ID") + @NotBlank(message = "流程实例 ID 不能为空") + private String processInstanceId; + + /** + *

+ * 图片类型和附件类型组件:请用 @see {@link UploadFieldDTO} 对象集合传入 + *

+     *     // form_image 为表单项的 key, value 为 UploadFileDTO 对象集合,如果前端使用了组件,一般建议回传所有属性;特殊情况下可以只传 fileUrl
+     *     "form_image": [{
+     *          "fileName": "",
+     *          "fileUrl": "http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960",
+     *          "fileKey": 123
+     *      }]
+     * 
+ */ + @ApiModelProperty(value = "通过表单创建流程时传入的初始表单数据") + private Map formVariables; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/model/FormModelCreateDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/model/FormModelCreateDTO.java index ecd0fadf2..06dc847b6 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/model/FormModelCreateDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/request/form/model/FormModelCreateDTO.java @@ -1,11 +1,16 @@ package cn.axzo.workflow.common.model.request.form.model; +import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.hibernate.validator.constraints.Length; +import javax.validation.Valid; import javax.validation.constraints.NotBlank; +import java.util.List; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; /** * 表单模型创建入参模型 @@ -17,7 +22,7 @@ import javax.validation.constraints.NotBlank; @Data public class FormModelCreateDTO { - @ApiModelProperty(value = "表单标识", example = "form_key", hidden = true) + @ApiModelProperty(value = "表单标识", example = "form_key") @Length(max = 255, message = "表单标识最长只支持255个字符") @NotBlank(message = "流程表单 key 不能为空") private String key; @@ -27,11 +32,14 @@ public class FormModelCreateDTO { @Length(max = 255, message = "表单名称最长只支持255个字符") private String name; - @ApiModelProperty(value = "表单分类", example = "business_form_key") - private String category; +// @ApiModelProperty(value = "表单分类", example = "business_form_key") +// private String category; + + @ApiModelProperty(value = "表单模型的 Json 结构") + @Valid + private List formFields; @ApiModelProperty(value = "租户 ID", example = "1") - @NotBlank(message = "租户 ID 不能为空") - private String tenantId; + private String tenantId = NO_TENANT_ID; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/admin/ProcessAdminVo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/admin/ProcessAdminVo.java new file mode 100644 index 000000000..65ac53e18 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/admin/ProcessAdminVo.java @@ -0,0 +1,85 @@ +package cn.axzo.workflow.common.model.response.admin; + +import cn.axzo.workflow.common.enums.AdminDataSource; +import cn.axzo.workflow.common.enums.AdminRoleType; +import cn.axzo.workflow.common.enums.AdminTypeEnum; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +public class ProcessAdminVo { + + /** + * 管理员配置id + */ + @ApiModelProperty("管理员配置id") + private Long processAdminId; + + /** + * 自然人id + */ + @ApiModelProperty("自然人id") + private Long personId; + + /** + * 单位id + */ + @ApiModelProperty("单位id") + private Long organizationalUnitId; + + /** + * 工作台ID + */ + @ApiModelProperty("工作台ID") + private Long workspaceId; + + /** + * 工作台类型,1-企业, 2-项目, 3-政务监管平台, 6-oms工作台,参考 WorkspaceType枚举 + */ + @ApiModelProperty("工作台ID") + private Integer workspaceType; + + /** + * 管理员类型, super_admin-超级管理员, common_admin-普通管理员 + */ + @ApiModelProperty("管理员类型") + private AdminTypeEnum adminType; + + /** + * 角色类型, organization_admin-单位超管, organization_worksapce_admin-项目内单位负责人, workspace_admin-项目超管,other-其他用户 + */ + @ApiModelProperty("角色类型") + private AdminRoleType roleType; + + /** + * 数据来源, system-系统录入, user-用户手动录入 + */ + @ApiModelProperty("数据来源") + private AdminDataSource dataSource; + + /** + * 创建者 + */ + @ApiModelProperty("创建者") + private Long createBy; + + /** + * 创建时间 + */ + @ApiModelProperty("创建时间") + private Date createAt; + + /** + * 更新者 + */ + @ApiModelProperty("更新者") + private Long updateBy; + + /** + * 更新时间 + */ + @ApiModelProperty("更新时间") + private Date updateAt; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/BatchOperationItemResultVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/BatchOperationItemResultVO.java new file mode 100644 index 000000000..9385548c7 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/BatchOperationItemResultVO.java @@ -0,0 +1,43 @@ +package cn.axzo.workflow.common.model.response.bpmn; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 批量处理每一项明细的响应模型 + * + * @author wangli + * @since 2024/5/13 11:28 + */ +@Data +@ApiModel("批量处理每一项明细的响应模型") +public class BatchOperationItemResultVO { + + /** + * 被处理数据的 ID + * 可以是实例 ID,也可以是任务 ID,根据调用的 API 不同而不同 + */ + @ApiModelProperty("被处理数据的 ID") + private String id; + + /** + * 处理的数据类型: 业务审批, 流程审批 + */ + @ApiModelProperty("处理的数据类型: 业务审批, 流程审批") + private String type; + + /** + * 该流程实例处理过程中是否发生错误 + */ + @ApiModelProperty("该流程实例处理过程中是否发生错误") + private Boolean hasError = false; + + /** + * 具体的错误信息 + */ + @ApiModelProperty("具体的错误信息") + private String errorMessage; + +} + diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/BatchOperationResultVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/BatchOperationResultVO.java new file mode 100644 index 000000000..c931fb089 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/BatchOperationResultVO.java @@ -0,0 +1,37 @@ +package cn.axzo.workflow.common.model.response.bpmn; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 批量处理结果响应模型 + * + * @author wangli + * @since 2024/5/13 11:28 + */ +@Data +@ApiModel("批量处理结果响应模型") +public class BatchOperationResultVO { + + /** + * 请求数量 + */ + @ApiModelProperty(value = "请求数量") + private Integer requestCount = 0; + + /** + * 失败数量 + */ + @ApiModelProperty(value = "失败数量") + private Integer failCount = 0; + + /** + * 批量操作处理详情 + */ + @ApiModelProperty(value = "批量操作处理详情") + private List details; +} + diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/model/BpmnModelDetailVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/model/BpmnModelDetailVO.java index ce2a09c4e..2d6df8e43 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/model/BpmnModelDetailVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/model/BpmnModelDetailVO.java @@ -1,6 +1,7 @@ package cn.axzo.workflow.common.model.response.bpmn.model; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel; +import io.swagger.annotations.Api; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -28,6 +29,30 @@ public class BpmnModelDetailVO extends BpmnModelBaseVO { @ApiModelProperty(value = "启用状态") private Integer status; + /** + * 打印开关 + */ + @ApiModelProperty(value = "打印状态") + private Integer printStatus; + + /** + * 打印文件名称 + */ + @ApiModelProperty(value = "打印文件名称") + private String printFileName; + + /** + * 打印模板名称 + */ + @ApiModelProperty(value = "打印模板名称") + private String printTemplateName; + + /** + * 打印模板配置内容 + */ + @ApiModelProperty(value = "打印模板配置内容") + private String printTemplateConfig; + /** * 描述 */ diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/model/doc/DocBaseVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/model/doc/DocBaseVO.java new file mode 100644 index 000000000..7fc298914 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/model/doc/DocBaseVO.java @@ -0,0 +1,97 @@ +package cn.axzo.workflow.common.model.response.bpmn.model.doc; + +import cn.axzo.workflow.common.enums.FileTypeEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 基础的流程关联文档的响应模型 + * + * @author wangli + * @since 2025-03-31 09:49 + */ +@ApiModel("基础的流程关联文档的响应模型") +@Data +@NoArgsConstructor +public class DocBaseVO { + + private Long id; + + /** + * 模型 ID + */ + @ApiModelProperty(value = "模型 ID") + private String modelId; + + /** + * 模型 KEY + */ + @ApiModelProperty(value = "模型 KEY") + private String modelKey; + + /** + * 文档对应文件的检索信息 + * word/excel 对应 wps 的 Id, + * hp 对应是 + * pdf 对应 oss 中的 fileKey + */ + @ApiModelProperty(value = "文档对应文件检索信息") + private String fileRelationId; + + /** + * 文档名称(用于归档) + */ + @ApiModelProperty(value = "文档名称") + private String fileName; + + /** + * 文档模板名称 + */ + @ApiModelProperty(value = "模板名称") + private String templateName; + + /** + * 归档位置 + */ + private String location; + + /** + * 文档类型 + */ + @ApiModelProperty(value = "文档类型") + private FileTypeEnum fileType; + + /** + * 文档业务类型 + */ + @ApiModelProperty(value = "业务类型") + private String tag; + + /** + * 文档状态 + */ + @ApiModelProperty(value = "文档状态") + private Boolean status; + + @ApiModelProperty(value = "是否是临时文档") + private Boolean tempFile; + /** + * 文档是否必选 + */ + @ApiModelProperty(value = "是否必选") + private Boolean require; + + /** + * 排序号 + */ + @ApiModelProperty(value = "排序号") + private Integer order; + + /** + * 租户 ID + */ + @ApiModelProperty(value = "租户 ID") + private String tenantId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnNodeConfigVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnNodeConfigVO.java new file mode 100644 index 000000000..e329c2c14 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnNodeConfigVO.java @@ -0,0 +1,88 @@ +package cn.axzo.workflow.common.model.response.bpmn.process; + +import cn.axzo.workflow.common.enums.ApprovalMethodEnum; +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.BpmnSignApproverLimit; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 发起流程前的节点列表中每个节点的模型配置信息 + * + * @author wangli + * @since 2025-03-26 09:40 + */ +@ApiModel("发起流程前的节点列表中每个节点的模型配置信息") +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class BpmnNodeConfigVO { + /** + * 审批方式, 审批节点与业务节点会有不同 + *

+ * human("human", "人工审批", ""), + * autoPassed("autoPassed", "自动通过", "[仅审批节点可能选该值]"), + * autoRejection("autoRejection", "自动驳回", "[仅审批节点可能选该值]"), + * nobody("nobody", "不设置审批人", "[仅业务节点可能有该值]"), + * bizSpecify("bizSpecify", "业务指定审批人", "[仅业务节点可能有该值]"), + */ + @ApiModelProperty(value = "审批方式") + private ApprovalMethodEnum approvalMethodEnum; + + /** + * 审批人所在范围 + *

+ * entWorkspace("entWorkspace", "企业工作台", "entWorkspaceProcessor"), + * govWorkspace("govWorkspace", "政务工作台", "govWorkspaceProcessor"), + * projectWorkspace("projectWorkspace", "项目工作台","projectWorkspaceProcessor"), + * preTaskUser("preTaskUser", "上节点审批人所在单位","preTaskUserProcessor"), + * preTaskSpecified("preTaskSpecified", "上节点审批人指定","preTaskUserProcessor"), + */ + @ApiModelProperty(value = "审批人所在范围") + private ApproverScopeEnum approverScopeEnum; + + /** + * 审批人指定 + *

+ * position("position", "指定岗位"), + * role("role", "指定角色"), + * identity("identity", "指定身份"), + * fixedPerson("fixedPerson", "固定人员"), 如果节点是该类型,则该节点下的人,会默认回显至该上级模型属性中 + * initiatorLeader("initiatorLeader", "发起人主管"), + * initiatorSpecified("initiatorSpecified", "发起人自选"), + */ + @ApiModelProperty(value = "审批人指定") + private ApproverSpecifyEnum approverSpecifyEnum; + + /** + * 审批人为空时的处理方式 + *

+ * autoPassed("autoPassed", "自动通过"), + * autoRejection("autoRejection", "自动驳回"), + * autoSkipped("autoSkipped", "自动跳过"), + * transferToAdmin("transferToAdmin", "转交给管理员"), + * specifyAssignee("specifyAssignee", "指定审批人"), + */ + @ApiModelProperty(value = "审批人为空时的处理方式") + private ApproverEmptyHandleTypeEnum approverEmptyHandleTypeEnum; + + /** + * 是否开启电子签名 + */ + @ApiModelProperty(value = "是否开启电子签名") + private Boolean signature; + + /** + * 签署确认节点审批人限制规则 + */ + @ApiModelProperty(value = "签署确认节点审批人限制规则") + private BpmnSignApproverLimit signApproverLimit; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessDefinitionVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessDefinitionVO.java index ac36be4f8..33ef85472 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessDefinitionVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessDefinitionVO.java @@ -2,12 +2,14 @@ package cn.axzo.workflow.common.model.response.bpmn.process; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotEmpty; import java.util.Date; +import java.util.List; @ApiModel("流程定义响应模型") @Data @@ -57,6 +59,11 @@ public class BpmnProcessDefinitionVO { @ApiModelProperty(value = "状态 1:生效中(激活) 2:历史(挂起)", required = true, example = "1", notes = "参见 SuspensionState 枚举") private Integer suspensionState; + /** + * 表单字段集合 + */ + @ApiModelProperty(value = "表单字段集合") + private List formFields; /** * 流程模型 JSON 数据 */ @@ -81,4 +88,10 @@ public class BpmnProcessDefinitionVO { @ApiModelProperty(value = "操作人信息") private BpmnTaskDelegateAssigner operator; + /** + * 部署 ID + */ + @ApiModelProperty(value = "部署 ID") + private String deploymentId; + } 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..8c9689523 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceLogVO.java @@ -0,0 +1,165 @@ +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 = "能否打印") + private Boolean catPrint; + + @ApiModelProperty(value = "程序计算按钮使用,非对外使用", hidden = true) + private transient BpmnButtonConf calculatingButtonConf; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceVO.java index 6612b6160..80c4fb378 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceVO.java @@ -3,6 +3,7 @@ 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.BpmnApproveConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import io.swagger.annotations.ApiModel; @@ -34,9 +35,15 @@ public class BpmnProcessInstanceVO { /** * 流程分类 */ - @ApiModelProperty(value = "流程分类", notes = "参见 bpm_model_category 数据字典", example = "1") + @ApiModelProperty(value = "流程分类", notes = "是审批业务的 value", example = "1") private String category; + /** + * 流程分类名称 + */ + @ApiModelProperty(value = "流程分类名称", notes = "是审批业务的 label", example = "在场管理") + private String categoryDesc; + /** * 审核状态 */ @@ -97,6 +104,12 @@ public class BpmnProcessInstanceVO { @ApiModelProperty(value = "流程模型全局兜底的按钮配置信息") private BpmnButtonConf defaultButtonConf; + /** + * 流程模型审批相关配置 + */ + @ApiModelProperty(value = "流程模型审批相关配置") + private BpmnApproveConf processApproveConf; + /** * 发起人信息 */ diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/ExtProcessLogVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/ExtProcessLogVO.java new file mode 100644 index 000000000..ccb8bb63e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/ExtProcessLogVO.java @@ -0,0 +1,53 @@ +package cn.axzo.workflow.common.model.response.bpmn.process; + +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.model.dto.OrgStructureSnapshotInfo; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 审批扩展日志表的响应模型 + * + * @author wangli + * @since 2025-07-08 10:09 + */ +@ApiModel("流程实例日志表响应模型") +@Data +public class ExtProcessLogVO { + @ApiModelProperty("日志表主键 ID") + private Long id; + + @ApiModelProperty("流程实例 ID") + private String processInstanceId; + + @ApiModelProperty("流程归属的租户 ID(工作台 ID)") + private String processTenantId; + + @ApiModelProperty("审批节点 ID") + private String activityId; + + @ApiModelProperty("审批节点名称") + private String activityName; + + @ApiModelProperty("审批任务 ID") + private String taskId; + + @ApiModelProperty("审批人自然人 ID") + private String assigneePersonId; + + @ApiModelProperty("审批人租户 ID(工作台 ID)") + private String assigneeTenantId; + + @ApiModelProperty("审批人姓名") + private String assigneeName; + + @ApiModelProperty("审批人单位 ID") + private String assigneeOuId; + + @ApiModelProperty("日志的处理状态") + private BpmnProcessInstanceResultEnum status; + + @ApiModelProperty("审批人在接受到审批任务时的组织快照信息") + private OrgStructureSnapshotInfo orgStructureSnapshotInfo; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/NodesByModelVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/NodesByModelVO.java new file mode 100644 index 000000000..7e9cdde46 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/NodesByModelVO.java @@ -0,0 +1,69 @@ +package cn.axzo.workflow.common.model.response.bpmn.process; + +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 io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 模型节点集合响应模型 + * + * @author wangli + * @since 2025-01-15 10:40 + */ +@ApiModel("模型节点集合响应模型") +@Data +@Accessors(chain = true) +public class NodesByModelVO { + + /** + * 节点 ID + */ + @ApiModelProperty(value = "节点 ID") + private String activityId; + + /** + * 节点名称 + */ + @ApiModelProperty(value = "节点名称") + private String activityName; + + /** + * 是否支持发起人自选人 + */ + @ApiModelProperty(value = "是否支持发起自选人") + private Boolean supportSpecify; + + /** + * 节点的人 + */ + private List assigners; + /** + * 节点模式(会、或签,普通节点) + */ + @ApiModelProperty(value = "节点模式(会、或签,普通节点)") + private BpmnFlowNodeMode nodeModel; + + /** + * 节点类型, 只要是以下类型 + *

+ * NODE_STARTER("NODE_STARTER", "发起人节点"), + * NODE_TASK("NODE_TASK", "审核节点"), + * NODE_BUSINESS("NODE_BUSINESS", "业务节点"), + * NODE_SIGN("NODE_SIGN", "签署确认节点"), + * NODE_CARBON_COPY("NODE_CARBON_COPY", "抄送节点"), + */ + @ApiModelProperty(value = "节点类型") + private BpmnFlowNodeType nodeType; + + /** + * 节点原始配置信息 + */ + @ApiModelProperty(value = "节点原始配置") + private BpmnNodeConfigVO nodeConfig; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/ProcessNodeDetailVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/ProcessNodeDetailVO.java index 0080dabd2..145e113eb 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/ProcessNodeDetailVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/ProcessNodeDetailVO.java @@ -4,6 +4,7 @@ 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.model.request.bpmn.BpmnButtonConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnUpgradeApprovalConf; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import lombok.Data; import lombok.experimental.Accessors; @@ -56,4 +57,13 @@ public class ProcessNodeDetailVO { */ private List forecastAssigners; + /** + * 提级审批配置 + */ + private BpmnUpgradeApprovalConf upgradeApprovalConf; + + /** + * 是未来节点 + */ + private Boolean futureNode; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/doc/DocPendingVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/doc/DocPendingVO.java new file mode 100644 index 000000000..78d5ac6bf --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/doc/DocPendingVO.java @@ -0,0 +1,47 @@ +package cn.axzo.workflow.common.model.response.bpmn.process.doc; + +import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 基础的流程关联文档待办中的响应模型 + * + * @author wangli + * @since 2025-03-31 09:49 + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel("基础的流程关联文档待办中的响应模型") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DocPendingVO extends DocBaseVO { + + /** + * oss 的 fileKey + */ + @ApiModelProperty(value = "oss 的 fileKey") + private String fileKey; + + /** + * 文件 OBS 地址 + */ + @ApiModelProperty(value = "文件 OBS 地址") + private String ossUrl; + + /** + * 文件大小,单位 Byte + */ + @ApiModelProperty(value = "文件大小,单位 byte") + private Long storageSize; + + /** + * 文档已读状态,true:已读 + */ + @ApiModelProperty(value = "文档已读状态,true:已读") + private Boolean readStatus; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskButtonVo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskButtonVo.java new file mode 100644 index 000000000..42d2f722d --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskButtonVo.java @@ -0,0 +1,60 @@ +package cn.axzo.workflow.common.model.response.bpmn.task; + +import cn.axzo.workflow.common.enums.BpmnProcessTaskResultEnum; +import cn.axzo.workflow.common.enums.ButtonVisibleScopeEnum; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; +import io.swagger.annotations.ApiModelProperty; +import lombok.*; + +import java.io.Serializable; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BpmnTaskButtonVo implements Serializable { + + private static final long serialVersionUID = -2701890396246979758L; + + /** + * 执行人任务状态 + */ + @ApiModelProperty(value = "执行人任务执行状态") + private BpmnProcessTaskResultEnum executorTaskResult; + + /** + * 发起人执行状态 + */ + @ApiModelProperty(value = "发起人任务执行状态") + private BpmnProcessTaskResultEnum initiatorTaskResult; + + /** + * 当前任务执行人以及发起人全量按钮 + */ + @ApiModelProperty(value = "当前任务相关以及发起人的全量按钮") + private List buttons; + + /** + * 需要隐藏的按钮 + */ + @ApiModelProperty(value = "需要隐藏的自定义按钮") + private List customHiddenButtons; + + /** + * 全量的配置按钮 + */ + @ApiModelProperty(value = "全量的配置按钮") + private List allConfigButtons; + + @Data + @EqualsAndHashCode(callSuper = true) + @AllArgsConstructor + @NoArgsConstructor + public static final class BpmnButtonMetaInfoWithVisibleScope extends BpmnButtonMetaInfo { + + private static final long serialVersionUID = 5758633314769798044L; + + private List visibleScopes; + } +} 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..d4322ce5f --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskInstanceLogVO.java @@ -0,0 +1,140 @@ +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.BpmnUpgradeApprovalConf; +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 Boolean signature; + /** + * 审批人快照信息 + */ + @ApiModelProperty(value = "审批人快照信息") + private BpmnTaskDelegateAssigner assigneeSnapshot; + /** + * 未完成节点多实例模式的审批人信息 + */ + @ApiModelProperty(value = "未完成节点多实例模式的审批人信息") + private List forecastAssignees; + + /** + * 提级审批配置 + */ + @ApiModelProperty(value = "提级审批配置") + private BpmnUpgradeApprovalConf upgradeApprovalConf; + + @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/category/CategoryGroupVarItemVo.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/category/CategoryGroupVarItemVo.java new file mode 100644 index 000000000..128c52c51 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/category/CategoryGroupVarItemVo.java @@ -0,0 +1,75 @@ +package cn.axzo.workflow.common.model.response.category; + + +import cn.axzo.workflow.common.enums.VarTypeEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CategoryGroupVarItemVo implements Serializable { + + private static final long serialVersionUID = -3349551294678140220L; + + /** + * 分组id + */ + private Long id; + + /** + * 上级分组id + */ + private Long parentGroupId; + + /** + * 分组名 + */ + private String groupName; + + /** + * 当前分组对应的变量 + */ + private List vars; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class CategoryVarItemVo implements Serializable { + + private static final long serialVersionUID = -1918203166603971593L; + + /** + * 变量id + */ + private Long id; + + /** + * 分组id + */ + private Long groupId; + + /** + * 变量类型,文本/图片 + */ + private VarTypeEnum type; + + /** + * 变量code + */ + private String code; + + /** + * 变量名称 + */ + private String name; + + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/category/CategoryItemVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/category/CategoryItemVO.java index b106432aa..63c39692c 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/category/CategoryItemVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/category/CategoryItemVO.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.common.model.response.category; +import cn.axzo.workflow.common.enums.BusinessTypeEnum; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -34,6 +35,15 @@ public class CategoryItemVO { @ApiModelProperty(value = "工作台类型值") private String workspaceTypeCode; + @ApiModelProperty(value = "图标") + private String icon; + + @ApiModelProperty(value = "业务类型") + private BusinessTypeEnum businessType; + + @ApiModelProperty(value = "是否展示在发起工作台") + private Boolean displayInitiateMenu; + @ApiModelProperty(value = "更新时间") private Date updateAt; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/es/ProcessInstanceDocumentVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/es/ProcessInstanceDocumentVO.java new file mode 100644 index 000000000..fe059f4bc --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/es/ProcessInstanceDocumentVO.java @@ -0,0 +1,97 @@ +package cn.axzo.workflow.common.model.response.es; + +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.util.Date; + +/** + * 搜索 ES 的流程实例相关数据模型 + * + * @author wangli + * @since 2024-10-09 14:26 + */ +@Data +@ApiModel("搜索 ES 的流程实例相关数据模型") +public class ProcessInstanceDocumentVO { + + /** + * 流程实例 ID + */ + private String instanceId; + + /** + * 流程实例名称 + */ + private String processInstanceName; + + /** + * 业务传入业务信息 + */ + private String businessKey; + + /** + * 流程实例使用的定义 ID + */ + private String processDefinitionId; + + /** + * 流程对应业务分类的类型,项目/单位/监管/OMS + */ + private String processCategoryType; + + /** + * 流程实例的发起时间 + */ + private Date instanceStartTime; + + /** + * 流程实例的结束时间 + */ + private Date instanceEndTime; + + /** + * 流程实例的持续时间 ms + */ + private Long durationInMillis; + + /** + * 该流程中上次操作完成的时间, 如果是已完成的实例, 就清空该值 + */ + private Date lastOperationTime; + + /** + * 租户 ID + */ + private String instanceTenantId; + + /** + * 流程实例业务状态 + */ + private String businessStatus; + + /** + * 发起人姓名 + */ + private String initiatorName; + + /** + * 实例对应的流程引擎服务端迭代版本 + */ + private String workflowEngineVersion; + + /** + * 是否代运营 + */ + private Boolean agent; + + /** + * 是否支持批量操作任务 + */ + private Boolean supportBatch; + + /** + * 是否开启同意操作时的签名 + */ + private Boolean userAgreeSignature; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/es/ProcessTaskDocumentVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/es/ProcessTaskDocumentVO.java new file mode 100644 index 000000000..ee6d28342 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/es/ProcessTaskDocumentVO.java @@ -0,0 +1,107 @@ +package cn.axzo.workflow.common.model.response.es; + +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.util.Date; + +/** + * 搜索 ES 的流程任务相关数据模型 + * + * @author wangli + * @since 2024-10-12 21:53 + */ +@Data +@ApiModel("搜索 ES 的流程任务相关数据模型") +public class ProcessTaskDocumentVO { + + /** + * 任务 ID + */ + private String taskId; + + /** + * 所属实例 ID + */ + private String processInstanceId; + + /** + * 任务定义 KEY,对应节点 ID + */ + private String taskDefinitionKey; + + /** + * 任务名称 + */ + private String taskName; + + /** + * 任务状态:审批中/通过/驳回/转交/加签... + */ + private String taskStatus; + + /** + * 操作建议 + */ + private String advice; + + /** + * 操作描述 + */ + private String operationDesc; + + /** + * 流程实例的发起时间 + */ + private Date taskStartTime; + + /** + * 流程实例的结束时间 + */ + private Date taskEndTime; + + /** + * 流程实例的持续时间 ms + */ + private Long duration; + + /** + * 归属租户,与 processInstance#tenantId 一致 + */ + private String taskTenantId; + + /** + * 审批人姓名 + */ + private String assigneeName; + + /** + * 审批人自然人 ID + */ + private String assigneePersonId; + + /** + * 审批人所属单位 ID + */ + private String assigneeOuId; + + /** + * 审批人所属租户 ID + */ + private String assigneeTenantId; + + /** + * 审批方式:配置审批人/业务指定/业务触发(不含人) + */ + private String approvalMethod; + + /** + * 节点类型:审批节点/业务节点/评论节点/抄送节点 + */ + private String nodeType; + + /** + * 节点模式:会签/或签 + */ + private String nodeMode; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/FormVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/FormVO.java new file mode 100644 index 000000000..025cb7ebe --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/FormVO.java @@ -0,0 +1,41 @@ +package cn.axzo.workflow.common.model.response.form; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 表单相应模型 + * + * @author wangli + * @since 2024-11-11 20:01 + */ +@ApiModel("表单响应模型") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FormVO { + + /** + * 业务标识 + */ + @ApiModelProperty(value = "业务标识") + private String key; + + /** + * 业务名称 + */ + @ApiModelProperty(value = "业务名称") + private String name; + + /** + * 租户 ID + */ + @ApiModelProperty(value = "租户 ID") + private String tenantId; + +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/definition/FormDefinitionVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/definition/FormDefinitionVO.java index c20bbb6ef..75ac226a7 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/definition/FormDefinitionVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/definition/FormDefinitionVO.java @@ -1,6 +1,6 @@ package cn.axzo.workflow.common.model.response.form.definition; -import cn.axzo.workflow.common.model.request.form.definition.FormFieldsDTO; +import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -16,22 +16,40 @@ import java.util.List; @ApiModel("表单定义的响应模型") @Data public class FormDefinitionVO { + /** + * 模型定义 ID + */ + @ApiModelProperty(value = "模型定义 ID") + private String formDefinitionId; + + /** + * 模型名称 + */ @ApiModelProperty(value = "模型名称") private String name; + /** + * 模型标识 + */ @ApiModelProperty(value = "模型 KEY") private String key; - @ApiModelProperty(value = "自定义分类") - private String category; - + /** + * 版本 + */ @ApiModelProperty(value = "版本") private Integer version; + /** + * 租户 ID + */ @ApiModelProperty(value = "租户") private String tenantId; + /** + * 表单模型字段 + */ @ApiModelProperty(value = "表单模型字段") - private List fields; + private List fields; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/instance/FormInstanceVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/instance/FormInstanceVO.java new file mode 100644 index 000000000..fd356056c --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/instance/FormInstanceVO.java @@ -0,0 +1,78 @@ +package cn.axzo.workflow.common.model.response.form.instance; + +import cn.axzo.workflow.common.model.response.form.model.FormModelVO; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.util.Date; + +/** + * 表单实例的响应模型 + * + * @author wangli + * @since 2024-11-14 10:05 + */ +@ApiModel("表单实例的响应模型") +@Data +public class FormInstanceVO { + /** + * 表单定义 ID + */ + private String id; + + /** + * 表单定义名称 + */ + private String name; + + /** + * 表单唯一标识 + */ + private String key; + + /** + * 表单定义版本 + */ + private Integer version; + + /** + * 表单模型数据 + */ + private FormModelVO formModel; + + /** + * 表单实例 ID + */ + private String formInstanceId; + + /** + * 表单数据操作人,格式:"ouId|personId" + * 示例数据:"5144|43893" + */ + private String submittedBy; + + /** + * 表单提交时间 + */ + private Date submittedDate; + + /** + * 这个表单数据关联的任务,如果是发起时提交的表单,则 taskId 为空 + */ + private String taskId; + + /** + * 流程实例 ID + */ + private String processInstanceId; + + /** + * 流程定义 ID + */ + private String processDefinitionId; + + /** + * 租户 ID + */ + private String tenantId; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/model/FormModelVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/model/FormModelVO.java new file mode 100644 index 000000000..e869c37ad --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/form/model/FormModelVO.java @@ -0,0 +1,35 @@ +package cn.axzo.workflow.common.model.response.form.model; + +import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +import java.util.List; + +/** + * 表单模型响应模型 + * + * @author wangli + * @since 2024-11-14 10:10 + */ +@ApiModel("表单模型响应模型") +@Data +public class FormModelVO { + /** + * 表单模型 ID + */ + private String id; + /** + * 表单模型名称 + */ + private String name; + /** + * 表单模型标识 + */ + private String key; + private Integer version; + /** + * 表单字段 + */ + private List fields; +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/MessagePushDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/MessagePushDTO.java index 7dd744432..184614bf7 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/MessagePushDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/MessagePushDTO.java @@ -1,6 +1,8 @@ package cn.axzo.workflow.common.model.response.mq; import cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum; +import cn.axzo.workflow.common.model.request.BpmnApproveConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import lombok.Data; import lombok.experimental.Accessors; @@ -29,6 +31,16 @@ public class MessagePushDTO implements Serializable { */ private String processInstanceId; + /** + * 流程归属的租户 ID + */ + private String adscriptionTenantId; + + /** + * 业务 ID + */ + private String processDefinitionKey; + /** * 流程任务 ID */ @@ -49,4 +61,23 @@ public class MessagePushDTO implements Serializable { */ private Map variables; + /** + * 可用按钮集合 + */ + private List buttons; + + /** + * 流程高级配置 + */ + private BpmnApproveConf processApproveConf; + + /** + * 节点电子签开关 + */ + private Boolean activitySignature; + /** + * 催办专用属性,催办时的终端类型(管理端、工人端) + */ + private String terminalType; + } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/ProcessInstanceDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/ProcessInstanceDTO.java index f1e193c1a..b36007eca 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/ProcessInstanceDTO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/ProcessInstanceDTO.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.common.model.response.mq; import cn.axzo.workflow.common.enums.ProcessInstanceEventEnum; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import lombok.Data; import lombok.experimental.Accessors; @@ -102,6 +103,11 @@ public class ProcessInstanceDTO implements Serializable { */ private BpmnNoticeConf noticeConf; + /** + * 模型中的签署配置 + */ + private BpmnSignConf signConf; + /** * 当前数据的流程引擎版本 */ 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 a3db4b04c..d356f394a 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; @@ -28,8 +29,11 @@ public class ProcessTaskDTO implements Serializable { private ProcessTaskEventEnum type; /** + * 请使用 {@link ProcessTaskDTO#processDefinitionKey} 代替访问此属性,未来将逐步废弃 + *

* 流程实例所属业务分类,同时也等于流程模型对应的业务分类 ID */ + @Deprecated private String category; /** @@ -41,6 +45,14 @@ public class ProcessTaskDTO implements Serializable { * 流程实例 ID */ private String processInstanceId; + /** + * 流程实例的businessKey + */ + private String businessKey; + /** + * 流程实例所属业务分类,同时也等于流程模型对应的业务分类 ID + */ + private String processDefinitionKey; /** * 流程定义中当前任务节点 Key @@ -82,6 +94,16 @@ public class ProcessTaskDTO implements Serializable { */ private BpmnTaskDelegateAssigner approver; + /** + * 被转交人信息 + */ + private BpmnTaskDelegateAssigner transferTargetApprover; + + /** + * 转交意见 + */ + private String transferAdvice; + /** * 通知方式 */ @@ -96,4 +118,13 @@ public class ProcessTaskDTO implements Serializable { * 当前数据的流程引擎版本 */ private String workflowEngineVersion; + /** + * 任务关联的附件 + */ + private List attachments; + + /** + * 审批意见 + */ + private String advice; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/WorkflowEngineStarterRpcInvokeDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/WorkflowEngineStarterRpcInvokeDTO.java new file mode 100644 index 000000000..de160efff --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/mq/WorkflowEngineStarterRpcInvokeDTO.java @@ -0,0 +1,54 @@ +package cn.axzo.workflow.common.model.response.mq; + +import java.io.Serializable; +import java.util.List; + +/** + * Starter RPC 调用事件模型 + * + * @author wangli + * @since 2024/5/30 11:21 + */ +public class WorkflowEngineStarterRpcInvokeDTO implements Serializable { + private static final long serialVersionUID = -1L; + + private String className; + + private String methodName; + + private List parameters; + + private String parameterTypesMd5; + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + public String getParameterTypesMd5() { + return parameterTypesMd5; + } + + public void setParameterTypesMd5(String parameterTypesMd5) { + this.parameterTypesMd5 = parameterTypesMd5; + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/print/PrintModelDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/print/PrintModelDTO.java new file mode 100644 index 000000000..6bdbc1f2e --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/print/PrintModelDTO.java @@ -0,0 +1,38 @@ +package cn.axzo.workflow.common.model.response.print; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 打印模板响应模型 + * + * @author wangli + * @since 2025-02-11 10:41 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PrintModelDTO { + + /** + * 打印文件名称 + */ + @ApiModelProperty(value = "打印文件名称") + private String printFileName; + + /** + * 打印模板名称 + */ + @ApiModelProperty(value = "打印模板名称") + private String printTemplateName; + + /** + * 打印模板内容(前端打印组件的生成的完全数据) + */ + @ApiModelProperty(value = "打印模板内容") + private String printTemplateConfig; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnNativeQueryUtil.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/BpmnNativeQueryUtil.java similarity index 95% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnNativeQueryUtil.java rename to workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/BpmnNativeQueryUtil.java index d28360c4a..c0f7b4f24 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnNativeQueryUtil.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/BpmnNativeQueryUtil.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.common.utils; +package cn.axzo.workflow.common.util; /** * @author wangli diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/ExpressionUtil.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/ExpressionUtil.java new file mode 100644 index 000000000..0b0dd9665 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/ExpressionUtil.java @@ -0,0 +1,40 @@ +package cn.axzo.workflow.common.util; + +import org.springframework.util.PropertyPlaceholderHelper; +import org.springframework.util.StringUtils; + +import java.util.Map; +import java.util.Properties; + +/** + * 处理字符串中含有变量的字符串 + * + * @author wangli + * @since 2024-11-15 17:57 + */ +public abstract class ExpressionUtil { + private static final String PREFIX = "${"; + private static final String SUFFIX = "}"; + + private ExpressionUtil() { + throw new RuntimeException("Instantiation not supported"); + } + + public static String parseString(String exp, Map variables) { + return parseString(null, null, exp, variables); + } + + public static String parseString(String prefix, String suffix, String exp, Map variables) { + if (!StringUtils.hasText(prefix) && !StringUtils.hasText(suffix)) { + prefix = PREFIX; + suffix = SUFFIX; + } + PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper(prefix, suffix); + Properties properties = new Properties(); + variables.forEach((k, v) -> properties.setProperty(k, String.valueOf(v))); + + // 自定义PlaceholderResolver实现类,用于处理占位符找不到对应值时返回空字符串 + PropertyPlaceholderHelper.PlaceholderResolver resolver = placeholder -> properties.getProperty(placeholder, ""); + return propertyPlaceholderHelper.replacePlaceholders(exp, resolver); + } +} diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/ThreadUtil.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/ThreadUtil.java new file mode 100644 index 000000000..42b09f932 --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/util/ThreadUtil.java @@ -0,0 +1,27 @@ +package cn.axzo.workflow.common.util; + + +import cn.axzo.workflow.common.enums.RpcInvokeModeEnum; + +/** + * 父子线程工具类 + * + * @author wangli + * @since 2024/5/29 23:57 + */ +public class ThreadUtil { + + final static InheritableThreadLocal threadLocal = new InheritableThreadLocal<>(); + + public static void set(RpcInvokeModeEnum rpcInvokeMode) { + threadLocal.set(rpcInvokeMode); + } + + public static RpcInvokeModeEnum get() { + return threadLocal.get(); + } + + public static void clear() { + threadLocal.remove(); + } +} diff --git a/workflow-engine-core/pom.xml b/workflow-engine-core/pom.xml index ee2d63944..d449a41c3 100644 --- a/workflow-engine-core/pom.xml +++ b/workflow-engine-core/pom.xml @@ -9,8 +9,7 @@ ${revision} workflow-engine-core jar - - workflow-engine-core + Workflow Engine Core 6.7.2 2.0.0-SNAPSHOT @@ -39,6 +38,10 @@ hutool-all provided + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + - + org.flowable @@ -86,13 +89,30 @@ cn.axzo.workflow workflow-engine-api + cn.axzo.workflow workflow-engine-common + + + cn.axzo.workflow + workflow-engine-form + + 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/BpmnModelRespCode.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnModelRespCode.java deleted file mode 100644 index 2f5f12a2e..000000000 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/code/BpmnModelRespCode.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.axzo.workflow.core.common.code; - -import cn.axzo.framework.domain.web.code.IModuleRespCode; -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 流程模型响应码 - * - * @author wangli - * @since 2023/12/14 16:25 - */ -@Getter -@AllArgsConstructor -public enum BpmnModelRespCode implements IModuleRespCode { - MODEL_KEY_EXISTS("001", "已经存在流程标识为【{}】的流程"), - MODEL_ID_NOT_EXISTS("002", "流程模型ID【{}】不存在"), - MODEL_KEY_NOT_EXISTS("003", "流程模型KEY【{}】不存在"), - MODEL_NOT_EXISTS("004", "流程模型不存在"), - BPMN_BYTES_NOT_EXISTS("005", "模型定义内容字节码不存在"), - ; - - private String code; - private String message; - - @Override - public String getModuleCode() { - return "03"; - } - - @Override - public String getProjectCode() { - return "998"; - } - - public void setCode(String code) { - this.code = code; - } - - - public void setMessage(String message) { - this.message = message; - } - -} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/ActivityOperationContext.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/ActivityOperationContext.java new file mode 100644 index 000000000..802dfac33 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/ActivityOperationContext.java @@ -0,0 +1,10 @@ +package cn.axzo.workflow.core.common.context; + +/** + * 活动(节点)相关操作上下文 + * + * @author wangli + * @since 2024/4/12 16:43 + */ +public class ActivityOperationContext extends CommonContext { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/CommonContext.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/CommonContext.java new file mode 100644 index 000000000..02eeb6713 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/CommonContext.java @@ -0,0 +1,98 @@ +package cn.axzo.workflow.core.common.context; + +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.bpmn.process.BpmnProcessInstanceVO; +import org.flowable.bpmn.model.Process; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.util.StringUtils; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * 事件处理器中通用的上下文 + * + * @author wangli + * @since 2024/4/9 21:00 + */ +public abstract class CommonContext implements OperationContext { + private T context; + private ProcessInstance processInstance; + private Deployment deployment; + private Process process; + private BpmnTaskDelegateAssigner initiator; + private BpmnProcessInstanceVO instanceVO; + private BpmnProcessDefinitionVO definitionVO; + private String processInstanceVersion; + private BpmnTaskDelegateAssigner lastOperationAssigner; + + public ProcessInstance getProcessInstance(Supplier supplier) { + if (Objects.isNull(processInstance)) { + processInstance = supplier.get(); + } + return processInstance; + } + + public Deployment getDeployment(Supplier supplier) { + if (Objects.isNull(deployment)) { + deployment = supplier.get(); + } + return deployment; + } + + public Process getProcess(Supplier supplier) { + if (Objects.isNull(process)) { + process = supplier.get(); + } + return process; + } + + public BpmnProcessInstanceVO getInstanceVO(Supplier supplier) { + if (Objects.isNull(instanceVO)) { + instanceVO = supplier.get(); + } + return instanceVO; + } + + public BpmnProcessDefinitionVO getDefinitionVO(Supplier supplier) { + if (Objects.isNull(definitionVO)) { + definitionVO = supplier.get(); + } + return definitionVO; + } + + public BpmnTaskDelegateAssigner getInitiator(Supplier supplier) { + if (Objects.isNull(initiator)) { + initiator = supplier.get(); + } + return initiator; + } + + public BpmnTaskDelegateAssigner getLastOperationAssigner(Supplier supplier) { + if (Objects.isNull(lastOperationAssigner)) { + lastOperationAssigner = supplier.get(); + } + return lastOperationAssigner; + } + + public String getProcessInstanceVersion(Supplier supplier) { + if (!StringUtils.hasText(processInstanceVersion)) { + processInstanceVersion = supplier.get(); + } + return processInstanceVersion; + } + + @Override + @SuppressWarnings("unchecked") + public void setContext(OperationContext context) { + this.context = (T) context; + } + + @SuppressWarnings("unchecked") + @Override + public T getContext() { + return context; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/NoticeOperationContext.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/NoticeOperationContext.java new file mode 100644 index 000000000..cb2a5e116 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/NoticeOperationContext.java @@ -0,0 +1,10 @@ +package cn.axzo.workflow.core.common.context; + +/** + * 通知相关操作上下文 + * + * @author wangli + * @since 2024/4/12 16:47 + */ +public class NoticeOperationContext extends CommonContext { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/OperationContext.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/OperationContext.java new file mode 100644 index 000000000..25f74bb2a --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/OperationContext.java @@ -0,0 +1,17 @@ +package cn.axzo.workflow.core.common.context; + + +/** + * 操作上下文接口 + * + * @author wangli + * @since 2024/4/9 10:05 + */ +public interface OperationContext { + + default void setContext(OperationContext context) { + } + + T getContext(); +} + diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/ProcessOperationContext.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/ProcessOperationContext.java new file mode 100644 index 000000000..9342cd93b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/ProcessOperationContext.java @@ -0,0 +1,34 @@ +package cn.axzo.workflow.core.common.context; + +import org.flowable.engine.ProcessEngineConfiguration; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * 流程相关操作上下文 + * + * @author wangli + * @since 2024/4/9 22:52 + */ +public class ProcessOperationContext extends CommonContext { + + private List processFileTags; + private ProcessEngineConfiguration processEngineConfiguration; + + public List getProcessFileTags(Supplier> supplier) { + if (CollectionUtils.isEmpty(processFileTags)) { + processFileTags = supplier.get(); + } + return processFileTags; + } + + public ProcessEngineConfiguration getProcessEngineConfiguration(Supplier supplier) { + if (Objects.isNull(processEngineConfiguration)) { + return supplier.get(); + } + return processEngineConfiguration; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/TaskOperationContext.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/TaskOperationContext.java new file mode 100644 index 000000000..9c52d02cf --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/context/TaskOperationContext.java @@ -0,0 +1,27 @@ +package cn.axzo.workflow.core.common.context; + +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * 任务相关操作上下文 + * + * @author wangli + * @since 2024/4/9 20:59 + */ +public class TaskOperationContext extends CommonContext { + + private List extAxHiTaskInsts = new ArrayList<>(); + + public List getExtAxHiTaskInsts(Supplier> supplier) { + if (CollectionUtils.isEmpty(extAxHiTaskInsts)) { + extAxHiTaskInsts.addAll(supplier.get()); + } + return extAxHiTaskInsts; + } + +} 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..06400d673 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,9 @@ import java.util.Arrays; * @since 2023/9/18 17:11 */ public enum BpmnProcessTaskResultEnum { - + HANDLING("HANDLING", "审批中"), // 修改这里 + PENDING("PENDING", "待处理"), + PROCESSED("PROCESSED", "已处理"), AUTO_SKIP("AUTO_SKIP", "任务自动跳过"), // 引擎默认的标识,不允许修改 MI_END("MI_END", "多实例任务运行结束"), @@ -17,8 +19,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/enums/MqLogEventType.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/enums/MqLogEventType.java new file mode 100644 index 000000000..d7ad3bb6b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/enums/MqLogEventType.java @@ -0,0 +1,11 @@ +package cn.axzo.workflow.core.common.enums; + +/** + * MQ log 事件类型 + * + * @author wangli + * @since 2024/5/15 15:36 + */ +public enum MqLogEventType { + INSERT, UPDATE, DELETE +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/ApiLogEvent.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/ApiLogEvent.java index 1bac74993..d9920b8ec 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/ApiLogEvent.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/ApiLogEvent.java @@ -13,15 +13,20 @@ import org.springframework.context.ApplicationEvent; public class ApiLogEvent extends ApplicationEvent { private String traceId; private String apiUrl; + private String requestApplicationName; + private String clientVersion; private Object requestBody; private Object responseBody; private Double takeTime; private String type; - public ApiLogEvent(String traceId, String apiUrl, Object requestBody, Object responseBody, Double takeTime, String type) { + public ApiLogEvent(String traceId, String apiUrl, String requestApplicationName, String clientVersion, + Object requestBody, Object responseBody, Double takeTime, String type) { super(apiUrl); this.traceId = traceId; this.apiUrl = apiUrl; + this.requestApplicationName = requestApplicationName; + this.clientVersion = clientVersion; this.requestBody = requestBody; this.responseBody = responseBody; this.takeTime = takeTime; @@ -32,10 +37,22 @@ public class ApiLogEvent extends ApplicationEvent { return traceId; } + public void setTraceId(String traceId) { + this.traceId = traceId; + } + public String getApiUrl() { return apiUrl; } + public String getRequestApplicationName() { + return requestApplicationName; + } + + public String getClientVersion() { + return clientVersion; + } + public Object getRequestBody() { return requestBody; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/ApiLogListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/ApiLogListener.java index d92acc91c..1d98e44cb 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/ApiLogListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/ApiLogListener.java @@ -1,17 +1,25 @@ package cn.axzo.workflow.core.common.event; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; import cn.axzo.workflow.core.repository.entity.ExtAxApiLog; import cn.axzo.workflow.core.service.ExtAxApiLogService; import cn.hutool.json.JSONUtil; +import com.google.common.base.Strings; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; import org.springframework.context.ApplicationListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.UUID; + +import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC; /** * Api Log 记录表操作监听 @@ -22,7 +30,8 @@ import java.util.List; @Component @Slf4j public class ApiLogListener implements ApplicationListener { - + @Resource + private SupportRefreshProperties refreshProperties; @Resource private ExtAxApiLogService apiLogService; private static final List EXCLUDE_URLS = new ArrayList<>(); @@ -35,9 +44,14 @@ public class ApiLogListener implements ApplicationListener { @Override @Async public void onApplicationEvent(ApiLogEvent event) { - if (EXCLUDE_URLS.contains(event.getApiUrl())) { + if (!refreshProperties.getApiLogEnable() || EXCLUDE_URLS.contains(event.getApiUrl())) { return; } + if (!supportApiType(event)) { + return; + } + rebuildTraceId(event); + ExtAxApiLog apiLog = new ExtAxApiLog(); apiLog.setTraceId(event.getTraceId()); apiLog.setApiUrl(event.getApiUrl()); @@ -47,4 +61,21 @@ public class ApiLogListener implements ApplicationListener { apiLog.setType(event.getType()); apiLogService.insert(apiLog); } + + private static void rebuildTraceId(ApiLogEvent event) { + if (Strings.isNullOrEmpty(event.getTraceId())) { + String traceId = Strings.isNullOrEmpty(event.getTraceId()) + ? UUID.randomUUID().toString().replaceAll("-", "") : event.getTraceId(); + MDC.put(CTX_LOG_ID_MDC, traceId); + event.setTraceId(traceId); + } + } + + private boolean supportApiType(ApiLogEvent event) { + if (!StringUtils.hasText(refreshProperties.getFilterApiType())) { + return true; + } + List list = Arrays.asList(refreshProperties.getFilterApiType().split(",")); + return list.contains(event.getType()); + } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/MqLogEvent.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/MqLogEvent.java new file mode 100644 index 000000000..5433ce7e6 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/MqLogEvent.java @@ -0,0 +1,89 @@ +package cn.axzo.workflow.core.common.event; + +import cn.axzo.workflow.core.common.enums.MqLogEventType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEvent; + +/** + * Mq Log Event 时间对象 + * + * @author wangli + * @since 2024/5/15 11:01 + */ +@Slf4j +public class MqLogEvent extends ApplicationEvent { + private String uniqueId; + private String messageId; + private String mqTag; + private String mqKey; + private String messageBody; + private String traceId; + private MqLogEventType eventType; + + public MqLogEvent(String uniqueId, String messageId, String mqTag, String mqKey, String messageBody, String traceId, MqLogEventType eventType) { + super(uniqueId); + this.uniqueId = uniqueId; + this.messageId = messageId; + this.mqTag = mqTag; + this.mqKey = mqKey; + this.messageBody = messageBody; + this.traceId = traceId; + this.eventType = eventType; + } + + public String getUniqueId() { + return uniqueId; + } + + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + public String getMessageId() { + return messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public String getMqTag() { + return mqTag; + } + + public void setMqTag(String tag) { + this.mqTag = tag; + } + + public String getMqKey() { + return mqKey; + } + + public void setMqKey(String key) { + this.mqKey = key; + } + + public String getMessageBody() { + return messageBody; + } + + public void setMessageBody(String messageBody) { + this.messageBody = messageBody; + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + + public MqLogEventType getEventType() { + return eventType; + } + + public void setEventType(MqLogEventType eventType) { + this.eventType = eventType; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/MqLogListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/MqLogListener.java new file mode 100644 index 000000000..8a9b6be6c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/event/MqLogListener.java @@ -0,0 +1,81 @@ +package cn.axzo.workflow.core.common.event; + +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.repository.entity.ExtAxMqLog; +import cn.axzo.workflow.core.service.ExtAxMqLogService; +import cn.hutool.json.JSONUtil; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * Api Log 记录表操作监听 + * + * @author wangli + * @since 2024/4/3 10:37 + */ +@Component +@Slf4j +public class MqLogListener implements ApplicationListener { + + @Resource + private SupportRefreshProperties refreshProperties; + @Resource + private ExtAxMqLogService mqLogService; + + + @SneakyThrows + @Override + public void onApplicationEvent(MqLogEvent event) { + log.info("receive mq log event"); + if (!refreshProperties.getMqLogEnable()) { + return; + } + switch (event.getEventType()) { + case INSERT: + insert(buildMqLogEntity(event)); + break; + case UPDATE: + update(buildMqLogEntity(event)); + break; + case DELETE: + delete(buildMqLogEntity(event)); + break; + default: + break; + } + } + + private ExtAxMqLog buildMqLogEntity(MqLogEvent event) { + ExtAxMqLog entity = new ExtAxMqLog(); + entity.setUniqueId(event.getUniqueId()); + entity.setMessageId(event.getMessageId()); + entity.setMqTag(event.getMqTag()); + entity.setMqKey(event.getMqKey()); + entity.setMessageBody(event.getMessageBody()); + entity.setTraceId(event.getTraceId()); + return entity; + } + + private void delete(ExtAxMqLog event) { + log.info("delete mq log, event: {}", JSONUtil.toJsonStr(event)); + event.setDeleteThreadName(Thread.currentThread().getName()); + mqLogService.delete(event); + } + + private void update(ExtAxMqLog event) { + log.info("update mq log, event: {}", JSONUtil.toJsonStr(event)); + event.setUpdateThreadName(Thread.currentThread().getName()); + mqLogService.update(event); + } + + private void insert(ExtAxMqLog event) { + log.info("insert mq log, event: {}", JSONUtil.toJsonStr(event)); + event.setInsertThreadName(Thread.currentThread().getName()); + mqLogService.insert(event); + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmTransformUtil.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmTransformUtil.java deleted file mode 100644 index 62575250f..000000000 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmTransformUtil.java +++ /dev/null @@ -1,432 +0,0 @@ -package cn.axzo.workflow.core.common.utils; - -import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; -import cn.axzo.workflow.common.enums.BpmnFlowNodeType; -import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode; -import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNodeProperty; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; -import com.google.common.collect.Lists; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.flowable.bpmn.BpmnAutoLayout; -import org.flowable.bpmn.converter.BpmnXMLConverter; -import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.EndEvent; -import org.flowable.bpmn.model.ExclusiveGateway; -import org.flowable.bpmn.model.FlowElement; -import org.flowable.bpmn.model.FlowableListener; -import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics; -import org.flowable.bpmn.model.Process; -import org.flowable.bpmn.model.SequenceFlow; -import org.flowable.bpmn.model.ServiceTask; -import org.flowable.bpmn.model.StartEvent; -import org.flowable.bpmn.model.UserTask; -import org.flowable.engine.delegate.TaskListener; -import org.flowable.engine.repository.Model; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; - -import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION; -import static cn.axzo.workflow.common.constant.BpmnConstants.END_EVENT_ID; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO; -import static cn.axzo.workflow.common.constant.BpmnConstants.OR_SIGN_EXPRESSION_ONE_PASS; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_META_DATA_FORMAT_ERROR; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_UNKNOW_NODE_TYPE; -import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION; -import static org.flowable.engine.delegate.BaseExecutionListener.EVENTNAME_END; -import static org.flowable.engine.delegate.BaseExecutionListener.EVENTNAME_START; - -/** - * 该工具是以前枢智版本使用,后续迭代都通过 BpmnJsonConverterUtil 该工具操作 - */ -@Slf4j -@Deprecated -public class BpmTransformUtil { - public static byte[] transformBpmnJsonToXml(BpmnJsonNode bpmnJson, Model model) { - return transformBpmnJsonToXml(bpmnJson, model, null); - } - - public static byte[] transformBpmnJsonToXml(BpmnJsonNode bpmnJson, Model model, String category) { - BpmnModel bpmnModel = new BpmnModel(); - Process process = new Process(); - bpmnModel.addProcess(process); - - process.setId(model.getKey()); - process.setName(model.getName()); - - if (BpmnFlowNodeType.NODE_STARTER.equals(bpmnJson.getType())) { - process.addFlowElement(createStartEvent(bpmnJson.getId(), Objects.nonNull(bpmnJson.getProperty()) ? - bpmnJson.getProperty().getFormKey() : "")); - } - - List sequenceFlows = Lists.newArrayList(); - Map childNodeMap = new HashMap<>(); - - String lastNode = null; - try { - lastNode = create(bpmnJson.getId(), bpmnJson.getChildren(), process, bpmnModel, sequenceFlows, - childNodeMap); - } catch (WorkflowEngineException e) { - throw e; - } catch (Exception e) { - throw new WorkflowEngineException(CONVERTOR_META_DATA_FORMAT_ERROR); - } - EndEvent endEvent = createEndEvent(); - process.addFlowElement(endEvent); - process.addFlowElement(sequenceFlow(lastNode, endEvent.getId(), sequenceFlows, childNodeMap, process)); - - - if (StringUtils.isNotBlank(category)) { - bpmnModel.setTargetNamespace(category); - } - new BpmnAutoLayout(bpmnModel).execute(); - byte[] xmlMeta = new BpmnXMLConverter().convertToXML(bpmnModel); - String xmlResult = new String(xmlMeta); - return xmlMeta; - - } - - private static String id(String prefix) { - return prefix + "_" + UUID.randomUUID().toString().replace("-", "").toLowerCase(); - } - - private static ServiceTask serviceTask(String name) { - ServiceTask serviceTask = new ServiceTask(); - serviceTask.setName(name); - return serviceTask; - } - - public static SequenceFlow sequenceFlow(String from, String to, List sequenceFlows, Map childNodeMap, Process process) { - SequenceFlow flow = new SequenceFlow(); - String sequenceFlowId = id("sequenceFlow"); - if (process.getFlowElement(from) != null && process.getFlowElement(from) instanceof ExclusiveGateway) { - BpmnJsonNode childNode = childNodeMap.get(to); - if (childNode != null) { - String parentId = childNode.getParentId(); - if (StringUtils.isNotBlank(parentId)) { - BpmnJsonNode parentNode = childNodeMap.get(parentId); - if (parentNode != null) { - if (BpmnFlowNodeType.NODE_CONDITION.equals(parentNode.getType())) { - sequenceFlowId = parentNode.getId(); - flow.setName(parentNode.getName()); - if (!ObjectUtils.isEmpty(parentNode.getProperty()) && !Boolean.TRUE.equals(parentNode.getProperty().getDefaultBranch())) { - //解析条件表达式 - // StringBuffer conditionExpression = new StringBuffer(); - // conditionExpression.append("${ ") - // .append("var:eq('") - // .append(parentNode.getProperty() - // .getConditionBranchKey()) - // .append("', ") - // .append(parentNode.getProperty() - // .getConditionBranchValue()) - // .append(")"); - // conditionExpression.append(" }"); - // - // flow.setConditionExpression(conditionExpression - // .toString()); - } - } - } - } - } - } - flow.setId(sequenceFlowId); - flow.setSourceRef(from); - flow.setTargetRef(to); - sequenceFlows.add(flow); - return flow; - } - - public static StartEvent createStartEvent(String id, String formKey) { - StartEvent startEvent = new StartEvent(); - startEvent.setId(id); - // startEvent.setInitiator("applyUserId"); - if (Objects.nonNull(formKey) && !formKey.trim().isEmpty()) { - startEvent.setFormKey(formKey); - } - return startEvent; - } - - public static EndEvent createEndEvent() { - EndEvent endEvent = new EndEvent(); - endEvent.setId(END_EVENT_ID); - return endEvent; - } - - - public static String create(String fromId, BpmnJsonNode flowNode, Process process, BpmnModel bpmnModel, - List sequenceFlows, Map childNodeMap) throws InvocationTargetException, IllegalAccessException { - String nodeType = flowNode.getType().getType(); - if (BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY.isEqual(nodeType)) { - return createExclusiveGatewayBuilder(fromId, flowNode, process, bpmnModel, sequenceFlows, childNodeMap); - } else if (BpmnFlowNodeType.NODE_TASK.isEqual(nodeType)) { - childNodeMap.put(flowNode.getId(), flowNode); - flowNode.setIncoming(Collections.singletonList(fromId)); - String id = createTask(process, flowNode, sequenceFlows, childNodeMap); - // 如果当前任务还有后续任务,则遍历创建后续任务 - BpmnJsonNode children = flowNode.getChildren(); - if (Objects.nonNull(children) && StringUtils.isNotBlank(children.getId())) { - return create(id, children, process, bpmnModel, sequenceFlows, childNodeMap); - } else { - return id; - } - } else if (BpmnFlowNodeType.NODE_STARTER.isEqual(nodeType)) { - childNodeMap.put(flowNode.getId(), flowNode); - flowNode.setIncoming(Collections.singletonList(fromId)); - String id = createTask(process, flowNode, sequenceFlows, childNodeMap); - // 如果当前任务还有后续任务,则遍历创建后续任务 - BpmnJsonNode children = flowNode.getChildren(); - if (Objects.nonNull(children) && StringUtils.isNotBlank(children.getId())) { - return create(id, children, process, bpmnModel, sequenceFlows, childNodeMap); - } else { - return id; - } - } else { - throw new WorkflowEngineException(CONVERTOR_UNKNOW_NODE_TYPE, nodeType); - } - } - - public static ExclusiveGateway createExclusiveGateWayEnd(String id) { - ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); - exclusiveGateway.setId(id); - return exclusiveGateway; - } - - - private static String createExclusiveGatewayBuilder(String formId, BpmnJsonNode flowNode, Process process, - BpmnModel bpmnModel, List sequenceFlows, - Map childNodeMap) throws InvocationTargetException, IllegalAccessException { - childNodeMap.put(flowNode.getId(), flowNode); - String name = flowNode.getName(); - String exclusiveGatewayId = flowNode.getId(); - ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); - exclusiveGateway.setId(exclusiveGatewayId); - exclusiveGateway.setName(name); - process.addFlowElement(exclusiveGateway); - process.addFlowElement(sequenceFlow(formId, exclusiveGatewayId, sequenceFlows, childNodeMap, process)); - - if (Objects.isNull(flowNode.getBranches()) && Objects.isNull(flowNode.getChildren())) { - return exclusiveGatewayId; - } - List branches = flowNode.getBranches(); - List incoming = Lists.newArrayListWithCapacity(branches.size()); - List conditions = Lists.newCopyOnWriteArrayList(); - for (BpmnJsonNode element : branches) { - if (!ObjectUtils.isEmpty(element.getProperty())) { - Boolean typeElse = element.getProperty().getDefaultBranch(); - if (Boolean.TRUE.equals(typeElse)) { - exclusiveGateway.setDefaultFlow(element.getId()); - } - } - - childNodeMap.put(element.getId(), element); - BpmnJsonNode children = element.getChildren(); - String nodeName = element.getName(); - - if (!ObjectUtils.isEmpty(element.getProperty()) && (Objects.isNull(children) || StringUtils.isBlank(children.getId()))) { - - incoming.add(exclusiveGatewayId); - Map condition = new HashMap(); - - //解析条件表达式 - // StringBuffer conditionExpression = new StringBuffer(); - // conditionExpression.append("${ "); - // conditionExpression.append("var:eq('" + element.getProperty().getConditionBranchKey - // () + "', " + element.getProperty().getConditionBranchValue() + ") "); - // conditionExpression.append(" }"); - // condition.put("nodeName", nodeName); - // condition.put("expression", conditionExpression.toString()); - - conditions.add(condition); - continue; - } - // 只生成一个任务,同时设置当前任务的条件 - children.setIncoming(Collections.singletonList(exclusiveGatewayId)); - String identifier = create(exclusiveGatewayId, children, process, bpmnModel, sequenceFlows, childNodeMap); - List flows = sequenceFlows.stream().filter(flow -> StringUtils.equals(exclusiveGatewayId, - flow.getSourceRef())) - .collect(Collectors.toList()); - flows.stream().forEach( - e -> { - if (StringUtils.isBlank(e.getName()) && StringUtils.isNotBlank(nodeName)) { - e.setName(nodeName); - } - // 设置条件表达式 - // if (Objects.isNull(e.getConditionExpression()) && StringUtils - // .isNotBlank(expression)) { - // e.setConditionExpression(expression); - // } - } - ); - if (Objects.nonNull(identifier)) { - incoming.add(identifier); - } - } - - - BpmnJsonNode childNode = flowNode.getChildren(); - - if (Objects.nonNull(childNode) && StringUtils.isNotBlank(childNode.getId())) { - String parentId = childNode.getParentId(); - BpmnJsonNode parentChildNode = childNodeMap.get(parentId); - if (BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY.equals(parentChildNode.getType())) { - String endExId = parentChildNode.getId() + "end"; - process.addFlowElement(createExclusiveGateWayEnd(endExId)); - if (incoming == null || incoming.isEmpty()) { - return create(exclusiveGatewayId, childNode, process, bpmnModel, sequenceFlows, - childNodeMap); - } else { - // 所有 service task 连接 end exclusive gateway - childNode.setIncoming(incoming); - FlowElement flowElement = bpmnModel.getFlowElement(incoming.get(0)); - // 1.0 先进行边连接, 暂存 nextNode - BpmnJsonNode nextNode = childNode.getChildren(); - childNode.setChildren(null); - String identifier = endExId; - for (int i = 0; i < incoming.size(); i++) { - process.addFlowElement(sequenceFlow(incoming.get(i), identifier, sequenceFlows, childNodeMap, - process)); - } - - // 针对 gateway 空任务分支 添加条件表达式 - if (!conditions.isEmpty()) { - FlowElement flowElement1 = bpmnModel.getFlowElement(identifier); - // 获取从 gateway 到目标节点 未设置条件表达式的节点 - List flows = sequenceFlows.stream().filter( - flow -> StringUtils.equals(flowElement1.getId(), flow.getTargetRef())) - .filter( - flow -> StringUtils.equals(flow.getSourceRef(), exclusiveGatewayId)) - .collect(Collectors.toList()); - flows.stream().forEach(sequenceFlow -> { - if (!conditions.isEmpty()) { - Map condition = conditions.get(0); - String nodeName = (String) condition.get("nodeName"); - String expression = (String) condition.get("expression"); - - if (StringUtils.isBlank(sequenceFlow.getName()) && StringUtils - .isNotBlank(nodeName)) { - sequenceFlow.setName(nodeName); - } - // 设置条件表达式 - if (Objects.isNull(sequenceFlow.getConditionExpression()) - && StringUtils.isNotBlank(expression)) { - sequenceFlow.setConditionExpression(expression); - } - conditions.remove(0); - } - }); - - } - - // 1.1 边连接完成后,在进行 nextNode 创建 - if (StringUtils.isNotBlank(childNode.getId())) { - return create(identifier, childNode, process, bpmnModel, sequenceFlows, - childNodeMap); - } else { - return identifier; - } - } - } - } - return exclusiveGatewayId; - } - - - private static String createTask(Process process, BpmnJsonNode flowNode, List sequenceFlows, - Map childNodeMap) { - List incoming = flowNode.getIncoming(); - // 自动生成id - // String id = id("serviceTask"); - String id = flowNode.getId(); - if (incoming != null && !incoming.isEmpty()) { - UserTask userTask = new UserTask(); - userTask.setName(flowNode.getName()); - userTask.setId(id); - process.addFlowElement(userTask); - process.addFlowElement(sequenceFlow(incoming.get(0), id, sequenceFlows, childNodeMap, process)); - - FlowableListener createTaskListener = new FlowableListener(); - createTaskListener.setEvent(TaskListener.EVENTNAME_ALL_EVENTS); - createTaskListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - - createTaskListener.setImplementation("${engineTaskEventListener}"); - List taskListeners = new ArrayList<>(); - taskListeners.add(createTaskListener); - userTask.setTaskListeners(taskListeners); - if ("root".equalsIgnoreCase(id)) { - } else { - List executionListeners = new ArrayList<>(); - createExecutionListener(EVENTNAME_START, executionListeners); - // End 事件,主要想用于多实例节点保留一条历史的任务信息, 后续研究过程中, 发现通过更优的解决方案, 所以注释掉下面一行 - // createExecutionListener(EVENTNAME_END, executionListeners); - userTask.setExecutionListeners(executionListeners); - - if (!ObjectUtils.isEmpty(flowNode.getProperty()) && Boolean.TRUE.equals(flowNode.getProperty().getIsMultiTask())) { - BpmnJsonNodeProperty property = flowNode.getProperty(); - BpmnFlowNodeMode mode = property.getMultiMode(); - - MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = - new MultiInstanceLoopCharacteristics(); - // 审批人集合参数 - multiInstanceLoopCharacteristics.setInputDataItem(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + userTask.getId()); - // 迭代集合 - multiInstanceLoopCharacteristics.setElementVariable("assigneeName"); - // 并行 - multiInstanceLoopCharacteristics.setSequential(false); - userTask.setAssignee("${assigneeName}"); - // 设置多实例属性 - userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics); - if (BpmnFlowNodeMode.OR.getType().equals(mode.getType())) { - multiInstanceLoopCharacteristics.setCompletionCondition(OR_SIGN_EXPRESSION_ONE_PASS); - } else if (BpmnFlowNodeMode.AND.getType().equals(mode.getType())) { - multiInstanceLoopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION); - } - } - // 设置审批人为空时,允许自动通过 - // if (!ObjectUtils.isEmpty(flowNode.getProperty()) && flowNode.getProperty() - // .getAllowSkip()) { - // userTask.setSkipExpression("${" + BPM_ALLOW_SKIP_USER_TASK + "}"); - // } - if (!ObjectUtils.isEmpty(flowNode.getProperty()) && StringUtils.isNotBlank(flowNode.getProperty().getFormKey())) { - userTask.setFormKey(flowNode.getProperty().getFormKey()); - } - } - } - return id; - } - - /** - * 根据 Execution 事件类型, 创建 Execution 监听器 - * - * @param eventName @see ExecutionListener - * @return - */ - private static void createExecutionListener(String eventName, List listeners) { - FlowableListener executionListener = new FlowableListener(); - executionListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - executionListener.setEvent(eventName); - switch (eventName) { - case EVENTNAME_START: - executionListener.setImplementation("${engineExecutionStartListener}"); - break; - case EVENTNAME_END: - executionListener.setImplementation("${engineExecutionEndListener}"); - break; - default: - return; - } - listeners.add(executionListener); - } - - -} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnExpressionTranslator.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnExpressionTranslator.java index 412cccba0..4933a7730 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnExpressionTranslator.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnExpressionTranslator.java @@ -1,16 +1,16 @@ package cn.axzo.workflow.core.common.utils; import cn.axzo.workflow.common.model.request.bpmn.BpmnCondition; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import com.google.common.collect.Lists; import java.util.List; import java.util.Objects; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_OPERATION_NUMBER_TYPE_ERROR; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_OPERATION_RADIO_TYPE_ERROR; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_OPERATION_STRING_TYPE_ERROR; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_OPERATION_NUMBER_TYPE_ERROR; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_OPERATION_RADIO_TYPE_ERROR; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_OPERATION_STRING_TYPE_ERROR; /** diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnJsonConverterUtil.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnJsonConverterUtil.java index 2450b8e65..fe050478b 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnJsonConverterUtil.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnJsonConverterUtil.java @@ -1,15 +1,21 @@ package cn.axzo.workflow.core.common.utils; import cn.axzo.workflow.common.enums.ApprovalMethodEnum; +import cn.axzo.workflow.common.enums.ModelBizTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +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.BpmnFieldConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignApproverLimit; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnUpgradeApprovalConf; import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.converter.json.AbstractBpmnJsonConverter; +import cn.axzo.workflow.core.converter.json.BoundaryEventJsonConverter; import cn.axzo.workflow.core.converter.json.EndEventJsonConverter; import cn.axzo.workflow.core.converter.json.ExclusiveGatewayJsonConverter; import cn.axzo.workflow.core.converter.json.NotSupportConverter; @@ -19,12 +25,14 @@ import cn.axzo.workflow.core.converter.json.SequenceFlowJsonConverter; import cn.axzo.workflow.core.converter.json.ServiceTaskJsonConverter; import cn.axzo.workflow.core.converter.json.StartEventJsonConverter; import cn.axzo.workflow.core.converter.json.UserTaskJsonConverter; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Lists; import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.BaseElement; +import org.flowable.bpmn.model.BoundaryEvent; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.ExclusiveGateway; @@ -63,8 +71,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.UUID; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_COMMON_ERROR; +import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVE_SUPPORT_BATCH_OPERATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVE_USER_AGREE_SIGNATURE; +import static cn.axzo.workflow.common.constant.BpmnConstants.AUTO_APPROVAL_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_META; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CARBON_COPY; @@ -75,6 +89,8 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_META; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_OPTION; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NOTICE; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CHECKED; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CODE; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DISABLED; @@ -84,20 +100,33 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_O import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE; import static cn.axzo.workflow.common.constant.BpmnConstants.END_EVENT_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_MODEL_BIZ_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_NODE_JSON; import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION; import static cn.axzo.workflow.common.constant.BpmnConstants.SEQUENCE_FLOW_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.START_EVENT_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_CARBON_COPY_MESSAGE_ID; -import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_CONFIG; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_ASSIGNERS; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_HISTORIES; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_POSITIONS; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_ROLES; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_EVENTS; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SIGN_PENDING_MESSAGE_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAGE_ID; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CONDITION; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EMPTY; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_COMMON_ERROR; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getButtonConfig; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getFieldConfig; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNoticeConfig; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getProcessApproveConf; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSignApproverLimit; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSignConfig; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getUpgradeApprovalConf; /** * BPMN json 格式转换工具 @@ -127,6 +156,7 @@ public final class BpmnJsonConverterUtil { CONVERTERS.put(UserTask.class, new UserTaskJsonConverter()); CONVERTERS.put(ServiceTask.class, new ServiceTaskJsonConverter()); CONVERTERS.put(ReceiveTask.class, new ReceiveTaskJsonConverter()); + CONVERTERS.put(BoundaryEvent.class, new BoundaryEventJsonConverter()); } /** @@ -141,9 +171,11 @@ public final class BpmnJsonConverterUtil { BpmnJsonNode bpmnJsonNode = JSON.parseObject(mainProcess.getAttributeValue(null, FLOW_NODE_JSON), BpmnJsonNode.class); model.setNode(bpmnJsonNode); + getProcessApproveConf(mainProcess).ifPresent(model::setApproveConf); getNoticeConfig(mainProcess).ifPresent(model::setNoticeConf); getButtonConfig(mainProcess).ifPresent(model::setButtonConf); getFieldConfig(mainProcess).ifPresent(model::setFieldConf); + getSignConfig(mainProcess).ifPresent(model::setSignConf); return model; } @@ -151,13 +183,19 @@ public final class BpmnJsonConverterUtil { * 将 json 格式数据转的 BpmnJsonNode 对象,转换成 Flowable 标准的模型 {@link BpmnModel} * * @param bpmnJsonNode json 格式对象,可以直接解析 + * @param approveConf 流程高审批相关高级配置 * @param noticeConf * @param buttonConf * @param fieldConf * @return {@link BpmnModel} */ - public static BpmnModel convertToBpmn(BpmnJsonNode bpmnJsonNode, String id, String name, String documentation, - BpmnNoticeConf noticeConf, BpmnButtonConf buttonConf, + public static BpmnModel convertToBpmn(BpmnJsonNode bpmnJsonNode, ModelBizTypeEnum modelBizType, + String id, String name, String formKey, + String documentation, + BpmnApproveConf approveConf, + BpmnSignConf signConf, + BpmnNoticeConf noticeConf, + BpmnButtonConf buttonConf, List fieldConf, String serverVersionStr) { if (Objects.isNull(bpmnJsonNode)) { @@ -165,22 +203,28 @@ public final class BpmnJsonConverterUtil { } // 提交的 BpmnJsonNode 全部都是任务是可执行节点, 这里的转换是全局都会增加开始和结束节点. 因为前段的提交的数据是不包含开始和结束 BpmnModel bpmnModel = new BpmnModel(); - + ExtensionAttribute modelType = new ExtensionAttribute(); + modelType.setName(FLOW_MODEL_BIZ_TYPE); + modelType.setValue(Objects.nonNull(modelBizType) ? modelBizType.getType() : ModelBizTypeEnum.FLOWABLE.getType()); ExtensionAttribute serverVersion = new ExtensionAttribute(); serverVersion.setName(FLOW_SERVER_VERSION); - //FIXME: 尽量在每次版本迭代时, 都修改这里的版本号, 用于以后特殊场景下消息流程相关数据时, 能区别不同时期的处理办法,还需要动态起来 serverVersion.setValue(serverVersionStr); ExtensionAttribute jsonMetaValue = new ExtensionAttribute(); jsonMetaValue.setName(FLOW_NODE_JSON); - jsonMetaValue.setValue(JSON.toJSONString(bpmnJsonNode)); + jsonMetaValue.setValue(JSONUtil.toJsonStr(bpmnJsonNode)); Process mainProcess = new Process(); mainProcess.setId(id); mainProcess.setName(name); mainProcess.setDocumentation(documentation); + mainProcess.addAttribute(modelType); mainProcess.addAttribute(serverVersion); mainProcess.addAttribute(jsonMetaValue); + //设置流程审批相关高级配置 + setProcessApproveConfigs(approveConf, mainProcess); + // 设置签署配置 + setSignConfig(signConf, mainProcess); // 设置流程的通知管理配置 setProcessNoticeConfig(noticeConf, mainProcess); // 设置流程的默认的按钮配置 @@ -191,13 +235,16 @@ public final class BpmnJsonConverterUtil { bpmnModel.addProcess(mainProcess); // 创建固定的开始节点 - mainProcess.addFlowElement(convertJsonToElement(StartEvent.class, mainProcess)); + mainProcess.addFlowElement(convertJsonToElement(StartEvent.class, mainProcess, formKey)); // 创建固定的结束节点 - mainProcess.addFlowElement(convertJsonToElement(EndEvent.class, mainProcess)); + mainProcess.addFlowElement(convertJsonToElement(EndEvent.class, mainProcess, formKey)); // 解析前端传入的模型设计 json - List lastNodeIds = create(bpmnJsonNode, mainProcess, bpmnModel, null, START_EVENT_ID); + List lastNodeIds = Lists.newArrayList(START_EVENT_ID); + if (StringUtils.hasText(bpmnJsonNode.getId())) { + lastNodeIds = create(formKey, bpmnJsonNode, mainProcess, bpmnModel, null, START_EVENT_ID); + } if (CollectionUtils.isEmpty(lastNodeIds)) { throw new WorkflowEngineException(CONVERTOR_COMMON_ERROR, "未找到链接结束节点的节点数据"); @@ -214,6 +261,39 @@ public final class BpmnJsonConverterUtil { return bpmnModel; } + private static void setSignConfig(BpmnSignConf signConf, Process mainProcess) { + if (Objects.isNull(signConf)) { + return; + } + ExtensionElement signConfigElement = new ExtensionElement(); + signConfigElement.setName(CONFIG_SIGN); + + if (Objects.nonNull(signConf.getSignType())) { + ExtensionElement signType = new ExtensionElement(); + signType.setName(CONFIG_SIGN_TYPE); + ExtensionAttribute signTypeAttr = new ExtensionAttribute(); + signTypeAttr.setName(ELEMENT_ATTRIBUTE_TYPE); + signTypeAttr.setValue(signConf.getSignType().getType()); + signType.addAttribute(signTypeAttr); + signType.setElementText(signConf.getSignType().getDesc()); + signConfigElement.addChildElement(signType); + } + + // 签署待办消息模板配置 + if (Objects.nonNull(signConf.getSignPendingProperty()) && Objects.nonNull(signConf.getSignPendingProperty().getPendingMessageId())) { + ExtensionElement signPendingMessage = new ExtensionElement(); + signPendingMessage.setName(TEMPLATE_SIGN_PENDING_MESSAGE_ID); + ExtensionAttribute pendingMessageAttribute = new ExtensionAttribute(); + pendingMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + pendingMessageAttribute.setValue(signConf.getSignPendingProperty().getPendingMessageId()); + signPendingMessage.addAttribute(pendingMessageAttribute); + signPendingMessage.setElementText(StringUtils.hasText(signConf.getSignPendingProperty().getViewJson()) ? + signConf.getSignPendingProperty().getViewJson() : ""); + signConfigElement.addChildElement(signPendingMessage); + } + mainProcess.addExtensionElement(signConfigElement); + } + private static void setProcessFieldConfig(List fieldConf, Process mainProcess) { if (CollectionUtils.isEmpty(fieldConf)) { return; @@ -313,6 +393,12 @@ public final class BpmnJsonConverterUtil { disabledAttribute.setName(ELEMENT_ATTRIBUTE_DISABLED); disabledAttribute.setValue(String.valueOf(i.getDisabled())); button.addAttribute(disabledAttribute); + + ExtensionAttribute typeAttribute = new ExtensionAttribute(); + typeAttribute.setName(ELEMENT_ATTRIBUTE_TYPE); + typeAttribute.setValue(StringUtils.hasText(i.getType()) ? i.getType() : "SYSTEM"); + button.addAttribute(typeAttribute); + initiator.addChildElement(button); }); buttonConfigElement.addChildElement(initiator); @@ -327,15 +413,88 @@ public final class BpmnJsonConverterUtil { noticeConfigElement.setName(CONFIG_NOTICE); // 通知消息模板配置 - if (Objects.nonNull(noticeConf.getNotice()) && Objects.nonNull(noticeConf.getNotice().getNoticeMessageId())) { + if (Objects.nonNull(noticeConf.getNotice()) && noticeConf.getNotice().getSendMessage() != null && noticeConf.getNotice().getSendMessage()) { ExtensionElement noticeMessage = new ExtensionElement(); - noticeMessage.setName(TEMPLATE_NOTICE_MESSAGE_ID); + noticeMessage.setName(TEMPLATE_NOTICE_MESSAGE_CONFIG); ExtensionAttribute noticeMessageAttribute = new ExtensionAttribute(); - noticeMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); - noticeMessageAttribute.setValue(noticeConf.getNotice().getNoticeMessageId()); + noticeMessageAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED); + noticeMessageAttribute.setValue(String.valueOf(noticeConf.getNotice().getSendMessage())); noticeMessage.addAttribute(noticeMessageAttribute); - noticeMessage.setElementText(StringUtils.hasText(noticeConf.getNotice().getViewJson()) ? - noticeConf.getNotice().getViewJson() : ""); + + ExtensionElement noticeEvents = new ExtensionElement(); + noticeEvents.setName(TEMPLATE_NOTICE_MESSAGE_EVENTS); + noticeEvents.setElementText(JSON.toJSONString(noticeConf.getNotice().getTriggerEvents())); + noticeMessage.addChildElement(noticeEvents); + + ExtensionElement noticeToObject = new ExtensionElement(); + noticeToObject.setName(TEMPLATE_NOTICE_MESSAGE_DESTINATION); + + if (Objects.nonNull(noticeConf.getNotice().getInitiator())) { + ExtensionElement initiator = new ExtensionElement(); + initiator.setName(TEMPLATE_NOTICE_MESSAGE_DESTINATION_INITIATOR); + ExtensionAttribute initiatorAttribute = new ExtensionAttribute(); + initiatorAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED); + initiatorAttribute.setValue(String.valueOf(noticeConf.getNotice().getInitiator().getSelected())); + initiator.addAttribute(initiatorAttribute); + if (StringUtils.hasText(noticeConf.getNotice().getInitiator().getViewJson())) { + initiator.setElementText(noticeConf.getNotice().getInitiator().getViewJson()); + } + noticeToObject.addChildElement(initiator); + } + + if (Objects.nonNull(noticeConf.getNotice().getHistories())) { + ExtensionElement histories = new ExtensionElement(); + histories.setName(TEMPLATE_NOTICE_MESSAGE_DESTINATION_HISTORIES); + ExtensionAttribute historiesAttribute = new ExtensionAttribute(); + historiesAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED); + historiesAttribute.setValue(String.valueOf(noticeConf.getNotice().getHistories().getSelected())); + histories.addAttribute(historiesAttribute); + if (StringUtils.hasText(noticeConf.getNotice().getHistories().getViewJson())) { + histories.setElementText(noticeConf.getNotice().getHistories().getViewJson()); + } + noticeToObject.addChildElement(histories); + } + + if (Objects.nonNull(noticeConf.getNotice().getRoles())) { + ExtensionElement roles = new ExtensionElement(); + roles.setName(TEMPLATE_NOTICE_MESSAGE_DESTINATION_ROLES); + ExtensionAttribute rolesAttribute = new ExtensionAttribute(); + rolesAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED); + rolesAttribute.setValue(String.valueOf(noticeConf.getNotice().getRoles().getSelected())); + roles.addAttribute(rolesAttribute); + if (StringUtils.hasText(noticeConf.getNotice().getRoles().getViewJson())) { + roles.setElementText(noticeConf.getNotice().getRoles().getViewJson()); + } + noticeToObject.addChildElement(roles); + } + + if (Objects.nonNull(noticeConf.getNotice().getPositions())) { + ExtensionElement positions = new ExtensionElement(); + positions.setName(TEMPLATE_NOTICE_MESSAGE_DESTINATION_POSITIONS); + ExtensionAttribute positionsAttribute = new ExtensionAttribute(); + positionsAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED); + positionsAttribute.setValue(String.valueOf(noticeConf.getNotice().getPositions().getSelected())); + positions.addAttribute(positionsAttribute); + if (StringUtils.hasText(noticeConf.getNotice().getPositions().getViewJson())) { + positions.setElementText(noticeConf.getNotice().getPositions().getViewJson()); + } + noticeToObject.addChildElement(positions); + } + + if (Objects.nonNull(noticeConf.getNotice().getAssigners())) { + ExtensionElement assigners = new ExtensionElement(); + assigners.setName(TEMPLATE_NOTICE_MESSAGE_DESTINATION_ASSIGNERS); + ExtensionAttribute assignersAttribute = new ExtensionAttribute(); + assignersAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED); + assignersAttribute.setValue(String.valueOf(noticeConf.getNotice().getAssigners().getSelected())); + assigners.addAttribute(assignersAttribute); + if (StringUtils.hasText(noticeConf.getNotice().getAssigners().getViewJson())) { + assigners.setElementText(noticeConf.getNotice().getAssigners().getViewJson()); + } + noticeToObject.addChildElement(assigners); + } + + noticeMessage.addChildElement(noticeToObject); noticeConfigElement.addChildElement(noticeMessage); } @@ -380,6 +539,44 @@ public final class BpmnJsonConverterUtil { mainProcess.addExtensionElement(noticeConfigElement); } + private static void setProcessApproveConfigs(BpmnApproveConf approveConf, Process mainProcess) { + if (Objects.isNull(approveConf)) { + return; + } + ExtensionElement approveConfigElement = new ExtensionElement(); + approveConfigElement.setName(CONFIG_APPROVE); + mainProcess.addExtensionElement(approveConfigElement); + + // 审批相关配置 + if (Objects.nonNull(approveConf.getSupportBatchOperation())) { + ExtensionElement config = new ExtensionElement(); + config.setName(APPROVE_SUPPORT_BATCH_OPERATION); + ExtensionAttribute configAttribute = new ExtensionAttribute(); + configAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + configAttribute.setValue(Boolean.toString(approveConf.getSupportBatchOperation())); + config.addAttribute(configAttribute); + approveConfigElement.addChildElement(config); + } + + if (Objects.nonNull(approveConf.getUserAgreeSignature())) { + ExtensionElement config = new ExtensionElement(); + config.setName(APPROVE_USER_AGREE_SIGNATURE); + ExtensionAttribute configAttribute = new ExtensionAttribute(); + configAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + configAttribute.setValue(Boolean.toString(approveConf.getUserAgreeSignature())); + config.addAttribute(configAttribute); + approveConfigElement.addChildElement(config); + } + if (Objects.nonNull(approveConf.getAutoApprovalType())) { + ExtensionElement config = new ExtensionElement(); + config.setName(AUTO_APPROVAL_TYPE); + ExtensionAttribute configAttribute = new ExtensionAttribute(); + configAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + configAttribute.setValue(approveConf.getAutoApprovalType().getType()); + config.addAttribute(configAttribute); + approveConfigElement.addChildElement(config); + } + } public static byte[] transformBytes(BpmnModel bpmnModel) { return new BpmnXMLConverter().convertToXML(bpmnModel); @@ -398,13 +595,14 @@ public final class BpmnJsonConverterUtil { /** * 创建对应类型节点并保存在 bpmnModel 中 * - * @param preNodeIds 上级节点的 Id 结合,用于链接当前创建节点 - * @param bpmnJsonNode 前端传入节点数据,不是全数据,而是对应层级 - * @param mainProcess 主 Process 对象 - * @param bpmnModel 最终的 BPMN model + * @param preNodeIds 上级节点的 Id 结合,用于链接当前创建节点 + * @param bpmnJsonNode 前端传入节点数据,不是全数据,而是对应层级 + * @param mainProcess 主 Process 对象 + * @param bpmnModel 最终的 BPMN model + * @param defaultConditionConnectNode 嵌套网关行为下,缓存的当前网关下的 child,如果当前层级下没有,则是上级网关的 child. * @return 创建的节点的 ID */ - private static List create(BpmnJsonNode bpmnJsonNode, Process mainProcess, + private static List create(String formKey, BpmnJsonNode bpmnJsonNode, Process mainProcess, BpmnModel bpmnModel, BpmnJsonNode defaultConditionConnectNode, String... preNodeIds) { // 设置来源节点 bpmnJsonNode.setIncoming(Lists.newArrayList(preNodeIds)); @@ -412,10 +610,10 @@ public final class BpmnJsonConverterUtil { Class clz = chooseNodeClass(bpmnJsonNode); // 根据 BpmnJsonNode 创建节点 - FlowElement flowElement = convertJsonToElement(clz, bpmnJsonNode, mainProcess); + FlowElement flowElement = convertJsonToElement(clz, bpmnJsonNode, mainProcess, formKey); mainProcess.addFlowElement(flowElement); - connectPreNodes(bpmnJsonNode, mainProcess, preNodeIds); + connectPreNodes(formKey, bpmnJsonNode, mainProcess, preNodeIds); // 只有网关才会涉及到 branch List branchLastNodeIds = new ArrayList<>(); @@ -457,7 +655,7 @@ public final class BpmnJsonConverterUtil { nextJsonNode.setPreJsonNode(branch); } SequenceFlow sequenceFlow = (SequenceFlow) convertJsonToElement(SequenceFlow.class, nextJsonNode, - mainProcess); + mainProcess, formKey); mainProcess.addFlowElement(sequenceFlow); // 设置网关默认流 @@ -469,7 +667,7 @@ public final class BpmnJsonConverterUtil { if (Objects.nonNull(branch.getChildren()) && StringUtils.hasLength(branch.getChildren().getId()) && !Objects.equals(NODE_EMPTY, branch.getChildren().getType())) { - branchLastNodeIds.addAll(create(branch.getChildren(), mainProcess, bpmnModel, + branchLastNodeIds.addAll(create(formKey, branch.getChildren(), mainProcess, bpmnModel, Objects.isNull(defaultConditionConnectNode) ? bpmnJsonNode.getChildren() : defaultConditionConnectNode)); } @@ -493,25 +691,29 @@ public final class BpmnJsonConverterUtil { children = children.getChildren(); } if (CollectionUtils.isEmpty(branchLastNodeIds)) { - return create(children, mainProcess, bpmnModel, defaultConditionConnectNode, flowElement.getId()); + //网关分支条件连接到下一节点,children节点不再连接前继网关节点 + if (bpmnJsonNode.getType() == NODE_EXCLUSIVE_GATEWAY) { + return create(formKey, children, mainProcess, bpmnModel, defaultConditionConnectNode); + } + return create(formKey, children, mainProcess, bpmnModel, defaultConditionConnectNode, flowElement.getId()); } - return create(children, mainProcess, bpmnModel, defaultConditionConnectNode, branchLastNodeIds.toArray(new String[0])); + return create(formKey, children, mainProcess, bpmnModel, defaultConditionConnectNode, branchLastNodeIds.toArray(new String[0])); } } - private static void connectPreNodes(BpmnJsonNode bpmnJsonNode, Process mainProcess, String[] preNodeIds) { + private static void connectPreNodes(String formKey, BpmnJsonNode bpmnJsonNode, Process mainProcess, String[] preNodeIds) { // 连接当前节点与前一个节点 if (Lists.newArrayList(preNodeIds).isEmpty()) { // first time entrance, do nothing. } else if (Lists.newArrayList(preNodeIds).size() == 1 && !NODE_CONDITION.equals(bpmnJsonNode.getType())) { bpmnJsonNode.setPreJsonNode(null); - mainProcess.addFlowElement(convertJsonToElement(SequenceFlow.class, bpmnJsonNode, mainProcess)); + mainProcess.addFlowElement(convertJsonToElement(SequenceFlow.class, bpmnJsonNode, mainProcess, formKey)); } else { // 将网关分支的最末级节点 ID 关联到网关的 children 节点上,网关协议转换才算闭环 Arrays.stream(preNodeIds).forEach(income -> { bpmnJsonNode.setIncoming(Lists.newArrayList(income)); bpmnJsonNode.setPreJsonNode(null); - FlowElement sequenceFlow = convertJsonToElement(SequenceFlow.class, bpmnJsonNode, mainProcess); + FlowElement sequenceFlow = convertJsonToElement(SequenceFlow.class, bpmnJsonNode, mainProcess, formKey); mainProcess.addFlowElement(sequenceFlow); }); } @@ -544,6 +746,10 @@ public final class BpmnJsonConverterUtil { clz = UserTask.class; } break; + case NODE_SIGN: + // 签署确认节点 + clz = UserTask.class; + break; case NODE_TRIGGER: // 这个类型目前暂不支持 case NODE_CARBON_COPY: @@ -557,14 +763,14 @@ public final class BpmnJsonConverterUtil { return clz; } - private static FlowElement convertJsonToElement(Class clz, Process process) { - return convertJsonToElement(clz, null, process); + private static FlowElement convertJsonToElement(Class clz, Process process, String formKey) { + return convertJsonToElement(clz, null, process, formKey); } private static FlowElement convertJsonToElement(Class clz, BpmnJsonNode bpmnJsonNode, - Process process) { + Process process, String formKey) { AbstractBpmnJsonConverter converter = CONVERTERS.getOrDefault(clz, new NotSupportConverter()); - FlowElement flowElement = converter.convertJsonToElement(bpmnJsonNode, process); + FlowElement flowElement = converter.convertJsonToElement(bpmnJsonNode, process, formKey); if (Objects.nonNull(bpmnJsonNode)) { converter.addNodeTypeAttribute(flowElement, copy(bpmnJsonNode)); converter.addJsonValueAttribute(flowElement, copy(bpmnJsonNode)); @@ -606,13 +812,20 @@ public final class BpmnJsonConverterUtil { String content = new String(bytes, StandardCharsets.UTF_8); BpmnModelCreateDTO model = JSON.parseObject(content, BpmnModelCreateDTO.class); - BpmnModel bpmnModel = convertToBpmn(model.getJsonModel().getNode(), "id", "测试", "remark", + BpmnModel bpmnModel = convertToBpmn(model.getJsonModel().getNode(), ModelBizTypeEnum.SIGN, "id", "测试", "test-form", "remark", + model.getJsonModel().getApproveConf(), + model.getJsonModel().getSignConf(), model.getJsonModel().getNoticeConf(), model.getJsonModel().getButtonConf(), model.getJsonModel().getFieldConf(), "1.3.1-SNAPSHOT"); - convertToJson(bpmnModel); + getNoticeConfig(bpmnModel.getMainProcess()); + getSignConfig(bpmnModel.getMainProcess()); + BpmnJsonModel bpmnJsonModel = convertToJson(bpmnModel); + FlowElement flowElement = bpmnModel.getFlowElement("node_350687681316"); + Optional upgradeApprovalConf = getUpgradeApprovalConf(flowElement); +// Optional signApproverLimit = getSignApproverLimit(flowElement); // ServiceTask serviceTask = (ServiceTask) bpmnModel.getFlowElement("node_946990365785"); // Optional> carbonCopyConfigs = BpmnMetaParserHelper.getCarbonCopyConfigs // (serviceTask); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnMetaParserHelper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnMetaParserHelper.java index 4667efaf3..9a6cc1f19 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnMetaParserHelper.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/BpmnMetaParserHelper.java @@ -4,8 +4,14 @@ import cn.axzo.workflow.common.enums.ApprovalMethodEnum; 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.enums.AutoApprovalTypeEnum; import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.enums.BpmnSignType; import cn.axzo.workflow.common.enums.CarbonCopyObjectType; +import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import cn.axzo.workflow.common.enums.SignApproverRoleLimitEnum; +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.BpmnCarbonCopyConf; @@ -14,8 +20,19 @@ import cn.axzo.workflow.common.model.request.bpmn.BpmnFieldConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnFieldOptionConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeProperty; +import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeReceiver; import cn.axzo.workflow.common.model.request.bpmn.BpmnPendingProperty; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignApproverLimit; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignPendingProperty; import cn.axzo.workflow.common.model.request.bpmn.BpmnSmsProperty; +import cn.axzo.workflow.common.model.request.bpmn.BpmnUpgradeApprovalConf; +import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; +import org.apache.commons.collections4.ListUtils; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.ExtensionElement; import org.flowable.bpmn.model.FlowElement; @@ -34,8 +51,15 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Random; +import java.util.stream.Collectors; +import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVE_SUPPORT_BATCH_OPERATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVE_USER_AGREE_SIGNATURE; +import static cn.axzo.workflow.common.constant.BpmnConstants.AUTO_APPROVAL_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_ACTIVITY_SIGNATURE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVAL_METHOD; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_EMPTY_HANDLE_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_SCOPE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_SPECIFY; @@ -52,21 +76,42 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_META; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_OPTION; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_PERMISSION; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NODE_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NOTICE; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_LIMIT; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_ORG_LIMIT; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_ROLE_LIMIT; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_APPROVER_SPECIFY; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CHECKED; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CODE; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DISABLED; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_KEY; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_NAME; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_ORDER; +import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_ORG_LIMIT; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE; import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION; +import static cn.axzo.workflow.common.constant.BpmnConstants.SUPPORT_BATCH_OPERATION_DEFAULT_VALUE; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_CARBON_COPY_MESSAGE_ID; -import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_CONFIG; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_ASSIGNERS; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_HISTORIES; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_POSITIONS; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_DESTINATION_ROLES; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_EVENTS; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SIGN_PENDING_MESSAGE_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAGE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_UPGRADE_APPROVAL_CONF; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_UPGRADE_APPROVAL_LIMIT_CONF; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_UPGRADE_APPROVAL_SPECIFY_VALUE; +import static cn.axzo.workflow.common.constant.BpmnConstants.USER_AGREE_SIGNATURE_DEFAULT_VALUE; /** * 协助解析 BPMN 文件中的自定义扩展字段和属性 @@ -82,6 +127,66 @@ public final class BpmnMetaParserHelper { return Optional.ofNullable(process.getAttributeValue(null, FLOW_SERVER_VERSION)); } + public static Optional getProcessApproveConf(Process process) { + List elements = process.getExtensionElements().getOrDefault(CONFIG_APPROVE, Collections.emptyList()); + BpmnApproveConf conf = new BpmnApproveConf(); + if (CollectionUtils.isEmpty(elements)) { + conf.setUserAgreeSignature(USER_AGREE_SIGNATURE_DEFAULT_VALUE); + conf.setSupportBatchOperation(SUPPORT_BATCH_OPERATION_DEFAULT_VALUE); + conf.setAutoApprovalType(AutoApprovalTypeEnum.NO_AUTO_APPROVAL); + } else { + elements.get(0).getChildElements().forEach((k, v) -> { + if (APPROVE_SUPPORT_BATCH_OPERATION.equals(k)) { + String value = v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE); + conf.setSupportBatchOperation(Boolean.valueOf(value)); + } else if (APPROVE_USER_AGREE_SIGNATURE.equals(k)) { + String value = v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE); + conf.setUserAgreeSignature(Boolean.valueOf(value)); + } else if (AUTO_APPROVAL_TYPE.equals(k)) { + String value = v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE); + AutoApprovalTypeEnum typeEnum = AutoApprovalTypeEnum.fromType(value); + conf.setAutoApprovalType(typeEnum == null ? AutoApprovalTypeEnum.NO_AUTO_APPROVAL : typeEnum); + } + }); + } + return Optional.of(conf); + } + + public static Optional getSignConfig(Process process) { + List elements = process.getExtensionElements().getOrDefault(CONFIG_SIGN, Collections.emptyList()); + if (CollectionUtils.isEmpty(elements)) { + return Optional.empty(); + } + BpmnSignConf conf = new BpmnSignConf(); + elements.get(0).getChildElements().forEach((k, v) -> { + if (CONFIG_SIGN_TYPE.equals(k)) { + conf.setSignType(BpmnSignType.valueOfType(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_TYPE))); + } else if (TEMPLATE_SIGN_PENDING_MESSAGE_ID.equals(k)) { + BpmnSignPendingProperty sign = new BpmnSignPendingProperty(); + sign.setPendingMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)); + sign.setViewJson(v.get(0).getElementText()); + conf.setSignPendingProperty(sign); + } + }); + return Optional.of(conf); + } + + public static Optional getSignApproverLimit(FlowElement flowElement) { + if (Objects.nonNull(flowElement) && flowElement instanceof UserTask) { + return getSignApproverLimit((UserTask) flowElement); + } + return Optional.empty(); + } + + public static Optional getSignApproverLimit(UserTask userTask) { + return defaultValid(userTask, CONFIG_SIGN_APPROVER_LIMIT).map(element -> { + BpmnSignApproverLimit signApproverLimit = new BpmnSignApproverLimit(); + signApproverLimit.setOrgLimit(SignApproverOrgLimitEnum.valueOfType(element.getChildElements().get(CONFIG_SIGN_APPROVER_ORG_LIMIT).get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE))); + signApproverLimit.setRoleLimit(SignApproverRoleLimitEnum.valueOfType(element.getChildElements().get(CONFIG_SIGN_APPROVER_ROLE_LIMIT).get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE))); + return signApproverLimit; + }); + } + /** * 获取流程模型全局兜底的消息模板相关配置 * @@ -96,10 +201,50 @@ public final class BpmnMetaParserHelper { } BpmnNoticeConf conf = new BpmnNoticeConf(); elements.get(0).getChildElements().forEach((k, v) -> { - if (TEMPLATE_NOTICE_MESSAGE_ID.equals(k)) { + if (TEMPLATE_NOTICE_MESSAGE_CONFIG.equals(k)) { BpmnNoticeProperty notice = new BpmnNoticeProperty(); - notice.setNoticeMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)); - notice.setViewJson(v.get(0).getElementText()); + notice.setSendMessage(Boolean.valueOf(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))); + + String events = v.get(0).getChildElements().getOrDefault(TEMPLATE_NOTICE_MESSAGE_EVENTS, Collections.emptyList()) + .get(0).getElementText(); + List resultEnums = JSON.parseArray(events, BpmnProcessInstanceResultEnum.class); + //触发时机为Null,设置为空列表 + notice.setTriggerEvents(resultEnums == null ? Collections.emptyList() : resultEnums); + + v.get(0).getChildElements().getOrDefault(TEMPLATE_NOTICE_MESSAGE_DESTINATION, Collections.emptyList()).get(0).getChildElements() + .forEach((a, b) -> { + if (Objects.equals(a, TEMPLATE_NOTICE_MESSAGE_DESTINATION_INITIATOR)) { + BpmnNoticeReceiver initiator = new BpmnNoticeReceiver(); + initiator.setSelected(Boolean.valueOf(b.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))); + initiator.setViewJson(b.get(0).getElementText()); + notice.setInitiator(initiator); + } + if (Objects.equals(a, TEMPLATE_NOTICE_MESSAGE_DESTINATION_HISTORIES)) { + BpmnNoticeReceiver histories = new BpmnNoticeReceiver(); + histories.setSelected(Boolean.valueOf(b.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))); + histories.setViewJson(b.get(0).getElementText()); + notice.setHistories(histories); + } + if (Objects.equals(a, TEMPLATE_NOTICE_MESSAGE_DESTINATION_ROLES)) { + BpmnNoticeReceiver roles = new BpmnNoticeReceiver(); + roles.setSelected(Boolean.valueOf(b.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))); + roles.setViewJson(b.get(0).getElementText()); + notice.setRoles(roles); + } + if (Objects.equals(a, TEMPLATE_NOTICE_MESSAGE_DESTINATION_POSITIONS)) { + BpmnNoticeReceiver positions = new BpmnNoticeReceiver(); + positions.setSelected(Boolean.valueOf(b.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))); + positions.setViewJson(b.get(0).getElementText()); + notice.setPositions(positions); + } + if (Objects.equals(a, TEMPLATE_NOTICE_MESSAGE_DESTINATION_ASSIGNERS)) { + BpmnNoticeReceiver assigners = new BpmnNoticeReceiver(); + assigners.setSelected(Boolean.valueOf(b.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))); + assigners.setViewJson(b.get(0).getElementText()); + notice.setAssigners(assigners); + } + }); + conf.setNotice(notice); } else if (TEMPLATE_CARBON_COPY_MESSAGE_ID.equals(k)) { BpmnCarbonCopyProperty carbonCopy = new BpmnCarbonCopyProperty(); @@ -229,7 +374,10 @@ public final class BpmnMetaParserHelper { buttonMetaInfo.setBtnKey(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_KEY)); buttonMetaInfo.setBtnName(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_NAME)); buttonMetaInfo.setChecked(Boolean.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED))); - buttonMetaInfo.setOrder(Integer.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_ORDER))); + buttonMetaInfo.setType(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_TYPE)); + //如果order不是字符类型或者为空,设置默认值0 + String order = i.getAttributeValue(null, ELEMENT_ATTRIBUTE_ORDER); + buttonMetaInfo.setOrder(org.apache.commons.lang3.StringUtils.isNumeric(order) ? Integer.parseInt(order) : 0); buttonMetaInfo.setDisabled(Boolean.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_DISABLED))); buttonMetaInfos.add(buttonMetaInfo); }); @@ -265,6 +413,9 @@ public final class BpmnMetaParserHelper { } public static Optional getNodeType(FlowElement flowElement) { + if (flowElement == null) { + return Optional.empty(); + } if (flowElement instanceof UserTask || flowElement instanceof ServiceTask || flowElement instanceof ReceiveTask) { return defaultValid(flowElement, CONFIG_NODE_TYPE).map(element -> BpmnFlowNodeType.valueOf(element.getElementText())); } @@ -275,18 +426,46 @@ public final class BpmnMetaParserHelper { return defaultValid(flowElement, CONFIG_APPROVAL_METHOD).map(element -> ApprovalMethodEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE))); } + public static Optional getApproverScope(FlowElement flowElement) { + if (Objects.nonNull(flowElement) && flowElement instanceof UserTask) { + return getApproverScope((UserTask) flowElement); + } + return Optional.empty(); + } + public static Optional getApproverScope(UserTask userTask) { return defaultValid(userTask, CONFIG_APPROVER_SCOPE).map(element -> ApproverScopeEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE))); } + public static Optional getApproverSpecify(FlowElement flowElement) { + if (Objects.nonNull(flowElement) && flowElement instanceof UserTask) { + return getApproverSpecify((UserTask) flowElement); + } + return Optional.empty(); + } + public static Optional getApproverSpecify(UserTask userTask) { return defaultValid(userTask, CONFIG_APPROVER_SPECIFY).map(element -> ApproverSpecifyEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE))); } + public static Optional getApproverSpecifyValue(FlowElement flowElement) { + if (Objects.nonNull(flowElement) && flowElement instanceof UserTask) { + return getApproverSpecifyValue((UserTask) flowElement); + } + return Optional.empty(); + } + public static Optional getApproverSpecifyValue(UserTask userTask) { return defaultValid(userTask, CONFIG_APPROVER_SPECIFY).map(element -> StringUtils.hasLength(element.getElementText()) ? element.getElementText() : "[]"); } + public static Optional getApproverEmptyHandleType(FlowElement flowElement) { + if (Objects.nonNull(flowElement) && flowElement instanceof UserTask) { + return getApproverEmptyHandleType((UserTask) flowElement); + } + return Optional.empty(); + } + public static Optional getApproverEmptyHandleType(UserTask userTask) { return defaultValid(userTask, CONFIG_APPROVER_EMPTY_HANDLE_TYPE).map(element -> ApproverEmptyHandleTypeEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE))); } @@ -295,6 +474,17 @@ public final class BpmnMetaParserHelper { return defaultValid(userTask, CONFIG_APPROVER_EMPTY_HANDLE_TYPE).map(element -> StringUtils.hasLength(element.getElementText()) ? element.getElementText() : "[]"); } + public static Boolean getActivitySignature(FlowElement flowElement) { + return defaultValid(flowElement, CONFIG_ACTIVITY_SIGNATURE).map(element -> Boolean.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE))).orElse(false); + } + + public static Boolean getSupportInitiatorSpecified(FlowElement flowElement) { + ApprovalMethodEnum approvalMethodEnum = getApprovalMethod(flowElement).orElse(null); + ApproverSpecifyEnum specifyEnum = getApproverSpecify(flowElement).orElse(null); + return Objects.nonNull(specifyEnum) + && Objects.equals(approvalMethodEnum, ApprovalMethodEnum.human) + && Objects.equals(specifyEnum, ApproverSpecifyEnum.initiatorSpecified); + } private static Optional defaultValid(FlowElement flowElement, String elementName) { if (Objects.isNull(flowElement)) { @@ -334,4 +524,115 @@ public final class BpmnMetaParserHelper { } return Optional.empty(); } + + public static Optional> getFormFieldPermissionConf(FlowElement flowElement) { + return defaultValid(flowElement, CONFIG_FIELD_PERMISSION).map(element -> JSON.parseObject(element.getElementText(), new TypeReference>() { + }.getType())); + } + + public static Optional> getFormFieldPermissionForCalc(FlowElement flowElement) { + List fieldMetaInfos = getFormFieldPermissionConf(flowElement).orElse(new ArrayList<>()); + return getFormFieldPermissionForModel(fieldMetaInfos); + } + + public static Optional> getFormFieldPermissionForModel(List fieldMetaInfos) { + if (CollectionUtils.isEmpty(fieldMetaInfos)) { + return Optional.empty(); + } + ImmutableTable result = fieldMetaInfos.stream() + .collect(ImmutableTable.toImmutableTable(FormPermissionMetaInfo::getFieldId, + FormPermissionMetaInfo::getFieldName, + FormPermissionMetaInfo::toBinary)); + return Optional.of(result); + } + + @SuppressWarnings("unchecked") + public static List mergeAllPermission(List... permissions) { + if (permissions.length == 0) { + return Collections.emptyList(); + } + if (permissions.length == 1) { + return permissions[0]; + } + List> combinedTable = new ArrayList<>(); + for (List permission : permissions) { + combinedTable.add(getFormFieldPermissionForModel(permission).orElse(ImmutableTable.of())); + } + + ImmutableTable> resultTable = combinedTable.stream() + .flatMap(table -> table.cellSet().stream()) + .collect(Collectors.collectingAndThen(Collectors.groupingBy(Table.Cell::getRowKey, Collectors.groupingBy(Table.Cell::getColumnKey, Collectors.mapping(Table.Cell::getValue, Collectors.toList()))), rowMap -> { + ImmutableTable.Builder> builder = ImmutableTable.builder(); + rowMap.forEach((rowKey, colMap) -> colMap.forEach((colKey, values) -> builder.put(rowKey, colKey, values))); + return builder.build(); + })); + + List permissionResult = new ArrayList<>(); + for (Table.Cell> cell : resultTable.cellSet()) { + int[] array = ListUtils.emptyIfNull(cell.getValue()).stream().mapToInt(Integer::intValue).toArray(); + int result = performBitwiseOr(array); + permissionResult.add(FormPermissionMetaInfo.fromBinary(cell.getRowKey(), cell.getColumnKey(), result)); + } + return permissionResult; + } + + public static Optional getUpgradeApprovalConf(FlowElement flowElement) { + if (Objects.isNull(flowElement) || !(flowElement instanceof UserTask)) { + return Optional.empty(); + } + return getUpgradeApprovalConf((UserTask) flowElement); + } + + public static Optional getUpgradeApprovalConf(UserTask userTask) { + return defaultValid(userTask, TEMPLATE_UPGRADE_APPROVAL_CONF).map(element -> { + BpmnUpgradeApprovalConf conf = new BpmnUpgradeApprovalConf(); + Boolean enabled = Boolean.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_CHECKED)); + conf.setEnabled(enabled); + if(Objects.equals(Boolean.TRUE, enabled)) { + conf.setOrgLimit(SignApproverOrgLimitEnum.valueOfType(element.getChildElements().get(TEMPLATE_UPGRADE_APPROVAL_LIMIT_CONF).get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_ORG_LIMIT))); + conf.setApproverSpecify(ApproverSpecifyEnum.valueOf(element.getChildElements().get(TEMPLATE_UPGRADE_APPROVAL_LIMIT_CONF).get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_APPROVER_SPECIFY))); + conf.setSpecifyValue(element.getChildElements().get(TEMPLATE_UPGRADE_APPROVAL_SPECIFY_VALUE).get(0).getElementText()); + } + return conf; + }); + } + + private static int performBitwiseOr(int... numbers) { + int result = 0; + for (int num : numbers) { + result |= num; + } + return result; + } + + // just for testing + private static List genericConf(int num) { + List fields = new ArrayList<>(); + Random random = new Random(); + for (int i = 0; i < num; i++) { + String fieldId = "field_" + i; + FormPermissionMetaInfo metaInfo = FormPermissionMetaInfo + .builder() + .fieldId(fieldId) + .fieldName(fieldId + "Name") + .editable(random.nextBoolean()) + .required(random.nextBoolean()) + .readonly(random.nextBoolean()) + .hidden(random.nextBoolean()) + .build(); + fields.add(metaInfo); + } + return fields; + } + + public static void main(String[] args) { + List conf1 = genericConf(5); + conf1.forEach(e -> System.out.println("conf1 ---> e.fieldId = " + e.getFieldId() + " ,e.getFieldName() = " + e.getFieldName() + " ,e.toBinary() = " + e.toBinary() + " ,e.toBinaryString() = " + e.toBinaryString())); + List conf2 = genericConf(5); + conf2.forEach(e -> System.out.println("conf2 ---> e.fieldId = " + e.getFieldId() + " ,e.getFieldName() = " + e.getFieldName() + " ,e.toBinary() = " + e.toBinary() + " ,e.toBinaryString() = " + e.toBinaryString())); + + List result = mergeAllPermission(conf1, conf2); + result.forEach(e -> System.out.println("result ---> e.fieldId = " + e.getFieldId() + " ,e.getFieldName() = " + e.getFieldName() + " ,e.toBinary() = " + e.toBinary() + " ,e.toBinaryString() = " + e.toBinaryString())); + } + } 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/common/utils/FormHelper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/FormHelper.java new file mode 100644 index 000000000..26d2bc007 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/FormHelper.java @@ -0,0 +1,90 @@ +package cn.axzo.workflow.core.common.utils; + +import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; +import cn.axzo.workflow.common.util.ExpressionUtil; +import com.google.common.collect.Lists; +import org.apache.commons.collections4.ListUtils; +import org.flowable.form.api.FormModel; +import org.flowable.form.model.FormField; +import org.flowable.form.model.SimpleFormModel; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_DEFAULT_VALUE; +import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_EDITABLE; +import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_HIDDEN; +import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_READONLY; +import static cn.axzo.workflow.common.constant.FormConstants.FIELD_PROPERTY_REQUIRED; + +/** + * 表单处理帮助类 + * + * @author wangli + * @since 2024-11-29 15:57 + */ +public class FormHelper { + private FormHelper() { + } + + /** + * 填充表单模型数据 + * + * @param formModel + * @param fieldPermission + * @param variables + * @param showOriginDefaultValue + * @return + */ + public static FormModel populateFormModel(SimpleFormModel formModel, + List fieldPermission, + Map variables, + boolean showOriginDefaultValue) { + if (Objects.isNull(formModel)) { + return null; + } + Map fieldMap = ListUtils.emptyIfNull(fieldPermission).stream() + .collect(Collectors.toMap(FormPermissionMetaInfo::getFieldId, Function.identity(), (s, t) -> s)); + + formModel.allFieldsAsMap().forEach((fieldId, formField) -> { + Map oldParams = CollectionUtils.isEmpty(formField.getParams()) ? new HashMap<>() : formField.getParams(); + FormPermissionMetaInfo defaultPermission = fieldMap.getOrDefault(fieldId, new FormPermissionMetaInfo()); + oldParams.put(FIELD_PROPERTY_REQUIRED, defaultPermission.getRequired()); + oldParams.put(FIELD_PROPERTY_EDITABLE, defaultPermission.getEditable()); + oldParams.put(FIELD_PROPERTY_READONLY, defaultPermission.getReadonly()); + oldParams.put(FIELD_PROPERTY_HIDDEN, defaultPermission.getHidden()); + if (oldParams.containsKey(FIELD_PROPERTY_DEFAULT_VALUE)) { + String defaultValue = oldParams.getOrDefault(FIELD_PROPERTY_DEFAULT_VALUE, "").toString(); + if (!showOriginDefaultValue) { + defaultValue = ExpressionUtil.parseString(String.valueOf(oldParams.get(FIELD_PROPERTY_DEFAULT_VALUE)), + CollectionUtils.isEmpty(variables) ? new HashMap<>() : variables); + } + oldParams.put(FIELD_PROPERTY_DEFAULT_VALUE, defaultValue); + if (injectDefaultValue(formField) && StringUtils.hasText(defaultValue)) { + formField.setValue(defaultValue); + } + } + formField.setParams(oldParams); + }); + return formModel; + } + + private static boolean injectDefaultValue(FormField field) { + if (Objects.isNull(field.getValue())) { + return true; + } + + if (field.getValue() instanceof String && Objects.equals("", field.getValue())) { + return false; + } + + return !(field.getValue() instanceof Collection) || CollectionUtils.isEmpty((Collection) field.getValue()); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/RpcInternalUtil.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/RpcInternalUtil.java deleted file mode 100644 index 548b96e1c..000000000 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/RpcInternalUtil.java +++ /dev/null @@ -1,127 +0,0 @@ -package cn.axzo.workflow.core.common.utils; - -import java.util.function.Consumer; -import java.util.function.Supplier; -import cn.axzo.basics.common.util.AssertUtil; -import cn.axzo.framework.domain.ServiceException; -import cn.axzo.framework.domain.web.BizException; -import cn.axzo.framework.domain.web.result.ApiPageResult; -import cn.axzo.framework.domain.web.result.ApiResult; -import cn.azxo.framework.common.model.CommonResponse; -import cn.hutool.core.lang.Assert; -import cn.hutool.http.HttpStatus; -import cn.hutool.json.JSONUtil; -import lombok.extern.slf4j.Slf4j; - -import static cn.axzo.framework.domain.web.code.BaseCode.BAD_REQUEST; - -/** - * 内部api 使用 服务与 yoke 下游服务 下游服务统一使用 ApiResult - * @author tanjie@axzo.cn - * @date 2022/5/23 11:08 - */ -@Slf4j -public class RpcInternalUtil { - - /** - * 常用的RPC请求返回值解析,如果 被请求方 返回非200会抛出异常 - */ - public static ApiResult rpcProcessor(Supplier> supplier, String operationType, Object... param) { - - return rpcProcessorMayThrow(supplier, operationType, (msg) -> { - throw new ServiceException(msg); - }, param); - } - - public static ApiResult rpcProcessorMayThrow(Supplier> supplier, String operationType, Consumer throwConsumer, Object... param) { - AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空"); - log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); - ApiResult result = null; - - try { - result = supplier.get(); - } catch (Throwable e) { - log.warn("rpc process error:{}", e.getMessage()); - throwConsumer.accept("服务调用异常"); - } - - log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); - Assert.notNull(result, "服务调用异常"); - // 200自定义处理 - if (HttpStatus.HTTP_OK != result.getCode()) { - throwConsumer.accept(result.getMsg()); - } - return result; - } - - /** - * 常用的RPC请求返回值解析,如果 被请求方 返回非200会抛出异常 - */ - public static ApiPageResult rpcPageProcessor(Supplier> supplier, String operationType, Object... param) { - - return rpcPageProcessorMayThrow(supplier, operationType, (msg) -> { - throw new ServiceException(msg); - }, param); - } - - public static ApiPageResult rpcPageProcessorMayThrow(Supplier> supplier, String operationType, Consumer throwConsumer, Object... param) { - AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空"); - log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); - ApiPageResult result = null; - try { - result = supplier.get(); - } catch (Throwable t) { - log.warn("rpc process error:{}", t.getMessage()); - throwConsumer.accept("服务调用异常"); - } - log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); - Assert.notNull(result, "服务调用异常"); - // 200自定义处理 - if (HttpStatus.HTTP_OK != result.getCode()) { - throwConsumer.accept(result.getMsg()); - } - return result; - } - - public static T checkAndGetData(ApiResult result) { - if (result.isError()) { - throw new BizException(result.getRespCode(), result.getMsg()); - } - T data = result.getData(); - if (data == null) { - throw new BizException(BAD_REQUEST, "数据不存在"); - } - return data; - } - - public static CommonResponse commonRpcProcessor(Supplier> supplier, String operationType, Object... param) { - - return commonRpcProcessorMayThrow(supplier, operationType, (msg) -> { - throw new ServiceException(msg); - }, param); - } - - public static CommonResponse commonRpcProcessorMayThrow(Supplier> supplier, String operationType, - Consumer throwConsumer, Object... param) { - AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空"); - log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); - CommonResponse result = null; - - try { - result = supplier.get(); - } catch (Throwable e) { - log.warn("rpc process error:{}", e.getMessage()); - throwConsumer.accept("服务调用异常"); - } - - log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); - Assert.notNull(result, "服务调用异常"); - // 200自定义处理 - if (HttpStatus.HTTP_OK != result.getCode()) { - throwConsumer.accept(result.getMsg()); - } - return result; - } - - -} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/SpringContextUtils.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/SpringContextUtils.java new file mode 100644 index 000000000..86871e9d1 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/SpringContextUtils.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.core.common.utils; + +import org.springframework.lang.Nullable; + +import java.util.Map; + +public class SpringContextUtils { + + private static SpringContextUtils.SpringContext springContext; + + public SpringContextUtils(SpringContextUtils.SpringContext springContext) { + SpringContextUtils.springContext = springContext; + } + + public static T getBean(Class clazz) { + return springContext.getBean(clazz); + } + + public static Map getBeansOfType(@Nullable Class type) { + return springContext.getBeansOfType(type); + } + + public interface SpringContext { + T getBean(Class var1); + + Map getBeansOfType(@Nullable Class type); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/TraceUtil.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/TraceUtil.java new file mode 100644 index 000000000..86fd83f41 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/common/utils/TraceUtil.java @@ -0,0 +1,24 @@ +package cn.axzo.workflow.core.common.utils; + +import org.slf4j.MDC; +import org.springframework.util.StringUtils; + +import static cn.axzo.workflow.common.constant.LogFieldConstants.X_REQUEST_ID; +import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC; + +/** + * 获取 TraceId + * + * @author wangli + * @since 2024/7/3 10:14 + */ +public final class TraceUtil { + + private TraceUtil() { + } + + public static String traceId() { + String xRequestId = MDC.get(X_REQUEST_ID); + return StringUtils.hasText(xRequestId) ? xRequestId : MDC.get(CTX_LOG_ID_MDC); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/CustomEventManager.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/CustomEventManager.java new file mode 100644 index 000000000..141dbc520 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/CustomEventManager.java @@ -0,0 +1,49 @@ +package cn.axzo.workflow.core.conf; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.stereotype.Component; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Spring event 优雅关闭处理器 + * + * @author wangli + * @since 2025-04-07 17:10 + */ +@Component +public class CustomEventManager implements ApplicationEventPublisherAware { + + private ApplicationEventPublisher publisher; + private final BlockingQueue eventQueue = new LinkedBlockingQueue<>(); + private int eventCount = 0; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + public synchronized void publishEvent(ApplicationEvent event) { + eventQueue.add(event); + eventCount++; + publisher.publishEvent(event); + } + + public synchronized void eventProcessed() { + eventQueue.poll(); + eventCount--; + } + + public synchronized boolean isAllEventsProcessed() { + return eventCount == 0; + } + + public synchronized void waitForEventsToBeProcessed() throws InterruptedException { + while (!isAllEventsProcessed()) { + Thread.sleep(100); + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/CustomJobServiceConfiguration.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/CustomJobServiceConfiguration.java new file mode 100644 index 000000000..3aae96886 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/CustomJobServiceConfiguration.java @@ -0,0 +1,37 @@ +package cn.axzo.workflow.core.conf; + +import cn.axzo.workflow.core.engine.job.service.CustomTimerJobEntityManagerImpl; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.AbstractEngineConfiguration; +import org.flowable.common.engine.impl.EngineConfigurator; +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.spring.SpringProcessEngineConfiguration; + +/** + * 自定义的任务服务的配置类 + * + * @author wangli + * @since 2025-03-12 14:18 + */ +@Slf4j +public class CustomJobServiceConfiguration implements EngineConfigurator { + + @Override + public void beforeInit(AbstractEngineConfiguration engineConfiguration) { + + } + + @Override + public void configure(AbstractEngineConfiguration engineConfiguration) { + if (!(engineConfiguration instanceof SpringProcessEngineConfiguration)) { + return; + } + JobServiceConfiguration jobServiceConfiguration = ((SpringProcessEngineConfiguration) engineConfiguration).getJobServiceConfiguration(); + jobServiceConfiguration.setTimerJobEntityManager(new CustomTimerJobEntityManagerImpl(jobServiceConfiguration, jobServiceConfiguration.getTimerJobDataManager())); + } + + @Override + public int getPriority() { + return 0; + } +} 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 cf147f57c..b65829847 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 @@ -1,19 +1,67 @@ package cn.axzo.workflow.core.conf; +import cn.axzo.workflow.core.common.utils.SpringContextUtils; import cn.axzo.workflow.core.engine.behavior.CustomActivityBehaviorFactory; -import cn.axzo.workflow.core.engine.id.DistributedTimeBasedIdGenerator; +import cn.axzo.workflow.core.engine.cmd.CustomCommandContextFactory; +import cn.axzo.workflow.core.engine.formhandler.CustomFormFieldHandler; +import cn.axzo.workflow.core.engine.id.BasedNacosSnowflakeIdGenerator; +import cn.axzo.workflow.core.engine.interceptor.CustomRetryInterceptor; +import cn.axzo.workflow.core.engine.job.AsyncAbortProcessInstanceJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncActivityCallbackJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncActivityLeaveJobHandler; +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.AsyncApproveTaskWithFormJobHandler; +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.AsyncRemindTaskJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncResetApproversUserTaskJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncTermNodeAlterJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncTransferUserTaskJobHandler; +import cn.axzo.workflow.core.engine.job.NextActivityConfigCheckJobHandler; +import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncJobLogClearTraceExceptionHandler; +import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncRunnableExceptionExceptionHandler; import cn.axzo.workflow.core.engine.persistence.CustomMybatisHistoricProcessInstanceDataManager; +import cn.axzo.workflow.core.service.BpmnProcessActivityService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import cn.azxo.framework.common.constatns.Constants; +import cn.hutool.core.util.ReflectUtil; +import com.alibaba.cloud.nacos.NacosDiscoveryProperties; +import com.alibaba.cloud.nacos.NacosServiceManager; import com.google.common.collect.Lists; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import feign.Target; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.flowable.common.engine.api.delegate.event.FlowableEventListener; import org.flowable.common.engine.impl.history.HistoryLevel; -import org.flowable.form.spring.SpringFormEngineConfiguration; +import org.flowable.job.service.JobProcessor; import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.spring.boot.EngineConfigurationConfigurer; +import org.slf4j.MDC; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.context.annotation.Profile; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.lang.Nullable; + +import javax.annotation.Resource; +import java.lang.reflect.Field; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Objects; import static org.flowable.common.engine.impl.AbstractEngineConfiguration.DB_SCHEMA_UPDATE_TRUE; @@ -23,37 +71,157 @@ import static org.flowable.common.engine.impl.AbstractEngineConfiguration.DB_SCH * @author wangli * @since 2023/7/13 11:18 */ +@Slf4j @Configuration public class FlowableConfiguration { + @Value("${workflow.enableVerboseExecutionTreeLogging:false}") + private Boolean enableVerboseExecutionTreeLogging; + @Bean public EngineConfigurationConfigurer processEngineConfigurer( ObjectProvider listeners, CustomActivityBehaviorFactory customActivityBehaviorFactory, - StringRedisTemplate stringRedisTemplate) { + ExtAxHiTaskInstService extAxHiTaskInstService, + BpmnProcessActivityService bpmnProcessActivityService, + List jobProcessors, + NacosServiceManager nacosServiceManager, + NacosDiscoveryProperties nacosDiscoveryProperties, + SupportRefreshProperties refreshProperties, + List configurers) { return configuration -> { configuration.setEnableHistoricTaskLogging(true); configuration.setHistoryLevel(HistoryLevel.AUDIT); configuration.setHistory(HistoryLevel.AUDIT.getKey()); + configuration.setJobProcessors(jobProcessors); configuration.setEventListeners(Lists.newArrayList(listeners)); configuration.setActivityBehaviorFactory(customActivityBehaviorFactory); configuration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_TRUE); configuration.setEnableSafeBpmnXml(false); // configuration.setCreateDiagramOnDeploy(false); - configuration.setIdGenerator(new DistributedTimeBasedIdGenerator(stringRedisTemplate)); +// configuration.setIdGenerator(new TimeBasedIdGenerator()); +// configuration.setIdGenerator(new DistributedTimeBasedIdGenerator(stringRedisTemplate)); + 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 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 AsyncActivityLeaveJobHandler(bpmnProcessActivityService)); + configuration.addCustomJobHandler(new AsyncActivityCallbackJobHandler()); + configuration.addCustomJobHandler(new AsyncApproveTaskWithFormJobHandler()); + configuration.addCustomJobHandler(new AsyncRemindTaskJobHandler(refreshProperties)); + configuration.addCustomJobHandler(new AsyncResetApproversUserTaskJobHandler(extAxHiTaskInstService)); + configuration.addCustomJobHandler(new NextActivityConfigCheckJobHandler()); + configurers.forEach(i -> configuration.addCustomJobHandler(i.getJobHandler())); + // 异步任务异常重试时间间隔 + configuration.setDefaultFailedJobWaitTime(30); + configuration.setAsyncFailedJobWaitTime(30); + configuration.getAsyncExecutorConfiguration().setAsyncJobLockTime(Duration.ofMinutes(5)); + configuration.getAsyncExecutorConfiguration().setGlobalAcquireLockEnabled(true); + configuration.setAddDefaultExceptionHandler(false); + configuration.setEnableVerboseExecutionTreeLogging(enableVerboseExecutionTreeLogging); + configuration.setCustomAsyncRunnableExecutionExceptionHandlers(Lists.newArrayList( + new CustomAsyncJobLogClearTraceExceptionHandler(), + // 移除掉该异常处理器,避免因为 job 抛出 WorkflowEngineException 时,不会重试的问题 +// new CustomWorkflowEngineExceptionHandler(), + new CustomAsyncRunnableExceptionExceptionHandler())); + configuration.setCommandContextFactory(new CustomCommandContextFactory()); + configuration.setCustomPreCommandInterceptors(Lists.newArrayList( + new CustomRetryInterceptor() + )); + // form configuration + configuration.setFormFieldValidationEnabled(true); + configuration.setFormFieldHandler(new CustomFormFieldHandler()); + configuration.setConfigurators(Lists.newArrayList(new CustomJobServiceConfiguration())); }; } @Bean - public EngineConfigurationConfigurer formEngineConfigurer() { - return configuration -> configuration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_TRUE); + public CustomActivityBehaviorFactory customActivityBehaviorFactory() { + return new CustomActivityBehaviorFactory(); } @Bean - public CustomActivityBehaviorFactory customActivityBehaviorFactory(ExtAxHiTaskInstService hiTaskInstService) { - CustomActivityBehaviorFactory customActivityBehaviorFactory = new CustomActivityBehaviorFactory(); - customActivityBehaviorFactory.setHiTaskInstService(hiTaskInstService); - return customActivityBehaviorFactory; + @Order + public SpringContextUtils springContextUtils(SpringContext springContext) { + return new SpringContextUtils(springContext); } + + @Configuration + public static class SpringContext implements SpringContextUtils.SpringContext, ApplicationContextAware { + private ApplicationContext applicationContext; + + public SpringContext() { + } + + public T getBean(Class clazz) { + return this.applicationContext.getBean(clazz); + } + + public Map getBeansOfType(@Nullable Class type) { + return this.applicationContext.getBeansOfType(type); + } + + public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + } + + @Configuration + @Slf4j + @Profile({"local","dev", "test"}) + public static class FeignConfiguration implements RequestInterceptor { + @Resource + private Environment environment; + @Value("${ossEnvUrl:http://dev-app.axzo.cn/oss}") + private String ossEvnUrl; + private static String POD_NAMESPACE; + + static { + Map env = System.getenv(); + if (env != null) { + POD_NAMESPACE = env.get("MY_POD_NAMESPACE"); + } + log.info("init FeignConfig, POD_NAMESPACE value is {}", POD_NAMESPACE); + } + + @SneakyThrows + @Override + public void apply(RequestTemplate requestTemplate) { + if (POD_NAMESPACE == null) { + Target.HardCodedTarget target = (Target.HardCodedTarget) requestTemplate.feignTarget(); + String url = requestTemplate.feignTarget().url(); + // 如需修改微服务地址,建议通过外部化参数来调整 + url = url.replace("http://oss:9123", ossEvnUrl); + String profile = environment.getProperty("spring.profiles.active"); + if (Objects.equals(profile, "test") && url.contains("dev-app.axzo.cn")) { + url = url.replace("dev-app", "test-api"); + } + if (Objects.equals(profile, "pre") && url.contains("dev-app.axzo.cn")) { + url = url.replace("dev-app", "pre-api"); + } + if (Objects.equals(profile, "live") && url.contains("dev-app.axzo.cn")) { + url = url.replace("dev-app", "live-api"); + } + requestTemplate.target(url); + Field field = ReflectUtil.getField(target.getClass(), "url"); + field.setAccessible(true); + field.set(target, url); + } + requestTemplate.header(Constants.CTX_LOG_ID_MDC, MDC.get(Constants.CTX_LOG_ID_MDC)); + requestTemplate.header("X-SERVER-NAME", environment.getProperty("spring.application.name")); + log.info("FeignConfig traceId:{}", MDC.get(Constants.CTX_LOG_ID_MDC)); + } + } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/ProcessExtConfigurer.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/ProcessExtConfigurer.java new file mode 100644 index 000000000..b3d6c6a11 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/ProcessExtConfigurer.java @@ -0,0 +1,13 @@ +package cn.axzo.workflow.core.conf; + +import org.flowable.job.service.JobHandler; + +/** + * 扩展加入自定义的任务处理器 + * + * @author wangli + * @since 2024-11-04 10:58 + */ +public interface ProcessExtConfigurer { + JobHandler getJobHandler(); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/RocketMqEventConfiguration.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/RocketMqEventConfiguration.java index ea9866d78..8e9b5b706 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/RocketMqEventConfiguration.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/RocketMqEventConfiguration.java @@ -1,21 +1,33 @@ package cn.axzo.workflow.core.conf; import cn.axzo.framework.rocketmq.DefaultEventConsumer; +import cn.axzo.framework.rocketmq.Event; import cn.axzo.framework.rocketmq.EventConsumer; import cn.axzo.framework.rocketmq.EventHandlerRepository; import cn.axzo.framework.rocketmq.EventProducer; import cn.axzo.framework.rocketmq.RocketMQEventProducer; -import com.alibaba.fastjson.JSON; +import cn.axzo.framework.rocketmq.utils.TraceUtils; +import cn.axzo.workflow.core.common.enums.MqLogEventType; +import cn.axzo.workflow.core.common.event.MqLogEvent; +import cn.axzo.workflow.core.mq.CustomRocketMQEventProducer; +import cn.hutool.core.util.IdUtil; +import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import javax.annotation.Resource; +import java.util.HashMap; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.function.Consumer; +import static cn.axzo.framework.rocketmq.RocketMQEventProducer.MQ_MESSAGE_ID; + /** * RocketMQ 全局配置 * @@ -31,25 +43,91 @@ public class RocketMqEventConfiguration { private String applicationName; @Value("${spring.profiles.active:dev}") private String activeProfile; + @Resource + private ApplicationEventPublisher applicationEventPublisher; private static final String DEFAULT_MODULE = "workflowEngine"; private static final String DEFAULT_EVENT = "topic_workflow_engine_"; @Bean EventProducer eventProducer(RocketMQTemplate rocketMQTemplate) { - return new RocketMQEventProducer(rocketMQTemplate, + return new CustomRocketMQEventProducer(rocketMQTemplate, DEFAULT_MODULE, applicationName, EventProducer.Context.builder() .meta(RocketMQEventProducer.RocketMQMessageMeta.builder() .topic(DEFAULT_EVENT + activeProfile) .build()) + .headers(new HashMap<>()) .syncSending(Boolean.TRUE) + .exceptionHandler(context -> { + log.error("MQ, send event error: {}, event: {}", + context.getThrowable().getCause().getMessage(), + context.getEvent().toPrettyJsonString(), + context.getThrowable()); + }) .build(), - null + getSendBeforeCallback(), + getSendAfterCallback(), + getTransactionRollbackHandler() ); } + /** + * 真实执行 MQ 发送前的回调, + *

+ * 将整个待发送的事件内容通过 spring 的事件分发器发送出去, 现目前主要是记录 MQ 的发送记录 + * + * @return + */ + private BiConsumer> getSendBeforeCallback() { + return (event, context) -> { + event.setEventId(IdUtil.simpleUUID()); + MqLogEvent mqLogEvent = new MqLogEvent(event.getEventId(), null, + event.getEventName(), event.getShardingKey(), + event.toPrettyJsonString(), TraceUtils.getOrCreateTraceId(), + MqLogEventType.INSERT); + log.info("mq_send_Before: {}, uniqueId: {}", event.getShardingKey(), event.getEventId()); + applicationEventPublisher.publishEvent(mqLogEvent); + }; + } + + /** + * 真实执行 MQ 发送后的回调 + *

+ * 将发送前的 MQ 发送记录更新 MQ 组件自己的 MessageId 字段。 + * + * @return + */ + private BiConsumer> getSendAfterCallback() { + return (event, context) -> { + String messageId = context.getHeaders().get(MQ_MESSAGE_ID); + MqLogEvent mqLogEvent = new MqLogEvent(event.getEventId(), messageId, + event.getEventName(), event.getShardingKey(), + event.toPrettyJsonString(), TraceUtils.getOrCreateTraceId(), + MqLogEventType.UPDATE); + log.info("mq_send_after: {}, uniqueId: {}, messageId: {}", event.getShardingKey(), event.getEventId(), messageId); + applicationEventPublisher.publishEvent(mqLogEvent); + }; + } + + /** + * 如果 MQ 注册的事务回滚后的回调 + *

+ * 将 MQ 发送记录更新为删除状态,意为这类数据可以不关注,可以物理删除,但该功能还是用逻辑删除。 + * + * @return + */ + private BiConsumer> getTransactionRollbackHandler() { + return (event, context) -> { + MqLogEvent mqLog = new MqLogEvent(event.getEventId(), null, event.getEventName(), + event.getShardingKey(), event.toPrettyJsonString(), TraceUtils.getOrCreateTraceId(), + MqLogEventType.DELETE); + log.info("mq_transaction_rollback: {}, uniqueId: {}", event.getShardingKey(), event.getEventId()); + applicationEventPublisher.publishEvent(mqLog); + }; + } + @Bean EventConsumer eventConsumer(EventHandlerRepository eventHandlerRepository) { Consumer callback = (eventWrapper) -> { @@ -83,7 +161,7 @@ public class RocketMqEventConfiguration { @Bean EventHandlerRepository eventHandlerRepository() { return new EventHandlerRepository((ex, logText) -> { - log.warn("MQ, handle warning {} , Exception: {}", logText, JSON.toJSONString(ex)); + log.warn("MQ, handle warning {} , Exception: {}", logText, JSONUtil.toJsonStr(ex)); if (Objects.nonNull(ex)) { throw new RuntimeException(ex); } 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 new file mode 100644 index 000000000..3cd9f1903 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/SupportRefreshProperties.java @@ -0,0 +1,95 @@ +package cn.axzo.workflow.core.conf; + +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.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 支持动态刷新配置属性 + * + * @author wangli + * @since 2024/5/15 15:07 + */ +@Component +@Data +@RefreshScope +public class SupportRefreshProperties { + + @Value("${workflow.apiLog.enable: false}") + private Boolean apiLogEnable; + + @Value("${workflow.mqLog.enable: false}") + private Boolean mqLogEnable; + + @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.key:''}") + private String key; + @Value("#{${workflow.assignee.map:{}}}") + private Map assigneeMap; + + @Value(value = "${workflow.alter.enable:false}") + private Boolean alterEnable; + /** + * 节点卡住多久才告警,单位分钟 + */ + @Value("${workflow.alter.pauseDelay:30}") + private Integer pauseDelay; + /** + * 业务节点暂停告警的次数, 0代表无限制 + */ + @Value(value = "${workflow.alter.retries:1}") + private Integer alterRetries; + + /** + * 业务节点任务执行间隔 + */ + @Value(value = "${workflow.alter.interval:60}") + private Integer alterInterval; + + /** + * 业务节点任务执行间隔的时间单位 + * SECONDS/HOURS/MINUTES 支持的几种单位 + */ + @Value(value = "${workflow.alter.intervalUnit:seconds}") + private TimeUnit alterIntervalUnit; + + @Value(value = "${workflow.alter.mobiles:}") + private List alterMobiles; + + /** + * 是否允许重复告警 + */ + @Value(value = "${workflow.alter.repeat:false}") + private Boolean repeatAlter; + + @Value(value = "${workflow.alter.sendDingTalk:true}") + private Boolean alterSendDingTalk; + + /** + * 用于控制转交管理员的 API + */ + @Value("${workflow.useNewToAdminApi:false}") + private Boolean useNewToAdminApi; + + @Value("${workflow.sendDingTalk:true}") + private Boolean sendDingTalk; + + @Value("${workflow.esSyncBatchSize:10}") + private Integer esSyncBatchSize; + + @Value("${workflow.imTemplateCode:}") + private String imTemplateCode; +} 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..f411169db --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ButtonConfTypeHandler.java @@ -0,0 +1,62 @@ +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.DeserializationFeature; +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(); + + static { + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + 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..587ca9a57 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListAssigneeTypeHandler.java @@ -0,0 +1,62 @@ +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.DeserializationFeature; +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(); + + static { + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + 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/conf/handler/ListFormFieldPermissionTypeHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListFormFieldPermissionTypeHandler.java new file mode 100644 index 000000000..476b96057 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListFormFieldPermissionTypeHandler.java @@ -0,0 +1,62 @@ +package cn.axzo.workflow.core.conf.handler; + +import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; +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.DeserializationFeature; +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 ListFormFieldPermissionTypeHandler extends AbstractJsonTypeHandler> { + private static ObjectMapper objectMapper = new ObjectMapper(); + + static { + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public ListFormFieldPermissionTypeHandler(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/conf/handler/ListSignFileDTOHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListSignFileDTOHandler.java new file mode 100644 index 000000000..92455d355 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListSignFileDTOHandler.java @@ -0,0 +1,64 @@ +package cn.axzo.workflow.core.conf.handler; + +import cn.axzo.workflow.common.model.dto.SignFileDTO; +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.DeserializationFeature; +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; + +/** + * 签署业务文件数据映射转换 + * + * @author wangli + * @since 2025-04-03 11:24 + */ +@Slf4j +@MappedTypes({List.class}) +@MappedJdbcTypes({JdbcType.VARCHAR}) +public class ListSignFileDTOHandler extends AbstractJsonTypeHandler> { + private static ObjectMapper objectMapper = new ObjectMapper(); + + static { + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public ListSignFileDTOHandler(Class type) { + if (log.isTraceEnabled()) { + log.trace("JacksonTypeHandler(" + type + ")"); + } + Assert.notNull(type, "Type argument cannot be null", new Object[0]); + } + + @Override + protected List parse(String json) { + try { + // 这里进行了json解析,同样在这里也可以进行字段查询后的处理,如对象内部的手机号字段的加密展示等 + return objectMapper.readValue(json, new TypeReference>() { + }); + } catch (IOException var3) { + throw new RuntimeException(var3); + } + } + + @Override + 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/conf/handler/ListSimpleDocDTOHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListSimpleDocDTOHandler.java new file mode 100644 index 000000000..3cd20f5a0 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/handler/ListSimpleDocDTOHandler.java @@ -0,0 +1,64 @@ +package cn.axzo.workflow.core.conf.handler; + +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +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.DeserializationFeature; +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; + +/** + * 签署业务文件数据映射转换 + * + * @author wangli + * @since 2025-04-03 11:24 + */ +@Slf4j +@MappedTypes({List.class}) +@MappedJdbcTypes({JdbcType.VARCHAR}) +public class ListSimpleDocDTOHandler extends AbstractJsonTypeHandler> { + private static ObjectMapper objectMapper = new ObjectMapper(); + + static { + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public ListSimpleDocDTOHandler(Class type) { + if (log.isTraceEnabled()) { + log.trace("JacksonTypeHandler(" + type + ")"); + } + Assert.notNull(type, "Type argument cannot be null", new Object[0]); + } + + @Override + protected List parse(String json) { + try { + // 这里进行了json解析,同样在这里也可以进行字段查询后的处理,如对象内部的手机号字段的加密展示等 + return objectMapper.readValue(json, new TypeReference>() { + }); + } catch (IOException var3) { + throw new RuntimeException(var3); + } + } + + @Override + 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/converter/json/AbstractBpmnJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/AbstractBpmnJsonConverter.java index f54aa879a..5b62fadfb 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/AbstractBpmnJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/AbstractBpmnJsonConverter.java @@ -1,8 +1,8 @@ package cn.axzo.workflow.core.converter.json; -import cn.axzo.framework.jackson.utility.JSON; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.hutool.json.JSONUtil; import org.flowable.bpmn.model.ExtensionElement; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.Process; @@ -12,7 +12,7 @@ import org.flowable.bpmn.model.UserTask; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NODE_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_NODE_JSON; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_NODE_TYPE_NOT_SUPPORT; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_NODE_TYPE_NOT_SUPPORT; /** * 抽象的 JSON 转 BPMN 协议的转换器 @@ -22,7 +22,7 @@ import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_NODE */ public abstract class AbstractBpmnJsonConverter { - public T convertJsonToElement(BpmnJsonNode node, Process process) { + public T convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { throw new WorkflowEngineException(CONVERTOR_NODE_TYPE_NOT_SUPPORT, node.getType().getType()); } @@ -43,7 +43,7 @@ public abstract class AbstractBpmnJsonConverter { || flowElement instanceof ReceiveTask) { ExtensionElement jsonValueElement = new ExtensionElement(); jsonValueElement.setName(FLOW_NODE_JSON); - jsonValueElement.setElementText(JSON.toJSONString(jsonNode)); + jsonValueElement.setElementText(JSONUtil.toJsonStr(jsonNode)); flowElement.addExtensionElement(jsonValueElement); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/BoundaryEventJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/BoundaryEventJsonConverter.java new file mode 100644 index 000000000..004e1a6d3 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/BoundaryEventJsonConverter.java @@ -0,0 +1,57 @@ +package cn.axzo.workflow.core.converter.json; + +import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode; +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.EventDefinition; +import org.flowable.bpmn.model.FlowableListener; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.TimerEventDefinition; +import org.flowable.engine.delegate.BaseExecutionListener; + +import java.util.ArrayList; +import java.util.List; + +import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION; + +/** + * 边界事件 + * + * @author wangli + * @since 2024-08-16 11:47 + */ +public class BoundaryEventJsonConverter extends AbstractBpmnJsonConverter { + @Override + public BoundaryEvent convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { + BoundaryEvent boundaryEvent = new BoundaryEvent(); + boundaryEvent.setId(node.getId() + "_boundaryEvent"); + boundaryEvent.setCancelActivity(false); + boundaryEvent.setAttachedToRefId(process.getId()); + + // 设置执行监听 + setExecutionListeners(boundaryEvent); + + // 设置 TimerEventDefinition + setTimerEventDefinition(boundaryEvent); + return boundaryEvent; + } + + private void setTimerEventDefinition(BoundaryEvent boundaryEvent) { + List eventDefinitions = new ArrayList<>(); + TimerEventDefinition timerEventDefinition = new TimerEventDefinition(); + timerEventDefinition.setEndDate("${timerEndDate}"); + eventDefinitions.add(timerEventDefinition); + boundaryEvent.setEventDefinitions(eventDefinitions); + } + + private void setExecutionListeners(BoundaryEvent boundaryEvent) { + List executionListeners = new ArrayList<>(); + // 设置执行监听 + FlowableListener executionListener = new FlowableListener(); + executionListener.setEvent(BaseExecutionListener.EVENTNAME_START); + executionListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + executionListener.setImplementation("${boundaryEventExecutionStartListener}"); + executionListeners.add(executionListener); + + boundaryEvent.setExecutionListeners(executionListeners); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/EndEventJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/EndEventJsonConverter.java index f8241eb6c..993bb9877 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/EndEventJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/EndEventJsonConverter.java @@ -15,9 +15,10 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.END_EVENT_ID; public class EndEventJsonConverter extends AbstractBpmnJsonConverter { @Override - public EndEvent convertJsonToElement(BpmnJsonNode node, Process process) { + public EndEvent convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { EndEvent endEvent = new EndEvent(); endEvent.setId(END_EVENT_ID); + endEvent.setName("结束"); return endEvent; } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ExclusiveGatewayJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ExclusiveGatewayJsonConverter.java index ad74ece80..cee14d6aa 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ExclusiveGatewayJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ExclusiveGatewayJsonConverter.java @@ -13,7 +13,7 @@ import org.flowable.bpmn.model.Process; public class ExclusiveGatewayJsonConverter extends AbstractBpmnJsonConverter { @Override - public ExclusiveGateway convertJsonToElement(BpmnJsonNode node, Process process) { + public ExclusiveGateway convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); exclusiveGateway.setId(node.getId()); exclusiveGateway.setName(node.getName()); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/NotSupportConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/NotSupportConverter.java index 3932ccec1..59b598721 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/NotSupportConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/NotSupportConverter.java @@ -1,11 +1,11 @@ package cn.axzo.workflow.core.converter.json; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.Process; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_NODE_TYPE_NOT_SUPPORT; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_NODE_TYPE_NOT_SUPPORT; /** @@ -17,7 +17,7 @@ import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_NODE public class NotSupportConverter extends AbstractBpmnJsonConverter { @Override - public NotSupportFlowElement convertJsonToElement(BpmnJsonNode node, Process process) { + public NotSupportFlowElement convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { throw new WorkflowEngineException(CONVERTOR_NODE_TYPE_NOT_SUPPORT, node.getType().getType(), node.getId()); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ParallelGatewayJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ParallelGatewayJsonConverter.java index 5e3d1772f..159647c82 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ParallelGatewayJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ParallelGatewayJsonConverter.java @@ -16,7 +16,7 @@ import java.util.Objects; public class ParallelGatewayJsonConverter extends AbstractBpmnJsonConverter { @Override - public ParallelGateway convertJsonToElement(BpmnJsonNode node, Process process) { + public ParallelGateway convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { ParallelGateway parallelGateway = new ParallelGateway(); parallelGateway.setId(node.getId()); parallelGateway.setName(node.getName()); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ReceiveTaskJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ReceiveTaskJsonConverter.java index e2282e81c..7830a2f43 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ReceiveTaskJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ReceiveTaskJsonConverter.java @@ -20,7 +20,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_V import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION; /** - * 接收任务节点 + * 目前用于处理业务节点接受设置审批人 * * @author wangli * @since 2023/10/13 17:01 @@ -28,7 +28,7 @@ import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DEL public class ReceiveTaskJsonConverter extends AbstractBpmnJsonConverter { @Override - public ReceiveTask convertJsonToElement(BpmnJsonNode node, Process process) { + public ReceiveTask convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { ReceiveTask receiveTask = new ReceiveTask(); receiveTask.setId(node.getId()); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/SequenceFlowJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/SequenceFlowJsonConverter.java index 3952c3c9e..a513839bf 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/SequenceFlowJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/SequenceFlowJsonConverter.java @@ -28,7 +28,7 @@ import static cn.axzo.workflow.core.common.utils.BpmnJsonConverterUtil.id; public class SequenceFlowJsonConverter extends AbstractBpmnJsonConverter { @Override - public SequenceFlow convertJsonToElement(BpmnJsonNode node, Process process) { + public SequenceFlow convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { List incoming = node.getIncoming(); SequenceFlow sequenceFlow = new SequenceFlow(); sequenceFlow.setId(id(SEQUENCE_FLOW_ID)); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ServiceTaskJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ServiceTaskJsonConverter.java index fc9596811..7e7f5d9a2 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ServiceTaskJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/ServiceTaskJsonConverter.java @@ -1,15 +1,14 @@ package cn.axzo.workflow.core.converter.json; +import cn.axzo.framework.jackson.utility.JSON; import cn.axzo.workflow.common.model.request.bpmn.BpmnCarbonCopyConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode; import org.flowable.bpmn.model.ExtensionAttribute; import org.flowable.bpmn.model.ExtensionElement; -import org.flowable.bpmn.model.FlowableListener; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.ServiceTask; import org.springframework.util.CollectionUtils; -import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -17,6 +16,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPIE import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_OBJECT; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_SPECIFY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_PERMISSION; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_NAME; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE; @@ -30,7 +30,7 @@ import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DEL */ public class ServiceTaskJsonConverter extends AbstractBpmnJsonConverter { @Override - public ServiceTask convertJsonToElement(BpmnJsonNode node, Process process) { + public ServiceTask convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { ServiceTask serviceTask = new ServiceTask(); serviceTask.setId(node.getId()); serviceTask.setName(node.getName()); @@ -40,6 +40,9 @@ public class ServiceTaskJsonConverter extends AbstractBpmnJsonConverter executionListeners = new ArrayList<>(); - - // // 设置执行监听 - // FlowableListener executionListener = new FlowableListener(); - // executionListener.setEvent(BaseExecutionListener.EVENTNAME_START); - // executionListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - // executionListener.setImplementation("${engineExecutionStartListener}"); - // executionListeners.add(executionListener); - // - // FlowableListener activityStartListener = new FlowableListener(); - // activityStartListener.setEvent(BaseExecutionListener.EVENTNAME_START); - // activityStartListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - // activityStartListener.setImplementation("${engineActivityStartEventListener}"); - // executionListeners.add(activityStartListener); - // - // FlowableListener activityTakeListener = new FlowableListener(); - // activityTakeListener.setEvent(BaseExecutionListener.EVENTNAME_TAKE); - // activityTakeListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - // activityTakeListener.setImplementation("${engineActivityTakeEventListener}"); - // executionListeners.add(activityTakeListener); - // - // FlowableListener activityEndListener = new FlowableListener(); - // activityEndListener.setEvent(BaseExecutionListener.EVENTNAME_END); - // activityEndListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); - // activityEndListener.setImplementation("${engineActivityEndEventListener}"); - // executionListeners.add(activityEndListener); - - serviceTask.setExecutionListeners(executionListeners); + private static void setFormFieldExtensionElement(BpmnJsonNode node, ServiceTask serviceTask) { + if (Objects.isNull(node.getProperty())) { + return; + } + ExtensionElement fieldElement = new ExtensionElement(); + fieldElement.setName(CONFIG_FIELD_PERMISSION); + fieldElement.setElementText(Objects.nonNull(node.getProperty().getFieldPermission()) ? + JSON.toJSONString(node.getProperty().getFieldPermission()) : null); + serviceTask.addExtensionElement(fieldElement); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/StartEventJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/StartEventJsonConverter.java index 643959f0f..80d9be308 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/StartEventJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/StartEventJsonConverter.java @@ -16,9 +16,11 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.START_EVENT_ID; public class StartEventJsonConverter extends AbstractBpmnJsonConverter { @Override - public StartEvent convertJsonToElement(BpmnJsonNode node, Process process) { + public StartEvent convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { StartEvent startEvent = new StartEvent(); startEvent.setId(START_EVENT_ID); + startEvent.setName("开始"); + startEvent.setFormKey(formKey); // startEvent.setInitiator(""); return startEvent; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/UserTaskJsonConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/UserTaskJsonConverter.java index 51ba281b5..e84adf3ce 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/UserTaskJsonConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/converter/json/UserTaskJsonConverter.java @@ -1,11 +1,15 @@ package cn.axzo.workflow.core.converter.json; +import cn.axzo.framework.jackson.utility.JSON; import cn.axzo.workflow.common.enums.ApprovalMethodEnum; +import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; import cn.axzo.workflow.common.enums.BpmnFlowNodeType; import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNodeProperty; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignApproverLimit; +import cn.axzo.workflow.common.model.request.bpmn.BpmnUpgradeApprovalConf; import org.flowable.bpmn.model.ExtensionAttribute; import org.flowable.bpmn.model.ExtensionElement; import org.flowable.bpmn.model.FlowableListener; @@ -14,6 +18,7 @@ import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.BaseExecutionListener; import org.flowable.engine.delegate.TaskListener; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.ArrayList; @@ -21,7 +26,8 @@ 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.BPM_ALLOW_SKIP_USER_TASK; +import static cn.axzo.workflow.common.constant.BpmnConstants.AUTO_APPROVAL_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_ACTIVITY_SIGNATURE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVAL_METHOD; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_EMPTY_HANDLE_TYPE; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_MODE_TYPE; @@ -33,12 +39,21 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_ import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_HISTORY; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_PERMISSION; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_LIMIT; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_ORG_LIMIT; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_SIGN_APPROVER_ROLE_LIMIT; +import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_APPROVER_SPECIFY; +import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CHECKED; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DESC; +import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_ORG_LIMIT; import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO; import static cn.axzo.workflow.common.constant.BpmnConstants.OR_SIGN_EXPRESSION_ONLY_ONE; import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID; -import static cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum.autoPassed; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_UPGRADE_APPROVAL_CONF; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_UPGRADE_APPROVAL_LIMIT_CONF; +import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_UPGRADE_APPROVAL_SPECIFY_VALUE; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_SIGN; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; import static cn.axzo.workflow.core.common.utils.BpmnJsonConverterUtil.buildMetaButton; import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION; @@ -52,34 +67,33 @@ import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DEL public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { @Override - public UserTask convertJsonToElement(BpmnJsonNode node, Process process) { + public UserTask convertJsonToElement(BpmnJsonNode node, Process process, String formKey) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); + userTask.setFormKey(formKey); // 设置会签或签 setMultiInstance(node, userTask); // 设置全局任务监听器 setTaskListeners(userTask); // 设置全局执行监听器 - setExecutionListeners(node, userTask); //以下是保存配置信息, 引擎完全不会 care 这里的信息 // "设置审批人" setApprovalExtensionElement(node, userTask); - // "权限设置" - setFieldExtensionElement(node, userTask); - // "高级设置" - setButtonExtensionElement(node, userTask); + // "表单权限设置" + setFormFieldExtensionElement(node, userTask); + // "高级设置",包含按钮配置,自动过审配置 + setAdvancedExtensionElement(node, userTask); // "待办消息模板配置" setPendingMessageExtensionElement(node, userTask); // 如果审批人为空时,同时选择了自动通过, 则设置跳过表达式, 但由于使用 Flowable 框架变量操作不当会引起后续节点异常, - // 所以统一通过 AutoOperatorEventListener 类解决自动操作 + // 所以统一通过 AutoOperatorEvent_101_Listener 类解决自动操作 // setEmptyAssigneeAutoPassed(node, userTask); - return userTask; } @@ -99,15 +113,9 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { userTask.addExtensionElement(pendingMessageElement); } - private static void setEmptyAssigneeAutoPassed(BpmnJsonNode node, UserTask userTask) { - if (Objects.nonNull(node.getProperty()) && - Objects.equals(node.getProperty().getApproverEmptyHandleType(), autoPassed)) { - userTask.setSkipExpression("${" + BPM_ALLOW_SKIP_USER_TASK + userTask.getId() + "}"); - } - } - private static void setMultiInstance(BpmnJsonNode node, UserTask userTask) { - if (Objects.isNull(node.getProperty()) || Objects.equals(Boolean.FALSE, node.getProperty().getIsMultiTask())) { + if (Objects.isNull(node.getProperty()) || Objects.equals(NODE_STARTER.getType(), userTask.getId()) + || Objects.equals(Boolean.FALSE, node.getProperty().getIsMultiTask())) { return; } MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = @@ -130,8 +138,8 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { } } - private static void setButtonExtensionElement(BpmnJsonNode node, UserTask userTask) { - if (Objects.isNull(node.getProperty())) { + private static void setAdvancedExtensionElement(BpmnJsonNode node, UserTask userTask) { + if (Objects.isNull(node.getProperty()) || Objects.isNull(node.getProperty().getButtonPermission())) { return; } BpmnButtonConf buttonConf = node.getProperty().getButtonPermission(); @@ -146,15 +154,23 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { } userTask.addExtensionElement(buttonConfigElement); + + //添加自动审批配置 + ExtensionElement autoApprovalExtensionElement = new ExtensionElement(); + ExtensionAttribute pendingMessageAttribute = new ExtensionAttribute(); + pendingMessageAttribute.setName(AUTO_APPROVAL_TYPE); + autoApprovalExtensionElement.addAttribute(pendingMessageAttribute); + userTask.addExtensionElement(autoApprovalExtensionElement); } - private static void setFieldExtensionElement(BpmnJsonNode node, UserTask userTask) { - if (Objects.isNull(node.getProperty())) { + private static void setFormFieldExtensionElement(BpmnJsonNode node, UserTask userTask) { + if (Objects.isNull(node.getProperty()) || CollectionUtils.isEmpty(node.getProperty().getFieldPermission())) { return; } ExtensionElement fieldElement = new ExtensionElement(); fieldElement.setName(CONFIG_FIELD_PERMISSION); - fieldElement.setElementText(node.getProperty().getFieldPermission()); + fieldElement.setElementText(Objects.nonNull(node.getProperty().getFieldPermission()) ? + JSON.toJSONString(node.getProperty().getFieldPermission()) : null); userTask.addExtensionElement(fieldElement); } @@ -164,87 +180,187 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { return; } - // 审批方式 - ExtensionElement approvalMethodElement = new ExtensionElement(); - approvalMethodElement.setName(CONFIG_APPROVAL_METHOD); + if (Objects.nonNull(property.getApprovalMethod())) { + // 审批方式 + ExtensionElement approvalMethodElement = new ExtensionElement(); + approvalMethodElement.setName(CONFIG_APPROVAL_METHOD); - ExtensionAttribute approvalMethodValueAttribute = new ExtensionAttribute(); - approvalMethodValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); - approvalMethodValueAttribute.setValue(property.getApprovalMethod().getType()); - approvalMethodElement.addAttribute(approvalMethodValueAttribute); + ExtensionAttribute approvalMethodValueAttribute = new ExtensionAttribute(); + approvalMethodValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approvalMethodValueAttribute.setValue(property.getApprovalMethod().getType()); + approvalMethodElement.addAttribute(approvalMethodValueAttribute); - ExtensionAttribute approvalMethodDescAttribute = new ExtensionAttribute(); - approvalMethodDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); - approvalMethodDescAttribute.setValue("审批方式"); - approvalMethodElement.addAttribute(approvalMethodDescAttribute); - userTask.addExtensionElement(approvalMethodElement); + ExtensionAttribute approvalMethodDescAttribute = new ExtensionAttribute(); + approvalMethodDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approvalMethodDescAttribute.setValue("审批方式"); + approvalMethodElement.addAttribute(approvalMethodDescAttribute); + userTask.addExtensionElement(approvalMethodElement); + } if (BpmnFlowNodeType.NODE_BUSINESS.equals(node.getType()) && ApprovalMethodEnum.nobody.equals(property.getApprovalMethod())) { // 业务节点有一种不设置审批人的选项, 除"审批方式"外都是空配置, 所以在此直接中断后续的扩展属性配置 return; } - // 审批人所在范围 - ExtensionElement approverScopeElement = new ExtensionElement(); - approverScopeElement.setName(CONFIG_APPROVER_SCOPE); - ExtensionAttribute approverScopeValueAttribute = new ExtensionAttribute(); - approverScopeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); - approverScopeValueAttribute.setValue(property.getApproverScope().getType()); - approverScopeElement.addAttribute(approverScopeValueAttribute); + // 签署确认节点特有的配置 + BpmnSignApproverLimit signApproverLimit = property.getSignApproverLimit(); + if (Objects.equals(NODE_SIGN, node.getType()) && Objects.nonNull(signApproverLimit) + && Objects.nonNull(signApproverLimit.getOrgLimit()) && Objects.nonNull(signApproverLimit.getRoleLimit())) { + ExtensionElement signApproveElement = new ExtensionElement(); + signApproveElement.setName(CONFIG_SIGN_APPROVER_LIMIT); - ExtensionAttribute approverScopeDescAttribute = new ExtensionAttribute(); - approverScopeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); - approverScopeDescAttribute.setValue("审批人所在范围"); - approverScopeElement.addAttribute(approverScopeDescAttribute); - userTask.addExtensionElement(approverScopeElement); + ExtensionElement signApproverOrgLimitElement = new ExtensionElement(); + signApproverOrgLimitElement.setName(CONFIG_SIGN_APPROVER_ORG_LIMIT); + ExtensionAttribute signApproverOrgLimitValueAttribute = new ExtensionAttribute(); + signApproverOrgLimitValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + signApproverOrgLimitValueAttribute.setValue(signApproverLimit.getOrgLimit().getType()); + ExtensionAttribute signApproverOrgLimitDescAttribute = new ExtensionAttribute(); + signApproverOrgLimitDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + signApproverOrgLimitDescAttribute.setValue(signApproverLimit.getOrgLimit().getDesc()); + signApproverOrgLimitElement.addAttribute(signApproverOrgLimitValueAttribute); + signApproverOrgLimitElement.addAttribute(signApproverOrgLimitDescAttribute); + signApproveElement.addChildElement(signApproverOrgLimitElement); - // 审批人指定 - ExtensionElement approverSpecifyElement = new ExtensionElement(); - approverSpecifyElement.setName(CONFIG_APPROVER_SPECIFY); + ExtensionElement signApproverRoleLimitElement = new ExtensionElement(); + signApproverRoleLimitElement.setName(CONFIG_SIGN_APPROVER_ROLE_LIMIT); + ExtensionAttribute signApproverRoleLimitValueAttribute = new ExtensionAttribute(); + signApproverRoleLimitValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + signApproverRoleLimitValueAttribute.setValue(signApproverLimit.getRoleLimit().getType()); + ExtensionAttribute signApproverRoleLimitDescAttribute = new ExtensionAttribute(); + signApproverRoleLimitDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + signApproverRoleLimitDescAttribute.setValue(signApproverLimit.getRoleLimit().getDesc()); + signApproverRoleLimitElement.addAttribute(signApproverRoleLimitValueAttribute); + signApproverRoleLimitElement.addAttribute(signApproverRoleLimitDescAttribute); + signApproveElement.addChildElement(signApproverRoleLimitElement); - ExtensionAttribute approverSpecifyValueAttribute = new ExtensionAttribute(); - approverSpecifyValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); - approverSpecifyValueAttribute.setValue(property.getApproverSpecify().getType()); - approverSpecifyElement.addAttribute(approverSpecifyValueAttribute); + userTask.addExtensionElement(signApproveElement); - ExtensionAttribute approverSpecifyDescAttribute = new ExtensionAttribute(); - approverSpecifyDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); - approverSpecifyDescAttribute.setValue("审批人指定"); - approverSpecifyElement.addAttribute(approverSpecifyDescAttribute); - // 审批人指定的具体值 - approverSpecifyElement.setElementText(property.getSpecifyValue()); - userTask.addExtensionElement(approverSpecifyElement); + if (Objects.isNull(property.getApproverSpecify())) { + property.setApproverSpecify(ApproverSpecifyEnum.signerRelated); + } + } - // 多人审批时审批模式 - ExtensionElement approverModeTypeElement = new ExtensionElement(); - approverModeTypeElement.setName(CONFIG_APPROVER_MODE_TYPE); + // TODO 签署确认节点的一些额外配置,需要在此处处理。 + if (Objects.nonNull(property.getApproverScope())) { + // 审批人所在范围 + ExtensionElement approverScopeElement = new ExtensionElement(); + approverScopeElement.setName(CONFIG_APPROVER_SCOPE); - ExtensionAttribute approverModeTypeValueAttribute = new ExtensionAttribute(); - approverModeTypeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); - approverModeTypeValueAttribute.setValue(property.getMultiMode().getType()); - approverModeTypeElement.addAttribute(approverModeTypeValueAttribute); + ExtensionAttribute approverScopeValueAttribute = new ExtensionAttribute(); + approverScopeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approverScopeValueAttribute.setValue(property.getApproverScope().getType()); + approverScopeElement.addAttribute(approverScopeValueAttribute); - ExtensionAttribute approverModeTypeDescAttribute = new ExtensionAttribute(); - approverModeTypeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); - approverModeTypeDescAttribute.setValue("多人审批时审批模式"); - approverModeTypeElement.addAttribute(approverModeTypeDescAttribute); - userTask.addExtensionElement(approverModeTypeElement); + ExtensionAttribute approverScopeDescAttribute = new ExtensionAttribute(); + approverScopeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approverScopeDescAttribute.setValue("审批人所在范围"); + approverScopeElement.addAttribute(approverScopeDescAttribute); + userTask.addExtensionElement(approverScopeElement); + } - // 审批人为空时 - ExtensionElement approverEmptyHandleTypeElement = new ExtensionElement(); - approverEmptyHandleTypeElement.setName(CONFIG_APPROVER_EMPTY_HANDLE_TYPE); - ExtensionAttribute approverEmptyHandleTypeValueAttribute = new ExtensionAttribute(); - approverEmptyHandleTypeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); - approverEmptyHandleTypeValueAttribute.setValue(property.getApproverEmptyHandleType().getType()); - approverEmptyHandleTypeElement.setElementText(StringUtils.hasLength(property.getEmptyApproverSpecify()) ? - property.getEmptyApproverSpecify() : "[]"); - approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeValueAttribute); + if (Objects.nonNull(property.getApproverSpecify())) { - ExtensionAttribute approverEmptyHandleTypeDescAttribute = new ExtensionAttribute(); - approverEmptyHandleTypeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); - approverEmptyHandleTypeDescAttribute.setValue("审批人为空时"); - approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeDescAttribute); - userTask.addExtensionElement(approverEmptyHandleTypeElement); + // 审批人指定 + ExtensionElement approverSpecifyElement = new ExtensionElement(); + approverSpecifyElement.setName(CONFIG_APPROVER_SPECIFY); + + ExtensionAttribute approverSpecifyValueAttribute = new ExtensionAttribute(); + approverSpecifyValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approverSpecifyValueAttribute.setValue(property.getApproverSpecify().getType()); + approverSpecifyElement.addAttribute(approverSpecifyValueAttribute); + + ExtensionAttribute approverSpecifyDescAttribute = new ExtensionAttribute(); + approverSpecifyDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approverSpecifyDescAttribute.setValue("审批人指定"); + approverSpecifyElement.addAttribute(approverSpecifyDescAttribute); + // 审批人指定的具体值 + approverSpecifyElement.setElementText(property.getSpecifyValue()); + userTask.addExtensionElement(approverSpecifyElement); + } + + if (Objects.nonNull(property.getMultiMode())) { + // 多人审批时审批模式 + ExtensionElement approverModeTypeElement = new ExtensionElement(); + approverModeTypeElement.setName(CONFIG_APPROVER_MODE_TYPE); + + ExtensionAttribute approverModeTypeValueAttribute = new ExtensionAttribute(); + approverModeTypeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approverModeTypeValueAttribute.setValue(property.getMultiMode().getType()); + approverModeTypeElement.addAttribute(approverModeTypeValueAttribute); + + ExtensionAttribute approverModeTypeDescAttribute = new ExtensionAttribute(); + approverModeTypeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approverModeTypeDescAttribute.setValue("多人审批时审批模式"); + approverModeTypeElement.addAttribute(approverModeTypeDescAttribute); + userTask.addExtensionElement(approverModeTypeElement); + } + + if (Objects.nonNull(property.getApproverEmptyHandleType())) { + // 审批人为空时 + ExtensionElement approverEmptyHandleTypeElement = new ExtensionElement(); + approverEmptyHandleTypeElement.setName(CONFIG_APPROVER_EMPTY_HANDLE_TYPE); + ExtensionAttribute approverEmptyHandleTypeValueAttribute = new ExtensionAttribute(); + approverEmptyHandleTypeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approverEmptyHandleTypeValueAttribute.setValue(property.getApproverEmptyHandleType().getType()); + approverEmptyHandleTypeElement.setElementText(StringUtils.hasLength(property.getEmptyApproverSpecify()) ? + property.getEmptyApproverSpecify() : "[]"); + approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeValueAttribute); + + ExtensionAttribute approverEmptyHandleTypeDescAttribute = new ExtensionAttribute(); + approverEmptyHandleTypeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approverEmptyHandleTypeDescAttribute.setValue("审批人为空时"); + approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeDescAttribute); + userTask.addExtensionElement(approverEmptyHandleTypeElement); + } + + if (Objects.equals(Boolean.TRUE, property.getSignature())) { + // 电子签名开关 + ExtensionElement signatureElement = new ExtensionElement(); + signatureElement.setName(CONFIG_ACTIVITY_SIGNATURE); + + ExtensionAttribute approverSignatureValueAttribute = new ExtensionAttribute(); + approverSignatureValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE); + approverSignatureValueAttribute.setValue(property.getSignature().toString()); + signatureElement.addAttribute(approverSignatureValueAttribute); + + ExtensionAttribute approverSignatureDescAttribute = new ExtensionAttribute(); + approverSignatureDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC); + approverSignatureDescAttribute.setValue("电子签名"); + signatureElement.addAttribute(approverSignatureDescAttribute); + + userTask.addExtensionElement(signatureElement); + } + + // “提级审批配置” + BpmnUpgradeApprovalConf upgradeApprovalConf = property.getUpgradeApprovalConf(); + if(Objects.nonNull(upgradeApprovalConf) && Objects.equals(Boolean.TRUE, upgradeApprovalConf.getEnabled())){ + ExtensionElement upgradeApprovalElement = new ExtensionElement(); + upgradeApprovalElement.setName(TEMPLATE_UPGRADE_APPROVAL_CONF); + ExtensionAttribute upgradeApprovalEnabledAttribute = new ExtensionAttribute(); + upgradeApprovalEnabledAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED); + upgradeApprovalEnabledAttribute.setValue(String.valueOf(Boolean.TRUE.equals(property.getUpgradeApprovalConf().getEnabled()))); + upgradeApprovalElement.addAttribute(upgradeApprovalEnabledAttribute); + + + ExtensionElement upgradeApprovalLimitElement = new ExtensionElement(); + upgradeApprovalLimitElement.setName(TEMPLATE_UPGRADE_APPROVAL_LIMIT_CONF); + + ExtensionAttribute upgradeApprovalLimitOrgAttribute = new ExtensionAttribute(); + upgradeApprovalLimitOrgAttribute.setName(ELEMENT_ATTRIBUTE_ORG_LIMIT); + upgradeApprovalLimitOrgAttribute.setValue(property.getUpgradeApprovalConf().getOrgLimit().getType()); + upgradeApprovalLimitElement.addAttribute(upgradeApprovalLimitOrgAttribute); + ExtensionAttribute upgradeApprovalLimitSpecifyTypeAttribute = new ExtensionAttribute(); + upgradeApprovalLimitSpecifyTypeAttribute.setName(ELEMENT_ATTRIBUTE_APPROVER_SPECIFY); + upgradeApprovalLimitSpecifyTypeAttribute.setValue(property.getUpgradeApprovalConf().getApproverSpecify().getType()); + upgradeApprovalLimitElement.addAttribute(upgradeApprovalLimitSpecifyTypeAttribute); + upgradeApprovalElement.addChildElement(upgradeApprovalLimitElement); + + ExtensionElement upgradeApprovalSpecifyElement = new ExtensionElement(); + upgradeApprovalSpecifyElement.setName(TEMPLATE_UPGRADE_APPROVAL_SPECIFY_VALUE); + upgradeApprovalSpecifyElement.setElementText(property.getUpgradeApprovalConf().getSpecifyValue()); + upgradeApprovalElement.addChildElement(upgradeApprovalSpecifyElement); + userTask.addExtensionElement(upgradeApprovalElement); + } } /** @@ -270,11 +386,12 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter { activityStartListener.setImplementation("${engineActivityStartEventListener}"); executionListeners.add(activityStartListener); - FlowableListener activityTakeListener = new FlowableListener(); + // take 事件在 Flowable 引擎中只针对 SequenceFlow 有效,所以这里不设置 take 事件 + /*FlowableListener activityTakeListener = new FlowableListener(); activityTakeListener.setEvent(BaseExecutionListener.EVENTNAME_TAKE); activityTakeListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); activityTakeListener.setImplementation("${engineActivityTakeEventListener}"); - executionListeners.add(activityTakeListener); + executionListeners.add(activityTakeListener);*/ FlowableListener activityEndListener = new FlowableListener(); activityEndListener.setEvent(BaseExecutionListener.EVENTNAME_END); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/deletage/approverscope/GovWorkspaceProcessor.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/deletage/approverscope/GovWorkspaceProcessor.java new file mode 100644 index 000000000..a5b07453d --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/deletage/approverscope/GovWorkspaceProcessor.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.core.deletage.approverscope; + +import cn.axzo.workflow.common.enums.WorkspaceType; +import cn.axzo.workflow.common.model.dto.CooperationOrgDTO; +import cn.axzo.workflow.common.model.dto.CooperationOrgDTO.OrgScope; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; + +/** + * "审批范围"选择"所在单位",这里的单位实际上是政务站点 + * + * @author syl + * @date 2023/11/21 + */ +@Component +public class GovWorkspaceProcessor implements ApproverScopeProcessor { + + @Override + public ApproverScopeDTO build(FlowElement flowElement, DelegateExecution execution) { + // 获取组织信息 + CooperationOrgDTO orgDTO = (CooperationOrgDTO) execution.getVariables().get(BIZ_ORG_RELATION); + ApproverScopeDTO build = ApproverScopeDTO.builder().build(); + if (Objects.isNull(orgDTO)) { + return build; + } + + if (!CollectionUtils.isEmpty(orgDTO.getOrgScopes())) { + List orgScopes = orgDTO.getOrgScopes().stream() + .filter(o -> Objects.equals(WorkspaceType.GOVERNMENT, WorkspaceType.getType(o.getWorkspaceType()))) + .collect(Collectors.toList()); + build.setOrgScopes(orgScopes); + } + + return build; + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/deletage/approverscope/PreTaskUserProcessor.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/deletage/approverscope/PreTaskUserProcessor.java index abbec1663..2afcf7d78 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/deletage/approverscope/PreTaskUserProcessor.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/deletage/approverscope/PreTaskUserProcessor.java @@ -38,6 +38,7 @@ public class PreTaskUserProcessor implements ApproverScopeProcessor { // 上级节点 String preActivityId = preUserTaskOpt.get().getId(); // 获取上一级节点 + @SuppressWarnings("unchecked") List assigners = (List) execution .getVariableLocal(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + preActivityId); List entOrg = CollectionUtils.emptyIfNull(assigners).stream() diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomActivityBehaviorFactory.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomActivityBehaviorFactory.java index a926d908a..1dd615d74 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomActivityBehaviorFactory.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomActivityBehaviorFactory.java @@ -1,6 +1,5 @@ package cn.axzo.workflow.core.engine.behavior; -import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import org.flowable.bpmn.model.Activity; import org.flowable.bpmn.model.ReceiveTask; import org.flowable.bpmn.model.ServiceTask; @@ -23,7 +22,6 @@ import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFacto * @since 2023/7/29 00:34 */ public class CustomActivityBehaviorFactory extends DefaultActivityBehaviorFactory { - private ExtAxHiTaskInstService hiTaskInstService; @Override public ParallelMultiInstanceBehavior createParallelMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) { @@ -43,7 +41,7 @@ public class CustomActivityBehaviorFactory extends DefaultActivityBehaviorFactor @Override public ReceiveTaskActivityBehavior createReceiveTaskActivityBehavior(ReceiveTask receiveTask) { - return new CustomReceiveTaskActivityBehavior(receiveTask, hiTaskInstService); + return new CustomReceiveTaskActivityBehavior(receiveTask); } @Override @@ -56,7 +54,4 @@ public class CustomActivityBehaviorFactory extends DefaultActivityBehaviorFactor serviceTask.getMapExceptions(), serviceTask.isTriggerable(), serviceTask); } - public void setHiTaskInstService(ExtAxHiTaskInstService hiTaskInstService) { - this.hiTaskInstService = hiTaskInstService; - } } 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 4aa632d3b..db15327e2 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 @@ -2,7 +2,6 @@ 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.service.ExtAxHiTaskInstService; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.ReceiveTask; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; @@ -13,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; @@ -31,11 +30,9 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCES @Slf4j public class CustomReceiveTaskActivityBehavior extends ReceiveTaskActivityBehavior { protected ReceiveTask receiveTask; - protected ExtAxHiTaskInstService hiTaskInstService; - public CustomReceiveTaskActivityBehavior(ReceiveTask receiveTask, ExtAxHiTaskInstService hiTaskInstService) { + public CustomReceiveTaskActivityBehavior(ReceiveTask receiveTask) { this.receiveTask = receiveTask; - this.hiTaskInstService = hiTaskInstService; } @Override @@ -51,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(); @@ -68,14 +66,20 @@ 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()); + + // 解决两个业务节点相邻时,无法正确的往下流转的问题 + TaskHelper.deleteTask(task, "complete business node", false, true, true); } 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 40a81a566..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 @@ -1,6 +1,8 @@ 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; @@ -18,13 +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 @@ -33,6 +38,7 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROV public class CustomServiceTaskDelegateExpressionActivityBehavior extends ServiceTaskDelegateExpressionActivityBehavior { protected final ServiceTask serviceTask; + // thread safe private TaskEntity task; public CustomServiceTaskDelegateExpressionActivityBehavior(String serviceTaskId, Expression expression, @@ -55,26 +61,39 @@ 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(); eventDispatcher.dispatchEvent(new ExtTaskInstCreateEvent(execution.getProcessInstanceId(), - execution.getCurrentActivityId(), task.getId(), APPROVED), + execution.getCurrentActivityId(), task.getId(), PROCESSING), processEngineConfiguration.getEngineCfgKey()); super.execute(execution); } -// @Override -// public void leave(DelegateExecution execution) { -// CommandContext commandContext = CommandContextUtil.getCommandContext(); -// ProcessEngineConfigurationImpl processEngineConfiguration = -// CommandContextUtil.getProcessEngineConfiguration(commandContext); -// -// FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); -// eventDispatcher.dispatchEvent(new ExtTaskInstUpdateEvent(execution.getProcessInstanceId(), -// execution.getCurrentActivityId(), task.getId(), APPROVED), -// processEngineConfiguration.getEngineCfgKey()); -// super.leave(execution); -// } + @Override + public void leave(DelegateExecution execution) { + CommandContext commandContext = CommandContextUtil.getCommandContext(); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); + + 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/behavior/CustomUserTaskActivityBehavior.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomUserTaskActivityBehavior.java index 22fd153b9..2a1ddc4be 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomUserTaskActivityBehavior.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/behavior/CustomUserTaskActivityBehavior.java @@ -187,9 +187,10 @@ public class CustomUserTaskActivityBehavior extends UserTaskActivityBehavior { ExpressionManager expressionManager, DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { if (Objects.equals(NODE_STARTER.getType(), execution.getCurrentActivityId())) { - BpmnTaskDelegateAssigner assigner = execution.getVariable(INTERNAL_INITIATOR, - BpmnTaskDelegateAssigner.class); - assignee = assigner.buildAssigneeId(); + BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(execution.getVariable(INTERNAL_INITIATOR)); + if (Objects.nonNull(assigner)) { + assignee = assigner.buildAssigneeId(); + } } super.handleAssignments(taskService, assignee, owner, candidateUsers, candidateGroups, task, expressionManager, execution, processEngineConfiguration); 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 new file mode 100644 index 000000000..85513cf84 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/AbstractCommand.java @@ -0,0 +1,43 @@ +package cn.axzo.workflow.core.engine.cmd; + + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; + +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.ENGINE_EXEC_EXCEPTION; + +/** + * 抽象的命令,用于将在 Command 中执行的逻辑所抛出的非 WorkflowEngineException 的异常都包装为 WorkflowEngineException + * + * @author wangli + * @since 2024/7/1 13:59 + */ +@Slf4j +public abstract class AbstractCommand implements Command { + + public abstract String paramToJsonString(); + + @Override + public T execute(CommandContext commandContext) { + try { + return executeInternal(commandContext); + } catch (WorkflowEngineException e) { + throw e; + } catch (Exception e) { + log.warn(e.getMessage(), e); + throw new WorkflowEngineException(ENGINE_EXEC_EXCEPTION, e, e.getMessage()); + } + } + + /** + * 实现该方法,默认就捕获了 command 一些引擎内部异常 + * + * @param commandContext + * @return + */ + public T executeInternal(CommandContext commandContext) { + return null; + } +} 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 new file mode 100644 index 000000000..2610ff38e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAbortProcessInstanceAsyncCmd.java @@ -0,0 +1,89 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +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; +import org.flowable.engine.HistoryService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.history.HistoricProcessInstanceQuery; +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.springframework.util.StringUtils; + +import java.io.Serializable; +import java.util.Objects; + +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.CANCELLED; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANT_ABORT; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; + +public class CustomAbortProcessInstanceAsyncCmd extends AbstractCommand implements Serializable { + + private final BpmnProcessInstanceAbortDTO dto; + + public CustomAbortProcessInstanceAsyncCmd(BpmnProcessInstanceAbortDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public Void execute(CommandContext commandContext) { + String processInstanceId = dto.getProcessInstanceId(); + String tenantId = dto.getTenantId(); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoryService historyService = processEngineConfiguration.getHistoryService(); + HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId); + if (StringUtils.hasText(tenantId)) { + query.processInstanceTenantId(tenantId); + } + HistoricProcessInstance instance = query.singleResult(); + if (Objects.isNull(instance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS); + } + + if (Objects.nonNull(instance.getEndTime())) { + throw new WorkflowEngineException(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); + } + + if (Objects.equals(instance.getBusinessStatus(), CANCELLED.getStatus())) { + throw new WorkflowEngineException(PROCESS_INSTANCE_CANT_ABORT); + } + startAsync(processEngineConfiguration, instance); + + return null; + } + + private void startAsync(ProcessEngineConfigurationImpl processEngineConfiguration, HistoricProcessInstance instance) { + JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService(); + + JobEntity job = jobService.createJob(); + // 这里的 executionId 可为 null + job.setExecutionId(instance.getId()); + job.setProcessInstanceId(instance.getId()); + job.setProcessDefinitionId(instance.getProcessDefinitionId()); + job.setElementId(AsyncAbortProcessInstanceJobHandler.TYPE); + job.setElementName(instance.getName()); + job.setJobHandlerType(AsyncAbortProcessInstanceJobHandler.TYPE); + job.setTenantId(instance.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + } + +} 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 15a121697..45a2eef3c 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 @@ -1,10 +1,13 @@ package cn.axzo.workflow.core.engine.cmd; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; +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.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 org.flowable.common.engine.impl.interceptor.Command; +import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; @@ -12,31 +15,21 @@ import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; 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.StringUtils; 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.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; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_NAME; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_DELETE_REASON; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_ABORT; -import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ASSIGNEE_SKIP_FLAT; -import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.*; +import static cn.axzo.workflow.common.constant.BpmnConstants.*; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_ABORT; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.ABORTED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.CANCELLED; -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.createVirtualTask; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.*; /** * 自定义的中止流程实例的命令实现 @@ -44,18 +37,42 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVir * @author wangli * @since 2024/1/2 18:19 */ -public class CustomAbortProcessInstanceCmd implements Command, Serializable { +public class CustomAbortProcessInstanceCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = -4141519915611160506L; private final String processInstanceId; private final String tenantId; private final String reason; + private final List attachmentList; + private final String nodeName; + private final BpmnTaskDelegateAssigner assigner; + private final String signatureUrl; private final ExtAxHiTaskInstService extAxHiTaskInstService; + private final String advice; - public CustomAbortProcessInstanceCmd(String processInstanceId, String tenantId, String reason, - ExtAxHiTaskInstService extAxHiTaskInstService) { - this.processInstanceId = processInstanceId; - this.tenantId = tenantId; - this.reason = reason; + public CustomAbortProcessInstanceCmd(BpmnProcessInstanceAbortDTO dto, ExtAxHiTaskInstService extAxHiTaskInstService) { + this.processInstanceId = dto.getProcessInstanceId(); + this.tenantId = dto.getTenantId(); + this.reason = dto.getReason(); + this.attachmentList = dto.getAttachmentList(); + this.nodeName = StringUtils.hasText(dto.getNodeName()) ? dto.getNodeName() : "系统中止"; + this.assigner = Objects.nonNull(dto.getAssigner()) ? dto.getAssigner() : + BpmnTaskDelegateAssigner.buildDummyAssigner("system", TASK_ASSIGNEE_SKIP_FLAT, ""); + this.signatureUrl = dto.getSignatureUrl(); this.extAxHiTaskInstService = extAxHiTaskInstService; + this.advice = dto.getAdvice(); + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("tenantId", tenantId); + params.put("reason", reason); + params.put("attachmentList", JSON.toJSONString(attachmentList)); + params.put("nodeName", nodeName); + params.put("assigner", assigner); + params.put("signatureUrl", signatureUrl); + return JSON.toJSONString(params); } @Override @@ -82,24 +99,28 @@ public class CustomAbortProcessInstanceCmd implements Command, Serializabl throw new WorkflowEngineException(PROCESS_INSTANCE_CANT_ABORT); } + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); Map variables = new HashMap<>(); - variables.put(INTERNAL_END_TENANT_ID, tenantId); - variables.put(INTERNAL_END_USER_ID, "system"); - variables.put(INTERNAL_END_USER_NAME, "系统"); variables.put(INTERNAL_DELETE_PROCESS_FLAG, INTERNAL_PROCESS_TYPE_ABORT); variables.put(INTERNAL_PROCESS_DELETE_REASON, reason); - RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); +// variables.put(INTERNAL_END_TENANT_ID, assigner.getTenantId()); +// variables.put(INTERNAL_END_USER_ID, assigner.buildAssigneeId()); +// variables.put(INTERNAL_END_USER_NAME, assigner.getAssignerName()); 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); + TaskEntity task = createVirtualTask(commandContext, extAxHiTaskInstService, processInstanceId, + nodeName, NODE_ABORT.getType(), advice, assigner, ABORTED.getStatus(), new AddComment(reason)); runtimeService.setVariable(task.getProcessInstanceId(), TASK_COMPLETE_OPERATION_TYPE + task.getId(), ABORTED); + + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, assigner); + completeVirtualTask(commandContext, task); + + runtimeService.setVariable(processInstanceId, CLOSE_PROCESS_ASSIGNER, assigner); 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..0937675e4 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerAsyncCmd.java @@ -0,0 +1,90 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.engine.job.AsyncActivityTriggerJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import org.apache.commons.lang3.StringUtils; +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.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.code.BpmnTaskRespCode.ACTIVITY_TRIGGER_NOT_EXISTS; + +/** + * 自定义(异步)流转业务姐弟那的命令器实现 + * + * @author wangli + * @since 2024-09-09 13:58 + */ +public class CustomActivityTriggerAsyncCmd extends AbstractCommand implements Serializable { + + private static final long serialVersionUID = 4037000685442842145L; + 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); + TaskEntity task = (TaskEntity) processEngineConfiguration.getTaskService().createTaskQuery() + .executionId(dto.getTriggerId()) + .taskDefinitionKey(StringUtils.isBlank(dto.getActivityId()) ? null : dto.getActivityId()) + .singleResult(); + if (Objects.isNull(task)) { + 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()) + .taskDefinitionKey(StringUtils.isBlank(dto.getActivityId()) ? null : dto.getActivityId()) + .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..df374ad18 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomActivityTriggerCmd.java @@ -0,0 +1,60 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO; +import com.alibaba.fastjson.JSON; +import org.apache.commons.lang3.StringUtils; +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.code.BpmnTaskRespCode.ACTIVITY_TRIGGER_NOT_EXISTS; +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; +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 long serialVersionUID = 5806130509691903321L; + 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()) + .taskDefinitionKey(StringUtils.isBlank(dto.getActivityId()) ? null : dto.getActivityId()) + .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/CustomAddTimerJobCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAddTimerJobCmd.java new file mode 100644 index 000000000..31cae5f7b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomAddTimerJobCmd.java @@ -0,0 +1,77 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.dto.TermNodeAddTimerJobDTO; +import cn.axzo.workflow.core.converter.json.NotSupportConverter; +import cn.axzo.workflow.core.engine.job.AsyncTermNodeAlterJobHandler; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.TimerEventDefinition; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.ManagementService; +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.TimerUtil; +import org.flowable.engine.runtime.Execution; +import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; +import org.springframework.util.CollectionUtils; + +import java.io.Serializable; +import java.util.List; + +/** + * 自定义添加定时任务的逻辑 + * + * @author wangli + * @since 2025-03-19 18:24 + */ +@Slf4j +public class CustomAddTimerJobCmd extends AbstractCommand implements Serializable { + private final TermNodeAddTimerJobDTO dto; + + public CustomAddTimerJobCmd(TermNodeAddTimerJobDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public Void executeInternal(CommandContext commandContext) { + log.info("CustomAddTimerJobCmd start. instanceId: {}, activityId: {}, timeCycle: {}", dto.getProcessInstanceId(), dto.getActivityId(), dto.getTimeCycle()); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + + ManagementService managementService = processEngineConfiguration.getManagementService(); + String tableName = managementService.getTableName(Execution.class); + List list = processEngineConfiguration.getRuntimeService() + .createNativeExecutionQuery() + .sql("SELECT * FROM " + tableName + " WHERE PROC_INST_ID_ = #{instanceId} AND ACT_ID_ = #{activityId} AND IS_ACTIVE_ = 1 AND TASK_COUNT_ = 1") + .parameter("instanceId", dto.getProcessInstanceId()) + .parameter("activityId", dto.getActivityId()) + .list(); + if (CollectionUtils.isEmpty(list)) { + return null; + } + + if (list.get(list.size() - 1) instanceof ExecutionEntity) { + ExecutionEntity executionEntity = (ExecutionEntity) list.get(list.size() - 1); + TimerEventDefinition timerEventDefinition = new TimerEventDefinition(); + timerEventDefinition.setTimeCycle(dto.getTimeCycle()); + TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, + new NotSupportConverter.NotSupportFlowElement(), + false, executionEntity, AsyncTermNodeAlterJobHandler.TYPE, + TimerEventHandler.createConfiguration(executionEntity.getCurrentActivityId(), null, + timerEventDefinition.getCalendarName())); + if (timerJob != null) { + CommandContextUtil.getTimerJobService().scheduleTimerJob(timerJob); + } + } else { + log.warn("未找到 execution entity"); + } + 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 new file mode 100644 index 000000000..c89d20c74 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskAsyncCmd.java @@ -0,0 +1,88 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +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.TaskService; +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.api.history.HistoricTaskInstance; +import org.flowable.task.api.history.HistoricTaskInstanceQuery; +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.engine.cmd.helper.CustomTaskHelper.validTask; + +/** + * 自定义(异步)审批通过任务的命令器实现 + * + * @author wangli + * @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) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public String executeInternal(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + TaskService taskService = processEngineConfiguration.getTaskService(); + + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult(); + + Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + log.info("任务不存在: {}", dto.getTaskId()); + } + validTask(historicTaskInstance, (TaskEntity) task, dto.getApprover(), null); + + return startAsync(processEngineConfiguration, task); + } + + 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(AsyncApproveTaskJobHandler.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/CustomApproveTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java index c36ac00da..c6fef6634 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 @@ -1,9 +1,14 @@ package cn.axzo.workflow.core.engine.cmd; +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.model.dto.SignatureDTO; 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 com.alibaba.fastjson.JSON; +import org.apache.commons.collections4.ListUtils; import org.flowable.common.engine.impl.identity.Authentication; -import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; @@ -15,15 +20,22 @@ import org.flowable.task.api.history.HistoricTaskInstanceQuery; import org.flowable.task.service.impl.persistence.entity.TaskEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; +import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER; import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; @@ -31,20 +43,26 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddA import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; /** - * 自定义审批通过任务的命令器实现 + * 自定义(同步)审批通过任务的命令器实现 * * @author wangli * @since 2024/1/4 15:50 */ -public class CustomApproveTaskCmd implements Command, Serializable { +public class CustomApproveTaskCmd extends AbstractCommand implements Serializable { private static final Logger log = LoggerFactory.getLogger(CustomApproveTaskCmd.class); private final String taskId; + /** + * advice 统一均为审批节点的审批意见,一般为用户在同意、驳回时填写的内容 + * 有值时,在日志中一般出现在 operationDesc 的下方 + */ private final String advice; /** + * operationDesc 统一为审批节点的动作描述,例如:某某转交、加签某某等 + * 在日志中一般出现在节点名称的下方 * 不传,默认"已通过" */ - private String operationDesc = "已通过"; + private String operationDesc; /** * 附件, 可为空 */ @@ -55,25 +73,50 @@ public class CustomApproveTaskCmd implements Command, Serializable { */ private final BpmnTaskDelegateAssigner nextApprover; - public CustomApproveTaskCmd(String taskId, String advice, List attachmentList, - BpmnTaskDelegateAssigner approver, BpmnTaskDelegateAssigner nextApprover) { - this.taskId = taskId; - this.advice = advice; - this.attachmentList = attachmentList; - this.approver = approver; - this.nextApprover = nextApprover; + /** + * 指定节点类型 + */ + private List nodeTypes; + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("taskId", taskId); + params.put("advice", advice); + params.put("operationDesc", operationDesc); + params.put("attachmentList", JSON.toJSONString(attachmentList)); + params.put("approver", JSON.toJSONString(approver)); + params.put("nextApprover", JSON.toJSONString(nextApprover)); + params.put("nodeTypes", JSON.toJSONString(nodeTypes)); + return JSON.toJSONString(params); } - public CustomApproveTaskCmd(String taskId, String advice, String operationDesc, List attachmentList, - BpmnTaskDelegateAssigner approver, BpmnTaskDelegateAssigner nextApprover) { - this.taskId = taskId; - this.advice = advice; - this.operationDesc = operationDesc; - this.attachmentList = attachmentList; - this.approver = approver; - this.nextApprover = nextApprover; + + public CustomApproveTaskCmd(BpmnTaskAuditDTO dto) { + this(dto, null); + if (Objects.nonNull(dto.getOperationDesc())) { + this.operationDesc = dto.getOperationDesc(); + } + } + + public CustomApproveTaskCmd(BpmnTaskAuditDTO dto, String operationDesc) { + this.taskId = dto.getTaskId(); + this.advice = dto.getAdvice(); + this.attachmentList = dto.getAttachmentList(); + this.approver = dto.getApprover(); + this.nextApprover = dto.getNextApprover(); + this.nodeTypes = dto.getNodeTypes(); + // 这里的不能直接使用字符串的比较,因为外部可能传入空字符串,比如发起人的通过时,就是传入的空字符串 + if (Objects.nonNull(operationDesc)) { + this.operationDesc = operationDesc; + } else { + this.operationDesc = "已同意"; + } } @Override + /** + * TODO 重要说明,该类与 CustomApproveTaskWithFormCmd 作用等同,当逻辑有调整时,需同步调整 + */ public Void execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); @@ -83,11 +126,11 @@ public class CustomApproveTaskCmd implements Command, Serializable { HistoricTaskInstance historicTaskInstance = taskQuery.taskId(taskId).singleResult(); - Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); - if (Objects.isNull(task)) { - log.info("任务不存在: {}", taskId); - } - validTask(historicTaskInstance, (TaskEntity) task, approver); + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult(); + validTask(historicTaskInstance, task, approver, nodeTypes); + + // TODO 所有的跟 Task 相关的动作都可以在这里进行扩展,用于扩展八大按钮标准动作以外的一些逻辑,但这里需要结合 Spring 能力,需设计好扩展点,否则无法进行扩展 + // 其他动态也应该在类似的地方预留扩展点 if (StringUtils.hasLength(advice)) { Authentication.setAuthenticatedUserId(approver.buildAssigneeId()); @@ -95,7 +138,7 @@ public class CustomApproveTaskCmd implements Command, Serializable { 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); @@ -107,16 +150,52 @@ public class CustomApproveTaskCmd implements Command, Serializable { 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()); + // 记录电子签名的图片 + recordSignature(task, runtimeService); - if (StringUtils.hasLength(task.getExecutionId())) { + runtimeService.setVariable(task.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, approver); + + executeSynchronous(task, taskService, runtimeService); + return null; + } + + private void recordSignature(TaskEntity task, RuntimeService runtimeService) { + List signatures = runtimeService.getVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, List.class); + if (Objects.isNull(signatures)) { + signatures = new ArrayList<>(); + } + Optional any = signatures.stream() + .filter(i -> Objects.equals(i.getActivityId(), task.getTaskDefinitionKey())).findAny(); + SignatureDTO dto = any.orElse(new SignatureDTO() + .setActivityId(task.getTaskDefinitionKey()) + .setActivityName(task.getName()) + .setSignatures(new ArrayList<>())); + ListUtils.emptyIfNull(attachmentList).stream() + .filter(i -> Objects.equals(i.getType(), AttachmentTypeEnum.signature)) + .findFirst() + .ifPresent(attachment -> dto.getSignatures().add(0, + new SignatureDTO.SignDetail() + .setSignature(attachment.getUrl()) + .setAdvice(advice))); + if (!any.isPresent()) { + signatures.add(dto); + } + + if (!CollectionUtils.isEmpty(signatures)) { + runtimeService.setVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, signatures); + } + } + + private void executeSynchronous(Task task, TaskService taskService, RuntimeService runtimeService) { + if (StringUtils.hasText(task.getExecutionId())) { // 正常完成流程任务,审批通过 taskService.complete(task.getId(), runtimeService.getVariables(task.getExecutionId())); } else { // 特殊的完成单独创建的任务 taskService.complete(task.getId()); } - return null; } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormAsyncCmd.java new file mode 100644 index 000000000..b0f1bc1db --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormAsyncCmd.java @@ -0,0 +1,82 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO; +import cn.axzo.workflow.core.engine.job.AsyncApproveTaskWithFormJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +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.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.impl.persistence.entity.TaskEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; + +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; + +/** + * 自定义(同步)审批通过任务的命令器实现 + * + * @author wangli + * @since 2024/1/4 15:50 + */ +public class CustomApproveTaskWithFormAsyncCmd extends AbstractCommand implements Serializable { + + private static final Logger log = LoggerFactory.getLogger(CustomApproveTaskWithFormAsyncCmd.class); + private final BpmnTaskAuditWithFormDTO dto; + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + public CustomApproveTaskWithFormAsyncCmd(BpmnTaskAuditWithFormDTO dto) { + this.dto = dto; + } + + @Override + public String execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + TaskService taskService = processEngineConfiguration.getTaskService(); + + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult(); + + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult(); + validTask(historicTaskInstance, task, dto.getApprover(), dto.getNodeTypes()); + + + return startAsync(processEngineConfiguration, task); + } + + 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(AsyncApproveTaskWithFormJobHandler.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/CustomApproveTaskWithFormCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormCmd.java new file mode 100644 index 000000000..a78912c29 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskWithFormCmd.java @@ -0,0 +1,271 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.enums.AttachmentTypeEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.SignatureDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; +import cn.axzo.workflow.core.common.utils.SpringContextUtils; +import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import com.alibaba.fastjson.JSON; +import org.apache.commons.collections4.ListUtils; +import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.form.api.FormDefinition; +import org.flowable.form.api.FormEngineConfigurationApi; +import org.flowable.form.api.FormRepositoryService; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_FIELD_VALIDATOR_ERROR; +import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER; +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; +import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_SUBMIT_FORM_VARIABLE; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; +import static cn.axzo.workflow.core.engine.cmd.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; + +/** + * 自定义(同步)审批通过任务的命令器实现 + * + * @author wangli + * @since 2024/1/4 15:50 + */ +public class CustomApproveTaskWithFormCmd extends AbstractCommand implements Serializable { + + private static final long serialVersionUID = 2366844598166684546L; + private static final Logger log = LoggerFactory.getLogger(CustomApproveTaskWithFormCmd.class); + private final String taskId; + /** + * advice 统一均为审批节点的审批意见,一般为用户在同意、驳回时填写的内容 + * 有值时,在日志中一般出现在 operationDesc 的下方 + */ + private final String advice; + /** + * operationDesc 统一为审批节点的动作描述,例如:某某转交、加签某某等 + * 在日志中一般出现在节点名称的下方 + * 不传,默认"已通过" + */ + private String operationDesc; + /** + * 附件, 可为空 + */ + private final List attachmentList; + private final BpmnTaskDelegateAssigner approver; + /** + * 下级节点的审批,可为空 + */ + private final BpmnTaskDelegateAssigner nextApprover; + + /** + * 指定节点类型 + */ + private final List nodeTypes; + + /** + * 表单数据 + */ + private final Map formVariables; + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("taskId", taskId); + params.put("advice", advice); + params.put("operationDesc", operationDesc); + params.put("attachmentList", JSON.toJSONString(attachmentList)); + params.put("approver", JSON.toJSONString(approver)); + params.put("nextApprover", JSON.toJSONString(nextApprover)); + params.put("nodeTypes", JSON.toJSONString(nodeTypes)); + params.put("formVariables", JSON.toJSONString(formVariables)); + return JSON.toJSONString(params); + } + + public CustomApproveTaskWithFormCmd(BpmnTaskAuditWithFormDTO dto) { + this(dto, null); + if (Objects.nonNull(dto.getOperationDesc())) { + this.operationDesc = dto.getOperationDesc(); + } + } + + public CustomApproveTaskWithFormCmd(BpmnTaskAuditWithFormDTO dto, String operationDesc) { + this.taskId = dto.getTaskId(); + this.advice = dto.getAdvice(); + this.attachmentList = dto.getAttachmentList(); + this.approver = dto.getApprover(); + this.nextApprover = dto.getNextApprover(); + this.nodeTypes = dto.getNodeTypes(); + this.formVariables = dto.getFormVariables(); + // 这里的不能直接使用字符串的比较,因为外部可能传入空字符串,比如发起人的通过时,就是传入的空字符串 + if (Objects.nonNull(operationDesc)) { + this.operationDesc = operationDesc; + } else { + this.operationDesc = "已同意"; + } + } + + @Override + /** + * TODO 重要说明,该类与 CustomApproveTaskCmd 作用等同,当逻辑有调整时,需同步调整 + */ + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + TaskService taskService = processEngineConfiguration.getTaskService(); + + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(taskId).singleResult(); + + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult(); + validTask(historicTaskInstance, task, approver, nodeTypes); + + // TODO 所有的跟 Task 相关的动作都可以在这里进行扩展,用于扩展八大按钮标准动作以外的一些逻辑,但这里需要结合 Spring 能力,需设计好扩展点,否则无法进行扩展 + // 其他动态也应该在类似的地方预留扩展点 + // formValidator(task); + + if (StringUtils.hasLength(advice)) { + Authentication.setAuthenticatedUserId(approver.buildAssigneeId()); + addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice); + Authentication.setAuthenticatedUserId(null); + } + + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, approver); + + Authentication.setAuthenticatedUserId(Objects.nonNull(approver) ? approver.buildAssigneeId() : null); + addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, operationDesc); + Authentication.setAuthenticatedUserId(null); + + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + if (Objects.nonNull(nextApprover)) { + // 主动设置下级审批人 + runtimeService.setVariable(task.getProcessInstanceId(), INTERNAL_SPECIFY_NEXT_APPROVER, + nextApprover); + } + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus()); + + // 记录电子签名的图片 + recordSignature(task, runtimeService); + + runtimeService.setVariable(task.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, approver); + + executeSynchronous(task, taskService, runtimeService, commandContext, historicTaskInstance.getTenantId()); + return null; + } + + /** + * 表单校验 + * + * @param task + */ + private void formValidator(TaskEntity task) { + ExtAxProcessLogService logService = SpringContextUtils.getBean(ExtAxProcessLogService.class); + ExtAxProcessLog logQuery = new ExtAxProcessLog(); + logQuery.setProcessInstanceId(task.getProcessInstanceId()); + logQuery.setTaskId(task.getId()); + List logs = logService.genericQuery(logQuery); + if (CollectionUtils.isEmpty(logs) || logs.size() != 1) { + throw new WorkflowEngineException(FORM_FIELD_VALIDATOR_ERROR); + } + ExtAxProcessLog axProcessLog = logs.get(0); + Map permissionMap = axProcessLog.getFormFieldPermissionConf().stream() + .collect(Collectors.toMap(FormPermissionMetaInfo::getFieldId, Function.identity(), (s, t) -> s)); + formVariables.forEach((k, v) -> { + FormPermissionMetaInfo permission = permissionMap.getOrDefault(k, new FormPermissionMetaInfo()); + if (Boolean.TRUE.equals(permission.getRequired())) { + if (Objects.isNull(v)) { + throw new WorkflowEngineException(FORM_FIELD_VALIDATOR_ERROR); + } + if (v instanceof String && !StringUtils.hasText((String) v)) { + throw new WorkflowEngineException(FORM_FIELD_VALIDATOR_ERROR); + } + } + }); + } + + private void executeSynchronous(Task task, TaskService taskService, RuntimeService runtimeService, CommandContext commandContext, String tenantId) { + ExtAxBpmnFormRelationService bpmnFormRelationService = SpringContextUtils.getBean(ExtAxBpmnFormRelationService.class); + ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(task.getProcessDefinitionId()); + if (Objects.nonNull(relation) && !CollectionUtils.isEmpty(formVariables)) { + log.info("带有表单的审批"); + FormEngineConfigurationApi formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(commandContext); + FormRepositoryService formRepositoryService = formEngineConfiguration.getFormRepositoryService(); + FormDefinition formDefinition = formRepositoryService.createFormDefinitionQuery() + .formDefinitionKey(relation.getKey()) + .deploymentId(relation.getFormDeploymentId()) + .singleResult(); + Authentication.setAuthenticatedUserId(approver.buildAssigneeId()); + runtimeService.setVariableLocal(task.getProcessInstanceId(), TASK_SUBMIT_FORM_VARIABLE, formVariables); + taskService.completeTaskWithForm(taskId, formDefinition.getId(), null, formVariables); + Authentication.setAuthenticatedUserId(null); + } else { + // 正常的审批 + log.info("普通的审批"); + if (StringUtils.hasText(task.getExecutionId())) { + // 正常完成流程任务,审批通过 + taskService.complete(task.getId(), runtimeService.getVariables(task.getExecutionId())); + } else { + // 特殊的完成单独创建的任务 + taskService.complete(task.getId()); + } + } + } + + private void recordSignature(TaskEntity task, RuntimeService runtimeService) { + List signatures = runtimeService.getVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, List.class); + if (Objects.isNull(signatures)) { + signatures = new ArrayList<>(); + } + Optional any = signatures.stream() + .filter(i -> Objects.equals(i.getActivityId(), task.getTaskDefinitionKey())).findAny(); + SignatureDTO dto = any.orElse(new SignatureDTO() + .setActivityId(task.getTaskDefinitionKey()) + .setActivityName(task.getName()) + .setSignatures(new ArrayList<>())); + ListUtils.emptyIfNull(attachmentList).stream() + .filter(i -> Objects.equals(i.getType(), AttachmentTypeEnum.signature)) + .findFirst() + .ifPresent(attachment -> dto.getSignatures().add(0, + new SignatureDTO.SignDetail() + .setSignature(attachment.getUrl()) + .setAdvice(advice))); + if (!any.isPresent()) { + signatures.add(dto); + } + + if (!CollectionUtils.isEmpty(signatures)) { + runtimeService.setVariable(task.getProcessInstanceId(), SIGNATURE_COLLECTION, signatures); + } + } +} 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..c5803be1e --- /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.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.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE; +import static cn.axzo.workflow.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..e734d6f33 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBackTaskCmd.java @@ -0,0 +1,194 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.utils.BpmnModelUtils; +import cn.axzo.workflow.core.common.utils.SpringContextUtils; +import cn.axzo.workflow.core.service.support.FlowNodeForecastService; +import com.alibaba.fastjson.JSON; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +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.bpmn.model.UserTask; +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.service.TaskService; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.BACK_TARGET_ACTIVITY_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS; +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.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 List targetTaskIds; + private final String advice; + private final String operationDesc; + private final List attachmentList; + private final BpmnTaskDelegateAssigner operator; + private final String processInstanceId; + private final String currentActivityId; + private final String toActivityId; + private final boolean validateApprover; + + private static final String OPERATION_DESC = "回退至"; + + public CustomBackTaskCmd(CustomBackParamsDto customBackParamsDto) { + if (customBackParamsDto == null) { + throw new NullPointerException("customBackParamsDto is null"); + } + if (customBackParamsDto.getOperator() == null) { + throw new NullPointerException("operator is null"); + } + this.targetTaskIds = customBackParamsDto.getTargetTaskIds(); + this.advice = customBackParamsDto.getAdvice(); + this.operationDesc = customBackParamsDto.getOperationDesc(); + this.attachmentList = customBackParamsDto.getAttachmentList(); + this.operator = customBackParamsDto.getOperator(); + this.processInstanceId = customBackParamsDto.getProcessInstanceId(); + this.currentActivityId = customBackParamsDto.getCurrentActivityId(); + this.toActivityId = customBackParamsDto.getToActivityId(); + this.validateApprover = customBackParamsDto.isValidateApprover(); + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("targetTaskIds", JSON.toJSONString(this.targetTaskIds)); + params.put("advice", advice); + params.put("operationDesc", OPERATION_DESC); + params.put("attachmentList", JSON.toJSONString(attachmentList)); + params.put("operator", JSON.toJSONString(operator)); + params.put("toActivityId", JSON.toJSONString(this.toActivityId)); + params.put("processInstanceId", JSON.toJSONString(this.processInstanceId)); + params.put("validateApprover", validateApprover); + return JSON.toJSONString(params); + } + + @Override + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + List taskList = processEngineConfiguration.getHistoryService() + .createHistoricTaskInstanceQuery() + .taskDefinitionKey(currentActivityId) + .processInstanceId(processInstanceId) + .list(); + List valueableTaskList = taskList.stream() + .filter(hi -> { + if (hi.getEndTime() != null) { + return false; + } + if (!CollectionUtils.isEmpty(targetTaskIds)) { + return targetTaskIds.contains(hi.getId()); + } + return true; + }) + .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(valueableTaskList)) { + throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS); + } + TaskService taskService = processEngineConfiguration.getTaskServiceConfiguration().getTaskService(); + if (validateApprover) { + valueableTaskList.forEach(hi -> validTask(hi, taskService.getTask(hi.getId()), operator, null)); + } + String processDefinitionId = valueableTaskList.get(0).getProcessDefinitionId(); + Process process = ProcessDefinitionUtil.getProcess(processDefinitionId); + FlowElement targetFlowElement = process.getFlowElement(toActivityId, true); + if (Objects.isNull(targetFlowElement)) { + throw new WorkflowEngineException(BACK_TARGET_ACTIVITY_NOT_EXISTS, toActivityId); + } + FlowElement sourceFlowElement = process.getFlowElement(currentActivityId, true); + // 退回节点到当前节点不可达到,不允许退回 + if (!BpmnModelUtils.isReachable(process, (FlowNode) targetFlowElement, (FlowNode) sourceFlowElement)) { + throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, toActivityId); + } + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + valueableTaskList.forEach(th -> { + TaskEntity task = taskService.getTask(th.getId()); + Authentication.setAuthenticatedUserId(operator.buildAssigneeId()); + batchAddAttachment(commandContext, processInstanceId, task, attachmentList, operator); + addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice); + addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, StringUtils.hasText(operationDesc) ? operationDesc : OPERATION_DESC + targetFlowElement.getName()); + Authentication.setAuthenticatedUserId(null); + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), BACKED.getStatus()); + }); + // 移除回退到的指定节点的变量,让 EngineExecutionStartListener 重新计算该节点的人 + List removeActivityIds = collectorThoughtFlowElement(processInstanceId, toActivityId, currentActivityId, true); + runtimeService.removeVariables(processInstanceId, removeActivityIds.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + i).collect(Collectors.toList())); + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstanceId) + .moveActivityIdsToSingleActivityId(Collections.singletonList(currentActivityId), toActivityId) + .changeState(); + return null; + } + + private List collectorThoughtFlowElement(String processInstanceId, String startActivityId, String currentActivityId, Boolean containsStart) { + FlowNodeForecastService forecastService = SpringContextUtils.getBean(FlowNodeForecastService.class); + List flowElements = forecastService.performProcessForecasting(processInstanceId, null, startActivityId, containsStart); + AtomicBoolean flag = new AtomicBoolean(true); + return flowElements.stream() + .map(FlowElement::getId) + .filter(id -> StringUtils.startsWithIgnoreCase(id, "node_")) + .filter(id -> { + if (Objects.equals(id, currentActivityId) && flag.get()) { + flag.set(false); + return true; + } + return flag.get(); + }).collect(Collectors.toList()); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static final class CustomBackParamsDto { + private List targetTaskIds; + private String advice; + private String operationDesc; + private List attachmentList; + private BpmnTaskDelegateAssigner operator; + private String processInstanceId; + private String currentActivityId; + private String toActivityId; + private boolean validateApprover; + } + +} 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..9cc21643e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBizSpecifyAssigneeToTaskAsyncCmd.java @@ -0,0 +1,77 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; +import cn.axzo.workflow.core.engine.job.AsyncActivitySetAssigneeJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +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.job.service.JobService; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; + +import java.io.Serializable; + +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; + +/** + * 自定的业务指定审批人命令实现 + * + * @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 76cba22d2..243063932 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 @@ -1,29 +1,40 @@ package cn.axzo.workflow.core.engine.cmd; +import cn.axzo.workflow.common.exception.WorkflowEngineException; 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 org.flowable.common.engine.impl.interceptor.Command; +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; 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.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ACTIVITY_BIZ_SET_ASSIGNEE_ERROR; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ACTIVITY_CANT_SET_ASSIGNEE; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.PROCESS_CANT_SET_ASSIGNEE; 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.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.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerCount; /** * 自定的业务指定审批人命令实现 @@ -31,7 +42,7 @@ import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_CANT_S * @author wangli * @since 2023/12/22 13:51 */ -public class CustomBizSpecifyAssigneeToTaskCmd implements Command, Serializable { +public class CustomBizSpecifyAssigneeToTaskCmd extends AbstractCommand implements Serializable { private final String executionId; private final List addedAssigners; @@ -41,32 +52,90 @@ public class CustomBizSpecifyAssigneeToTaskCmd implements Command, Seri this.addedAssigners = addedAssigners; } + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("executionId", executionId); + params.put("addedAssigners", addedAssigners); + return JSON.toJSONString(params); + } + + public static Task getOperateTask(TaskService taskService, String executionId) { + return taskService.createTaskQuery().executionId(executionId) + .taskAssignee(NO_ASSIGNEE) + .singleResult(); + } + + /** + * 校验 + * + * @param runtimeService + * @param task + * @param assigners + */ + public static void validate(RuntimeService runtimeService, String executionId, Task task, List assigners) { + validTask(task, executionId); + //校验审批人数量是否超过限制 + validTaskAssignerCount(runtimeService, (TaskEntity) task, assigners); + } + @Override public Boolean execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); TaskService taskService = processEngineConfiguration.getTaskService(); + TaskEntity task = (TaskEntity) getOperateTask(taskService, executionId); + //校验 + validate(processEngineConfiguration.getRuntimeService(), executionId, task, addedAssigners); - Task task = taskService.createTaskQuery().executionId(executionId) - .taskAssignee(NO_ASSIGNEE) - .singleResult(); - - validTask(task); + validProcessInstance(commandContext, task); changeAssigneeSnapshot(commandContext, task); addAssignee(commandContext, taskService, task); + clearAlterTimeJob(commandContext, task); + return true; } + /** + * 清空告警的任务 + * + * @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); + HistoryService historyService = processEngineConfiguration.getHistoryService(); + HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); + if (Objects.isNull(processInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, task.getProcessInstanceId()); + } + if (!Objects.equals(PROCESSING.getStatus(), processInstance.getBusinessStatus())) { + throw new WorkflowEngineException(PROCESS_CANT_SET_ASSIGNEE); + } + } + + private void changeAssigneeSnapshot(CommandContext commandContext, Task task) { ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + CommandContextUtil.getProcessEngineConfiguration(commandContext); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + @SuppressWarnings("unchecked") 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)) { @@ -76,8 +145,8 @@ public class CustomBizSpecifyAssigneeToTaskCmd implements Command, Seri } 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) { @@ -85,16 +154,14 @@ public class CustomBizSpecifyAssigneeToTaskCmd implements Command, Seri return; } - addedAssigners.forEach(i -> { - CustomTaskHelper.addMultiTask(commandContext, (TaskEntity) task, i); - }); + addedAssigners.forEach(i -> CustomTaskHelper.addMultiTask(commandContext, (TaskEntity) task, i)); taskService.setAssignee(task.getId(), HIDDEN_ASSIGNEE_ID); ((TaskEntity) task).setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), DELETED.getStatus()); - CustomTaskHelper.deleteMultiTask(commandContext, (TaskEntity) task); + CustomTaskHelper.deleteMultiTask(commandContext, task); } - private void validTask(Task task) { + private static void validTask(Task task, String executionId) { if (Objects.isNull(task)) { throw new WorkflowEngineException(ACTIVITY_BIZ_SET_ASSIGNEE_ERROR, executionId); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBusinessNodeTimeoutCallbackCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBusinessNodeTimeoutCallbackCmd.java new file mode 100644 index 000000000..85e11405e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBusinessNodeTimeoutCallbackCmd.java @@ -0,0 +1,84 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; +import cn.axzo.workflow.core.engine.job.AsyncActivityCallbackJobHandler; +import com.alibaba.fastjson.JSON; +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.jobexecutor.TimerEventHandler; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.TimerUtil; +import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +/** + * 业务节点触发倒计时回调 + * + * @author wangli + * @since 2024-08-20 16:59 + */ +public class CustomBusinessNodeTimeoutCallbackCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = 1L; + private final String triggerId; + private final String endTime; + private final Map variables; + + public CustomBusinessNodeTimeoutCallbackCmd(String triggerId, String endTime, Map variables) { + this.triggerId = triggerId; + this.endTime = endTime; + this.variables = variables; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("triggerId", triggerId); + params.put("endTime", endTime); + return JSON.toJSONString(params); + } + + @Override + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + ExecutionEntity executionEntity = (ExecutionEntity) processEngineConfiguration.getRuntimeService().createExecutionQuery().executionId(triggerId).singleResult(); + if (executionEntity == null) { + return null; + } + + String newEndTime = parseDateTimeWithTimeZone(); + + TimerEventDefinition timerEventDefinition = new TimerEventDefinition(); + timerEventDefinition.setTimeDate(newEndTime); + + TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, executionEntity.getCurrentFlowElement(), + true, executionEntity, AsyncActivityCallbackJobHandler.TYPE, TimerEventHandler.createConfiguration(executionEntity.getCurrentActivityId(), + timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); + if (timerJob != null) { + timerJob.setCustomValues(JSON.toJSONString(new BpmnActivityTimeoutCallbackDTO(triggerId, endTime, variables))); + CommandContextUtil.getTimerJobService().scheduleTimerJob(timerJob); + } + return null; + } + + private String parseDateTimeWithTimeZone() { + DateTimeFormatter inputFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime localDateTime = LocalDateTime.parse(endTime, inputFormat); + + // 转换为东八区的ZonedDateTime + ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai")); + + // 格式化为包含时区偏移量的ISO 8601字符串 + DateTimeFormatter outputFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + return zonedDateTime.format(outputFormat); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBusinessNodeTimeoutTriggerCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBusinessNodeTimeoutTriggerCmd.java new file mode 100644 index 000000000..579770edc --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomBusinessNodeTimeoutTriggerCmd.java @@ -0,0 +1,84 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO; +import cn.axzo.workflow.core.engine.job.AsyncActivityLeaveJobHandler; +import com.alibaba.fastjson.JSON; +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.jobexecutor.TimerEventHandler; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.TimerUtil; +import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 业务节点触发倒计时 + * + * @author wangli + * @since 2024-08-16 15:38 + */ +public class CustomBusinessNodeTimeoutTriggerCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = 1L; + private final String triggerId; + private final String endTime; + private final Map variables; + + public CustomBusinessNodeTimeoutTriggerCmd(String triggerId, String endTime, Map variables) { + this.triggerId = triggerId; + this.endTime = endTime; + this.variables = variables; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("triggerId", triggerId); + params.put("endTime", endTime); + return JSON.toJSONString(params); + } + + @Override + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + ExecutionEntity executionEntity = (ExecutionEntity) processEngineConfiguration.getRuntimeService().createExecutionQuery().executionId(triggerId).singleResult(); + if (Objects.isNull(executionEntity)) { + return null; + } + String newEndTime = parseDateTimeWithTimeZone(); + + TimerEventDefinition timerEventDefinition = new TimerEventDefinition(); + timerEventDefinition.setTimeDate(newEndTime); + + TimerJobEntity timerJob = TimerUtil.createTimerEntityForTimerEventDefinition(timerEventDefinition, executionEntity.getCurrentFlowElement(), + true, executionEntity, AsyncActivityLeaveJobHandler.TYPE, TimerEventHandler.createConfiguration(executionEntity.getCurrentActivityId(), + timerEventDefinition.getEndDate(), timerEventDefinition.getCalendarName())); + if (timerJob != null) { + timerJob.setCustomValues(JSON.toJSONString(new BpmnActivityTimeoutTriggerDTO(triggerId, endTime, variables))); + CommandContextUtil.getTimerJobService().scheduleTimerJob(timerJob); + } + return null; + } + + private String parseDateTimeWithTimeZone() { + DateTimeFormatter inputFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime localDateTime = LocalDateTime.parse(endTime, inputFormat); + + // 转换为东八区的ZonedDateTime + ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai")); + + // 格式化为包含时区偏移量的ISO 8601字符串 + DateTimeFormatter outputFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + return zonedDateTime.format(outputFormat); + } +} 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 new file mode 100644 index 000000000..4052f92f6 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCancelProcessInstanceAsyncCmd.java @@ -0,0 +1,89 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +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; +import org.flowable.engine.HistoryService; +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 java.io.Serializable; +import java.util.Objects; + +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANT_CANCEL; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.ABORTED; + +public class CustomCancelProcessInstanceAsyncCmd extends AbstractCommand implements Serializable { + + private final SuperBpmnProcessInstanceCancelDTO dto; + + public CustomCancelProcessInstanceAsyncCmd(SuperBpmnProcessInstanceCancelDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public Void execute(CommandContext commandContext) { + String processInstanceId = dto.getProcessInstanceId(); + BpmnTaskDelegateAssigner initiator = dto.getInitiator(); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + + HistoryService historyService = processEngineConfiguration.getHistoryService(); + HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + if (Objects.isNull(instance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS); + } + + if (Objects.nonNull(instance.getEndTime())) { + throw new WorkflowEngineException(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); + } + + if (Objects.isNull(initiator) || !initiator.comparePersonIdToOther(instance.getStartUserId())) { + throw new WorkflowEngineException(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); + } + + if (Objects.equals(instance.getBusinessStatus(), ABORTED.getStatus())) { + throw new WorkflowEngineException(PROCESS_INSTANCE_CANT_CANCEL); + } + startAsync(processEngineConfiguration, instance); + return null; + } + + private void startAsync(ProcessEngineConfigurationImpl processEngineConfiguration, HistoricProcessInstance instance) { + JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService(); + + JobEntity job = jobService.createJob(); + // 这里的 executionId 可为 null + job.setExecutionId(instance.getId()); + job.setProcessInstanceId(instance.getId()); + job.setProcessDefinitionId(instance.getProcessDefinitionId()); + job.setElementId(AsyncCancelProcessInstanceJobHandler.TYPE); + job.setElementName(instance.getName()); + job.setJobHandlerType(AsyncCancelProcessInstanceJobHandler.TYPE); + job.setTenantId(instance.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + } + +} 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 93c447265..f09b7bbf2 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 @@ -1,38 +1,45 @@ package cn.axzo.workflow.core.engine.cmd; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; +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.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 org.flowable.common.engine.impl.interceptor.Command; +import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.history.HistoricProcessInstanceQuery; 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; 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.COMMENT_TYPE_OPERATION_DESC; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANT_CANCEL; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; +import static cn.axzo.workflow.common.constant.BpmnConstants.CANCEL_PROCESS_NODE_DEF_KEY_NAME; +import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER; 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; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_NAME; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_DELETE_REASON; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_CANCEL; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CANCEL; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.ABORTED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.CANCELLED; -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_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.batchAddAttachment; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.completeVirtualTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVirtualTask; /** @@ -41,33 +48,52 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.createVir * @author wangli * @since 2024/1/2 18:19 */ -public class CustomCancelProcessInstanceCmd implements Command, Serializable { +public class CustomCancelProcessInstanceCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = 6556399267210245511L; private final String processInstanceId; private final String tenantId; private final String reason; private final BpmnTaskDelegateAssigner initiator; + private final String nodeName; + private final List attachmentList; private final ExtAxHiTaskInstService extAxHiTaskInstService; + private final Boolean superAdmin; - public CustomCancelProcessInstanceCmd(String processInstanceId, String tenantId, String reason, - BpmnTaskDelegateAssigner initiator, + public CustomCancelProcessInstanceCmd(SuperBpmnProcessInstanceCancelDTO dto, ExtAxHiTaskInstService extAxHiTaskInstService) { - this.processInstanceId = processInstanceId; - this.tenantId = tenantId; - this.reason = reason; - this.initiator = initiator; + this.processInstanceId = dto.getProcessInstanceId(); + this.tenantId = dto.getTenantId(); + this.reason = dto.getReason(); + this.initiator = dto.getInitiator(); + this.nodeName = StringUtils.hasText(dto.getNodeName()) ? dto.getNodeName() : "发起人撤回"; + this.attachmentList = dto.getAttachmentList(); + this.superAdmin = Objects.nonNull(dto.getSuperAdmin()) && dto.getSuperAdmin(); this.extAxHiTaskInstService = extAxHiTaskInstService; } + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("tenantId", tenantId); + params.put("reason", reason); + params.put("initiator", initiator); + return JSON.toJSONString(params); + } + @Override public Void execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); HistoryService historyService = processEngineConfiguration.getHistoryService(); - HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery() - .processInstanceId(processInstanceId) - .processInstanceTenantId(tenantId) - .singleResult(); + HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId); + if (Objects.equals(Boolean.TRUE, superAdmin)) { + query.processInstanceTenantId(tenantId); + } + + HistoricProcessInstance instance = query.singleResult(); if (Objects.isNull(instance)) { throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS); } @@ -76,7 +102,7 @@ public class CustomCancelProcessInstanceCmd implements Command, Serializab throw new WorkflowEngineException(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } - if (Objects.isNull(initiator) || !initiator.comparePersonIdToOther(instance.getStartUserId())) { + if (!Objects.equals(Boolean.TRUE, superAdmin) && !initiator.comparePersonIdToOther(instance.getStartUserId())) { throw new WorkflowEngineException(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } @@ -84,22 +110,38 @@ public class CustomCancelProcessInstanceCmd implements Command, Serializab throw new WorkflowEngineException(PROCESS_INSTANCE_CANT_CANCEL); } + List tasks = processEngineConfiguration.getTaskService() + .createTaskQuery() + .processInstanceId(processInstanceId) + .active() + .list(); + String taskDefKey = ""; + if (!CollectionUtils.isEmpty(tasks)) { + taskDefKey = tasks.get(0).getTaskDefinitionKey(); + } Map variables = new HashMap<>(); - variables.put(INTERNAL_END_TENANT_ID, tenantId); - variables.put(INTERNAL_END_USER_ID, initiator.buildAssigneeId()); - variables.put(INTERNAL_END_USER_NAME, initiator.getAssignerName()); +// variables.put(INTERNAL_END_TENANT_ID, tenantId); +// variables.put(INTERNAL_END_USER_ID, initiator.buildAssigneeId()); +// variables.put(INTERNAL_END_USER_NAME, initiator.getAssignerName()); variables.put(INTERNAL_DELETE_PROCESS_FLAG, INTERNAL_PROCESS_TYPE_CANCEL); variables.put(INTERNAL_PROCESS_DELETE_REASON, reason); + variables.put(CANCEL_PROCESS_NODE_DEF_KEY_NAME, taskDefKey); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); 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, "已撤回"); + TaskEntity task = createVirtualTask(commandContext, extAxHiTaskInstService, processInstanceId, + nodeName, NODE_CANCEL.getType(), reason, initiator, CANCELLED.getStatus(), new AddComment(CANCELLED.getDesc())); + + batchAddAttachment(commandContext, processInstanceId, task, attachmentList, initiator); + + completeVirtualTask(commandContext, task); + + runtimeService.setVariable(task.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, initiator); return null; } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCarbonCopyUserSelectorCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCarbonCopyUserSelectorCmd.java index 5e7956892..0a3a4eb15 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCarbonCopyUserSelectorCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCarbonCopyUserSelectorCmd.java @@ -6,9 +6,11 @@ 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.engine.listener.EngineExecutionStartListener; import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.CustomProperty; import org.flowable.bpmn.model.ServiceTask; import org.flowable.common.engine.impl.interceptor.Command; @@ -26,16 +28,20 @@ import org.springframework.util.CollectionUtils; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; +import static cn.axzo.workflow.common.constant.BpmnConstants.CARBON_ASSIGNER_LIMIT_NUMBER; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; 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.NO_ASSIGNEE; import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.getLimitedElementList; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.removeDuplicateByPersonId; /** @@ -44,7 +50,8 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.removeDup * @author wangli * @since 18/03/2024 11:33 */ -public class CustomCarbonCopyUserSelectorCmd implements Command>, Serializable { +@Slf4j +public class CustomCarbonCopyUserSelectorCmd extends AbstractCommand> implements Serializable { private static final long serialVersionUID = 1L; private final String processInstanceId; @@ -82,6 +89,15 @@ public class CustomCarbonCopyUserSelectorCmd implements Command params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("carbons", carbons); + params.put("serviceVersion", serviceVersion); + return JSON.toJSONString(params); + } + @Override public List execute(CommandContext commandContext) { if (CollectionUtils.isEmpty(carbons)) { @@ -90,11 +106,22 @@ public class CustomCarbonCopyUserSelectorCmd implements Command list = new ArrayList<>(); + List executions = new ArrayList<>(); if (Objects.isNull(execution)) { - list.addAll(runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list()); + executions.addAll(runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list()); } else { - list.add((Execution) execution); + executions.add((Execution) execution); + } + //流程为空,表示已经执行完,去历史版本获取 + if (CollectionUtils.isEmpty(executions)) { + List hisVarInst = processEngineConfiguration.getHistoryService().createHistoricVariableInstanceQuery() + .processInstanceId(processInstanceId) + .variableName(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + serviceTask.getId()).list(); + if (!CollectionUtils.isEmpty(hisVarInst)) { + return (List) hisVarInst.get(0).getValue(); + } + log.warn("executions is empty,get historic task assigner list empty,processInstanceId: {},serviceTaskId: {}", processInstanceId, serviceTask.getId()); + return Collections.emptyList(); } List assigners = new ArrayList<>(); carbons.forEach(carbon -> { @@ -106,25 +133,33 @@ public class CustomCarbonCopyUserSelectorCmd implements Command getApproverRelationUser(BpmnCarbonCopyConf carbon, @@ -149,13 +184,14 @@ public class CustomCarbonCopyUserSelectorCmd implements Command taskInstances = historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) // .includeProcessVariables() @@ -181,13 +217,13 @@ public class CustomCarbonCopyUserSelectorCmd implements Command { + private final String propertyName; + + public CustomClearPropertyCmd(String propertyName) { + this.propertyName = propertyName; + } + + @Override + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + PropertyEntity entity = processEngineConfiguration.getPropertyEntityManager().findById(propertyName); + if (Objects.nonNull(entity)) { + processEngineConfiguration.getPropertyEntityManager().delete(entity.getId()); + } + 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 new file mode 100644 index 000000000..ca0111bde --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContext.java @@ -0,0 +1,66 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException; +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 java.sql.SQLIntegrityConstraintViolationException; +import java.util.Objects; + +/** + * 对 CommandContext 中的 WorkflowEngineException 进行日志降级 + * + * @author wangli + * @since 2024/5/21 09:46 + */ +public class CustomCommandContext extends CommandContext { + 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); + } + + @Override + protected void logException() { + if (exception instanceof FlowableException && !((FlowableException) exception).isLogged()) { + return; + } + + if (exception instanceof FlowableOptimisticLockingException) { + // reduce log level, as normally we're not interested in logging this exception + LOGGER.debug("Optimistic locking exception : {}", exception.getMessage(), exception); + + } else if (exception instanceof FlowableException && ((FlowableException) exception).isReduceLogLevel()) { + // reduce log level, because this may have been caused because of job deletion due to cancelActiviti="true" + 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) { + Throwable rootCause = getRootCause(exception); + if (rootCause instanceof MySQLTransactionRollbackException) { + LOGGER.warn("MySQL transaction rollback exception : {}", rootCause.getMessage(), rootCause); + } if (rootCause instanceof SQLIntegrityConstraintViolationException){ + LOGGER.warn("SQL constraint violation exception : {}", rootCause.getMessage(), rootCause); + } else { + LOGGER.warn("persistence error while closing command context:{}", exception.getMessage(), exception); + } + } else { + LOGGER.error("Error while closing command context", exception); + } + } + + private Throwable getRootCause(Throwable throwable) { + while (Objects.nonNull(throwable.getCause())) { + throwable = throwable.getCause(); + } + return throwable; + } +} 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 new file mode 100644 index 000000000..6e3cd116e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCommandContextFactory.java @@ -0,0 +1,21 @@ +package cn.axzo.workflow.core.engine.cmd; + +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.common.engine.impl.interceptor.CommandContextFactory; + +/** + * CommandContextFactory + * + * @author wangli + * @since 2024/5/21 09:45 + */ +public class CustomCommandContextFactory extends CommandContextFactory { + + @Override + public CommandContext createCommandContext(Command cmd) { + CommandContext commandContext = new CustomCommandContext(cmd); + commandContext.setSessionFactories(sessionFactories); + return commandContext; + } +} 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 ba4d007b9..d71afb765 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 @@ -3,13 +3,12 @@ package cn.axzo.workflow.core.engine.cmd; 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.common.exception.WorkflowEngineException; 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; @@ -20,22 +19,27 @@ import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEnt 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.SerializableType; -import org.springframework.util.StringUtils; +import org.flowable.variable.service.impl.types.StringType; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Date; +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.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.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; /** @@ -44,7 +48,7 @@ import static org.flowable.task.api.Task.DEFAULT_PRIORITY; * @author wangli * @since 2023/12/26 16:14 */ -public class CustomCommentTaskCmd implements Command, Serializable { +public class CustomCommentTaskCmd extends AbstractCommand implements Serializable { private final String processInstanceId; private final BpmnTaskDelegateAssigner operator; private final String comment; @@ -63,14 +67,25 @@ public class CustomCommentTaskCmd implements Command, Serializable { this.extAxHiTaskInstService = extAxHiTaskInstService; } + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("operator", operator); + params.put("comment", comment); + params.put("commentExt", commentExt); + params.put("attachmentList", attachmentList); + return JSON.toJSONString(params); + } + @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); } @@ -89,32 +104,34 @@ public class CustomCommentTaskCmd implements Command, Serializable { 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, JSON.toJSONString(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; } @@ -122,17 +139,16 @@ public class CustomCommentTaskCmd implements Command, Serializable { 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()); historicVariableInstance.setName(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + task.getId()); - SerializableType variableType = - new SerializableType(processEngineConfiguration.isSerializableVariableTypeTrackDeserializedObjects()); + StringType variableType = new StringType(processEngineConfiguration.getMaxLengthString()); historicVariableInstance.setVariableType(variableType); - historicVariableInstance.setBytes(objectToByteArray(operator)); + historicVariableInstance.setTextValue(operator.toJson()); historicVariableInstance.setCreateTime(new Date()); historicVariableInstance.setLastUpdatedTime(new Date()); historicVariableService.insertHistoricVariableInstance(historicVariableInstance); 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 d204e31d3..f4ca286fe 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 @@ -1,27 +1,43 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.SimpleTaskDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO; 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.tx.listener.RobotTaskTransactionListener; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import com.alibaba.fastjson.JSON; +import org.flowable.common.engine.impl.cfg.TransactionState; 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.context.Context; 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; import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.DUMMY_TASK_NOT_EXISTS; 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.TASK_ASSIGNEE_SKIP_FLAT; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_ROBOT; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.DUMMY_TASK_NOT_EXISTS; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment; /** * 自定义的完成虚拟任务命令实现 @@ -29,20 +45,39 @@ import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.DUMMY_TASK_NOT_ * @author wangli * @since 2023/12/27 11:24 */ -public class CustomCompleteDummyTaskCmd implements Command, Serializable { +public class CustomCompleteDummyTaskCmd extends AbstractCommand implements Serializable { private final String processInstanceId; private final String taskId; - private final String flowNodeName; - private final String operationDesc; + private String flowNodeName; + private String operationDesc; + private final List attachmentList; + private final BpmnTaskDelegateAssigner operator; private final ExtAxHiTaskInstService extAxHiTaskInstService; + private final ExtAxProcessLogService extAxProcessLogService; - public CustomCompleteDummyTaskCmd(String processInstanceId, String taskId, String flowNodeName, - String operationDesc, ExtAxHiTaskInstService extAxHiTaskInstService) { - this.processInstanceId = processInstanceId; - this.taskId = taskId; - this.flowNodeName = flowNodeName; - this.operationDesc = operationDesc; + public CustomCompleteDummyTaskCmd(BpmnRobotTaskCompleteDTO dto, + ExtAxHiTaskInstService extAxHiTaskInstService, + ExtAxProcessLogService extAxProcessLogService) { + this.processInstanceId = dto.getProcessInstanceId(); + this.taskId = dto.getTaskId(); + if (Objects.nonNull(dto.getRobotNode())) { + this.flowNodeName = dto.getRobotNode().getFlowNodeName(); + this.operationDesc = dto.getRobotNode().getOperationDesc(); + } + this.attachmentList = dto.getAttachmentList(); + this.operator = dto.getApprover(); this.extAxHiTaskInstService = extAxHiTaskInstService; + this.extAxProcessLogService = extAxProcessLogService; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("taskId", taskId); + params.put("flowNodeName", flowNodeName); + params.put("operationDesc", operationDesc); + return JSON.toJSONString(params); } @Override @@ -51,25 +86,33 @@ public class CustomCompleteDummyTaskCmd implements Command, Serializable { 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); } - task.setName(flowNodeName); - taskService.saveTask(task); + if (StringUtils.hasText(flowNodeName)) { + task.setName(flowNodeName); + taskService.saveTask(task); + } - Authentication.setAuthenticatedUserId("system"); - CustomTaskHelper.addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, operationDesc); - Authentication.setAuthenticatedUserId(null); + if (StringUtils.hasText(operationDesc)) { + Authentication.setAuthenticatedUserId("system"); + CustomTaskHelper.addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, operationDesc); + Authentication.setAuthenticatedUserId(null); + } + BpmnTaskDelegateAssigner assignee = BpmnTaskDelegateAssigner.buildDummyAssigner("system", + TASK_ASSIGNEE_SKIP_FLAT, "系统"); + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, assignee); + + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus()); taskService.complete(task.getId()); continueProcessingTask(commandContext); CustomTaskHelper.createExtTaskInst(extAxHiTaskInstService, processInstanceId, task.getId(), - NODE_ROBOT.getType(), BpmnTaskDelegateAssigner.buildDummyAssigner("system", - TASK_ASSIGNEE_SKIP_FLAT, "系统"), BpmnProcessInstanceResultEnum.APPROVED.getStatus()); + NODE_ROBOT.getType(), assignee, BpmnProcessInstanceResultEnum.APPROVED.getStatus()); return null; } @@ -81,11 +124,18 @@ public class CustomCompleteDummyTaskCmd implements Command, Serializable { if (CollectionUtils.isEmpty(taskList)) { return; } + + List restoreTasks = new ArrayList<>(); taskList.stream().filter(i -> !Objects.equals(i.getTaskDefinitionKey(), NODE_ROBOT.getType())) - .filter(i -> Objects.nonNull(i.getOwner())) + .filter(i -> Objects.equals(i.getAssignee(), HIDDEN_ASSIGNEE_ID)) .forEach(i -> { taskService.setAssignee(i.getId(), i.getOwner()); taskService.setOwner(i.getId(), null); + + restoreTasks.add(new SimpleTaskDTO(i.getProcessInstanceId(), i.getId())); }); + + Context.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, new RobotTaskTransactionListener(restoreTasks, extAxProcessLogService)); } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskAsyncCmd.java new file mode 100644 index 000000000..51baf7fff --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCountersignUserTaskAsyncCmd.java @@ -0,0 +1,78 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; +import cn.axzo.workflow.core.engine.job.AsyncCountersignUserTaskJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +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.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.impl.persistence.entity.TaskEntity; + +import java.io.Serializable; + +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerCount; + +public class CustomCountersignUserTaskAsyncCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = -6072764832727629141L; + private final BpmnTaskCountersignDTO dto; + + public CustomCountersignUserTaskAsyncCmd(BpmnTaskCountersignDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult(); + + TaskService taskService = processEngineConfiguration.getTaskService(); + Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult(); + + validTask(historicTaskInstance, (TaskEntity) task, dto.getOriginAssigner(), null); + +// validTaskAssignerDuplicated(commandContext, (TaskEntity) task, dto.getTargetAssignerList()); + + validTaskAssignerCount(processEngineConfiguration.getRuntimeService(), (TaskEntity) task, dto.getTargetAssignerList()); + + startAsync(processEngineConfiguration, task); + return null; + } + + private void 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(AsyncCountersignUserTaskJobHandler.TYPE); + job.setTenantId(task.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + } + +} 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 fbaa2e1ed..b7748ace0 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,31 +4,40 @@ 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; import lombok.extern.slf4j.Slf4j; -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; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.task.api.Task; +import org.flowable.task.api.TaskInfo; 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.CollectionUtils; import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; -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.getDuplicatePendingTasks; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; -import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerDuplicated; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerCount; /** * 自定义的加签用户任务命令实现 @@ -39,8 +48,9 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask * @since 2023/12/25 14:05 */ @Slf4j -public class CustomCountersignUserTaskCmd implements Command, Serializable { +public class CustomCountersignUserTaskCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = -2354973133616698898L; private final BpmnCountersignTypeEnum countersignType; private final String originTaskId; private final BpmnTaskDelegateAssigner originTaskAssignee; @@ -62,6 +72,18 @@ public class CustomCountersignUserTaskCmd implements Command, Serializable this.extAxHiTaskInstService = extAxHiTaskInstService; } + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("countersignType", countersignType.getType()); + params.put("originTaskId", originTaskId); + params.put("originTaskAssignee", originTaskAssignee); + params.put("advice", advice); + params.put("attachmentList", JSON.toJSONString(attachmentList)); + params.put("targetTaskAssigneeList", JSON.toJSONString(targetTaskAssigneeList)); + return JSON.toJSONString(params); + } + @Override public Void execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = @@ -71,31 +93,46 @@ public class CustomCountersignUserTaskCmd implements Command, Serializable 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); + validTask(historicTaskInstance, task, originTaskAssignee, null); - List taskDelegateAssigners = - validTaskAssignerDuplicated(commandContext, (TaskEntity) task, targetTaskAssigneeList); + validTaskAssignerCount(processEngineConfiguration.getRuntimeService(), task, targetTaskAssigneeList); resolveOriginTask(commandContext, extAxHiTaskInstService, taskService, task); - batchAddAttachment(commandContext, task.getProcessInstanceId(), task.getId(), attachmentList, + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, originTaskAssignee); - switch (countersignType) { - case FORWARD_COUNTERSIGN: - // TODO - break; - case BACK_COUNTERSIGN: - // TODO - break; - default: - // share_counterSign - shareCountSign(commandContext, (TaskEntity) task, taskDelegateAssigners); - break; + //查询重复任务 + List duplicatePendingTasks = getDuplicatePendingTasks(commandContext, task, targetTaskAssigneeList); + List valuTargetAssigneeList; + if (CollectionUtils.isEmpty(duplicatePendingTasks)) { + valuTargetAssigneeList = targetTaskAssigneeList; + } else { + valuTargetAssigneeList = new ArrayList<>(); + Set duplicateAssigneeSet = duplicatePendingTasks.stream().map(TaskInfo::getAssignee).collect(Collectors.toSet()); + for (BpmnTaskDelegateAssigner assigner : targetTaskAssigneeList) { + if (!duplicateAssigneeSet.contains(assigner.buildAssigneeId())) { + valuTargetAssigneeList.add(assigner); + } + } + } + //需要加签的 + if (!CollectionUtils.isEmpty(valuTargetAssigneeList)) { + switch (countersignType) { + case FORWARD_COUNTERSIGN: + // 加签的一种方式:前加签,具体定义由后续产品需求来定 + break; + case BACK_COUNTERSIGN: + // 加签的另一种方式 + break; + default: + // 共享签,不区分顺序 + shareCountSign(commandContext, task, valuTargetAssigneeList); + break; + } } - return null; } @@ -104,39 +141,43 @@ public class CustomCountersignUserTaskCmd implements Command, Serializable * * @param commandContext * @param taskEntity - * @param taskDelegateAssigners 当前任务的原审批人列表 */ - private void shareCountSign(CommandContext commandContext, TaskEntity taskEntity, - List taskDelegateAssigners) { + private void shareCountSign(CommandContext commandContext, TaskEntity taskEntity, List valuTargetAssigneeList) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); // 这个节点下所有审批人快照 String activityListSnapshot = INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(); - taskDelegateAssigners.addAll(targetTaskAssigneeList); + List taskDelegateAssigners = + processEngineConfiguration.getRuntimeService().getVariable(taskEntity.getProcessInstanceId(), activityListSnapshot, List.class); + taskDelegateAssigners.addAll(valuTargetAssigneeList); runtimeService.setVariable(taskEntity.getProcessInstanceId(), activityListSnapshot, taskDelegateAssigners); log.info("正在进行加签任务:{},待加签人合并列表:{}", taskEntity.getId(), JSONUtil.toJsonStr(taskDelegateAssigners)); - targetTaskAssigneeList.forEach(assigner -> { - CustomTaskHelper.addMultiTask(commandContext, taskEntity, assigner); - }); + valuTargetAssigneeList.forEach(assigner -> CustomTaskHelper.addMultiTask(commandContext, taskEntity, assigner)); } private void resolveOriginTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService, - TaskService taskService, Task task) { + TaskService taskService, TaskEntity task) { // 构建评论内容 StringBuilder message = new StringBuilder("添加"); - for (int i = 0; i < targetTaskAssigneeList.size(); i++) { + 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("人进行审批"); + if (targetTaskAssigneeList.size() > end) { + message.append("等"); + } + message.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/CustomCreateDummyTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCreateDummyTaskCmd.java index ba6cda820..d5813dc26 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCreateDummyTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomCreateDummyTaskCmd.java @@ -1,13 +1,14 @@ package cn.axzo.workflow.core.engine.cmd; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; 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.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; @@ -21,7 +22,9 @@ import org.springframework.util.CollectionUtils; import java.io.Serializable; import java.util.Date; +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_OPERATION_DESC; @@ -29,10 +32,10 @@ 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.TASK_ASSIGNEE_SKIP_FLAT; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_ROBOT; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.DUMMY_TASK_CANT_CREATED; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.DUMMY_TASK_CANT_REPEAT_CREATE; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.DUMMY_TASK_CREATED_ERROR; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.DUMMY_TASK_CREATED_NOT_SUPPORT; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.DUMMY_TASK_CANT_CREATED; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.DUMMY_TASK_CANT_REPEAT_CREATE; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.DUMMY_TASK_CREATED_ERROR; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.DUMMY_TASK_CREATED_NOT_SUPPORT; import static org.flowable.task.api.Task.DEFAULT_PRIORITY; /** @@ -41,22 +44,31 @@ import static org.flowable.task.api.Task.DEFAULT_PRIORITY; * @author wangli * @since 2023/12/27 10:21 */ -public class CustomCreateDummyTaskCmd implements Command, Serializable { +public class CustomCreateDummyTaskCmd extends AbstractCommand implements Serializable { private final String processInstanceId; private final String flowNodeName; private final String operationDesc; private final BpmnTaskDelegateAssigner operator; private final ExtAxHiTaskInstService extAxHiTaskInstService; - public CustomCreateDummyTaskCmd(String processInstanceId, String flowNodeName, String operationDesc, - BpmnTaskDelegateAssigner operator, ExtAxHiTaskInstService extAxHiTaskInstService) { - this.processInstanceId = processInstanceId; - this.flowNodeName = flowNodeName; - this.operationDesc = operationDesc; - this.operator = operator; + public CustomCreateDummyTaskCmd(BpmnRobotTaskCreateDTO dto, ExtAxHiTaskInstService extAxHiTaskInstService) { + this.processInstanceId = dto.getProcessInstanceId(); + this.flowNodeName = dto.getRobotNode().getFlowNodeName(); + this.operationDesc = dto.getRobotNode().getOperationDesc(); + this.operator = dto.getApprover(); this.extAxHiTaskInstService = extAxHiTaskInstService; } + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("flowNodeName", flowNodeName); + params.put("operationDesc", operationDesc); + params.put("operator", operator); + return JSON.toJSONString(params); + } + /** * 在同一个实例下,不允许创建多个执行中的虚拟任务节点 * @@ -104,30 +116,32 @@ public class CustomCreateDummyTaskCmd implements Command, Serializable { task.setTaskDefinitionKey(NODE_ROBOT.getType()); task.setPriority(DEFAULT_PRIORITY); task.setCreateTime(new Date()); - // 创建临时节点 - taskService.saveTask(task); Authentication.setAuthenticatedUserId("system"); CustomTaskHelper.addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, operationDesc); Authentication.setAuthenticatedUserId(null); + // 创建临时节点 + taskService.saveTask(task); + if (Objects.nonNull(operator)) { CommandContextUtil.getEntityCache().findInCache(HistoricTaskInstanceEntity.class).stream() .filter(i -> Objects.equals(i.getId(), task.getId())).findAny() .ifPresent(i -> i.setAssignee(operator.buildAssigneeId())); } - completeProcessingTask(commandContext); + pauseProcessingTask(commandContext); CustomTaskHelper.createExtTaskInst(extAxHiTaskInstService, processInstanceId, task.getId(), NODE_ROBOT.getType(), Objects.isNull(operator) ? BpmnTaskDelegateAssigner.buildDummyAssigner("system", - TASK_ASSIGNEE_SKIP_FLAT, "系统") : operator, + TASK_ASSIGNEE_SKIP_FLAT, "") : operator, BpmnProcessInstanceResultEnum.PROCESSING.getStatus()); return task.getId(); } - private void completeProcessingTask(CommandContext commandContext) { + // 将正执行的任务切换为能被隐藏的标识 + private void pauseProcessingTask(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); TaskService taskService = processEngineConfiguration.getTaskService(); @@ -139,4 +153,5 @@ public class CustomCreateDummyTaskCmd implements Command, Serializable { }); } } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomForecastUserTaskAssigneeCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomForecastUserTaskAssigneeCmd.java index 89a959701..c127eaadb 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomForecastUserTaskAssigneeCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomForecastUserTaskAssigneeCmd.java @@ -4,10 +4,10 @@ import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum; 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.EngineExecutionStartListener; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.UserTask; -import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; @@ -18,9 +18,13 @@ import org.springframework.util.CollectionUtils; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVAL_ASSIGNER_LIMIT_NUMBER; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecify; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.getLimitedElementList; /** * 自定义的推测用户任务的审批人的命令实现 @@ -29,7 +33,8 @@ import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprove * @since 2023/12/11 17:56 */ @Slf4j -public class CustomForecastUserTaskAssigneeCmd implements Command>, Serializable { +public class CustomForecastUserTaskAssigneeCmd extends AbstractCommand> implements Serializable { + private static final long serialVersionUID = 2217101915677599319L; private final String processInstanceId; private final UserTask userTask; private final EngineExecutionStartListener engineExecutionStartListener; @@ -41,6 +46,13 @@ public class CustomForecastUserTaskAssigneeCmd implements Command params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + return JSON.toJSONString(params); + } + @Override public List execute(CommandContext commandContext) { ProcessEngineConfigurationImpl processEngineConfiguration = @@ -51,10 +63,8 @@ public class CustomForecastUserTaskAssigneeCmd implements Command forecastAssigners = new ArrayList<>(); - getApproverSpecify(userTask).ifPresent(specify -> { - forecastAssigners.addAll(engineExecutionStartListener.approverSelect(specify.getType(), userTask, - (DelegateExecution) list.get(0), false)); - }); + getApproverSpecify(userTask).ifPresent(specify -> forecastAssigners.addAll(engineExecutionStartListener.approverSelect(specify.getType(), userTask, + (DelegateExecution) list.get(0), false))); // 如果没找到审批人,加载管理员 if (CollectionUtils.isEmpty(forecastAssigners)) { @@ -67,7 +77,7 @@ public class CustomForecastUserTaskAssigneeCmd implements Command forecastAssigners.addAll(JSON.parseArray(s, BpmnTaskDelegateAssigner.class))); break; @@ -77,7 +87,8 @@ public class CustomForecastUserTaskAssigneeCmd implements Command variableLocal = new HashMap<>(); + if (Objects.equals(PROCESSING, BpmnProcessInstanceResultEnum.valueOfStatus(instance.getBusinessStatus()))) { + Map taskSubmitFormVariables = + processEngineConfiguration.getRuntimeService().getVariableLocal(processInstanceId, + TASK_SUBMIT_FORM_VARIABLE, Map.class); + if (!CollectionUtils.isEmpty(taskSubmitFormVariables)) { + variableLocal.putAll(taskSubmitFormVariables); + } + } else { + HistoricVariableInstance historicVariableInstance = processEngineConfiguration.getHistoryService() + .createHistoricVariableInstanceQuery() + .processInstanceId(processInstanceId) + .variableName(TASK_SUBMIT_FORM_VARIABLE) + .singleResult(); + if (Objects.nonNull(historicVariableInstance)) { + variableLocal.putAll((Map) historicVariableInstance.getValue()); + } + } + FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(); + if (CollectionUtils.isEmpty(variableLocal)) { + + FormService formService = formEngineConfiguration.getFormService(); + List formInstances = + formService.createFormInstanceQuery().processInstanceId(processInstanceId) + .orderBySubmittedDate().desc().listPage(0, 1); + if (!CollectionUtils.isEmpty(formInstances)) { + // { + // "values" : { + // "form_mutil_text1" : "表单变量 2", + // "form_text1" : "表单变量 1", + // "form_image1" : "[{\"fileUrl\":\"http://gips2.baidu.com/it/u=195724436,3554684702&fm=3028&app=3028&f=JPEG&fmt=auto?w=1280&h=960\"}]" + // } + //} + return formInstances.get(0).getFormValueBytes(); + } + if (throwException) { + throw new WorkflowEngineException(FORM_INSTANCE_DATA_NOT_FOUND, processInstanceId); + } else { + return null; + } + } else { + ObjectMapper objectMapper = formEngineConfiguration.getObjectMapper(); + ObjectNode submittedFormValuesJson = objectMapper.createObjectNode(); + + ObjectNode valuesNode = submittedFormValuesJson.putObject("values"); + variableLocal.forEach((k, variableValue) -> { + if (variableValue == null) { + valuesNode.putNull(k); + } else if (variableValue instanceof Long) { + valuesNode.put(k, (Long) variableValue); + + } else if (variableValue instanceof Double) { + valuesNode.put(k, (Double) variableValue); + + } else if (variableValue instanceof Boolean) { + valuesNode.put(k, (Boolean) variableValue); + + } else if (variableValue instanceof LocalDate) { + valuesNode.put(k, ((LocalDate) variableValue).toString()); + + } else { + valuesNode.put(k, variableValue.toString()); + } + }); + try { + return objectMapper.writeValueAsBytes(submittedFormValuesJson); + } catch (JsonProcessingException e) { + if (throwException) { + throw new WorkflowEngineException(FORM_INSTANCE_DATA_NOT_FOUND, processInstanceId); + } else { + return null; + } + } + } + + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetFormInstanceModelCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetFormInstanceModelCmd.java new file mode 100644 index 000000000..3fca8df22 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetFormInstanceModelCmd.java @@ -0,0 +1,421 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.AmountFieldDTO; +import cn.axzo.workflow.common.model.dto.UploadFieldDTO; +import cn.axzo.workflow.core.engine.cmd.helper.FormFieldClone; +import cn.axzo.workflow.form.engine.model.CustomSimpleFormModel; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.common.engine.impl.el.VariableContainerWrapper; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.form.api.FormInstance; +import org.flowable.form.api.FormInstanceInfo; +import org.flowable.form.api.FormInstanceQuery; +import org.flowable.form.engine.FormEngineConfiguration; +import org.flowable.form.engine.impl.cmd.GetFormInstanceModelCmd; +import org.flowable.form.engine.impl.util.CommandContextUtil; +import org.flowable.form.model.ExpressionFormField; +import org.flowable.form.model.FormContainer; +import org.flowable.form.model.FormField; +import org.flowable.form.model.FormFieldTypes; +import org.flowable.form.model.Option; +import org.flowable.form.model.OptionFormField; +import org.flowable.form.model.SimpleFormModel; +import org.joda.time.LocalDate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_CUSTOM_COMPONENT; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_CHANGE_SIGNATURE_ORDER; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_CUSTOM_COMPONENT; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_IMAGE; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_INPUT; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_RECTIFY_ORDER; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_TASK_ORDER; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_TEXTAREA; +import static cn.axzo.workflow.form.engine.cmd.CustomGetVariablesFromFormSubmissionCmd.fmtConvert; +import static cn.axzo.workflow.form.engine.cmd.CustomGetVariablesFromFormSubmissionCmd.parseToLocalDateTime; + +/** + * 自定义的获取表单模型和最新表单内容的命令实现 + * + * @author wangli + * @since 2024-11-25 19:52 + */ +public class CustomGetFormInstanceModelCmd extends GetFormInstanceModelCmd { + private static final Logger log = LoggerFactory.getLogger(CustomGetFormInstanceModelCmd.class); + private static final long serialVersionUID = 1L; + + public CustomGetFormInstanceModelCmd(String formInstanceId, Map variables) { + super(formInstanceId, variables); + } + + public CustomGetFormInstanceModelCmd(String formDefinitionKey, String formDefinitionId, String taskId, String processInstanceId, Map variables) { + super(formDefinitionKey, formDefinitionId, taskId, processInstanceId, variables); + } + + public CustomGetFormInstanceModelCmd(String formDefinitionKey, String parentDeploymentId, String formDefinitionId, String taskId, String processInstanceId, Map variables) { + super(formDefinitionKey, parentDeploymentId, formDefinitionId, taskId, processInstanceId, variables); + } + + public CustomGetFormInstanceModelCmd(String formDefinitionKey, String parentDeploymentId, String formDefinitionId, String taskId, String processInstanceId, String tenantId, Map variables, boolean fallbackToDefaultTenant) { + super(formDefinitionKey, parentDeploymentId, formDefinitionId, taskId, processInstanceId, tenantId, variables, fallbackToDefaultTenant); + } + + @Override + protected FormInstance resolveFormInstance(CommandContext commandContext) { + FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(); + FormInstanceQuery formInstanceQuery = formEngineConfiguration.getFormService().createFormInstanceQuery().formDefinitionId(formDefinitionId); + if (formInstanceId != null) { + formInstanceQuery.id(formInstanceId); + + } else if (processInstanceId != null) { + formInstanceQuery.processInstanceId(processInstanceId); + + if (StringUtils.hasText(taskId)) { + formInstanceQuery.taskId(taskId); + } else if (Objects.equals("", taskId)) { + formInstanceQuery.withoutTaskId(); + } + + } else if (scopeId != null) { + formInstanceQuery.scopeId(scopeId); + formInstanceQuery.scopeType(scopeType); + + if (taskId == null) { + formInstanceQuery.withoutTaskId(); + } + + } else { + return null; + } + + List formInstances = formInstanceQuery.orderBySubmittedDate().desc().list(); + // 这里约束前端每次编辑表单提交时,都是提交全量的字段,所以这里只取最后的数据即可。 + // 如果以后开放给业务传入,那么这里的逻辑可能就需要调整 + if (!formInstances.isEmpty()) { + return formInstances.get(0); + } + + log.info("未查询到流程实例关联的表单实例数据"); + return null; + } + + @Override + public void fillVariablesWithFormValues(Map submittedFormFieldMap, List allFields) { + for (FormField field : allFields) { + + JsonNode fieldValueNode = submittedFormFieldMap.get(field.getId()); + + if (fieldValueNode == null || fieldValueNode.isNull()) { + continue; + } + + String fieldType = field.getType(); + String fieldValue = fieldValueNode.asText(); + + if (FormFieldTypes.DATE.equals(fieldType)) { + try { + if (org.apache.commons.lang3.StringUtils.isNotEmpty(fieldValue)) { + LocalDateTime dateValue = parseToLocalDateTime(fieldValue, fmtConvert(field.getParam("fmt").toString())); + if (Objects.nonNull(dateValue)) { + variables.put(field.getId(), dateValue.format(DateTimeFormatter.ofPattern(fmtConvert(field.getParam("fmt").toString())))); + } else { + variables.put(field.getId(), fieldValue); + } + } + } catch (Exception e) { + log.error("Error parsing form date value for process instance {} and task {} with value {}", processInstanceId, taskId, fieldValue, e); + } +// } else if (FormFieldTypes.UPLOAD.equals(fieldType) || FormConstants.FORM_FIELD_TYPE_IMAGE.equals(fieldType)) { +// FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(); +// ObjectMapper objectMapper = formEngineConfiguration.getObjectMapper(); +// try { +// List uploadFiles = objectMapper.readValue(fieldValue, new TypeReference>() { +// }); +// variables.put(field.getId(), uploadFiles); +// } catch (JsonProcessingException e) { +// throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_UPLOAD); +// } + } else if (fieldValueNode.isBoolean()) { + variables.put(field.getId(), fieldValueNode.asBoolean()); + + } else if (fieldValueNode.isLong()) { + variables.put(field.getId(), fieldValueNode.asLong()); + + } else if (fieldValueNode.isDouble()) { + variables.put(field.getId(), fieldValueNode.asDouble()); + + } else { + variables.put(field.getId(), fieldValue); + } + } + } + + @Override + protected void fillFormInstanceValues(FormInstanceInfo formInstanceModel, FormInstance formInstance, Map formInstanceFieldMap, ObjectMapper objectMapper) { + try { + JsonNode submittedNode = objectMapper.readTree(formInstance.getFormValueBytes()); + if (submittedNode == null) { + return; + } + + if (submittedNode.get("values") != null) { + JsonNode valuesNode = submittedNode.get("values"); + Iterator fieldIdIterator = valuesNode.fieldNames(); + while (fieldIdIterator.hasNext()) { + String fieldId = fieldIdIterator.next(); + JsonNode valueNode = valuesNode.get(fieldId); + formInstanceFieldMap.put(fieldId, valueNode); + } + } + + if (submittedNode.get("flowable_form_outcome") != null) { + JsonNode outcomeNode = submittedNode.get("flowable_form_outcome"); + if (!outcomeNode.isNull() && org.apache.commons.lang3.StringUtils.isNotEmpty(outcomeNode.asText())) { + formInstanceModel.setSelectedOutcome(outcomeNode.asText()); + } + } + + } catch (Exception e) { + throw new FlowableException("Error parsing form instance " + formInstance.getId(), e); + } + } + + @Override + protected void fillFormFieldValues(FormInstance formInstance, FormInstanceInfo formInstanceModel, CommandContext commandContext) { + FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(); + CustomSimpleFormModel formModel = new CustomSimpleFormModel((SimpleFormModel) formInstanceModel.getFormModel()); + List allFields = formModel.listAllFields(); + if (allFields != null) { + + Map formInstanceFieldMap = new HashMap<>(); + if (formInstance != null) { + fillFormInstanceValues(formInstanceModel, formInstance, formInstanceFieldMap, formEngineConfiguration.getObjectMapper()); + fillVariablesWithFormValues(formInstanceFieldMap, allFields); + } + + for (FormField field : allFields) { + if (field instanceof OptionFormField) { + OptionFormField optionFormField = (OptionFormField) field; + if (optionFormField.getOptionsExpression() != null) { + // Drop down options to be populated from an expression + Expression optionsExpression = formEngineConfiguration.getExpressionManager().createExpression(optionFormField.getOptionsExpression()); + Object value = null; + try { + value = optionsExpression.getValue(new VariableContainerWrapper(variables)); + } catch (Exception e) { + throw new FlowableException("Error getting value for optionsExpression: " + optionFormField.getOptionsExpression(), e); + } + if (value instanceof List) { + @SuppressWarnings("unchecked") + List

+ * 自定义指定实例下的指定名称的变量数据 + * + * @author wangli + * @since 2024-10-14 13:43 + */ +public class CustomGetHistoricVariablesCmd implements Command> { + + private final String processInstanceId; + private final List variableNames; + + public CustomGetHistoricVariablesCmd(String processInstanceId, List variableNames) { + this.processInstanceId = processInstanceId; + this.variableNames = variableNames; + } + + @Override + public List execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoryService historyService = processEngineConfiguration.getHistoryService(); + + if (StringUtils.hasText(processInstanceId) && !CollectionUtils.isEmpty(variableNames)) { + NativeHistoricVariableInstanceQuery query = historyService.createNativeHistoricVariableInstanceQuery() + .sql(buildQuerySql(commandContext)) + .parameter("processInstanceId", processInstanceId); + if (!CollectionUtils.isEmpty(variableNames)) { + for (int i = 0; i < variableNames.size(); i++) { + query.parameter("variableName" + i, variableNames.get(i)); + } + } + return query.list(); + } + return Collections.emptyList(); + } + + private String buildQuerySql(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + ManagementService managementService = processEngineConfiguration.getManagementService(); + StringBuilder baseQuerySql = new StringBuilder("SELECT * ") + .append(" FROM ") + .append(managementService.getTableName(HistoricVariableInstance.class)) + .append(" WHERE 1 = 1") + .append(" AND PROC_INST_ID_ = #{processInstanceId}"); + + if (!CollectionUtils.isEmpty(variableNames)) { + baseQuerySql.append(" AND ("); + for (int i = 0; i < variableNames.size(); i++) { + if (i != 0) { + baseQuerySql.append(" OR "); + } + baseQuerySql.append(" NAME_ = #{variableName").append(i).append("}"); + } + } + return baseQuerySql.append(")").toString(); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetModelByDefinitionIdCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetModelByDefinitionIdCmd.java new file mode 100644 index 000000000..0234b7505 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetModelByDefinitionIdCmd.java @@ -0,0 +1,42 @@ +package cn.axzo.workflow.core.engine.cmd; + +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; + +/** + * 通过流程定义ID获取流程模型 + * + * @author wangli + * @since 2025-04-19 10:17 + */ +public class CustomGetModelByDefinitionIdCmd implements Command { + private final String processDefinitionId; + + public CustomGetModelByDefinitionIdCmd(String processDefinitionId) { + this.processDefinitionId = processDefinitionId; + } + + @Override + public Model execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil + .getProcessEngineConfiguration(commandContext).getProcessEngineConfiguration(); + RepositoryService repositoryService = processEngineConfiguration.getRepositoryService(); + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(processDefinitionId) + .singleResult(); + + // 获取部署ID + String deploymentId = processDefinition.getDeploymentId(); + + // 通过部署ID查询模型 + Model model = repositoryService.createModelQuery() + .deploymentId(deploymentId) + .singleResult(); + return model; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetModelDocsCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetModelDocsCmd.java new file mode 100644 index 000000000..222f77e2d --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetModelDocsCmd.java @@ -0,0 +1,182 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO; +import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO; +import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc; +import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper; +import cn.axzo.workflow.core.service.ExtAxReModelService; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.variable.api.history.HistoricVariableInstance; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_QUERY_ERROR; +import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_ID_ILLEGAL; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_PROCESS_ENABLE_DOC_IDS; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; +import static cn.axzo.workflow.core.service.impl.ExtAxModelDocServiceImpl.buildQueryWrapper; + + +/** + * 自定义的获取指定流程实例或指定流程定义版本对应的模型中关联的文档元数据 + * + * @author wangli + * @since 2025-04-02 10:25 + */ +public class CustomGetModelDocsCmd implements Command> { + + private String processInstanceId; + /** + * eg. we1:1:202504021034000000001 or we1 + */ + private String processDefinitionId; + private String tenantId; + private Boolean filterEnable = true; + /** + * 该属性,只对流程实例入参生效,过滤出发起流程时,勾选的文档 + */ + private Boolean filterSelect = false; + private final ExtAxModelDocMapper extAxModelDocMapper; + private final ExtAxReModelService extAxReModelService; + + public CustomGetModelDocsCmd(String processInstanceId, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) { + this(processInstanceId, false, extAxModelDocMapper, extAxReModelService); + } + + public CustomGetModelDocsCmd(String processInstanceId, Boolean filterSelect, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) { + this(processInstanceId, filterSelect, true, extAxModelDocMapper, extAxReModelService); + } + + public CustomGetModelDocsCmd(String processInstanceId, Boolean filterSelect, Boolean filterEnable, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) { + this.processInstanceId = processInstanceId; + this.filterSelect = filterSelect; + this.extAxModelDocMapper = extAxModelDocMapper; + this.filterEnable = filterEnable; + this.extAxReModelService = extAxReModelService; + } + + public CustomGetModelDocsCmd(String processDefinitionId, String tenantId, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) { + this.processDefinitionId = processDefinitionId; + this.tenantId = StringUtils.hasText(tenantId) ? tenantId : NO_TENANT_ID; + this.extAxModelDocMapper = extAxModelDocMapper; + this.extAxReModelService = extAxReModelService; + } + + public CustomGetModelDocsCmd(String processInstanceId, String processDefinitionId, String tenantId, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) { + this.processInstanceId = processInstanceId; + this.processDefinitionId = processDefinitionId; + this.tenantId = tenantId; + this.extAxModelDocMapper = extAxModelDocMapper; + this.extAxReModelService = extAxReModelService; + } + + public CustomGetModelDocsCmd(String processInstanceId, String processDefinitionId, String tenantId, Boolean filterSelect, Boolean filterEnable, ExtAxModelDocMapper extAxModelDocMapper, ExtAxReModelService extAxReModelService) { + this.processInstanceId = processInstanceId; + this.processDefinitionId = processDefinitionId; + this.tenantId = tenantId; + this.filterSelect = filterSelect; + this.filterEnable = filterEnable; + this.extAxModelDocMapper = extAxModelDocMapper; + this.extAxReModelService = extAxReModelService; + } + + @Override + public List execute(CommandContext commandContext) { + check(); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + RepositoryService repositoryService = processEngineConfiguration.getRepositoryService(); + ProcessDefinition processDefinition; + List enableDocIds = new ArrayList<>(); + if (StringUtils.hasText(processInstanceId)) { + HistoryService historyService = processEngineConfiguration.getHistoryService(); + HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (filterSelect) { + if (Objects.isNull(instance)) { + return Collections.emptyList(); + } + if (Objects.equals(PROCESSING.getStatus(), instance.getBusinessStatus())) { + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + List docIds = (List) runtimeService.getVariable(processInstanceId, SIGN_PROCESS_ENABLE_DOC_IDS, List.class); + if (!CollectionUtils.isEmpty(docIds)) { + enableDocIds.addAll(docIds); + } + } else { + HistoricVariableInstance variableInstance = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).variableName(SIGN_PROCESS_ENABLE_DOC_IDS).singleResult(); + if (Objects.nonNull(variableInstance)) { + enableDocIds.addAll((List) variableInstance.getValue()); + } + } + } + processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(instance.getProcessDefinitionId()).singleResult(); + } else { + List definitions = repositoryService.createProcessDefinitionQuery() + .processDefinitionKey(parseProcessDefinitionKey()) + .list(); + if (CollectionUtils.isEmpty(definitions)) { + return Collections.emptyList(); + } + Optional first = definitions.stream().filter(i -> i.getTenantId().equals(tenantId)) + .max(Comparator.comparing(ProcessDefinition::getVersion)); + if (first.isPresent()) { + processDefinition = first.get(); + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + Model model = commandExecutor.execute(new CustomGetModelByDefinitionIdCmd(processDefinition.getId())); + BpmnModelExtVO modelStatus = extAxReModelService.getStatusByModelId(model.getId()); + if (Objects.equals(0, modelStatus.getStatus())) { + processDefinition = definitions.stream().filter(i -> i.getTenantId().equals(NO_TENANT_ID)).max(Comparator.comparing(ProcessDefinition::getVersion)).get(); + } + } else { + processDefinition = first.orElseGet(() -> definitions.stream().filter(i -> i.getTenantId().equals(NO_TENANT_ID)).max(Comparator.comparing(ProcessDefinition::getVersion)).get()); + } + } + + ExtAxModelDoc query = new ExtAxModelDoc(); + query.setModelKey(processDefinition.getKey()); + if (Objects.equals(Boolean.TRUE, filterEnable)) { + query.setStatus(filterEnable); + } + query.setTenantId(processDefinition.getTenantId()); + query.setTempFile(false); + List docs = extAxModelDocMapper.selectList(buildQueryWrapper(query)); + + if (filterSelect) { + docs = docs.stream().filter(i -> enableDocIds.contains(i.getId())).collect(Collectors.toList()); + } + return BeanMapper.copyList(docs, DocBaseVO.class, (s, t) -> t.setFileType(FileTypeEnum.valueOfType(s.getFileType()))); + } + + private String parseProcessDefinitionKey() { + if (StringUtils.hasText(processDefinitionId)) { + return processDefinitionId.split(":")[0]; + } + throw new WorkflowEngineException(PROCESS_DEFINITION_ID_ILLEGAL); + } + + private void check() { + if (!StringUtils.hasText(processInstanceId) && !StringUtils.hasText(processDefinitionId)) { + throw new WorkflowEngineException(MODEL_FILE_QUERY_ERROR); + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesCmd.java new file mode 100644 index 000000000..bd84f4cf0 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesCmd.java @@ -0,0 +1,100 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.dto.SignatureDTO; +import cn.axzo.workflow.core.common.utils.SpringContextUtils; +import cn.axzo.workflow.core.service.CategoryService; +import com.alibaba.fastjson.JSON; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.HistoryService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; + +/** + * 自定义获取流程实例中用于打印的变量命令实现 + * + * @author wangli + * @since 2025-01-21 11:47 + */ +public class CustomGetProcessInstanceVariablesCmd extends AbstractCommand> implements Serializable { + + private final String processInstanceId; + private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); + + public CustomGetProcessInstanceVariablesCmd(String processInstanceId) { + this.processInstanceId = processInstanceId; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + return JSON.toJSONString(params); + } + + @Override + public Map executeInternal(CommandContext commandContext) { + Map variables = new HashMap<>(); + variables.put(PRINT_VAR_PROCESS_INSTANCE_ID, processInstanceId); + if (!StringUtils.hasText(processInstanceId)) { + return variables; + } + + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + HistoryService historyService = processEngineConfiguration.getHistoryService(); + HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId).includeProcessVariables().singleResult(); + // 添加流程开始时间 + variables.put(PRINT_VAR_PROCESS_START_TIME, sdf.format(instance.getStartTime())); + variables.put(PRINT_VAR_PROCESS_END_TIME, Objects.nonNull(instance.getEndTime()) ? sdf.format(instance.getEndTime()) : null); + // 添加流程业务 ID + addProcessDefinitionKey(variables, instance); + + Map processVariables = instance.getProcessVariables(); + // 发起人流程引擎内部模型,需要外部调用该命令时,再解析 + variables.put(PRINT_VAR_PROCESS_INITIATOR, processVariables.get(INTERNAL_INITIATOR)); + + // 电子签名 + addSignature(variables, (List) processVariables.get(SIGNATURE_COLLECTION)); + + return variables; + } + + private void addSignature(Map variables, List signatures) { + if (CollectionUtils.isEmpty(signatures)) { + return; + } + signatures.forEach(sign -> { + if (!Objects.equals(NODE_STARTER.getType(), sign.getActivityId())) { + variables.put(sign.getActivityId(), sign.getSignatures()); + } + }); + } + + private void addProcessDefinitionKey(Map variables, HistoricProcessInstance historicProcessInstance) { + CategoryService categoryService = SpringContextUtils.getBean(CategoryService.class); + categoryService.get(BPM_MODEL_CATEGORY, historicProcessInstance.getProcessDefinitionKey()).ifPresent(category -> { + variables.put(PRINT_VAR_PROCESS_DEFINITION_KEY, category.getLabel() + "(" + category.getValue() + ")"); + }); + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesToObjectCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesToObjectCmd.java new file mode 100644 index 000000000..6c119416e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetProcessInstanceVariablesToObjectCmd.java @@ -0,0 +1,311 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.enums.VarTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.ContactsPersonDTO; +import cn.axzo.workflow.common.model.dto.SignatureDTO; +import cn.axzo.workflow.common.model.dto.VariableObjectDTO; +import cn.axzo.workflow.common.model.request.category.CategoryGroupVarSearchDto; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; +import cn.axzo.workflow.common.model.response.category.CategoryItemVO; +import cn.axzo.workflow.core.common.utils.SpringContextUtils; +import cn.axzo.workflow.core.service.CategoryGroupService; +import cn.axzo.workflow.core.service.CategoryService; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.HistoryService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.form.api.FormInfo; +import org.flowable.form.api.FormRepositoryService; +import org.flowable.form.api.FormService; +import org.flowable.form.model.FormContainer; +import org.flowable.form.model.FormField; +import org.flowable.form.model.FormFieldTypes; +import org.flowable.form.model.SimpleFormModel; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS; +import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_VARIABLE; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME_DESC; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; + +/** + * 自定义获取流程实例中的变量命令实现 + *

+ * 返回的变量包括如下: + * 1、业务名称、审批编号、发起事件、发起人对象信息、节点签名和意见 + * 2、业务管理中的变量 + * 3、表单中变量 + * + * @author wangli + * @since 2025-01-21 11:47 + */ +@Slf4j +public class CustomGetProcessInstanceVariablesToObjectCmd extends AbstractCommand> implements Serializable { + + private final String processInstanceId; + private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); + private static final List SUPPORTED_FORM_TYPES = Lists.newArrayList("input", "date", "textarea", "image", "contacts", "amount"); + + public CustomGetProcessInstanceVariablesToObjectCmd(String processInstanceId) { + this.processInstanceId = processInstanceId; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + return JSON.toJSONString(params); + } + + @Override + public List executeInternal(CommandContext commandContext) { + List returnVariables = new ArrayList<>(); + if (!StringUtils.hasText(processInstanceId)) { + return returnVariables; + } + returnVariables.add(VariableObjectDTO.builder() + .key(PRINT_VAR_PROCESS_INSTANCE_ID) + .desc(PRINT_VAR_PROCESS_INSTANCE_ID_DESC) + .value(processInstanceId) + .build()); + + + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + HistoryService historyService = processEngineConfiguration.getHistoryService(); + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId).includeProcessVariables().singleResult(); + // 添加流程开始时间 + returnVariables.add(VariableObjectDTO.builder() + .key(PRINT_VAR_PROCESS_START_TIME) + .desc(PRINT_VAR_PROCESS_START_TIME_DESC) + .value(sdf.format(historicProcessInstance.getStartTime())) + .build()); + returnVariables.add(VariableObjectDTO.builder() + .key(PRINT_VAR_PROCESS_END_TIME) + .desc(PRINT_VAR_PROCESS_END_TIME_DESC) + .value(Objects.nonNull(historicProcessInstance.getEndTime()) ? sdf.format(historicProcessInstance.getEndTime()) : null) + .build()); + + Map processVariables = historicProcessInstance.getProcessVariables(); + // 添加流程业务 ID 和业务下分组变量信息 + addProcessDefinitionKeyAndVariables(returnVariables, (Map) processVariables.getOrDefault(SIGN_VARIABLE, new HashMap()), historicProcessInstance); + + // 发起人流程引擎内部模型,需要外部调用该命令时,再解析; 当前命令所在 module 不支持调用二方接口 + returnVariables.add(VariableObjectDTO.builder() + .key(PRINT_VAR_PROCESS_INITIATOR) + .desc(PRINT_VAR_PROCESS_INITIATOR_DESC) + .value(processVariables.get(INTERNAL_INITIATOR)) + .type(VariableObjectDTO.Type.obj) + .build()); + + // 电子签名 + addSignature(returnVariables, (List) processVariables.get(SIGNATURE_COLLECTION)); + + // 表单中的变量 + addFormFieldValue(commandContext, historicProcessInstance, returnVariables, processVariables); + + return returnVariables; + } + + private void addFormFieldValue(CommandContext commandContext, HistoricProcessInstance instance, List variables, Map processVariables) { + Map formFieldValues = new HashMap<>(); + byte[] formInstanceValue = commandContext.getCommandExecutor().execute(new CustomGetFormInstanceLatestValuesCmd(processInstanceId, false)); + if (!ObjectUtils.isEmpty(formInstanceValue)) { + JSONObject treeNode = JSON.parseObject(new String(formInstanceValue)); + formFieldValues.putAll(treeNode.getJSONObject("values").getInnerMap()); + } + + FormRepositoryService formRepositoryService = CommandContextUtil.getFormRepositoryService(commandContext); + FormInfo formModel; + try { + formModel = formRepositoryService.getFormModelByKey(instance.getProcessDefinitionKey(), instance.getTenantId(), true); + } catch (FlowableObjectNotFoundException e) { + log.warn("can't found form model"); + throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); + } + if (CollectionUtils.isEmpty(formFieldValues)) { + + FormService formService = CommandContextUtil.getFormService(commandContext); + formFieldValues.putAll(formService.getVariablesFromFormSubmission(formModel, processVariables, null)); + } + + if (CollectionUtils.isEmpty(formFieldValues)) { + return; + } + + + List formFields = ((SimpleFormModel) formModel.getFormModel()).getFields(); + + formFields.forEach(formField -> { + FormContainer formContainer = (FormContainer) formField; + formContainer.getFields().get(0).forEach(field -> { + if (!(field instanceof FormContainer) && SUPPORTED_FORM_TYPES.contains(field.getType())) { + Object fieldValue = formFieldValues.getOrDefault(field.getId(), null); + if (Objects.nonNull(fieldValue)) { + if (Objects.equals(field.getType(), FormFieldTypes.AMOUNT)) { + if (StringUtils.hasText(fieldValue.toString())) { + JSONObject jsonObject = JSON.parseObject(fieldValue.toString()); + BigDecimal standardNumerals = jsonObject.getBigDecimal("standardNumerals"); + if (Objects.nonNull(standardNumerals)) { + variables.add(VariableObjectDTO.builder() + .key(field.getId() + "_standardNumerals") + .desc(field.getName() + "小写") + .value(standardNumerals) + .type(convert(field.getType())) + .build()); + } + if (Boolean.parseBoolean(field.getParam("toUpper").toString())) { + String uppercaseNumerals = jsonObject.getString("uppercaseNumerals"); + if (StringUtils.hasText(uppercaseNumerals)) { + variables.add(VariableObjectDTO.builder() + .key(field.getId() + "_uppercaseNumerals") + .desc(field.getName() + "大写") + .value(uppercaseNumerals) + .type(convert(field.getType())) + .build()); + } + } + } + } else if (Objects.equals(field.getType(), "contacts")) { + if (StringUtils.hasText(fieldValue.toString()) + && fieldValue.toString().startsWith("[") + && fieldValue.toString().endsWith("]")) { + List names = JSON.parseArray((String) fieldValue, ContactsPersonDTO.class) + .stream().map(ContactsPersonDTO::getRealName).collect(Collectors.toList()); + variables.add(VariableObjectDTO.builder() + .key(field.getId()) + .desc(field.getName()) + .value(StringUtils.collectionToCommaDelimitedString(names)) + .type(convert(field.getType())) + .build()); + } + } else { + variables.add(VariableObjectDTO.builder() + .key(field.getId()) + .desc(field.getName()) + .value(fieldValue) + .type(convert(field.getType())) + .build()); + } + } + } + }); + }); + } + + private void addSignature(List variables, List signatures) { + if (CollectionUtils.isEmpty(signatures)) { + return; + } + signatures.forEach(sign -> { + if (!Objects.equals(NODE_STARTER.getType(), sign.getActivityId())) { + variables.add(VariableObjectDTO.builder() + .key(sign.getActivityId()) + .desc(sign.getActivityName()) + .value(sign.getSignatures()) + .type(VariableObjectDTO.Type.signatureAndAdvice) + .build()); + } + }); + } + + private void addProcessDefinitionKeyAndVariables(List variables, Map bizVariables, HistoricProcessInstance historicProcessInstance) { + CategoryService categoryService = SpringContextUtils.getBean(CategoryService.class); + categoryService.get(BPM_MODEL_CATEGORY, historicProcessInstance.getProcessDefinitionKey()).ifPresent(category -> { + variables.add(VariableObjectDTO.builder() + .key(PRINT_VAR_PROCESS_DEFINITION_KEY) + .desc(PRINT_VAR_PROCESS_DEFINITION_KEY_DESC) + .value(category.getLabel() + "(" + category.getValue() + ")") + .build()); + // 添加业务下的变量 + addVariables(variables, category, bizVariables); + }); + } + + private void addVariables(List variables, CategoryItemVO category, Map bizVariables) { + CategoryGroupService categoryServiceGroup = SpringContextUtils.getBean(CategoryGroupService.class); + List groupVariables = categoryServiceGroup.searchGroupAndVarList(CategoryGroupVarSearchDto.builder() + .dictId(category.getId()) + .category(category.getValue()) + .build()); + + groupVariables.forEach(group -> { + if (!CollectionUtils.isEmpty(group.getVars())) { + group.getVars().forEach(variable -> { + Object value = bizVariables.getOrDefault(variable.getCode(), null); + if (Objects.isNull(value)) { + // 目前为了减少 wps 替换耗时,所以将没有值的变量,直接踢出 + return; + } + variables.add(VariableObjectDTO.builder() + .key(variable.getCode()) + .desc(group.getGroupName() + variable.getName()) + .value(value) + .type(convert(variable.getType())) + .build()); + }); + } + }); + + + } + + private VariableObjectDTO.Type convert(VarTypeEnum varType) { + switch (varType) { + case TEXT: + return VariableObjectDTO.Type.text; + case PICTURE: + return VariableObjectDTO.Type.img; + default: + return VariableObjectDTO.Type.obj; + } + } + + private VariableObjectDTO.Type convert(String type) { + switch (type) { + case "input": + case "text": + case "date": + case "textarea": + case "amount": + case "contacts": + return VariableObjectDTO.Type.text; + case "image": + return VariableObjectDTO.Type.img; + default: + return VariableObjectDTO.Type.obj; + } + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetPropertyCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetPropertyCmd.java new file mode 100644 index 000000000..cb257c202 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomGetPropertyCmd.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.hutool.core.date.DateUtil; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntity; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.util.Date; +import java.util.Objects; + +/** + * 获取 ACT_GE_PROPERTY 表的指定属性值的命令 + * + * @author wangli + * @since 2024-09-30 15:15 + */ +public class CustomGetPropertyCmd implements Command { + private final String propertyName; + + public CustomGetPropertyCmd(String propertyName) { + this.propertyName = propertyName; + } + + @Override + public Date execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + PropertyEntity entity = processEngineConfiguration.getPropertyEntityManager().findById(propertyName); + if (Objects.nonNull(entity)) { + return DateUtil.parseDate(entity.getValue()); + } + return null; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomInsertPropertyCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomInsertPropertyCmd.java new file mode 100644 index 000000000..2e1441f17 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomInsertPropertyCmd.java @@ -0,0 +1,43 @@ +package cn.axzo.workflow.core.engine.cmd; + +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntity; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntityImpl; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; + +import java.util.Objects; + +/** + * 操作 ACT_GE_PROPERTY 表的写命令 + * + * @author wangli + * @since 2024-09-30 15:15 + */ +public class CustomInsertPropertyCmd implements Command { + private final String propertyName; + private final String propertyValue; + + public CustomInsertPropertyCmd(String propertyName, String propertyValue) { + this.propertyName = propertyName; + this.propertyValue = propertyValue; + } + + @Override + public Void execute(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + PropertyEntity entity = processEngineConfiguration.getPropertyEntityManager().findById(propertyName); + if (Objects.nonNull(entity)) { + entity.setValue(propertyValue); + processEngineConfiguration.getPropertyEntityManager().update(entity); + } else { + entity = new PropertyEntityImpl(); + entity.setName(propertyName); + entity.setValue(propertyValue); + processEngineConfiguration.getPropertyEntityManager().insert(entity); + } + return null; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomNoticeDestinationUserSelectorCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomNoticeDestinationUserSelectorCmd.java new file mode 100644 index 000000000..c6eba1159 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomNoticeDestinationUserSelectorCmd.java @@ -0,0 +1,184 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; +import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeProperty; +import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeReceiver; +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.engine.listener.EngineExecutionStartListener; +import cn.axzo.workflow.core.engine.model.NoticeFlowElement; +import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter; +import com.alibaba.fastjson.JSON; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.runtime.Execution; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.variable.api.history.HistoricVariableInstance; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +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.core.engine.cmd.helper.CustomTaskHelper.removeDuplicateByPersonId; + +/** + * 消息通知,根据模型全局配置查找通知用户 + * + * @author wangli + * @since 2024/5/11 09:42 + */ +public class CustomNoticeDestinationUserSelectorCmd extends AbstractCommand> implements Serializable { + private static final long serialVersionUID = 1L; + private final EngineExecutionStartListener engineExecutionStartListener; + private final BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter; + private final String serviceVersion; + /** + * 流程模型对应的工作台类型, 这个值在创建流程时,已经放入到了流程变量中 + */ + private final Integer workspaceType; + private final BpmnNoticeProperty noticeProperty; + private final String processInstanceId; + private final BpmnTaskDelegateAssigner initiator; + + public CustomNoticeDestinationUserSelectorCmd(EngineExecutionStartListener engineExecutionStartListener, BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter, String serviceVersion, Integer workspaceType, BpmnNoticeProperty noticeProperty, String processInstanceId, BpmnTaskDelegateAssigner initiator) { + this.engineExecutionStartListener = engineExecutionStartListener; + this.historicTaskInstanceConverter = historicTaskInstanceConverter; + this.serviceVersion = serviceVersion; + this.workspaceType = workspaceType; + this.noticeProperty = noticeProperty; + this.processInstanceId = processInstanceId; + this.initiator = initiator; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("serviceVersion", serviceVersion); + params.put("workspaceType", workspaceType); + params.put("noticeProperty", noticeProperty); + params.put("processInstanceId", processInstanceId); + params.put("initiator", initiator); + return JSON.toJSONString(params); + } + + @Override + public List execute(CommandContext commandContext) { + if (!noticeProperty.getSendMessage()) { + return Collections.emptyList(); + } + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + List assigners = new ArrayList<>(); + checkAndBuildInitiator(assigners); + checkAndBuildHistories(assigners, processEngineConfiguration); + checkAndBuildRoles(assigners, processEngineConfiguration); + checkAndBuildPositions(assigners, processEngineConfiguration); + checkAndBuildAssigners(assigners); + return removeDuplicateByPersonId(assigners); + } + + private void checkAndBuildAssigners(List assigners) { + if (Objects.isNull(noticeProperty.getAssigners()) || !noticeProperty.getAssigners().getSelected()) { + return; + } + assigners.addAll(JSON.parseArray(noticeProperty.getAssigners().getViewJson(), BpmnTaskDelegateAssigner.class)); + } + + private void checkAndBuildPositions(List assigners, ProcessEngineConfigurationImpl processEngineConfiguration) { + if (Objects.isNull(noticeProperty.getPositions()) || !noticeProperty.getPositions().getSelected()) { + return; + } + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + List executions = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list(); + assigners.addAll(engineExecutionStartListener.approverSelect(ApproverSpecifyEnum.position.getType(), + buildCustomFlowElement(ApproverSpecifyEnum.position, noticeProperty.getPositions()), + (DelegateExecution) executions.get(0), false)); + } + + private void checkAndBuildRoles(List assigners, ProcessEngineConfigurationImpl processEngineConfiguration) { + if (Objects.isNull(noticeProperty.getRoles()) || !noticeProperty.getRoles().getSelected()) { + return; + } + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + List executions = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).list(); + assigners.addAll(engineExecutionStartListener.approverSelect(ApproverSpecifyEnum.role.getType(), + buildCustomFlowElement(ApproverSpecifyEnum.role, noticeProperty.getRoles()), + (DelegateExecution) executions.get(0), false)); + } + + private void checkAndBuildHistories(List assigners, ProcessEngineConfigurationImpl processEngineConfiguration) { + if (Objects.isNull(noticeProperty.getHistories()) || !noticeProperty.getHistories().getSelected()) { + return; + } + HistoryService historyService = processEngineConfiguration.getHistoryService(); + // 历史真正有同意和驳回动作的人 + List taskInstances = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(processInstanceId) + // .includeProcessVariables() + .orderByHistoricTaskInstanceStartTime() + .desc().list(); + List vos = + historicTaskInstanceConverter.toVosSkipSystemOperation(taskInstances, + serviceVersion); + + 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)); + vos.forEach(vo -> { + HistoricVariableInstance assginerSnapshot = + 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); + } + if (Objects.nonNull(assginerSnapshot)) { + BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(assginerSnapshot.getValue()); + if (Objects.nonNull(assigner) && !Objects.equals(assigner.buildAssigneeId(), NO_ASSIGNEE)) { + assigners.add(assigner); + } + } + }); + } + + /** + * 判断通知对象是否勾选发起人,如勾选则将发起人加入发送目标对象集合中 + * + * @param assigners + */ + private void checkAndBuildInitiator(List assigners) { + if (Objects.isNull(noticeProperty.getInitiator()) || !noticeProperty.getInitiator().getSelected()) { + return; + } + assigners.add(initiator); + } + + private NoticeFlowElement buildCustomFlowElement(ApproverSpecifyEnum specifyEnum, BpmnNoticeReceiver receiver) { + NoticeFlowElement noticeFlowElement = new NoticeFlowElement(); + noticeFlowElement.setId("noticeFlowElement"); + noticeFlowElement.setName("消息推送模拟流程元素"); + noticeFlowElement.setWorkspaceType(workspaceType); + noticeFlowElement.setSpecifyEnum(specifyEnum); + noticeFlowElement.setCustomValues(receiver.getViewJson()); + return noticeFlowElement; + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomOverrideFormVariablesByLatestInstanceCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomOverrideFormVariablesByLatestInstanceCmd.java new file mode 100644 index 000000000..7e19c0fb0 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomOverrideFormVariablesByLatestInstanceCmd.java @@ -0,0 +1,106 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import com.alibaba.fastjson.JSON; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.form.api.FormInstance; +import org.flowable.form.api.FormService; +import org.flowable.form.engine.FormEngineConfiguration; +import org.flowable.form.engine.impl.persistence.entity.FormInstanceEntity; +import org.flowable.form.engine.impl.util.CommandContextUtil; +import org.springframework.util.CollectionUtils; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_INSTANCE_DATA_NOT_FOUND; + +/** + * 覆写指定流程最后一次操作的表单中指定 key 的内容 + * + * @author wangli + * @since 2025-02-08 10:14 + */ +@Slf4j +public class CustomOverrideFormVariablesByLatestInstanceCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = 1L; + private final String processInstanceId; + private final Map formVariables; + + public CustomOverrideFormVariablesByLatestInstanceCmd(String processInstanceId, Map formVariables) { + this.processInstanceId = processInstanceId; + this.formVariables = formVariables; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("formVariables", formVariables); + return JSON.toJSONString(params); + } + + @Override + public Void executeInternal(CommandContext commandContext) { + FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(); + FormService formService = formEngineConfiguration.getFormService(); + List formInstances = formService.createFormInstanceQuery().processInstanceId(processInstanceId) + .orderBySubmittedDate().desc().listPage(0, 1); + if (CollectionUtils.isEmpty(formInstances)) { + throw new WorkflowEngineException(FORM_INSTANCE_DATA_NOT_FOUND, processInstanceId); + } + FormInstanceEntity formInstance = (FormInstanceEntity) formInstances.get(0); + + ObjectMapper objectMapper = CommandContextUtil.getFormEngineConfiguration().getObjectMapper(); + + try { + JsonNode jsonNode = objectMapper.readTree(formInstance.getFormValueBytes()); + if (Objects.isNull(jsonNode)) { + return null; + } + JsonNode valuesNode = jsonNode.get("values"); + if (Objects.nonNull(valuesNode)) { + ObjectNode valuesObjectNode = (ObjectNode) valuesNode; + + Iterator fieldIdIterator = valuesNode.fieldNames(); + while (fieldIdIterator.hasNext()) { + String fieldId = fieldIdIterator.next(); + + formVariables.forEach((k, v) -> { + if (Objects.equals(k, fieldId)) { + if (v instanceof Collection) { + try { + valuesObjectNode.set(k, new TextNode(objectMapper.writeValueAsString(v))); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } else { + valuesObjectNode.set(k, new TextNode(v.toString())); + } + } + }); + } + } + + byte[] formValueBytes = objectMapper.writeValueAsBytes(jsonNode); + log.info("更新后表单内容: {}", new String(formInstance.getFormValueBytes())); + formInstance.setFormValueBytes(formValueBytes); + CommandContextUtil.getFormInstanceEntityManager().update(formInstance); + } catch (Exception e) { + throw new FlowableException("Error parsing form instance " + formInstance.getId(), e); + } + return null; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskAsyncCmd.java new file mode 100644 index 000000000..090886159 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskAsyncCmd.java @@ -0,0 +1,78 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.framework.jackson.utility.JSON; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.core.engine.job.AsyncRejectTaskJobHandler; +import cn.hutool.json.JSONUtil; +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.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.impl.persistence.entity.TaskEntity; + +import java.io.Serializable; + +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; + +/** + * 自定义驳回任务的命令实现 + * + * @author wangli + * @since 2024/1/4 13:36 + */ +public class CustomRejectionTaskAsyncCmd extends AbstractCommand implements Serializable { + private final BpmnTaskAuditDTO dto; + + public CustomRejectionTaskAsyncCmd(BpmnTaskAuditDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public String executeInternal(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult(); + + TaskService taskService = processEngineConfiguration.getTaskService(); + Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult(); + + validTask(historicTaskInstance, (TaskEntity) task, dto.getApprover(), null); + + return startAsync(processEngineConfiguration, task); + } + + 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(AsyncRejectTaskJobHandler.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/CustomRejectionTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java index 5c41e6a83..c32b6a469 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 @@ -1,10 +1,13 @@ package cn.axzo.workflow.core.engine.cmd; +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 org.flowable.common.engine.impl.interceptor.Command; +import com.alibaba.fastjson.JSON; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; @@ -19,18 +22,18 @@ 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.COMMENT_TYPE_OPERATION_DESC; +import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER; 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; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_NAME; 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.SKIP_MQ; 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; @@ -40,21 +43,49 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask * @author wangli * @since 2024/1/4 13:36 */ -public class CustomRejectionTaskCmd implements Command, Serializable { +public class CustomRejectionTaskCmd extends AbstractCommand implements Serializable { private final String taskId; private final String advice; + private String operationDesc; private final List attachmentList; private final BpmnTaskDelegateAssigner approver; private final ExtAxHiTaskInstService extAxHiTaskInstService; + /** + * 可为 null, 用于底层对当前操作的节点进行一些额外的过滤 + */ + private final List nodeTypes; - public CustomRejectionTaskCmd(String taskId, String advice, List attachmentList, - BpmnTaskDelegateAssigner approver, ExtAxHiTaskInstService extAxHiTaskInstService) { - this.taskId = taskId; - this.advice = advice; - this.attachmentList = attachmentList; - this.approver = approver; + public CustomRejectionTaskCmd(BpmnTaskAuditDTO dto, ExtAxHiTaskInstService extAxHiTaskInstService) { + this(dto, extAxHiTaskInstService, null); + if (Objects.nonNull(dto.getOperationDesc())) { + this.operationDesc = dto.getOperationDesc(); + } + } + + public CustomRejectionTaskCmd(BpmnTaskAuditDTO dto, ExtAxHiTaskInstService extAxHiTaskInstService, String operationDesc) { + this.taskId = dto.getTaskId(); + this.advice = dto.getAdvice(); + if (Objects.nonNull(operationDesc)) { + this.operationDesc = operationDesc; + } else { + this.operationDesc = "已驳回"; + } + this.attachmentList = dto.getAttachmentList(); + this.approver = dto.getApprover(); this.extAxHiTaskInstService = extAxHiTaskInstService; + this.nodeTypes = dto.getNodeTypes(); + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("taskId", taskId); + params.put("advice", advice); + params.put("attachmentList", attachmentList); + params.put("approver", approver); + params.put("nodeTypes", nodeTypes); + return JSON.toJSONString(params); } @Override @@ -66,17 +97,19 @@ public class CustomRejectionTaskCmd implements Command, Serializable { 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); + validTask(historicTaskInstance, task, approver, nodeTypes); - ((TaskEntity) task).setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), 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)); + virtualTask.setTransientVariable(SKIP_MQ, true); - Task virtualTask = createVirtualTask(commandContext, extAxHiTaskInstService, task.getProcessInstanceId(), task.getName(), - task.getTaskDefinitionKey(), advice, approver, REJECTED.getStatus()); - addComment(commandContext, virtualTask, COMMENT_TYPE_OPERATION_DESC, "已驳回"); - - batchAddAttachment(commandContext, task.getProcessInstanceId(), task.getId(), attachmentList, approver); + batchAddAttachment(commandContext, task.getProcessInstanceId(), virtualTask, attachmentList, approver); + completeVirtualTask(commandContext, virtualTask); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); finishProcessInstance(commandContext, runtimeService, task, advice); @@ -86,14 +119,16 @@ public class CustomRejectionTaskCmd implements Command, Serializable { private void finishProcessInstance(CommandContext commandContext, RuntimeService runtimeService, Task task, String reason) { Map variables = new HashMap<>(); - variables.put(INTERNAL_END_TENANT_ID, approver.getTenantId()); - variables.put(INTERNAL_END_USER_NAME, approver.getAssignerName()); - variables.put(INTERNAL_END_USER_ID, approver.buildAssigneeId()); +// variables.put(INTERNAL_END_TENANT_ID, approver.getTenantId()); +// variables.put(INTERNAL_END_USER_NAME, approver.getAssignerName()); +// variables.put(INTERNAL_END_USER_ID, approver.buildAssigneeId()); variables.put(INTERNAL_DELETE_PROCESS_FLAG, INTERNAL_PROCESS_TYPE_REJECT); variables.put(INTERNAL_PROCESS_DELETE_REASON, reason); runtimeService.setVariables(task.getProcessInstanceId(), variables); + runtimeService.setVariable(task.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, approver); 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/CustomRemindTaskAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRemindTaskAsyncCmd.java new file mode 100644 index 000000000..64f17dac4 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRemindTaskAsyncCmd.java @@ -0,0 +1,106 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskRemindDTO; +import cn.axzo.workflow.core.engine.job.AsyncApproveTaskJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncRemindTaskJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import org.apache.commons.collections.CollectionUtils; +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.ProcessInstance; +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.TaskQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANT_REMIND; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.REMIND_TASK_TOO_MANY; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_REMIND_ERROR_NOT_EXISTS; + +/** + * TODO + * + * @author wangli + * @since 2025-06-13 14:01 + */ +public class CustomRemindTaskAsyncCmd extends AbstractCommand implements Serializable { + private static final Logger logger = LoggerFactory.getLogger(CustomRemindTaskAsyncCmd.class); + private final BpmnTaskRemindDTO dto; + + public CustomRemindTaskAsyncCmd(BpmnTaskRemindDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public String executeInternal(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + ProcessInstance processInstance = + runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult(); + if (Objects.isNull(processInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_CANT_REMIND, dto.getProcessInstanceId()); + } + + TaskService taskService = processEngineConfiguration.getTaskService(); + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(dto.getProcessInstanceId()); + if (StringUtils.hasLength(dto.getTaskDefinitionKey())) { + taskQuery.taskDefinitionKey(dto.getTaskDefinitionKey()); + } + List list = taskQuery.active().list(); + if (CollectionUtils.isEmpty(list)) { + throw new WorkflowEngineException(TASK_REMIND_ERROR_NOT_EXISTS); + } + if (!StringUtils.hasText(dto.getTaskDefinitionKey())) { + List taskDefinitionKeys = list.stream().map(Task::getTaskDefinitionKey).distinct().collect(Collectors.toList()); + if (taskDefinitionKeys.isEmpty()) { + throw new WorkflowEngineException(TASK_REMIND_ERROR_NOT_EXISTS); + } else if (taskDefinitionKeys.size() > 1) { + throw new WorkflowEngineException(REMIND_TASK_TOO_MANY); + } else { + dto.setTaskDefinitionKey(taskDefinitionKeys.get(0)); + } + } + return startAsync(processEngineConfiguration, list.get(0)); + } + + 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(AsyncRemindTaskJobHandler.TYPE); + job.setTenantId(task.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + return job.getId(); + } +} \ No newline at end of file diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRemindTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRemindTaskCmd.java new file mode 100644 index 000000000..8ba3f71ae --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRemindTaskCmd.java @@ -0,0 +1,141 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +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.engine.event.MessagePushEventBuilder; +import cn.axzo.workflow.core.engine.event.MessagePushEventImpl; +import cn.axzo.workflow.core.engine.event.MessagePushEventType; +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Lists; +import org.apache.commons.collections.CollectionUtils; +import org.flowable.bpmn.model.Process; +import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.flowable.task.api.TaskQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.util.HashMap; +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.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_CANT_REMIND; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.REMIND_TASK_TOO_MANY; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_REMIND_ERROR_NOT_EXISTS; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature; + +/** + * 自定义(同步)催办任务的命令器实现 + * + * @author wangli + * @since 2025-06-13 13:39 + */ +public class CustomRemindTaskCmd extends AbstractCommand implements Serializable { + private static final Logger log = LoggerFactory.getLogger(CustomRemindTaskCmd.class); + private final SupportRefreshProperties refreshProperties; + /** + * 操作催办时的终端类型,管理端cm、工人端cmp + */ + private final String terminalType; + private final String processInstanceId; + private String taskDefinitionKey; + /** + * 催办方式 + *

+ * IM, 如果为空,默认是 IM + */ + private final List remindTypes; + + public CustomRemindTaskCmd(String terminalType, String processInstanceId, String taskDefinitionKey, List remindTypes, SupportRefreshProperties refreshProperties) { + this.terminalType = terminalType; + this.processInstanceId = processInstanceId; + this.taskDefinitionKey = taskDefinitionKey; + this.remindTypes = CollectionUtils.isEmpty(remindTypes) ? Lists.newArrayList("IM") : remindTypes; + this.refreshProperties = refreshProperties; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("terminalType", terminalType); + params.put("processInstanceId", processInstanceId); + params.put("taskDefinitionKey", taskDefinitionKey); + params.put("remindTypes", remindTypes); + return JSON.toJSONString(params); + } + + @Override + public Void executeInternal(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + ProcessInstance processInstance = + runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + if (Objects.isNull(processInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_CANT_REMIND, processInstanceId); + } + + TaskService taskService = processEngineConfiguration.getTaskService(); + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId); + if (StringUtils.hasLength(taskDefinitionKey)) { + taskQuery.taskDefinitionKey(taskDefinitionKey); + } + List list = taskQuery.active().list(); + if (CollectionUtils.isEmpty(list)) { + throw new WorkflowEngineException(TASK_REMIND_ERROR_NOT_EXISTS); + } + if (!StringUtils.hasText(taskDefinitionKey)) { + List taskDefinitionKeys = list.stream().map(Task::getTaskDefinitionKey).distinct().collect(Collectors.toList()); + if (taskDefinitionKeys.isEmpty()) { + throw new WorkflowEngineException(TASK_REMIND_ERROR_NOT_EXISTS); + } else if (taskDefinitionKeys.size() > 1) { + throw new WorkflowEngineException(REMIND_TASK_TOO_MANY); + } else { + taskDefinitionKey = taskDefinitionKeys.get(0); + } + } + FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); + Process process = ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId()); + Optional noticeConfig = + BpmnMetaParserHelper.getNoticeConfig(process); + + Map variables = runtimeService.getVariables(processInstanceId, list.stream() + .map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getId()).collect(Collectors.toList())); + List assigners = variables.values().stream() + .map(BpmnTaskDelegateAssigner::toObjectCompatible) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + + // 过滤出未审批的任何,用选择的方式去发送消息 + remindTypes.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(), + getActivitySignature(process.getFlowElement(taskDefinitionKey)), + refreshProperties.getImTemplateCode(), terminalType); + eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey()); + }); + }); + }); + return null; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomResetTaskApproversAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomResetTaskApproversAsyncCmd.java new file mode 100644 index 000000000..c563f1cc9 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomResetTaskApproversAsyncCmd.java @@ -0,0 +1,81 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskResetApproversDTO; +import cn.axzo.workflow.core.engine.job.AsyncResetApproversUserTaskJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +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.job.service.JobService; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; + +import java.io.Serializable; + +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; + +/** + * 重置节点下的所有审批人 + * + * @author wangli + * @since 2025-06-24 17:17 + */ +@Slf4j +public class CustomResetTaskApproversAsyncCmd extends AbstractCommand implements Serializable { + + private static final long serialVersionUID = 6231755400617981273L; + private final BpmnTaskResetApproversDTO dto; + + public CustomResetTaskApproversAsyncCmd(BpmnTaskResetApproversDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public Void executeInternal(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult(); + + TaskService taskService = processEngineConfiguration.getTaskService(); + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult(); + + validTask(historicTaskInstance, task, dto.getOriginAssigner(), null); + + startAsync(processEngineConfiguration, task); + return null; + } + + private void 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(AsyncResetApproversUserTaskJobHandler.TYPE); + job.setTenantId(task.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomResetTaskApproversCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomResetTaskApproversCmd.java new file mode 100644 index 000000000..ea73cc903 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomResetTaskApproversCmd.java @@ -0,0 +1,154 @@ +package cn.axzo.workflow.core.engine.cmd; + +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.service.ExtAxHiTaskInstService; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +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.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.api.history.HistoricTaskInstance; +import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +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_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO; +import static cn.axzo.workflow.common.constant.BpmnConstants.RESET_TASK_ASSIGNER_SHOW_NUMBER; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ASSIGNEE_SKIP_FLAT; +import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.UPGRADED; +import static cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner.buildDummyAssigner; +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; + +/** + * 重置节点下的所有审批人 + * + * @author wangli + * @since 2025-06-24 17:17 + */ +@Slf4j +public class CustomResetTaskApproversCmd extends AbstractCommand implements Serializable { + + private final String originTaskId; + private final String advice; + private final List attachmentList; + private final BpmnTaskDelegateAssigner originTaskAssignee; + private final List targetTaskAssigneeList; + private final ExtAxHiTaskInstService extAxHiTaskInstService; + + public CustomResetTaskApproversCmd(String originTaskId, String advice, List attachmentList, BpmnTaskDelegateAssigner originTaskAssignee, List targetTaskAssigneeList, ExtAxHiTaskInstService extAxHiTaskInstService) { + this.originTaskId = originTaskId; + this.advice = advice; + this.attachmentList = attachmentList; + this.originTaskAssignee = originTaskAssignee; + this.targetTaskAssigneeList = targetTaskAssigneeList; + this.extAxHiTaskInstService = extAxHiTaskInstService; + } + + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("originTaskId", originTaskId); + params.put("advice", advice); + params.put("attachmentList", JSON.toJSONString(attachmentList)); + params.put("originTaskAssignee", originTaskAssignee); + params.put("targetTaskAssigneeList", JSON.toJSONString(targetTaskAssigneeList)); + return JSON.toJSONString(params); + } + + @Override + public Void executeInternal(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = + processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(originTaskId).singleResult(); + + TaskService taskService = processEngineConfiguration.getTaskService(); + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(originTaskId).singleResult(); + + validTask(historicTaskInstance, task, originTaskAssignee, null); + + resolveOriginTask(commandContext, taskService, task); + batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, + originTaskAssignee); + resetActivityApprovers(commandContext, task, targetTaskAssigneeList, taskService); + + return null; + } + + private void resetActivityApprovers(CommandContext commandContext, TaskEntity taskEntity, List taskDelegateAssigners, TaskService taskService) { + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + + // 提前查询正在待审批的任务, 用于最后移除 + List tasks = taskService.createTaskQuery().processInstanceId(taskEntity.getProcessInstanceId()).active().list(); + + // 这个节点下所有审批人快照 + String activityListSnapshot = + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(); + Map variables = new HashMap<>(); + variables.put(activityListSnapshot, taskDelegateAssigners); + variables.put(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + taskEntity.getTaskDefinitionKey(), taskDelegateAssigners.stream().map(BpmnTaskDelegateAssigner::buildAssigneeId).collect(Collectors.toList())); + runtimeService.setVariables(taskEntity.getProcessInstanceId(), variables); + log.info("正在进行提级任务:{},提级审批人列表:{}", taskEntity.getId(), JSONUtil.toJsonStr(taskDelegateAssigners)); + + // 将提级审批人加签 + taskDelegateAssigners.forEach(assigner -> CustomTaskHelper.addMultiTask(commandContext, taskEntity, assigner)); + + taskEntity.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskEntity.getId(), UPGRADED.getStatus()); + + CustomTaskHelper.deleteMultiTasks(commandContext, tasks); + // 移除引擎内部用于确定人的快照变量 +// runtimeService.removeVariables(taskEntity.getProcessInstanceId(), variables.keySet()); + } + + private void resolveOriginTask(CommandContext commandContext, TaskService taskService, TaskEntity task) { + BpmnTaskDelegateAssigner assigner = buildDummyAssigner("upgrade", TASK_ASSIGNEE_SKIP_FLAT, "dummyApprover"); + task.setAssignee(assigner.buildAssigneeId()); + task.setScopeType("UPGRADE"); + + Authentication.setAuthenticatedUserId(originTaskAssignee.buildAssigneeId()); + + // 构建评论内容 + StringBuilder message = new StringBuilder("提级给"); + int end = Math.min(targetTaskAssigneeList.size(), RESET_TASK_ASSIGNER_SHOW_NUMBER); + //加签人员数量显示指定个数 + for (int i = 0; i < end; i++) { + message.append(targetTaskAssigneeList.get(i).getAssignerName()); + if (i < end - 1) { + message.append("、"); + } + } + if (targetTaskAssigneeList.size() > end) { + message.append("等") + .append(targetTaskAssigneeList.size()) + .append("人"); + } + message.append("审批"); + addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, message.toString()); + addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice); + Authentication.setAuthenticatedUserId(null); + + taskService.saveTask(task); + } + +} 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..35eeacb5b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTermNodePausingAlertCmd.java @@ -0,0 +1,25 @@ +package cn.axzo.workflow.core.engine.cmd; + +import org.flowable.common.engine.impl.interceptor.CommandContext; + +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/CustomTransferUserTaskAsyncCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTransferUserTaskAsyncCmd.java new file mode 100644 index 000000000..56acb3a26 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomTransferUserTaskAsyncCmd.java @@ -0,0 +1,79 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; +import cn.axzo.workflow.core.engine.job.AsyncTransferUserTaskJobHandler; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +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.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.impl.persistence.entity.TaskEntity; + +import java.io.Serializable; + +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TRANSFER_TO_SELF; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; + +/** + * 异步转交任务的命令执行器 + */ +public class CustomTransferUserTaskAsyncCmd extends AbstractCommand implements Serializable { + + private static final long serialVersionUID = -1847844435303542933L; + + private final BpmnTaskTransferDTO dto; + + public CustomTransferUserTaskAsyncCmd(BpmnTaskTransferDTO dto) { + this.dto = dto; + } + + @Override + public String paramToJsonString() { + return JSON.toJSONString(dto); + } + + @Override + public Void execute(CommandContext commandContext) { + //不能转交给自己 + if (dto.getOriginAssigner().buildAssigneeId().equals(dto.getTargetAssigner().buildAssigneeId())) { + throw new WorkflowEngineException(TRANSFER_TO_SELF); + } + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoricTaskInstanceQuery taskQuery = processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery(); + HistoricTaskInstance historicTaskInstance = taskQuery.taskId(dto.getTaskId()).singleResult(); + TaskService taskService = processEngineConfiguration.getTaskService(); + Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).singleResult(); + validTask(historicTaskInstance, (TaskEntity) task, dto.getOriginAssigner(), null); + startAsync(processEngineConfiguration, task); + return null; + } + + private void 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(AsyncTransferUserTaskJobHandler.TYPE); + job.setTenantId(task.getTenantId()); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + } + +} 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 15594adae..480c051d6 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 @@ -1,11 +1,13 @@ package cn.axzo.workflow.core.engine.cmd; +import cn.axzo.workflow.common.enums.ProcessTaskEventEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import com.alibaba.fastjson.JSON; import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringUtils; 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; @@ -15,27 +17,32 @@ 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 org.springframework.util.CollectionUtils; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; -import java.util.Optional; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TRANSFER_TO_SELF; 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_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ASSIGNEE_SKIP_FLAT; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.TRANSFER_TO; +import static cn.axzo.workflow.common.constant.BpmnConstants.TRANSFER_TO_ADVICE; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.TRANSFER; import static cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner.buildDummyAssigner; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ASSIGNEE_HAS_BEEN_EXISTS; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addMultiTask; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment; -import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.deleteMultiTask; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.deleteMultiTasks; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.getDuplicatePendingTasks; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask; -import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTaskAssignerDuplicated; /** * 转交任务的命令实现 @@ -43,26 +50,44 @@ import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask * @author wangli * @since 2023/12/22 10:22 */ -public class CustomTransferUserTaskCmd implements Command, Serializable { +public class CustomTransferUserTaskCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = -8011156125774066571L; private final String originTaskId; private final BpmnTaskDelegateAssigner originTaskAssignee; private final String advice; private final List attachmentList; private final BpmnTaskDelegateAssigner targetTaskAssignee; + private final String additionalOpeDesc; public CustomTransferUserTaskCmd(String originTaskId, BpmnTaskDelegateAssigner originTaskAssignee, String advice, - List attachmentList, BpmnTaskDelegateAssigner targetTaskAssignee) { + List attachmentList, BpmnTaskDelegateAssigner targetTaskAssignee, + String additionalOpeDesc) { this.originTaskId = originTaskId; this.originTaskAssignee = originTaskAssignee; this.advice = advice; this.attachmentList = attachmentList; this.targetTaskAssignee = targetTaskAssignee; + this.additionalOpeDesc = additionalOpeDesc; } + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("originTaskId", originTaskId); + params.put("originTaskAssignee", originTaskAssignee); + params.put("advice", advice); + params.put("attachmentList", attachmentList); + params.put("targetTaskAssignee", targetTaskAssignee); + return JSON.toJSONString(params); + } @Override public Void execute(CommandContext commandContext) { + //不能转交给自己 + if (targetTaskAssignee.buildAssigneeId().equals(originTaskAssignee.buildAssigneeId())) { + throw new WorkflowEngineException(TRANSFER_TO_SELF); + } ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); HistoricTaskInstanceQuery taskQuery = @@ -70,59 +95,68 @@ public class CustomTransferUserTaskCmd implements Command, Serializable { 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); + validTask(historicTaskInstance, task, originTaskAssignee, null); - validTaskAssignerDuplicated(commandContext, (TaskEntity) 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); + List duplicatePendingTasks = getDuplicatePendingTasks(commandContext, task, Lists.newArrayList(targetTaskAssignee)); + //转交目标人不存在任务,再生成转交任务 + if (CollectionUtils.isEmpty(duplicatePendingTasks)) { + addMultiTask(commandContext, task, targetTaskAssignee); + } + task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), TRANSFER.getStatus()); + // 结束被转交任务 + deleteMultiTasks(commandContext, Collections.singletonList(task)); + task.setTransientVariable(TRANSFER_TO, targetTaskAssignee); + task.setTransientVariable(TRANSFER_TO_ADVICE, advice); + processEngineConfiguration.getListenerNotificationHelper().executeTaskListeners(task, ProcessTaskEventEnum.PROCESS_TASK_TRANSFER.getTag()); 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_OPERATION_DESC, "转交给" + targetTaskAssignee.getAssignerName() + (StringUtils.isNotBlank(additionalOpeDesc) ? additionalOpeDesc : "")); + addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice); Authentication.setAuthenticatedUserId(null); + + taskService.saveTask(task); } public void processAssignee(ProcessEngineConfigurationImpl processEngineConfiguration, Task task) { RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + @SuppressWarnings("unchecked") List originAssingeeList = runtimeService.getVariable(task.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), List.class); - Optional exists = originAssingeeList.stream() - .filter(i -> Objects.equals(i.buildAssigneeId(), targetTaskAssignee.buildAssigneeId())).findAny(); - if (exists.isPresent()) { - throw new WorkflowEngineException(ASSIGNEE_HAS_BEEN_EXISTS); - } - + List resultList = new ArrayList<>(); + boolean containsTargetAssignee = false; for (BpmnTaskDelegateAssigner assigner : originAssingeeList) { - if (Objects.equals(assigner.buildAssigneeId(), originTaskAssignee.buildAssigneeId())) { - originAssingeeList.remove(assigner); - break; + if (!Objects.equals(assigner.buildAssigneeId(), originTaskAssignee.buildAssigneeId())) { + resultList.add(assigner); + } + if (Objects.equals(assigner.buildAssigneeId(), targetTaskAssignee.buildAssigneeId())) { + containsTargetAssignee = true; } } - originAssingeeList.add(targetTaskAssignee); + if (!containsTargetAssignee) { + resultList.add(targetTaskAssignee); + } runtimeService.setVariable(task.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + task.getTaskDefinitionKey(), - originAssingeeList); + resultList); } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/GetFormInstanceAndPermissionCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/GetFormInstanceAndPermissionCmd.java new file mode 100644 index 000000000..c93079208 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/GetFormInstanceAndPermissionCmd.java @@ -0,0 +1,239 @@ +package cn.axzo.workflow.core.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.common.utils.FormHelper; +import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.service.BpmnProcessTaskForEsService; +import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; +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.history.HistoricProcessInstance; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.form.api.FormDefinition; +import org.flowable.form.api.FormEngineConfigurationApi; +import org.flowable.form.api.FormInstanceInfo; +import org.flowable.form.api.FormRepositoryService; +import org.flowable.form.model.SimpleFormModel; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_PARAM_ERROR; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; + +/** + * 获取指定审批实例的表单内容以及字段权限 + * + * @author wangli + * @since 2024-11-13 14:24 + */ +@Slf4j +public class GetFormInstanceAndPermissionCmd implements Command { + private final ExtAxBpmnFormRelationService bpmnFormRelationService; + private final BpmnProcessTaskForEsService bpmnProcessTaskForEsService; + private final BpmnTaskDelegateAssigner assigner; + private String processInstanceId; + private String taskId; + private Boolean showOriginDefaultValue = false; + + /** + * 全权通过实例查询一切 + * + * @param processInstanceId + */ + public GetFormInstanceAndPermissionCmd(ExtAxBpmnFormRelationService bpmnFormRelationService, + BpmnProcessTaskForEsService bpmnProcessTaskForEsService, + BpmnTaskDelegateAssigner assigner, + String processInstanceId) { + this.bpmnFormRelationService = bpmnFormRelationService; + this.bpmnProcessTaskForEsService = bpmnProcessTaskForEsService; + this.assigner = assigner; + this.processInstanceId = processInstanceId; + } + + /** + * 通过实例和任务查询 + * + * @param processInstanceId 可为空,则用 taskId 反查实例相关 + * @param taskId + */ + public GetFormInstanceAndPermissionCmd(ExtAxBpmnFormRelationService bpmnFormRelationService, + BpmnProcessTaskForEsService bpmnProcessTaskForEsService, + BpmnTaskDelegateAssigner assigner, + String processInstanceId, + String taskId, + Boolean showOriginDefaultValue) { + this.bpmnFormRelationService = bpmnFormRelationService; + this.bpmnProcessTaskForEsService = bpmnProcessTaskForEsService; + this.assigner = assigner; + this.processInstanceId = processInstanceId; + this.taskId = taskId; + this.showOriginDefaultValue = showOriginDefaultValue; + } + + @Override + public FormInstanceInfo execute(CommandContext commandContext) { + preCheckParam(); + + // 获取表单定义+字段内容 + FormInstanceInfo formInstanceInfo = getFormInstanceInfo(commandContext); + if (Objects.isNull(formInstanceInfo)) { + return null; + } + + // 获取字段权限 + List fieldPermission = getFieldPermission(commandContext); + + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoryService historyService = processEngineConfiguration.getHistoryService(); + HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .includeProcessVariables() + .singleResult(); + SimpleFormModel formModel = (SimpleFormModel) formInstanceInfo.getFormModel(); + Map processVariables = instance.getProcessVariables(); + + FormHelper.populateFormModel(formModel, fieldPermission, processVariables, showOriginDefaultValue); + return formInstanceInfo; + } + + private List getFieldPermission(CommandContext commandContext) { + List logs = bpmnProcessTaskForEsService.queryProcessLogByProcessInstanceId(processInstanceId); + + Map activityMap = logs.stream() + .collect(Collectors.toMap(ExtAxProcessLog::getActivityId, Function.identity(), (s, t) -> s)); + + // 待审批的节点 + List processingLogs = logs.stream() + .filter(e -> (Objects.equals(PROCESSING.getStatus(), e.getStatus()) && Objects.isNull(e.getEndTime()))) + .collect(Collectors.toList()); + + List list = binaryMerge(processingLogs, activityMap); + if (!CollectionUtils.isEmpty(list)) { + return list; + } + + // 已结束的审批节点含开始节点 + List finishLogs = logs.stream() + .filter(e -> !Objects.equals(PROCESSING.getStatus(), e.getStatus()) && Objects.nonNull(e.getEndTime())) + .collect(Collectors.toList()); + + list = binaryMerge(finishLogs, activityMap); + if (!CollectionUtils.isEmpty(list)) { + // 这里都是非审批的,统一将所有可见,必填的节点转成只读. [完全忽略隐藏属性] + list.forEach(FormPermissionMetaInfo::toReadonly); + return list; + } + + // 默认全字段都是隐藏 + // 假设通过分享功能,分享了审批,那么被分享人不应该看到表单 + ExtAxProcessLog starterLog = logs.stream().filter(e -> Objects.equals(NODE_STARTER.getType(), e.getActivityId())) + .findFirst().orElse(new ExtAxProcessLog()); + ListUtils.emptyIfNull(starterLog.getFormFieldPermissionConf()).forEach(perm -> { + // 防预编程, 强制设置为 hidden + perm.setRequired(false); + perm.setEditable(false); + perm.setReadonly(false); + perm.setHidden(true); + }); + return CollectionUtils.isEmpty(starterLog.getFormFieldPermissionConf()) ? new ArrayList<>() : starterLog.getFormFieldPermissionConf(); + } + + @SuppressWarnings("unchecked") + private List binaryMerge(List logs, Map activityMap) { + List> permissions = new ArrayList<>(); + collectActivityIds(logs).forEach(id -> { + ExtAxProcessLog log = activityMap.getOrDefault(id, null); + if (Objects.nonNull(log)) { + permissions.add(log.getFormFieldPermissionConf()); + } + }); + return BpmnMetaParserHelper.mergeAllPermission(permissions.toArray(new List[0])); + } + + private List collectActivityIds(List logs) { + List activityIds = new ArrayList<>(); + if (Objects.isNull(assigner)) { + return activityIds; + } + if (!CollectionUtils.isEmpty(logs)) { + Map> nodeUserMap = rebuildToNodeUserMap(logs); + + nodeUserMap.forEach((activityId, assigners) -> { + assigners.forEach(u -> { + if (Objects.equals(u.getPersonId(), assigner.getPersonId())) { + activityIds.add(activityId); + } + }); + }); + } + return activityIds; + } + + private static Map> rebuildToNodeUserMap(List logs) { + Map> assignerMap = new HashMap<>(); + logs.forEach(e -> { + if (assignerMap.containsKey(e.getActivityId())) { + if (!CollectionUtils.isEmpty(e.getAssigneeFull())) { + assignerMap.get(e.getActivityId()).addAll(e.getAssigneeFull()); + } + } else { + assignerMap.put(e.getActivityId(), new ArrayList<>(ListUtils.emptyIfNull(e.getAssigneeFull()))); + } + }); + return assignerMap; + } + + private FormInstanceInfo getFormInstanceInfo(CommandContext commandContext) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoryService historyService = processEngineConfiguration.getHistoryService(); + + HistoricProcessInstance instance; + if (StringUtils.hasText(processInstanceId)) { + instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + } else { + HistoricTaskInstance task = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult(); + if (Objects.isNull(task)) { + throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS); + } + instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); + } + if (Objects.isNull(instance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId); + } + processInstanceId = instance.getId(); + ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(instance.getProcessDefinitionId()); + if (Objects.isNull(relation)) { + // 没有配置表单 + return null; + } + + FormEngineConfigurationApi formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(commandContext); + FormRepositoryService formRepositoryService = formEngineConfiguration.getFormRepositoryService(); + FormDefinition formDefinition = formRepositoryService.createFormDefinitionQuery().deploymentId(relation.getFormDeploymentId()).singleResult(); + + // 获取表单定义+字段内容 + return commandContext.getCommandExecutor().execute(new CustomGetFormInstanceModelCmd(null, null, formDefinition.getId(), taskId, processInstanceId, instance.getTenantId(), null, false)); + } + + private void preCheckParam() { + if (!StringUtils.hasText(processInstanceId) && !StringUtils.hasText(taskId)) { + throw new WorkflowEngineException(FORM_PARAM_ERROR); + } + } +} 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 2860ce20a..ab58d0007 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 @@ -1,14 +1,23 @@ package cn.axzo.workflow.core.engine.cmd.helper; +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.BpmnTaskDelegateAssigner; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO; +import cn.axzo.workflow.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; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.Process; import org.flowable.common.engine.impl.cfg.IdGenerator; import org.flowable.common.engine.impl.identity.Authentication; 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; @@ -17,19 +26,22 @@ import org.flowable.engine.impl.persistence.entity.AttachmentEntity; import org.flowable.engine.impl.persistence.entity.CommentEntity; 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.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; import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity; import org.flowable.task.service.impl.persistence.entity.TaskEntity; import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl; +import org.flowable.variable.api.history.HistoricVariableInstance; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -39,15 +51,22 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; +import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVAL_ASSIGNER_LIMIT_NUMBER; import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE; 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.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; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ASSIGNEE_HAS_BEEN_EXISTS; -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.common.code.BpmnTaskRespCode.ASSIGNEE_HAS_BEEN_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ASSIGNER_NUMBER_EXCEEDS_NUMBER_LIMIT; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_TYPE_MISMATCH; import static org.flowable.task.api.Task.DEFAULT_PRIORITY; /** @@ -56,6 +75,7 @@ import static org.flowable.task.api.Task.DEFAULT_PRIORITY; * @author wangli * @since 2023/12/22 10:59 */ +@Slf4j public class CustomTaskHelper { public static void addMultiTask(CommandContext commandContext, TaskEntity originTask, @@ -75,20 +95,32 @@ public class CustomTaskHelper { setParentTaskId(originTask, subExecution); } - public static void deleteMultiTask(CommandContext commandContext, TaskEntity originTask) { - if (Objects.isNull(originTask)) { + public static void deleteMultiTask(CommandContext commandContext, Task originTask) { + if (originTask == null) { return; } - ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(commandContext); + deleteMultiTasks(commandContext, Collections.singletonList(originTask)); + } + + public static void deleteMultiTasks(CommandContext commandContext, List originTasks) { + if (CollectionUtils.isEmpty(originTasks)) { + return; + } + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); - runtimeService.deleteMultiInstanceExecution(originTask.getExecutionId(), false); + originTasks.stream() + .filter(Objects::nonNull) + .forEach(originTask -> runtimeService.deleteMultiInstanceExecution(originTask.getExecutionId(), false)); } private static void setParentTaskId(TaskEntity originTask, Execution subExecution) { - CommandContextUtil.getEntityCache().findInCache(TaskEntity.class).stream().filter(i -> Objects.equals(i.getExecutionId(), subExecution.getId())).findAny().ifPresent(i -> i.setParentTaskId(originTask.getId())); + CommandContextUtil.getEntityCache().findInCache(TaskEntity.class).stream() + .filter(i -> Objects.equals(i.getExecutionId(), subExecution.getId())) + .findAny().ifPresent(i -> i.setParentTaskId(originTask.getId())); - CommandContextUtil.getEntityCache().findInCache(HistoricTaskInstanceEntity.class).stream().filter(i -> Objects.equals(i.getExecutionId(), subExecution.getId())).findAny().ifPresent(i -> i.setParentTaskId(originTask.getId())); + CommandContextUtil.getEntityCache().findInCache(HistoricTaskInstanceEntity.class).stream() + .filter(i -> Objects.equals(i.getExecutionId(), subExecution.getId())) + .findAny().ifPresent(i -> i.setParentTaskId(originTask.getId())); } /** @@ -99,13 +131,14 @@ public class CustomTaskHelper { * @param originTaskAssigner */ public static void validTask(HistoricTaskInstance historicTaskInstance, TaskEntity taskEntity, - BpmnTaskDelegateAssigner originTaskAssigner) { - if (Objects.isNull(taskEntity)) { - throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS); + BpmnTaskDelegateAssigner originTaskAssigner, List nodeTypes) { + if (Objects.nonNull(historicTaskInstance) && + (Objects.nonNull(historicTaskInstance.getEndTime()) || Objects.isNull(taskEntity))) { + throw new WorkflowEngineException(TASK_HAS_BEEN_COMPLETE); } - if (Objects.nonNull(historicTaskInstance) && Objects.nonNull(historicTaskInstance.getEndTime())) { - throw new WorkflowEngineException(TASK_HAS_BEEN_COMPLETE); + if (Objects.isNull(historicTaskInstance) && Objects.isNull(taskEntity)) { + throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS); } // FIXME by wangli: currentAssignee 去掉 OUID 前的代码 // if (Objects.nonNull(originTaskAssigner) && @@ -125,6 +158,35 @@ public class CustomTaskHelper { throw new WorkflowEngineException(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF); } } + //校验类型,指定类型和当前任务类型是否匹配 + if (!CollectionUtils.isEmpty(nodeTypes)) { + Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId()); + FlowElement currentFlowElement = process.getFlowElement(taskEntity.getTaskDefinitionKey()); + BpmnFlowNodeType nodeType = BpmnMetaParserHelper.getNodeType(currentFlowElement).orElse(NODE_EMPTY); + //不包含对应的任务 + if (!nodeTypes.contains(nodeType)) { +// log.warn(TASK_TYPE_MISMATCH.getMessage(), 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(","))); + } + } + } + + + public static List getDuplicatePendingTasks(CommandContext commandContext, + TaskEntity taskEntity, + List targetAssigneeList) { + if (CollectionUtils.isEmpty(targetAssigneeList)) { + return Collections.emptyList(); + } + ProcessEngineConfiguration processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + List assigneeStrList = targetAssigneeList.stream() + .flatMap(ta -> Arrays.stream(new String[]{ta.buildAssigneeId(), ta.buildAssigneeId_1_2_1()})) + .collect(Collectors.toList()); + return processEngineConfiguration.getTaskService() + .createTaskQuery() + .processInstanceId(taskEntity.getProcessInstanceId()) + .taskAssigneeIds(assigneeStrList) + .list(); } /** @@ -162,15 +224,35 @@ public class CustomTaskHelper { return taskAssignerListSnapshot; } + /** + * 校验人员数量是否超过限制 + * + * @param runtimeService + * @param taskEntity + * @param targetAssigneeList + * @return + */ + public static List validTaskAssignerCount(RuntimeService runtimeService, + TaskEntity taskEntity, + List targetAssigneeList) { + // 这个节点下所有审批人快照 + String activityListSnapshot = INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(); + List taskAssignerListSnapshot = runtimeService.getVariable(taskEntity.getProcessInstanceId(), activityListSnapshot, List.class); + if (taskAssignerListSnapshot.size() + targetAssigneeList.size() > APPROVAL_ASSIGNER_LIMIT_NUMBER) { + throw new WorkflowEngineException(ASSIGNER_NUMBER_EXCEEDS_NUMBER_LIMIT); + } + return taskAssignerListSnapshot; + } + /** * 保存附件 * * @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; @@ -180,21 +262,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); @@ -216,8 +307,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) { @@ -256,14 +346,14 @@ public class CustomTaskHelper { * @param nodeName 节点自定义显示名称 * @param taskDefinitionKey 节点自定标识 * @param advice 审批意见 - * @param assigner 审批人 + * @param assigner 审批人,可为空 * @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(); @@ -283,34 +373,49 @@ public class CustomTaskHelper { task.setTaskDefinitionKey(taskDefinitionKey); task.setPriority(DEFAULT_PRIORITY); task.setCreateTime(new Date()); - // 创建临时节点 - taskService.saveTask(task); - CommandContextUtil.getEntityCache().findInCache(TaskEntity.class).stream() - .filter(i -> Objects.equals(i.getId(), task.getId())).findAny() - .ifPresent(i -> i.setAssignee(assigner.buildAssigneeId())); + if (Objects.nonNull(assigner)) { + CommandContextUtil.getEntityCache().findInCache(TaskEntity.class).stream() + .filter(i -> Objects.equals(i.getId(), task.getId())).findAny() + .ifPresent(i -> i.setAssignee(assigner.buildAssigneeId())); - CommandContextUtil.getEntityCache().findInCache(HistoricTaskInstanceEntity.class).stream() - .filter(i -> Objects.equals(i.getId(), task.getId())).findAny() - .ifPresent(i -> i.setAssignee(assigner.buildAssigneeId())); + CommandContextUtil.getEntityCache().findInCache(HistoricTaskInstanceEntity.class).stream() + .filter(i -> Objects.equals(i.getId(), task.getId())).findAny() + .ifPresent(i -> i.setAssignee(assigner.buildAssigneeId())); + } // 添加审批意见 - addAdvice(commandContext, task, advice, assigner.buildAssigneeId()); + 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); - // 设置快照信息 - task.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + task.getId(), assigner); + // 保存任务 + taskService.saveTask(task); - // 完成临时节点 - taskService.complete(task.getId()); + if (Objects.nonNull(assigner)) { + // 设置快照信息 + task.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + task.getId(), assigner.toJson()); + } + + // 完成临时节点, 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); @@ -331,7 +436,7 @@ public class CustomTaskHelper { entity.setTaskDefinitionKey(taskDefinitionKey); entity.setTaskId(taskId); entity.setStatus(StringUtils.hasLength(status) ? status : PROCESSING.getStatus()); - entity.setAssignee(assignee.buildAssigneeId()); + entity.setAssignee(Objects.nonNull(assignee) ? assignee.buildAssigneeId() : null); extAxHiTaskInstService.save(entity); } } @@ -347,4 +452,89 @@ public class CustomTaskHelper { .values()); } + /** + * 获取指定数量元素 + * + * @param assigners 原始列表列表 + * @return 截取后的列表 + */ + public static List getLimitedElementList(List assigners, Integer limitNumber) { + if (limitNumber == null || limitNumber <= 0) { + throw new IllegalArgumentException("limit number must be greater than 0"); + } + if (CollectionUtils.isEmpty(assigners) || assigners.size() <= limitNumber) { + return assigners; + } + return new ArrayList<>(assigners.subList(0, limitNumber)); + } + + public static List getHistoryOperationUsers(CommandContext commandContext, String processInstanceId, + BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter, + String serviceVersion) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + HistoryService historyService = processEngineConfiguration.getHistoryService(); + + List result = new ArrayList<>(); + List taskInstances = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(processInstanceId) + .orderByHistoricTaskInstanceStartTime() + .desc().list(); + List vos = historicTaskInstanceConverter.toVosSkipSystemOperation(taskInstances, + serviceVersion); + + 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)); + vos.forEach(vo -> { + HistoricVariableInstance assginerSnapshot = + 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); + } + if (Objects.nonNull(assginerSnapshot)) { + BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(assginerSnapshot.getValue()); + if (Objects.nonNull(assigner) && !Objects.equals(assigner.buildAssigneeId(), NO_ASSIGNEE)) { + result.add(assigner); + } + } + }); + return result; + } + + public static List getAllCarbonUsers(CommandContext commandContext, String processInstanceId, String processDefinitionId) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + Process process = ProcessDefinitionUtil.getProcess(processDefinitionId); + List taskDefinitionKeys = process.getFlowElements().stream() + .filter(i -> Objects.equals(BpmnMetaParserHelper.getNodeType(i).orElse(null), BpmnFlowNodeType.NODE_CARBON_COPY)) + .map(FlowElement::getId) + .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(taskDefinitionKeys)) { + return Collections.emptyList(); + } + + List variableInstances = processEngineConfiguration.getHistoryService().createHistoricVariableInstanceQuery() + .variableNameLike(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT) + .processInstanceId(processInstanceId) + .list(); + if (CollectionUtils.isEmpty(variableInstances)) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + taskDefinitionKeys.forEach(j -> { + variableInstances.stream() + .filter(i -> Objects.equals(i.getVariableName(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + j)) + .findFirst().ifPresent(i -> { + result.addAll((List) i.getValue()); + }); + }); + return result; + } + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/FormFieldClone.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/FormFieldClone.java new file mode 100644 index 000000000..4c4e98fb8 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/helper/FormFieldClone.java @@ -0,0 +1,42 @@ +package cn.axzo.workflow.core.engine.cmd.helper; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.form.model.FormField; +import org.flowable.form.model.LayoutDefinition; +import org.springframework.util.CollectionUtils; + +import java.util.HashMap; +import java.util.Objects; + +/** + * FormField Clone + * + * @author wangli + * @since 2025-01-24 00:11 + */ +@Slf4j +public class FormFieldClone { + + public static FormField clone(FormField formField) { + FormField clone = new FormField(); + clone.setId(formField.getId()); + clone.setName(formField.getName()); + clone.setType(formField.getType()); + clone.setValue(formField.getValue()); + clone.setRequired(formField.isRequired()); + clone.setReadOnly(formField.isReadOnly()); + clone.setOverrideId(formField.isOverrideId()); + clone.setPlaceholder(formField.getPlaceholder()); + + LayoutDefinition cloneLayout = new LayoutDefinition(); + if (Objects.nonNull(formField.getLayout())) { + cloneLayout.setRow(formField.getLayout().getRow()); + clone.setLayout(cloneLayout); + } + + if (!CollectionUtils.isEmpty(formField.getParams())) { + clone.setParams(new HashMap<>(formField.getParams())); + } + return clone; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/BizCallbackEvent.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/BizCallbackEvent.java new file mode 100644 index 000000000..19ca255bd --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/BizCallbackEvent.java @@ -0,0 +1,27 @@ +package cn.axzo.workflow.core.engine.event; + +import org.flowable.common.engine.api.delegate.event.FlowableEvent; + +import java.util.Map; + +/** + * 业务节点回调事件 + * + * @author wangli + * @since 2024-08-20 17:24 + */ +public interface BizCallbackEvent extends FlowableEvent { + String getActivityId(); + + String getActivityName(); + + String getProcessInstanceId(); + + String getProcessDefinitionId(); + + String getBusinessKey(); + + String getExecutionId(); + + Map getVariables(); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/BizCallbackEventImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/BizCallbackEventImpl.java new file mode 100644 index 000000000..9b7258ccc --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/BizCallbackEventImpl.java @@ -0,0 +1,108 @@ +package cn.axzo.workflow.core.engine.event; + +import org.apache.commons.lang3.StringUtils; +import org.flowable.common.engine.api.FlowableIllegalArgumentException; +import org.flowable.common.engine.api.delegate.event.FlowableEventType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 业务节点回调事件 + * + * @author wangli + * @since 2024-08-20 17:25 + */ +public class BizCallbackEventImpl implements BizCallbackEvent { + private final BizCallbackEventType type; + private final String activityId; + private final String activityName; + private final String processInstanceId; + private final String processDefinitionId; + private final String businessKey; + private final String executionId; + private final Map variables; + + public BizCallbackEventImpl(BizCallbackEventType type, String activityId, String activityName, String processInstanceId, String processDefinitionId, String businessKey, String executionId, Map variables) { + this.type = type; + this.activityId = activityId; + this.activityName = activityName; + this.processInstanceId = processInstanceId; + this.processDefinitionId = processDefinitionId; + this.businessKey = businessKey; + this.executionId = executionId; + this.variables = variables; + } + + /** + * @return type of event. + */ + @Override + public FlowableEventType getType() { + return BizCallbackEventType.CALLBACK; + } + + @Override + public String getActivityId() { + return activityId; + } + + @Override + public String getActivityName() { + return activityName; + } + + @Override + public String getProcessInstanceId() { + return processInstanceId; + } + + @Override + public String getProcessDefinitionId() { + return processDefinitionId; + } + + @Override + public String getBusinessKey() { + return businessKey; + } + + @Override + public String getExecutionId() { + return executionId; + } + + @Override + public Map getVariables() { + return variables; + } + + public enum BizCallbackEventType implements FlowableEventType { + CALLBACK, + ; + public static final BizCallbackEventType[] EMPTY_ARRAY = new BizCallbackEventType[]{}; + + public static BizCallbackEventType[] getTypesFromString(String string) { + List result = new ArrayList<>(); + if (string != null && !string.isEmpty()) { + String[] split = StringUtils.split(string, ","); + for (String typeName : split) { + boolean found = false; + for (BizCallbackEventType type : values()) { + if (typeName.toUpperCase().equals(type.name())) { + result.add(type); + found = true; + break; + } + } + if (!found) { + throw new FlowableIllegalArgumentException("Invalid event-type: " + typeName); + } + } + } + + return result.toArray(EMPTY_ARRAY); + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/DocChangeEvent.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/DocChangeEvent.java new file mode 100644 index 000000000..af78c6866 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/DocChangeEvent.java @@ -0,0 +1,43 @@ +package cn.axzo.workflow.core.engine.event; + +import cn.axzo.workflow.common.model.dto.BizDocDTO; + +import java.util.List; + +/** + * 模型关联的文档变更事件对象 + * + * @author wangli + * @since 2025-04-07 16:51 + */ +public interface DocChangeEvent { + + /** + * 业务 ID + * + * @return + */ + String getKey(); + + /** + * 工作台 ID + * + * @return + */ + Long getWorkspaceId(); + + /** + * 修改后的文档 + * + * @return + */ + List getNewSettings(); + + /** + * 修改前的文档 + * + * @return + */ + List getOldSettings(); + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/DocChangeEventImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/DocChangeEventImpl.java new file mode 100644 index 000000000..bbec824ef --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/DocChangeEventImpl.java @@ -0,0 +1,68 @@ +package cn.axzo.workflow.core.engine.event; + +import cn.axzo.workflow.common.model.dto.BizDocDTO; +import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc; +import org.springframework.context.ApplicationEvent; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 模型关联的文档变更事件对象 + * + * @author wangli + * @since 2025-04-07 16:51 + */ +public class DocChangeEventImpl extends ApplicationEvent implements DocChangeEvent { + + private final String key; + + private final Long workspaceId; + + private final List newSettings; + + private final List oldSettings; + + public DocChangeEventImpl(String key, String workspaceId, List newSettings, List oldSettings) { + super(key); + this.key = key; + this.workspaceId = StringUtils.hasText(workspaceId) ? Long.valueOf(workspaceId) : null; + this.newSettings = convert(newSettings); + this.oldSettings = convert(oldSettings); + } + + private List convert(List docs) { + if (CollectionUtils.isEmpty(docs)) { + return Collections.emptyList(); + } + return docs.stream().map(i -> BizDocDTO.builder() + .archiveName(i.getTemplateName()) + .archiveCode(i.getTag()) + .enabled(i.getStatus()) + .build()).collect(Collectors.toList()); + } + + @Override + public String getKey() { + return key; + } + + @Override + public Long getWorkspaceId() { + return workspaceId; + } + + @Override + public List getNewSettings() { + return newSettings; + } + + @Override + public List getOldSettings() { + return oldSettings; + } + +} \ No newline at end of file diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/ExtTaskInstEvent.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/ExtTaskInstEvent.java index dcb420a30..ee9713065 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/ExtTaskInstEvent.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/ExtTaskInstEvent.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.core.engine.event; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import org.flowable.common.engine.api.delegate.event.FlowableEvent; +import org.flowable.common.engine.api.delegate.event.FlowableEventType; /** * 接收任务的扩展任务记录表创建的事件 @@ -10,6 +11,8 @@ import org.flowable.common.engine.api.delegate.event.FlowableEvent; * @since 2024/2/5 18:26 */ public interface ExtTaskInstEvent extends FlowableEvent { + @Override + FlowableEventType getType(); String getProcessInstanceId(); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/ExtTaskInstUpdateEvent.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/ExtTaskInstUpdateEvent.java index 1756a0e75..1f71a42ff 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/ExtTaskInstUpdateEvent.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/ExtTaskInstUpdateEvent.java @@ -1,6 +1,7 @@ package cn.axzo.workflow.core.engine.event; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import lombok.Data; import org.flowable.common.engine.api.delegate.event.FlowableEventType; import static cn.axzo.workflow.core.engine.event.ReceiveTaskEventType.UPDATE; @@ -11,6 +12,7 @@ import static cn.axzo.workflow.core.engine.event.ReceiveTaskEventType.UPDATE; * @author wangli * @since 27/03/2024 15:02 */ +@Data public class ExtTaskInstUpdateEvent implements ExtTaskInstEvent { private final String processInstanceId; private final String activityId; @@ -35,31 +37,6 @@ public class ExtTaskInstUpdateEvent implements ExtTaskInstEvent { this.resultEnum = resultEnum; } - @Override - public String getProcessInstanceId() { - return processInstanceId; - } - - @Override - public String getActivityId() { - return activityId; - } - - @Override - public String getTaskId() { - return taskId; - } - - @Override - public String getAssignee() { - return assignee; - } - - @Override - public BpmnProcessInstanceResultEnum getResultEnum() { - return resultEnum; - } - @Override public FlowableEventType getType() { return UPDATE; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEvent.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEvent.java index 03abf8d3e..2faaf1362 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEvent.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEvent.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.event; +import cn.axzo.workflow.common.model.request.BpmnApproveConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import org.flowable.common.engine.api.delegate.event.FlowableEvent; @@ -18,10 +19,24 @@ public interface MessagePushEvent extends FlowableEvent { BpmnNoticeConf getNoticeConfig(); + BpmnApproveConf getProcessApproveConfig(); + + Boolean getActivitySignature(); + String getProcessInstanceId(); + String getProcessDefinitionId(); + + String getProcessDefinitionKey(); + + String getCurrentTaskDefinitionKey(); + String getTenantId(); String getTaskId(); + String getImTemplateCode(); + + String getTerminalType(); + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventBuilder.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventBuilder.java index 5af3d70cf..168032593 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventBuilder.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventBuilder.java @@ -1,17 +1,21 @@ package cn.axzo.workflow.core.engine.event; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.BpmnApproveConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import java.util.List; -import static cn.axzo.workflow.core.common.code.OtherRespCode.MES_PUSH_OBJECT_BUILD_ERROR; +import static cn.axzo.workflow.common.code.OtherRespCode.MESSAGE_PUSH_EVENT_BUILD_ERROR; +import static cn.axzo.workflow.common.code.OtherRespCode.MES_PUSH_OBJECT_BUILD_ERROR; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.CARBON_COPY; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.CARBON_COPY_COMPLETE; +import static cn.axzo.workflow.core.engine.event.MessagePushEventType.IM; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.NOTICE; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.PENDING_COMPLETE; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.PENDING_PUSH; +import static cn.axzo.workflow.core.engine.event.MessagePushEventType.PENDING_ROLLBACK; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.SMS; /** @@ -23,21 +27,28 @@ import static cn.axzo.workflow.core.engine.event.MessagePushEventType.SMS; public class MessagePushEventBuilder { public static MessagePushEventImpl createEvent(MessagePushEventType type, List assigners, - BpmnNoticeConf noticeConf, String processInstanceId, - String tenantId, String taskId) { + BpmnNoticeConf noticeConf, String processInstanceId, String processDefinitionKey, + String tenantId, String taskId, Boolean activitySignature) { + return createEvent(type, assigners, noticeConf, processInstanceId, processDefinitionKey, tenantId, taskId, activitySignature, null, null); + } + public static MessagePushEventImpl createEvent(MessagePushEventType type, List assigners, + BpmnNoticeConf noticeConf, String processInstanceId, String processDefinitionKey, + String tenantId, String taskId, Boolean activitySignature, String imTemplateCode, String terminalType) { switch (type) { case NOTICE: - return createNoticeEvent(assigners, noticeConf, processInstanceId, tenantId, taskId); + return createNoticeEvent(assigners, noticeConf, processInstanceId, processDefinitionKey, tenantId, taskId, activitySignature); case PENDING_PUSH: - return createPendingPushEvent(assigners, noticeConf, processInstanceId, tenantId, taskId); + throw new WorkflowEngineException(MESSAGE_PUSH_EVENT_BUILD_ERROR); case PENDING_COMPLETE: - return createPendingCompleteEvent(assigners, noticeConf, processInstanceId, tenantId, taskId); + return createPendingCompleteEvent(assigners, noticeConf, processInstanceId, processDefinitionKey, tenantId, taskId, activitySignature); case CARBON_COPY: - return createCarbonCopyEvent(assigners, noticeConf, processInstanceId, tenantId); + return createCarbonCopyEvent(assigners, noticeConf, processInstanceId, processDefinitionKey, tenantId, taskId, activitySignature); case CARBON_COPY_COMPLETE: - return createCarbonCopyCompleteEvent(assigners, noticeConf, processInstanceId, tenantId); + return createCarbonCopyCompleteEvent(assigners, noticeConf, processInstanceId, processDefinitionKey, tenantId, activitySignature); case SMS: - return createSmsEvent(assigners, noticeConf, processInstanceId, tenantId, taskId); + return createSmsEvent(assigners, noticeConf, processInstanceId, processDefinitionKey, tenantId, taskId, activitySignature); + case IM: + return createImEvent(assigners, imTemplateCode, processInstanceId, terminalType); default: throw new WorkflowEngineException(MES_PUSH_OBJECT_BUILD_ERROR); } @@ -45,50 +56,80 @@ public class MessagePushEventBuilder { public static MessagePushEventImpl createNoticeEvent(List assigners, BpmnNoticeConf noticeConf - , String processInstanceId, String tenantId, String taskId) { + , String processInstanceId, String processDefinitionKey, String tenantId, String taskId, Boolean activitySignature) { MessagePushEventImpl newEvent = new MessagePushEventImpl(NOTICE, assigners, noticeConf, processInstanceId, - tenantId, taskId); + processDefinitionKey, tenantId, taskId); + newEvent.setActivitySignature(activitySignature); return newEvent; } public static MessagePushEventImpl createPendingPushEvent(List assigners, - BpmnNoticeConf noticeConf, String processInstanceId, - String tenantId, String taskId) { - MessagePushEventImpl newEvent = new MessagePushEventImpl(PENDING_PUSH, assigners, noticeConf, processInstanceId, - tenantId, taskId); + BpmnNoticeConf noticeConf, + BpmnApproveConf processApproveConf, + String processInstanceId, + String processDefinitionId, + String processDefinitionKey, + String currentTaskDefinitionKey, + String tenantId, String taskId, + Boolean activitySignature) { + MessagePushEventImpl newEvent = new MessagePushEventImpl(PENDING_PUSH, assigners, noticeConf, processApproveConf, + processInstanceId, processDefinitionId, processDefinitionKey, currentTaskDefinitionKey, tenantId, taskId); + newEvent.setActivitySignature(activitySignature); return newEvent; } public static MessagePushEventImpl createPendingCompleteEvent(List assigners, BpmnNoticeConf noticeConf, String processInstanceId, - String tenantId, String taskId) { + String processDefinitionKey, + String tenantId, String taskId, Boolean activitySignature) { MessagePushEventImpl newEvent = new MessagePushEventImpl(PENDING_COMPLETE, assigners, noticeConf, - processInstanceId, - tenantId, taskId); + processInstanceId, processDefinitionKey, tenantId, taskId); + newEvent.setActivitySignature(activitySignature); return newEvent; } + public static MessagePushEvent createPendingRollbackEvent(String processInstanceId, String processDefinitionKey, + String tenantId, String taskId, BpmnNoticeConf noticeConf) { + return new MessagePushEventImpl(PENDING_ROLLBACK, null, noticeConf, processInstanceId, processDefinitionKey, tenantId, taskId); + } + public static MessagePushEventImpl createCarbonCopyEvent(List assigners, BpmnNoticeConf noticeConf, String processInstanceId, - String tenantId) { + String processDefinitionKey, + String tenantId, + String taskId, + Boolean activitySignature) { MessagePushEventImpl newEvent = new MessagePushEventImpl(CARBON_COPY, assigners, noticeConf, - processInstanceId, tenantId, null); + processInstanceId, processDefinitionKey, tenantId, taskId); + newEvent.setActivitySignature(activitySignature); return newEvent; } public static MessagePushEventImpl createCarbonCopyCompleteEvent(List assigners, BpmnNoticeConf noticeConf, String processInstanceId, - String tenantId) { + String processDefinitionKey, + String tenantId, Boolean activitySignature) { MessagePushEventImpl newEvent = new MessagePushEventImpl(CARBON_COPY_COMPLETE, assigners, noticeConf, - processInstanceId, tenantId, null); + processInstanceId, processDefinitionKey, tenantId, null); + newEvent.setActivitySignature(activitySignature); return newEvent; } public static MessagePushEventImpl createSmsEvent(List assigners, BpmnNoticeConf noticeConf, - String processInstanceId, String tenantId, String taskId) { + String processInstanceId, + String processDefinitionKey, + String tenantId, String taskId, Boolean activitySignature) { MessagePushEventImpl newEvent = new MessagePushEventImpl(SMS, assigners, noticeConf, processInstanceId, - tenantId, taskId); + processDefinitionKey, tenantId, taskId); + newEvent.setActivitySignature(activitySignature); return newEvent; } + + public static MessagePushEventImpl createImEvent(List assigners, + String imTemplateCode, + String processInstanceId, + String terminalType) { + return new MessagePushEventImpl(IM, assigners, imTemplateCode, processInstanceId, terminalType); + } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventImpl.java index 28dabc436..6870fd908 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventImpl.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.event; +import cn.axzo.workflow.common.model.request.BpmnApproveConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import org.flowable.common.engine.api.FlowableIllegalArgumentException; @@ -16,11 +17,21 @@ import java.util.List; public class MessagePushEventImpl implements MessagePushEvent { private FlowableEventType type; + /** + * 只有抄送功能,再使用该属性时,传入的是多人;其他情况都是单人 + */ private List assigners; private BpmnNoticeConf noticeConfig; + private BpmnApproveConf processApproveConfig; + private Boolean activitySignature; private String processInstanceId; + private String processDefinitionId; + private String processDefinitionKey; + private String currentTaskDefinitionKey; private String tenantId; private String taskId; + private String imTemplateCode; + private String terminalType; public MessagePushEventImpl(FlowableEventType type) { if (type == null) { @@ -35,18 +46,57 @@ public class MessagePushEventImpl implements MessagePushEvent { * 该对象的消费场景会对 taskId 做条件判断 * * @param type - * @param assigner + * @param assigners * @param noticeConfig * @param processInstanceId * @param tenantId * @param taskId */ - public MessagePushEventImpl(FlowableEventType type, List assigners, - BpmnNoticeConf noticeConfig, String processInstanceId, String tenantId, String taskId) { + public MessagePushEventImpl(FlowableEventType type, List assigners, BpmnNoticeConf noticeConfig, + String processInstanceId, String processDefinitionKey, String tenantId, String taskId) { this.type = type; this.assigners = assigners; this.noticeConfig = noticeConfig; this.processInstanceId = processInstanceId; + this.processDefinitionKey = processDefinitionKey; + this.tenantId = tenantId; + this.taskId = taskId; + } + + public MessagePushEventImpl(FlowableEventType type, List assigners, String imTemplateCode, + String processInstanceId, String terminalType) { + this(type, assigners, null, processInstanceId, null, null, null); + this.imTemplateCode = imTemplateCode; + this.terminalType = terminalType; + } + + /** + * 使用该对象时需遵守,如果是 task 级的,一定要传 taskId, 其他参数则都是必传 + *

+ * 该对象的消费场景会对 taskId 做条件判断 + * + * @param type + * @param assigners + * @param noticeConfig + * @param processApproveConfig + * @param processInstanceId + * @param processDefinitionId + * @param currentTaskDefinitionKey + * @param tenantId + * @param taskId + */ + public MessagePushEventImpl(FlowableEventType type, List assigners, + BpmnNoticeConf noticeConfig, + BpmnApproveConf processApproveConfig, + String processInstanceId, String processDefinitionId, String processDefinitionKey, + String currentTaskDefinitionKey, String tenantId, String taskId) { + this.type = type; + this.assigners = assigners; + this.noticeConfig = noticeConfig; + this.processApproveConfig = processApproveConfig; + this.processInstanceId = processInstanceId; + this.processDefinitionId = processDefinitionId; + this.currentTaskDefinitionKey = currentTaskDefinitionKey; this.tenantId = tenantId; this.taskId = taskId; } @@ -78,6 +128,15 @@ public class MessagePushEventImpl implements MessagePushEvent { this.noticeConfig = noticeConfig; } + @Override + public BpmnApproveConf getProcessApproveConfig() { + return processApproveConfig; + } + + public void setProcessApproveConfig(BpmnApproveConf processApproveConfig) { + this.processApproveConfig = processApproveConfig; + } + @Override public String getProcessInstanceId() { return processInstanceId; @@ -87,6 +146,32 @@ public class MessagePushEventImpl implements MessagePushEvent { this.processInstanceId = processInstanceId; } + public String getProcessDefinitionId() { + return processDefinitionId; + } + + public void setProcessDefinitionId(String processDefinitionId) { + this.processDefinitionId = processDefinitionId; + } + + @Override + public String getProcessDefinitionKey() { + return processDefinitionKey; + } + + public void setProcessDefinitionKey(String processDefinitionKey) { + this.processDefinitionKey = processDefinitionKey; + } + + @Override + public String getCurrentTaskDefinitionKey() { + return currentTaskDefinitionKey; + } + + public void setCurrentTaskDefinitionKey(String currentTaskDefinitionKey) { + this.currentTaskDefinitionKey = currentTaskDefinitionKey; + } + @Override public String getTenantId() { return tenantId; @@ -105,6 +190,33 @@ public class MessagePushEventImpl implements MessagePushEvent { this.taskId = taskId; } + public void setActivitySignature(Boolean activitySignature) { + this.activitySignature = activitySignature; + } + + @Override + public Boolean getActivitySignature() { + return activitySignature; + } + + @Override + public String getImTemplateCode() { + return imTemplateCode; + } + + public void setImTemplateCode(String imTemplateCode) { + this.imTemplateCode = imTemplateCode; + } + + @Override + public String getTerminalType() { + return terminalType; + } + + public void setTerminalType(String terminalType) { + this.terminalType = terminalType; + } + @Override public String toString() { return getClass() + " - " + type; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventType.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventType.java index e4c1191eb..c4a8d6efb 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventType.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/event/MessagePushEventType.java @@ -20,11 +20,15 @@ public enum MessagePushEventType implements FlowableEventType { */ NOTICE, /** - * 推送待办 + * 推送审批待办 */ PENDING_PUSH, /** - * 完成待办 + * 恢复审批待办,异步执行待办任务失败,通知消息恢复待办 + */ + PENDING_ROLLBACK, + /** + * 完成审批待办 */ PENDING_COMPLETE, /** @@ -39,6 +43,10 @@ public enum MessagePushEventType implements FlowableEventType { * 推送短信 */ SMS, + /** + * IM 消息 + */ + IM, ; public static final MessagePushEventType[] EMPTY_ARRAY = new MessagePushEventType[]{}; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/formhandler/CustomFormFieldHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/formhandler/CustomFormFieldHandler.java new file mode 100644 index 000000000..de17ccaf1 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/formhandler/CustomFormFieldHandler.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.core.engine.formhandler; + +import org.flowable.content.api.ContentService; +import org.flowable.engine.impl.formhandler.DefaultFormFieldHandler; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.form.api.FormFieldHandler; +import org.flowable.form.api.FormInfo; + +import java.util.Map; + +/** + * 自定义的具体的表单组件类型处理器 + * + * @author wangli + * @since 2025-01-22 13:58 + */ +public class CustomFormFieldHandler extends DefaultFormFieldHandler implements FormFieldHandler { + @Override + public void handleFormFieldsOnSubmit(FormInfo formInfo, + String taskId, + String processInstanceId, + String scopeId, + String scopeType, + Map variables, + String tenantId) { + ContentService contentService = CommandContextUtil.getContentService(); + if (contentService == null || formInfo == null) { + return; + } + } + + @Override + public void enrichFormFields(FormInfo formInfo) { + super.enrichFormFields(formInfo); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/id/BasedNacosSnowflakeIdGenerator.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/id/BasedNacosSnowflakeIdGenerator.java new file mode 100644 index 000000000..2d9c2d12b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/id/BasedNacosSnowflakeIdGenerator.java @@ -0,0 +1,121 @@ +package cn.axzo.workflow.core.engine.id; + +import com.alibaba.cloud.nacos.NacosDiscoveryProperties; +import com.alibaba.cloud.nacos.NacosServiceManager; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.Event; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.cfg.IdGenerator; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +/** + * 基于 Nacos 的雪花算法 ID 生成器 + * + * @author wangli + * @since 2024/5/24 21:04 + */ +@Slf4j +public class BasedNacosSnowflakeIdGenerator implements IdGenerator { + + private final NacosServiceManager nacosServiceManager; + private final NacosDiscoveryProperties nacosDiscoveryProperties; + private static final long MAX_SEQUENCE = 99_999_999L; + private static final AtomicLong sequence = new AtomicLong(0); + private static volatile String lastTimestamp = ""; + private static int nodeId; + + public BasedNacosSnowflakeIdGenerator(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) { + this.nacosServiceManager = nacosServiceManager; + this.nacosDiscoveryProperties = nacosDiscoveryProperties; + init(); + } + + /** + * 获取当前服务所有节点 + 增加服务监听 + * + * @throws NacosException + */ + @SneakyThrows + private void init() { + NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties()); + namingService.subscribe(nacosDiscoveryProperties.getService(), new EventListener() { + @Override + public void onEvent(Event event) { + if (-1 == nacosDiscoveryProperties.getPort()) { + return; + } + nodeId = calcNodeId(((NamingEvent) event).getInstances()); + } + }); + } + + /** + * 用ip+port计算服务列表的索引 + * + * @param instanceList + * @return + */ + private int calcNodeId(List instanceList) { + List ipPosrList = instanceList.stream() + .map(x -> dealIpPort(x.getIp(), x.getPort())) + .sorted(Comparator.naturalOrder()) + .collect(Collectors.toList()); + return ipPosrList.indexOf(dealIpPort(nacosDiscoveryProperties.getIp(), nacosDiscoveryProperties.getPort())) + 1; + } + + /** + * ip补0 + 端口号 + * + * @param ip + * @param port + * @return + */ + private static Long dealIpPort(String ip, int port) { + String[] ips = ip.split("\\."); + StringBuilder sbr = new StringBuilder(); + for (int i = 0; i < ips.length; i++) { + sbr.append(new DecimalFormat("000").format(Integer.parseInt(ips[i]))); + } + return Long.parseLong(sbr.toString() + port); + } + + @Override + public synchronized String getNextId() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmm"); + String timestamp = dateFormat.format(new Date()); + + if (timestamp.equals(lastTimestamp)) { + long currentSequence = sequence.incrementAndGet() % MAX_SEQUENCE; + if (currentSequence == 0) { + // 如果递增数已达到最大值,则等待下一毫秒 + timestamp = tilNextTimestamp(timestamp, dateFormat); + } + } else { + sequence.set(0); + } + + lastTimestamp = timestamp; + + return timestamp + String.format("%d%08d", nodeId, sequence.get()); + } + + private String tilNextTimestamp(String lastTimestamp, SimpleDateFormat dateFormat) { + String timestamp = dateFormat.format(new Date()); + while (timestamp.equals(lastTimestamp)) { + timestamp = dateFormat.format(new Date()); + } + return timestamp; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/id/DistributedTimeBasedIdGenerator.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/id/DistributedTimeBasedIdGenerator.java index d91ad36c4..209ed0473 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/id/DistributedTimeBasedIdGenerator.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/id/DistributedTimeBasedIdGenerator.java @@ -68,7 +68,7 @@ public class DistributedTimeBasedIdGenerator implements IdGenerator { + "return dateStr .. idStr;\n"; @Override - public synchronized String getNextId() { + public String getNextId() { String now = DateUtil.format(DateUtil.date(), "yyyyMMddHHmm"); DefaultRedisScript redisScript = new DefaultRedisScript<>(); 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 new file mode 100644 index 000000000..c88d7a782 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java @@ -0,0 +1,91 @@ +package cn.axzo.workflow.core.engine.interceptor; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.common.utils.TraceUtil; +import cn.axzo.workflow.core.engine.cmd.AbstractCommand; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.exceptions.PersistenceException; +import org.flowable.common.engine.impl.interceptor.AbstractCommandInterceptor; +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 static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_ASYNC_COMMAND_EXECUTION_RETRY_GIVE_UP; + +/** + * 基于 RetryInterceptor 的命令重试拦截器, 本意想用来处理多人同时操作同一个任务, 但会存在超过重试次数后,会丢弃原有的命令. + * + * @author wangli + * @since 2024/7/1 13:51 + */ +@Slf4j +public class CustomRetryInterceptor extends AbstractCommandInterceptor { + + protected int numOfRetries = 3; + protected int waitTimeInMs = 60; + protected int waitIncreaseFactor = 5; + + @Override + 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); + waitBeforeRetry(waitTime); + waitTime *= waitIncreaseFactor; + } + + try { + // try to execute the command + if (AbstractCommand.class.isAssignableFrom(command.getClass())) { + // 如果在以后,重试三次也不能解决的话, 可以利用这里的拿到的参数,重新自动构造CMD,并执行. + log.warn("Executing command params: {} traceId:{} ", TraceUtil.traceId(), + ((AbstractCommand) command).paramToJsonString()); + } + return next.execute(config, command, commandExecutor); + + } 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), lastException.getMessage()); + } + + protected void waitBeforeRetry(long waitTime) { + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + log.warn("I am interrupted while waiting for a retry."); + } + } + + public void setNumOfRetries(int numOfRetries) { + this.numOfRetries = numOfRetries; + } + + public void setWaitIncreaseFactor(int waitIncreaseFactor) { + this.waitIncreaseFactor = waitIncreaseFactor; + } + + public void setWaitTimeInMs(int waitTimeInMs) { + this.waitTimeInMs = waitTimeInMs; + } + + public int getNumOfRetries() { + return numOfRetries; + } + + public int getWaitIncreaseFactor() { + return waitIncreaseFactor; + } + + public int getWaitTimeInMs() { + return waitTimeInMs; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AbstractExecuteWithLockJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AbstractExecuteWithLockJobHandler.java new file mode 100644 index 000000000..a877d8617 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AbstractExecuteWithLockJobHandler.java @@ -0,0 +1,74 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.common.utils.SpringContextUtils; +import cn.axzo.workflow.core.repository.entity.ExtAxProperty; +import cn.axzo.workflow.core.service.ExtAxPropertyService; +import com.alibaba.nacos.common.utils.ThreadUtils; +import org.apache.commons.lang.StringUtils; +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DuplicateKeyException; + +import java.util.concurrent.TimeUnit; + +import static cn.axzo.workflow.common.code.OtherRespCode.ASYNC_JOB_EXECUTION_ERROR; + +public abstract class AbstractExecuteWithLockJobHandler extends AbstractJobHandler implements JobHandler { + + private static final Logger log = LoggerFactory.getLogger(AbstractExecuteWithLockJobHandler.class); + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + String processInstanceId = job.getProcessInstanceId(), jobId = job.getId(); + if (StringUtils.isBlank(processInstanceId) || StringUtils.isBlank(jobId)) { + log.warn("processInstanceId or lockOwner is empty,cannot execute with lock,jobId:{},processInstanceId:{}", job.getId(), job.getProcessInstanceId()); + executeInternal(job, configuration, variableScope, commandContext); + return; + } + try { + if (acquireLock(processInstanceId, jobId)) { + log.info("job acquire lock success,processInstanceId:{},jobId:{}", processInstanceId, jobId); + executeInternal(job, configuration, variableScope, commandContext); + } else { + log.warn("get lock failed,processInstanceId:{},jobId:{}", processInstanceId, jobId); + throw new WorkflowEngineException(ASYNC_JOB_EXECUTION_ERROR, processInstanceId); + } + } finally { + releaseLock(processInstanceId, jobId); + } + } + + private boolean acquireLock(String processInstanceId, String jobId) { + int attemptNo = 3, acquireCount = 0; + ExtAxPropertyService extAxPropertyService = SpringContextUtils.getBean(ExtAxPropertyService.class); + do { + try { + ExtAxProperty extAxProperty = new ExtAxProperty(); + extAxProperty.setName(processInstanceId); + extAxProperty.setValue(jobId); + extAxPropertyService.add(extAxProperty); + return true; + } catch (DuplicateKeyException e) { + //删除15分钟前的数据,相当于超时时间是15分钟 + extAxPropertyService.deleteByNameWithDuration(processInstanceId, 15L, TimeUnit.MINUTES); + //睡眠2秒 + ThreadUtils.sleep(2 * 1000); + acquireCount++; + log.warn("acquireLock error,processInstanceId:{},jobId:{},acquireCount:{}", processInstanceId, jobId, acquireCount); + } + } while (acquireCount < attemptNo); + return false; + } + + private void releaseLock(String processInstanceId, String jobId) { + ExtAxPropertyService extAxPropertyService = SpringContextUtils.getBean(ExtAxPropertyService.class); + extAxPropertyService.delete(processInstanceId, jobId); + } + + public abstract void executeInternal(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AbstractJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AbstractJobHandler.java new file mode 100644 index 000000000..b8afadec5 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AbstractJobHandler.java @@ -0,0 +1,50 @@ +package cn.axzo.workflow.core.engine.job; + +import org.flowable.job.service.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +/** + * 记录每个 JOB 是否被多台机器同时处理 + * + * @author wangli + * @since 2024/6/28 10:28 + */ +public abstract class AbstractJobHandler implements JobHandler { + + private static final Logger log = LoggerFactory.getLogger(AbstractJobHandler.class); + + protected final void log(JobEntity job) { + try { + // 获取所有网络接口 + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface iface = interfaces.nextElement(); + + // 如果是活动的,并且不是回环接口(即非本地接口) + if (iface.isUp() && !iface.isLoopback()) { + List addresses = Collections.list(iface.getInetAddresses()); + + // 遍历每个接口上的 IP 地址 + for (InetAddress address : addresses) { + // 检查地址是否是 IPv4 类型的,并且不是回环地址 + if (address instanceof Inet4Address && !address.isLoopbackAddress()) { + log.warn(" current job ID: {} will be execution by IP:{}", job.getId(), address.getHostAddress()); + } + } + } + } + } catch (SocketException e) { + // ignore + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceJobHandler.java new file mode 100644 index 000000000..cacbc303f --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncAbortProcessInstanceJobHandler.java @@ -0,0 +1,37 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; +import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceCmd; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +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; + +@Slf4j +public class AsyncAbortProcessInstanceJobHandler extends AbstractExecuteWithLockJobHandler implements JobHandler { + public static final String TYPE = "async-abort-instance"; + private final ExtAxHiTaskInstService extAxHiTaskInstService; + + public AsyncAbortProcessInstanceJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + this.extAxHiTaskInstService = extAxHiTaskInstService; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void executeInternal(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncAbortProcessInstanceHandler executing...,jobInfo:{}", JSONUtil.toJsonStr(job)); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnProcessInstanceAbortDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnProcessInstanceAbortDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomAbortProcessInstanceCmd(dto, extAxHiTaskInstService)); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityCallbackJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityCallbackJobHandler.java new file mode 100644 index 000000000..7c1021400 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityCallbackJobHandler.java @@ -0,0 +1,72 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.engine.event.BizCallbackEventImpl; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.HistoryService; +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.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.task.api.Task; +import org.flowable.variable.api.delegate.VariableScope; +import org.springframework.util.CollectionUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; + +/** + * 业务节点的触发离开的任务处理器 + * + * @author wangli + * @since 2024-08-16 16:29 + */ +@Slf4j +public class AsyncActivityCallbackJobHandler extends AbstractJobHandler implements JobHandler { + public static final String TYPE = "business-node-callback"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.warn("AsyncActivityLeaveJobHandler exec start..."); + BpmnActivityTimeoutCallbackDTO dto = JSON.parseObject(job.getCustomValues(), BpmnActivityTimeoutCallbackDTO.class); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); + TaskService taskService = processEngineConfiguration.getTaskService(); + Task task = taskService.createTaskQuery().executionId(dto.getTriggerId()).singleResult(); + + HistoryService historyService = processEngineConfiguration.getHistoryService(); + HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().includeProcessVariables() + .processInstanceId(task.getProcessInstanceId()).singleResult(); + if (Objects.isNull(processInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, task.getProcessInstanceId()); + } + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); + runtimeService.setVariables(processInstance.getId(), dto.getVariables()); + + Map processVariables = new HashMap<>(processInstance.getProcessVariables()); + if (!CollectionUtils.isEmpty(dto.getVariables())) { + processVariables.putAll(dto.getVariables()); + } + eventDispatcher.dispatchEvent(new BizCallbackEventImpl(BizCallbackEventImpl.BizCallbackEventType.CALLBACK, + task.getTaskDefinitionKey(), task.getName(), + task.getProcessInstanceId(), task.getProcessDefinitionId(), + processInstance.getBusinessKey(), dto.getTriggerId(), + processVariables), + processEngineConfiguration.getEngineCfgKey()); + } +} 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 new file mode 100644 index 000000000..ea5f658eb --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncActivityLeaveJobHandler.java @@ -0,0 +1,39 @@ +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; +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; + +/** + * 业务节点的触发离开的任务处理器 + * + * @author wangli + * @since 2024-08-16 16:29 + */ +@Slf4j +public class AsyncActivityLeaveJobHandler extends AbstractJobHandler implements JobHandler { + private final BpmnProcessActivityService bpmnProcessActivityService; + public static final String TYPE = "business-node-leave-task"; + + public AsyncActivityLeaveJobHandler(BpmnProcessActivityService bpmnProcessActivityService) { + this.bpmnProcessActivityService = bpmnProcessActivityService; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + 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(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/AsyncApproveTaskJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncApproveTaskJobHandler.java new file mode 100644 index 000000000..494e4c9d9 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncApproveTaskJobHandler.java @@ -0,0 +1,55 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd; +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; + +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; + +/** + * 异步的通过任务的处理器 + * + * @author wangli + * @since 2024/4/15 22:41 + */ +@Slf4j +public class AsyncApproveTaskJobHandler extends AbstractExecuteWithLockJobHandler implements JobHandler { + public static final String TYPE = "async-approve-task"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void executeInternal(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncApproveTaskJobHandler executing..."); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnTaskAuditDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnTaskAuditDTO.class); + Task task = processEngineConfiguration.getTaskService().createTaskQuery().taskId(dto.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + return; + } + CustomApproveTaskCmd command; + if (Objects.equals(task.getTaskDefinitionKey(), NODE_STARTER.getType())) { + // 这里的 operationDesc 设置为“” 是为了在日志中不显示(已通过) + command = new CustomApproveTaskCmd(dto, ""); + } else { + command = new CustomApproveTaskCmd(dto); + } + processEngineConfiguration.getCommandExecutor().execute(command); + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncApproveTaskWithFormJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncApproveTaskWithFormJobHandler.java new file mode 100644 index 000000000..9c4f36862 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncApproveTaskWithFormJobHandler.java @@ -0,0 +1,55 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO; +import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskWithFormCmd; +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; + +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; + +/** + * 异步的通过任务的处理器 + * + * @author wangli + * @since 2024/4/15 22:41 + */ +@Slf4j +public class AsyncApproveTaskWithFormJobHandler extends AbstractExecuteWithLockJobHandler implements JobHandler { + public static final String TYPE = "async-approve-task-with-form"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void executeInternal(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncApproveTaskWithFormJobHandler executing..."); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnTaskAuditWithFormDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnTaskAuditWithFormDTO.class); + Task task = processEngineConfiguration.getTaskService().createTaskQuery().taskId(dto.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + return; + } + CustomApproveTaskWithFormCmd command; + if (Objects.equals(task.getTaskDefinitionKey(), NODE_STARTER.getType())) { + // 这里的 operationDesc 设置为“” 是为了在日志中不显示(已通过) + command = new CustomApproveTaskWithFormCmd(dto, ""); + } else { + command = new CustomApproveTaskWithFormCmd(dto); + } + processEngineConfiguration.getCommandExecutor().execute(command); + } + +} 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..2c5b642e7 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncBackTaskJobHandler.java @@ -0,0 +1,52 @@ +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.Collections; +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( + CustomBackTaskCmd.CustomBackParamsDto.builder() + .targetTaskIds(Collections.singletonList(dto.getTaskId())) + .advice(dto.getAdvice()) + .operationDesc(dto.getOperationDesc()) + .attachmentList(dto.getAttachmentList()) + .operator(dto.getApprover()) + .processInstanceId(task.getProcessInstanceId()) + .currentActivityId(task.getTaskDefinitionKey()) + .toActivityId(dto.getToActivityId()) + .validateApprover(true) + .build())); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceJobHandler.java new file mode 100644 index 000000000..fa171c9fa --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCancelProcessInstanceJobHandler.java @@ -0,0 +1,40 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +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; + +@Slf4j +public class AsyncCancelProcessInstanceJobHandler extends AbstractJobHandler implements JobHandler { + + public static final String TYPE = "async-cancel-process"; + + private final ExtAxHiTaskInstService extAxHiTaskInstService; + + public AsyncCancelProcessInstanceJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + this.extAxHiTaskInstService = extAxHiTaskInstService; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncCancelProcessInstanceHandler executing...,jobInfo:{}", JSONUtil.toJsonStr(job)); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnProcessInstanceCancelDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnProcessInstanceCancelDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomCancelProcessInstanceCmd((SuperBpmnProcessInstanceCancelDTO) dto, extAxHiTaskInstService)); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCountersignUserTaskJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCountersignUserTaskJobHandler.java new file mode 100644 index 000000000..5d0013b03 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncCountersignUserTaskJobHandler.java @@ -0,0 +1,47 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; +import cn.axzo.workflow.core.engine.cmd.CustomCountersignUserTaskCmd; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +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; + +@Slf4j +public class AsyncCountersignUserTaskJobHandler extends AbstractJobHandler implements JobHandler { + + public static final String TYPE = "async-countersign-task"; + + private final ExtAxHiTaskInstService extAxHiTaskInstService; + + public AsyncCountersignUserTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + this.extAxHiTaskInstService = extAxHiTaskInstService; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncCountersignUserTaskJobHandler executing..."); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnTaskCountersignDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnTaskCountersignDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum.valueOfType(dto.getCountersignType()), + dto.getTaskId(), + dto.getOriginAssigner(), + dto.getAdvice(), + dto.getAttachmentList(), + dto.getTargetAssignerList(), + extAxHiTaskInstService)); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncExtTaskInstJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncExtTaskInstJobHandler.java new file mode 100644 index 000000000..1f0ab8cc0 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncExtTaskInstJobHandler.java @@ -0,0 +1,59 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.engine.event.ExtTaskInstUpdateEvent; +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import com.alibaba.fastjson.JSONObject; +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; + +import java.util.Objects; + +import static cn.axzo.workflow.common.code.AsyncJobRespCode.DATA_NOT_EXISTS; + +/** + * 异步操作扩展任务表的处理器 + * + * @author wangli + * @since 2024/4/29 20:22 + */ +@Slf4j +public class AsyncExtTaskInstJobHandler extends AbstractJobHandler implements JobHandler { + public static final String TYPE = "async-update-ext-task-inst"; + private final ExtAxHiTaskInstService extAxHiTaskInstService; + + public AsyncExtTaskInstJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + this.extAxHiTaskInstService = extAxHiTaskInstService; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log(job); + ExtTaskInstUpdateEvent event = JSONObject.parseObject(job.getCustomValues(), ExtTaskInstUpdateEvent.class); + String taskId = event.getTaskId(); + String processInstanceId = event.getProcessInstanceId(); + String assignee = event.getAssignee(); + BpmnProcessInstanceResultEnum resultEnum = event.getResultEnum(); + + ExtAxHiTaskInst existence = extAxHiTaskInstService.getByTaskId(taskId, processInstanceId); + if (Objects.nonNull(existence)) { + log.info("更新扩展任务实例表数据: taskId:{}, instanceId:{}, currentStatus:{}, anticipateStatus: {}", + taskId, processInstanceId, existence.getStatus(), resultEnum.getStatus()); + } else { + log.warn("无法正确更新扩展任务表数据: taskId:{}, instanceId:{}, anticipateStatus: {}", + taskId, processInstanceId, resultEnum.getStatus()); + throw new WorkflowEngineException(DATA_NOT_EXISTS, processInstanceId, taskId); + } + extAxHiTaskInstService.updateByTaskIdAndInstanceId(taskId, processInstanceId, assignee, resultEnum); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncRejectTaskJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncRejectTaskJobHandler.java new file mode 100644 index 000000000..c52832ba5 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncRejectTaskJobHandler.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.core.engine.cmd.CustomRejectionTaskCmd; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +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/4/16 11:11 + */ +@Slf4j +public class AsyncRejectTaskJobHandler extends AbstractExecuteWithLockJobHandler implements JobHandler { + public static final String TYPE = "async-reject-task"; + private final ExtAxHiTaskInstService extAxHiTaskInstService; + + public AsyncRejectTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + this.extAxHiTaskInstService = extAxHiTaskInstService; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void executeInternal(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncRejectTaskJobHandler executing..."); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnTaskAuditDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnTaskAuditDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomRejectionTaskCmd(dto, extAxHiTaskInstService)); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncRemindTaskJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncRemindTaskJobHandler.java new file mode 100644 index 000000000..e423f204b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncRemindTaskJobHandler.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskRemindDTO; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.engine.cmd.CustomRemindTaskCmd; +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 2025-06-13 14:13 + */ +@Slf4j +public class AsyncRemindTaskJobHandler extends AbstractExecuteWithLockJobHandler implements JobHandler { + public static final String TYPE = "async-remind-task"; + private final SupportRefreshProperties refreshProperties; + + public AsyncRemindTaskJobHandler(SupportRefreshProperties refreshProperties) { + this.refreshProperties = refreshProperties; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void executeInternal(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncRejectTaskJobHandler executing..."); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnTaskRemindDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnTaskRemindDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomRemindTaskCmd(dto.getTerminalType(), + dto.getProcessInstanceId(), dto.getTaskDefinitionKey(), dto.getRemindTypes(), refreshProperties)); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncResetApproversUserTaskJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncResetApproversUserTaskJobHandler.java new file mode 100644 index 000000000..984435e79 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncResetApproversUserTaskJobHandler.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskResetApproversDTO; +import cn.axzo.workflow.core.engine.cmd.CustomResetTaskApproversCmd; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +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; + +@Slf4j +public class AsyncResetApproversUserTaskJobHandler extends AbstractJobHandler implements JobHandler { + + public static final String TYPE = "async-reset-approves-task"; + + private final ExtAxHiTaskInstService extAxHiTaskInstService; + + public AsyncResetApproversUserTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) { + this.extAxHiTaskInstService = extAxHiTaskInstService; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncCountersignUserTaskJobHandler executing..."); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnTaskResetApproversDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnTaskResetApproversDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomResetTaskApproversCmd(dto.getTaskId(), + dto.getAdvice(), + dto.getAttachmentList(), + dto.getOriginAssigner(), + dto.getTargetAssignerList(), + 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..192da572c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncTermNodeAlterJobHandler.java @@ -0,0 +1,131 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.basics.common.util.NumberUtil; +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.engine.tx.listener.DeleteTimerJobTransactionListener; +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.cfg.TransactionState; +import org.flowable.common.engine.impl.context.Context; +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 org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.List; + +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("AsyncTermNodeAlterJobHandler 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(); + List tasks = taskService.createTaskQuery() + .processInstanceId(dto.getProcessInstanceId()) + .taskDefinitionKey(dto.getActivityId()) + .active() + .list(); + + StringBuilder sb = new StringBuilder(); + tasks.forEach(e -> { + sb.append("id:").append(e.getId()).append(", assignee: ").append(e.getAssignee()); + }); + log.info("tasks size:{}", JSON.toJSONString(sb)); + if (CollectionUtils.isEmpty(tasks) || tasks.size() > 1 || hasAssignee(tasks.get(0).getAssignee())) { + deleteTimerJob(dto); + return; + } + if (DateUtil.compare(DateUtil.date(), DateUtil.offsetMinute(tasks.get(0).getCreateTime(), refreshProperties.getPauseDelay())) <= 0) { + return; + } + + // 不允许重复告警 + if (!refreshProperties.getRepeatAlter() && dto.getRetries() > 0) { + deleteTimerJob(dto); + return; + } + + + if (refreshProperties.getAlterRetries() == 0 || dto.getRetries() < refreshProperties.getAlterRetries()) { + // 发送告警对象 + Alter alter = SpringContextUtils.getBean(Alter.class); + AlterDTO alterDTO = new AlterDTO(); + alterDTO.setProcessInstanceId(dto.getProcessInstanceId()); + alterDTO.setActivityId(dto.getActivityId()); + alterDTO.setTaskId(tasks.get(0).getId()); + alterDTO.setStartTime(tasks.get(0).getCreateTime()); + alterDTO.setPrettyStartTime(DateUtil.formatDateTime(tasks.get(0).getCreateTime())); + if (Boolean.TRUE.equals(refreshProperties.getAlterSendDingTalk())) { + alter.invoke(alterDTO); + + // 记录告警次数 + incRetries(job, dto, runtimeService, activityId); + + if (refreshProperties.getAlterRetries() != 0 && dto.getRetries() >= refreshProperties.getAlterRetries()) { + deleteTimerJob(dto); + } + } + } + } + + private void incRetries(JobEntity job, TermNodePausingDTO dto, RuntimeService runtimeService, String activityId) { + dto.setRetries(dto.getRetries() + 1); + runtimeService.setVariable(job.getProcessInstanceId(), BIZ_NODE_ALTER + activityId, dto); + } + + private void deleteTimerJob(TermNodePausingDTO dto) { + Context.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, + new DeleteTimerJobTransactionListener(dto)); + } + + private Boolean hasAssignee(String assignee) { + if (!StringUtils.hasText(assignee)) { + return false; + } + String[] split = assignee.split("\\|"); + return split.length == 2 && NumberUtil.isPositiveNumber(Long.valueOf(split[1])); + } + + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncTransferUserTaskJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncTransferUserTaskJobHandler.java new file mode 100644 index 000000000..f424cd2ae --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/AsyncTransferUserTaskJobHandler.java @@ -0,0 +1,34 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; +import cn.axzo.workflow.core.engine.cmd.CustomTransferUserTaskCmd; +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; + +@Slf4j +public class AsyncTransferUserTaskJobHandler extends AbstractJobHandler implements JobHandler { + + public static final String TYPE = "async-transfer-task"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("AsyncTransferUserTaskJobHandler executing..."); + log(job); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + BpmnTaskTransferDTO dto = JSONUtil.toBean(job.getCustomValues(), BpmnTaskTransferDTO.class); + processEngineConfiguration.getCommandExecutor().execute(new CustomTransferUserTaskCmd(dto.getTaskId(), + dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssigner(), dto.getAdditionalOpeDesc())); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/NextActivityConfigCheckJobHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/NextActivityConfigCheckJobHandler.java new file mode 100644 index 000000000..8c7fc199c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/NextActivityConfigCheckJobHandler.java @@ -0,0 +1,186 @@ +package cn.axzo.workflow.core.engine.job; + +import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum; +import cn.axzo.workflow.common.model.NextNodePreCheckAlterDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.utils.SpringContextUtils; +import cn.axzo.workflow.core.deletage.BpmnTaskAssigneeSelector; +import cn.axzo.workflow.core.listener.Alter; +import cn.axzo.workflow.core.service.support.FlowNodeForecastService; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.ReceiveTask; +import org.flowable.bpmn.model.ServiceTask; +import org.flowable.bpmn.model.UserTask; +import org.flowable.common.engine.api.query.QueryProperty; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.delegate.DelegateExecution; +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.JobHandler; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.variable.api.delegate.VariableScope; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprovalMethod; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecify; + +/** + * 检查节点配置是否合法 + * + * @author wangli + * @since 2025-07-08 19:44 + */ +@Slf4j +public class NextActivityConfigCheckJobHandler extends AbstractJobHandler implements JobHandler { + public static final String TYPE = "next-activity-config-check"; + + @Override + public String getType() { + return TYPE; + } + + @Override + public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) { + log.info("NextActivityConfigCheckJobHandler executing... 当前节点为: {}", job.getJobHandlerConfiguration()); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + FlowNodeForecastService forecastService = SpringContextUtils.getBean(FlowNodeForecastService.class); + String currentActivityId = job.getJobHandlerConfiguration(); + List flowElements = forecastService.performProcessForecasting(job.getProcessInstanceId(), null, currentActivityId, false); + if (CollectionUtils.isEmpty(flowElements)) { + return; + } + try { + doCheck(job, flowElements, processEngineConfiguration); + } catch (Exception e) { + // 有任何异常,则通过钉钉告警 + log.warn("NextActivityConfigCheckJobHandler msg: {}", e.getMessage(), e); + Alter alter = SpringContextUtils.getBean(Alter.class); + FlowElement flowElement = ListUtils.emptyIfNull(flowElements).stream().filter(i -> i instanceof UserTask || i instanceof ReceiveTask || i instanceof ServiceTask).findFirst().orElse(null); + NextNodePreCheckAlterDTO alterDTO = new NextNodePreCheckAlterDTO(); + alterDTO.setProcessInstanceId(job.getProcessInstanceId()); + alterDTO.setActivityId(Objects.nonNull(flowElement) ? flowElement.getId() : null); + alterDTO.setErrorMsg(e.getMessage()); + alter.invoke(alterDTO); + } + } + + private void doCheck(JobEntity job, List flowElements, ProcessEngineConfigurationImpl processEngineConfiguration) { + AtomicReference checkActivityId = new AtomicReference<>(""); + ListUtils.emptyIfNull(flowElements).stream() + .filter(i -> i instanceof UserTask || i instanceof ReceiveTask || i instanceof ServiceTask) + .findFirst().ifPresent(flowElement -> { + log.info("NextActivityConfigCheckJobHandler, 实例 ID:{}, 计算节点为:{}", job.getJobHandlerConfiguration(), flowElement.getId()); + getApprovalMethod(flowElement).ifPresent(method -> { + switch (method) { + case autoPassed: + case autoRejection: + case bizSpecify: + case nobody: + break; + default: + List executions = processEngineConfiguration.getRuntimeService().createExecutionQuery() + .processInstanceId(job.getProcessInstanceId()) + .orderBy((QueryProperty) () -> "START_TIME_").desc().list(); + if (CollectionUtils.isEmpty(executions)) { + return; + } + // 这里只会是 human 这一种情况, 因为 nobody 在转 BPMN 协议时,Activity 直接变成了 ReceiveTask 节点了。 + List assigners = new ArrayList<>(); + getApproverSpecify(flowElement).ifPresent(specify -> { + assigners.addAll(approverSelect(specify.getType(), flowElement, (DelegateExecution) executions.get(0), true)); + }); + + // 审批候选人为空时的兜底 + emptyAssigneeHandle(assigners, flowElement, (DelegateExecution) executions.get(0)); + break; + } + }); + checkActivityId.set(flowElement.getId()); + }); + checkActivityId.get(); + } + + public List approverSelect(String type, FlowElement flowElement, + DelegateExecution execution, + Boolean throwException) { + List selectors = new ArrayList<>(SpringContextUtils.getBeansOfType(BpmnTaskAssigneeSelector.class).values()); + List assigners = new ArrayList<>(); + ListUtils.emptyIfNull(selectors).forEach(select -> { + if (select.support(type)) { + log.info("NextActivityConfigCheckJobHandler-审批任务节点 Id:{}, Name: {}, 审批人指定枚举: {}", flowElement.getId(), flowElement.getName(), type); + List selected = select.select(flowElement, execution, throwException); + log.info("NextActivityConfigCheckJobHandler-审批任务节点 Id:{} 的审批人集合为: {}", flowElement.getId(), JSONUtil.toJsonStr(selected)); + assigners.addAll(selected); + } + }); + return assigners; + } + + /** + * 计算节点的待审批人为空时, 执行模型配置中的审批人为空时的处理方式 + * + * @param assigners 节点计算的待审批人集合, 可能为空 + * @param flowElement 当前节点 + * @param execution 当前执行实例 + */ + private void emptyAssigneeHandle(List assigners, FlowElement flowElement, + DelegateExecution execution) { + // 审批人为空并且当前节点设置了自动跳过条件 + if (!CollectionUtils.isEmpty(assigners)) { + return; + } + log.info("NextActivityConfigCheckJobHandler-当前节点id: [{}], name: [{}] 审批人为空, 将执行审批人为空的兜底配置!", flowElement.getId(), flowElement.getName()); + getApproverEmptyHandleType(flowElement).ifPresent(type -> { + log.info("NextActivityConfigCheckJobHandler-节点兜底的配置模式:[{}]", type.getType()); + switch (type) { + case autoPassed: + case autoRejection: + break; + case transferToAdmin: + try { + assigners.addAll(approverSelect(ApproverEmptyHandleTypeEnum.transferToAdmin.getType(), flowElement, + execution, true)); + if (CollectionUtils.isEmpty(assigners)) { + throw new IllegalArgumentException("审批人为空后转交管理员仍然为空"); + } + } catch (Exception e) { + if (e instanceof IllegalArgumentException) { + throw new RuntimeException(e.getMessage()); + } else { + throw new RuntimeException("审批人为空后转交管理员失败, 内部计算信息:" + e.getMessage()); + } + } + break; + case specifyAssignee: + try { + assigners.addAll(approverSelect(ApproverEmptyHandleTypeEnum.specifyAssignee.getType(), flowElement, + execution, true)); + if (CollectionUtils.isEmpty(assigners)) { + throw new IllegalArgumentException("审批人为空后转交指定人员仍然为空,可能被指定人员已离职"); + } + } catch (Exception e) { + if (e instanceof IllegalArgumentException) { + throw new RuntimeException(e.getMessage()); + } else { + throw new RuntimeException("审批人为空后转交指定人员失败,内部计算信息:" + e.getMessage()); + } + } + break; + default: + break; + } + + }); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomAsyncJobLogClearTraceExceptionHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomAsyncJobLogClearTraceExceptionHandler.java new file mode 100644 index 000000000..3911a1c7c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomAsyncJobLogClearTraceExceptionHandler.java @@ -0,0 +1,21 @@ +package cn.axzo.workflow.core.engine.job.exception.handle; + +import cn.axzo.workflow.common.constant.LogFieldConstants; +import cn.azxo.framework.common.constatns.Constants; +import lombok.extern.slf4j.Slf4j; +import org.flowable.job.api.JobInfo; +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.job.service.impl.asyncexecutor.AsyncRunnableExecutionExceptionHandler; +import org.slf4j.MDC; + +@Slf4j +public class CustomAsyncJobLogClearTraceExceptionHandler implements AsyncRunnableExecutionExceptionHandler { + + @Override + public boolean handleException(JobServiceConfiguration jobServiceConfiguration, JobInfo job, Throwable exception) { + //执行异常,删除日志字段 + MDC.remove(LogFieldConstants.X_REQUEST_ID); + MDC.remove(Constants.CTX_LOG_ID_MDC); + return false; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomAsyncRunnableExceptionExceptionHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomAsyncRunnableExceptionExceptionHandler.java new file mode 100644 index 000000000..b19b26ffb --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomAsyncRunnableExceptionExceptionHandler.java @@ -0,0 +1,71 @@ +package cn.axzo.workflow.core.engine.job.exception.handle; + +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandConfig; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.job.api.JobInfo; +import org.flowable.job.service.InternalJobCompatibilityManager; +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.job.service.event.impl.FlowableJobEventBuilder; +import org.flowable.job.service.impl.asyncexecutor.AsyncRunnableExecutionExceptionHandler; +import org.flowable.job.service.impl.asyncexecutor.FailedJobCommandFactory; +import org.flowable.job.service.impl.persistence.entity.AbstractRuntimeJobEntity; + +/** + * 默认的异步任务执行异常处理器 + * + * @author wangli + * @since 2024/4/29 17:43 + */ +@Slf4j +public class CustomAsyncRunnableExceptionExceptionHandler implements AsyncRunnableExecutionExceptionHandler { + + @Override + public boolean handleException(final JobServiceConfiguration jobServiceConfiguration, final JobInfo job, final Throwable exception) { + jobServiceConfiguration.getCommandExecutor().execute(new Command() { + + @Override + public Void execute(CommandContext commandContext) { + + // Finally, Throw the exception to indicate the ExecuteAsyncJobCmd failed + String message = "Job " + job.getId() + " failed"; + log.warn(message, exception); + + if (job instanceof AbstractRuntimeJobEntity) { + AbstractRuntimeJobEntity runtimeJob = (AbstractRuntimeJobEntity) job; + InternalJobCompatibilityManager internalJobCompatibilityManager = jobServiceConfiguration.getInternalJobCompatibilityManager(); + if (internalJobCompatibilityManager != null && internalJobCompatibilityManager.isFlowable5Job(runtimeJob)) { + internalJobCompatibilityManager.handleFailedV5Job(runtimeJob, exception); + return null; + } + } + + CommandConfig commandConfig = jobServiceConfiguration.getCommandExecutor().getDefaultConfig().transactionRequiresNew(); + FailedJobCommandFactory failedJobCommandFactory = jobServiceConfiguration.getFailedJobCommandFactory(); + Command cmd = failedJobCommandFactory.getCommand(job.getId(), exception); + + log.info("Using FailedJobCommandFactory '{}' and command of type '{}'", failedJobCommandFactory.getClass(), cmd.getClass()); + jobServiceConfiguration.getCommandExecutor().execute(commandConfig, cmd); + + // Dispatch an event, indicating job execution failed in a + // try-catch block, to prevent the original exception to be swallowed + FlowableEventDispatcher eventDispatcher = jobServiceConfiguration.getEventDispatcher(); + if (eventDispatcher != null && eventDispatcher.isEnabled()) { + try { + eventDispatcher.dispatchEvent(FlowableJobEventBuilder.createEntityExceptionEvent( + FlowableEngineEventType.JOB_EXECUTION_FAILURE, job, exception), jobServiceConfiguration.getEngineName()); + } catch (Throwable ignore) { + log.warn("Exception occurred while dispatching job failure event, ignoring.", ignore); + } + } + + return null; + } + }); + + return true; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomWorkflowEngineExceptionHandler.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomWorkflowEngineExceptionHandler.java new file mode 100644 index 000000000..6bcd575ad --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/exception/handle/CustomWorkflowEngineExceptionHandler.java @@ -0,0 +1,42 @@ +package cn.axzo.workflow.core.engine.job.exception.handle; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import lombok.extern.slf4j.Slf4j; +import org.flowable.job.api.JobInfo; +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.job.service.impl.asyncexecutor.AsyncRunnableExecutionExceptionHandler; + +import java.util.Objects; + +/** + * 解决异步任务执行过程中, 抛出的 WorkflowEngineException 正常的业务异常. + * + * @author wangli + * @since 2024/6/26 15:53 + */ +@Slf4j +public class CustomWorkflowEngineExceptionHandler implements AsyncRunnableExecutionExceptionHandler { + @Override + public boolean handleException(JobServiceConfiguration jobServiceConfiguration, JobInfo job, Throwable e) { + Throwable rootCause = getRootCause(e); +// if (rootCause.getClass().isAssignableFrom(WorkflowEngineException.class)) { +// WorkflowEngineException workflowEngineException = (WorkflowEngineException) rootCause; +// if (Objects.equals(workflowEngineException.getCode(), "99806020")) { +// log.info("AsyncApproveTaskJobHandler execute exception code: {} info: {}", +// workflowEngineException.getCode(), rootCause.getMessage(), rootCause); +// return true; +// } +// } + log.warn("Async Runnable Execution Exception: {}", rootCause.getMessage(), e); + boolean assignableFrom = WorkflowEngineException.class.isAssignableFrom(rootCause.getClass()); + log.info("CustomWorkflowEngineExceptionHandler result : {}" , assignableFrom); + return assignableFrom; + } + + private Throwable getRootCause(Throwable throwable) { + while (Objects.nonNull(throwable.getCause())) { + throwable = throwable.getCause(); + } + return throwable; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/service/CustomTimerJobEntityManagerImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/service/CustomTimerJobEntityManagerImpl.java new file mode 100644 index 000000000..413c0563e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/service/CustomTimerJobEntityManagerImpl.java @@ -0,0 +1,26 @@ +package cn.axzo.workflow.core.engine.job.service; + +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; +import org.flowable.job.service.impl.persistence.entity.TimerJobEntityManagerImpl; +import org.flowable.job.service.impl.persistence.entity.data.TimerJobDataManager; + +/** + * + * @author wangli + * @since 2025-03-12 14:14 + */ +public class CustomTimerJobEntityManagerImpl extends TimerJobEntityManagerImpl { + public CustomTimerJobEntityManagerImpl(JobServiceConfiguration jobServiceConfiguration, TimerJobDataManager jobDataManager) { + super(jobServiceConfiguration, jobDataManager); + } + + @Override + protected TimerJobEntity createTimer(JobEntity te) { + TimerJobEntity timer = super.createTimer(te); + timer.setElementId(te.getElementId()); + timer.setElementName(te.getElementName()); + return timer; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/service/JobLogProcessor.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/service/JobLogProcessor.java new file mode 100644 index 000000000..7e82dec55 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/service/JobLogProcessor.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.core.engine.job.service; + +import cn.axzo.workflow.common.constant.LogFieldConstants; +import cn.axzo.workflow.core.engine.job.utils.AsyncJobUtils; +import cn.azxo.framework.common.constatns.Constants; +import lombok.extern.slf4j.Slf4j; +import org.flowable.job.service.JobProcessor; +import org.flowable.job.service.JobProcessorContext; +import org.flowable.job.service.impl.persistence.entity.AbstractJobEntity; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class JobLogProcessor implements JobProcessor { + + @Override + public void process(JobProcessorContext jobProcessorContext) { + if (jobProcessorContext.getPhase() == JobProcessorContext.Phase.BEFORE_EXECUTE) { + AbstractJobEntity jobEntity = jobProcessorContext.getJobEntity(); + String customValues = jobEntity.getCustomValues(); + if (AsyncJobUtils.isWrappedCustomValues(customValues)) { + AsyncJobUtils.WrappedJobCustomValuesInfo bean = AsyncJobUtils.convert2WrappedCustomValuesInfo(customValues); + if (bean != null) { + //原始的值重新设置回去 + String originalCustomValues = bean.getCustomValues(); + jobEntity.setCustomValues(originalCustomValues); + MDC.put(LogFieldConstants.X_REQUEST_ID, bean.getXRequestId()); + MDC.put(Constants.CTX_LOG_ID_MDC, bean.getCtxLogId()); + return; + } + } + //不是包装的customValues,MDC删除log的两个字段 + MDC.remove(LogFieldConstants.X_REQUEST_ID); + MDC.remove(Constants.CTX_LOG_ID_MDC); + } else if (jobProcessorContext.getPhase() == JobProcessorContext.Phase.BEFORE_CREATE) {//持久化之前做处理 + AbstractJobEntity jobEntity = jobProcessorContext.getJobEntity(); + String customValues = jobEntity.getCustomValues(); + jobEntity.setCustomValues(AsyncJobUtils.getWrappedCustomValues(customValues)); + } + } + + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/utils/AsyncJobUtils.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/utils/AsyncJobUtils.java new file mode 100644 index 000000000..05f2de5f5 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/job/utils/AsyncJobUtils.java @@ -0,0 +1,50 @@ +package cn.axzo.workflow.core.engine.job.utils; + +import cn.axzo.workflow.common.constant.LogFieldConstants; +import cn.azxo.framework.common.constatns.Constants; +import cn.hutool.json.JSONUtil; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.MDC; + +@Slf4j +public class AsyncJobUtils { + + private static final String WRAPPED_PREFIX = "@@wrapped@@"; + + public static String getWrappedCustomValues(String originalCustomValues) { + WrappedJobCustomValuesInfo jobLogInfo = new WrappedJobCustomValuesInfo(); + String traceId = MDC.get(LogFieldConstants.X_REQUEST_ID); + String cxtLogId = MDC.get(Constants.CTX_LOG_ID_MDC); + jobLogInfo.setCtxLogId(cxtLogId); + jobLogInfo.setXRequestId(traceId); + jobLogInfo.setCustomValues(originalCustomValues); + String jsonStr = JSONUtil.toJsonStr(jobLogInfo); + return WRAPPED_PREFIX + jsonStr; + } + + public static boolean isWrappedCustomValues(String customValues) { + return customValues != null && !customValues.trim().isEmpty() && customValues.startsWith(WRAPPED_PREFIX); + } + + public static WrappedJobCustomValuesInfo convert2WrappedCustomValuesInfo(String wrappedCustomValues) { + WrappedJobCustomValuesInfo bean = null; + if (isWrappedCustomValues(wrappedCustomValues)) { + try { + String str = StringUtils.substring(wrappedCustomValues, WRAPPED_PREFIX.length()); + bean = JSONUtil.toBean(str, WrappedJobCustomValuesInfo.class); + } catch (Exception e) { + log.warn(e.getMessage(), e); + } + } + return bean; + } + + @Data + public static final class WrappedJobCustomValuesInfo { + private String xRequestId; + private String ctxLogId; + private String customValues; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityCallbackEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityCallbackEventListener.java new file mode 100644 index 000000000..40c298feb --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityCallbackEventListener.java @@ -0,0 +1,66 @@ +package cn.axzo.workflow.core.engine.listener; + +import cn.axzo.workflow.core.common.context.ActivityOperationContext; +import cn.axzo.workflow.core.engine.event.BizCallbackEvent; +import cn.axzo.workflow.core.engine.event.BizCallbackEventImpl; +import cn.axzo.workflow.core.listener.BpmnActivityEventListener; +import com.google.common.collect.ImmutableSet; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.AbstractFlowableEventListener; +import org.flowable.common.engine.api.delegate.event.FlowableEvent; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static cn.axzo.workflow.core.engine.event.BizCallbackEventImpl.BizCallbackEventType.CALLBACK; + +/** + * 业务节点回调事件 + * + * @author wangli + * @since 2024-08-20 17:47 + */ +@Slf4j +@Component +public class EngineActivityCallbackEventListener extends AbstractFlowableEventListener { + @Resource + ObjectProvider> activityListeners; + + public static final Set BIZ_CALLBACK_EVENTS = + ImmutableSet.builder() + .add(CALLBACK) + .build(); + + @Override + public void onEvent(FlowableEvent flowableEvent) { + if (flowableEvent instanceof BizCallbackEvent) { + BizCallbackEvent event = (BizCallbackEvent) flowableEvent; + BizCallbackEventImpl.BizCallbackEventType type = (BizCallbackEventImpl.BizCallbackEventType) event.getType(); + if (BIZ_CALLBACK_EVENTS.contains(type)) { + switch (type) { + case CALLBACK: + getOrderedListeners().forEach(i -> i.onCallback(event)); + break; + default: + } + } + } + } + + private List getOrderedListeners() { + ActivityOperationContext context = new ActivityOperationContext(); + List orderListeners = new ArrayList<>(); + activityListeners.ifAvailable(orderListeners::addAll); + orderListeners.forEach(i -> i.setContext(context)); + return orderListeners; + } + + @Override + public boolean isFailOnException() { + return true; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityEndEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityEndEventListener.java index 461ca1024..daa2f2694 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityEndEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityEndEventListener.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.listener; +import cn.axzo.workflow.core.common.context.ActivityOperationContext; import cn.axzo.workflow.core.listener.BpmnActivityEventListener; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; @@ -30,15 +31,17 @@ public class EngineActivityEndEventListener implements ExecutionListener { if (Objects.nonNull(execution.getTransientVariable(EVENT_NAME + execution.getCurrentActivityId()))) { return; } - log.info("EngineActivityEndEventListener Event: {}, ActivityId: {}", execution.getEventName(), - execution.getCurrentActivityId()); + log.info("EngineActivityEndEventListener Event: {}, ActivityId: {},ProcessInstanceId:{}", execution.getEventName(), + execution.getCurrentActivityId(), execution.getProcessInstanceId()); getOrderedListeners().forEach(i -> i.onEnd(execution)); execution.setTransientVariable(EVENT_NAME + execution.getCurrentActivityId(), true); } private List getOrderedListeners() { + ActivityOperationContext context = new ActivityOperationContext(); List orderListeners = new ArrayList<>(); activityListeners.ifAvailable(orderListeners::addAll); + orderListeners.forEach(i -> i.setContext(context)); return orderListeners; } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityExtEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityExtEventListener.java index 4fd6f0e02..b5401fdd9 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityExtEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityExtEventListener.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.listener; +import cn.axzo.workflow.core.common.context.ActivityOperationContext; import cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEvent; import cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEventType; import cn.axzo.workflow.core.listener.BpmnActivityEventListener; @@ -60,8 +61,10 @@ public class EngineActivityExtEventListener extends AbstractFlowableEventListene } private List getOrderedListeners() { + ActivityOperationContext context = new ActivityOperationContext(); List orderListeners = new ArrayList<>(); activityListeners.ifAvailable(orderListeners::addAll); + orderListeners.forEach(i -> i.setContext(context)); return orderListeners; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityStartEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityStartEventListener.java index 10360b7e8..43b6e4901 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityStartEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityStartEventListener.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.listener; +import cn.axzo.workflow.core.common.context.ActivityOperationContext; import cn.axzo.workflow.core.listener.BpmnActivityEventListener; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; @@ -30,15 +31,17 @@ public class EngineActivityStartEventListener implements ExecutionListener { if (Objects.nonNull(execution.getTransientVariable(EVENT_NAME + execution.getCurrentActivityId()))) { return; } - log.info("EngineActivityStartEventListener Event: {}, ActivityId: {}", execution.getEventName(), - execution.getCurrentActivityId()); + log.info("EngineActivityStartEventListener Event: {}, ActivityId: {}, ProcessInstanceId: {}", execution.getEventName(), + execution.getCurrentActivityId(), execution.getProcessInstanceId()); getOrderedListeners().forEach(i -> i.onStart(execution)); execution.setTransientVariable(EVENT_NAME + execution.getCurrentActivityId(), true); } private List getOrderedListeners() { + ActivityOperationContext context = new ActivityOperationContext(); List orderListeners = new ArrayList<>(); activityListeners.ifAvailable(orderListeners::addAll); + orderListeners.forEach(i -> i.setContext(context)); return orderListeners; } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityTakeEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityTakeEventListener.java index d0a2b9961..18a9bafcc 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityTakeEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineActivityTakeEventListener.java @@ -30,8 +30,8 @@ public class EngineActivityTakeEventListener implements ExecutionListener { if (Objects.nonNull(execution.getTransientVariable(EVENT_NAME + execution.getCurrentActivityId()))) { return; } - log.info("EngineActivityTakeEventListener Event: {}, ActivityId: {}", execution.getEventName(), - execution.getCurrentActivityId()); + log.info("EngineActivityTakeEventListener Event: {}, ActivityId: {}, ProcessInstanceId: {}", execution.getEventName(), + execution.getCurrentActivityId(), execution.getProcessInstanceId()); getOrderedListeners().forEach(i -> i.onTake(execution)); execution.setTransientVariable(EVENT_NAME + execution.getCurrentActivityId(), true); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineAsyncJobEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineAsyncJobEventListener.java new file mode 100644 index 000000000..a5c667986 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineAsyncJobEventListener.java @@ -0,0 +1,95 @@ +package cn.axzo.workflow.core.engine.listener; + +import cn.axzo.workflow.common.constant.LogFieldConstants; +import cn.axzo.workflow.core.engine.job.utils.AsyncJobUtils; +import cn.axzo.workflow.core.listener.BpmnAsyncJobEventListener; +import cn.azxo.framework.common.constatns.Constants; +import cn.hutool.json.JSONUtil; +import com.google.common.collect.ImmutableSet; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEvent; +import org.flowable.common.engine.impl.event.FlowableEntityEventImpl; +import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; +import org.flowable.job.service.impl.persistence.entity.JobEntity; +import org.slf4j.MDC; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * 引擎异步任务事件监听器 + * + * @author wangli + * @since 2024/4/29 16:48 + */ +@Slf4j +@Component +public class EngineAsyncJobEventListener extends AbstractFlowableEngineEventListener { + + @Resource + ObjectProvider> asyncJobListeners; + + public static final Set ASYNC_JOB_EVENTS = + ImmutableSet.builder() + .add(FlowableEngineEventType.JOB_CANCELED) + .add(FlowableEngineEventType.JOB_EXECUTION_SUCCESS) + .add(FlowableEngineEventType.JOB_EXECUTION_FAILURE) + .add(FlowableEngineEventType.JOB_RETRIES_DECREMENTED) + .add(FlowableEngineEventType.JOB_REJECTED) + .add(FlowableEngineEventType.JOB_RESCHEDULED) + .add(FlowableEngineEventType.JOB_MOVED_TO_DEADLETTER) + .build(); + + public EngineAsyncJobEventListener() { + super(ASYNC_JOB_EVENTS); + } + + @Override + public void onEvent(FlowableEvent flowableEvent) { + StopWatch stopWatch = new StopWatch("EngineAsyncJobEventListener"); + stopWatch.start("async-job-event-listener"); + if (ASYNC_JOB_EVENTS.contains(flowableEvent.getType())) { + if (flowableEvent instanceof FlowableEntityEventImpl) { + resolveJobInfoBeforeOperate((FlowableEntityEventImpl) flowableEvent); + } + getOrderedListeners().forEach(i -> { + if (i.support((FlowableEngineEventType) flowableEvent.getType())) { + i.notify(flowableEvent); + } + }); + } + stopWatch.stop(); + log.info("StopWatch '{}': running time = {} 's", stopWatch.getLastTaskName(), stopWatch.getTotalTimeSeconds()); + } + + private List getOrderedListeners() { + List orderListeners = new ArrayList<>(); + asyncJobListeners.ifAvailable(orderListeners::addAll); + return orderListeners; + } + + private void resolveJobInfoBeforeOperate(FlowableEntityEventImpl flowableEvent) { + if (flowableEvent == null) { + return; + } + Object entity = flowableEvent.getEntity(); + log.info("resolve job info,eventInfo:{},jobEntity:{}", JSONUtil.toJsonStr(flowableEvent), entity); + if (entity instanceof JobEntity) { + JobEntity jobInfo = (JobEntity) entity; + String customValues = jobInfo.getCustomValues(); + if (AsyncJobUtils.isWrappedCustomValues(customValues)) { + AsyncJobUtils.WrappedJobCustomValuesInfo wrappedInfo = AsyncJobUtils.convert2WrappedCustomValuesInfo(customValues); + String originalCustomValues = wrappedInfo.getCustomValues(); + jobInfo.setCustomValues(originalCustomValues); + MDC.put(LogFieldConstants.X_REQUEST_ID, wrappedInfo.getXRequestId()); + MDC.put(Constants.CTX_LOG_ID_MDC, wrappedInfo.getCtxLogId()); + } + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineCarbonCopyEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineCarbonCopyEventListener.java index 26a1057a8..dce16737d 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineCarbonCopyEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineCarbonCopyEventListener.java @@ -3,7 +3,6 @@ package cn.axzo.workflow.core.engine.listener; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; -import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; import cn.axzo.workflow.core.engine.cmd.CustomCarbonCopyUserSelectorCmd; import cn.axzo.workflow.core.engine.event.MessagePushEventBuilder; import cn.axzo.workflow.core.engine.event.MessagePushEventImpl; @@ -37,7 +36,10 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCarbonCopyConfigs; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNoticeConfig; +import static cn.axzo.workflow.core.listener.AbstractBpmnEventListener.parseProcessDefinitionKey; /** * 抄送功能的具体实现 @@ -66,7 +68,7 @@ public class EngineCarbonCopyEventListener implements JavaDelegate { @Override public void execute(DelegateExecution execution) { - log.info("EngineCarbonCopyEventListener exec..."); + log.info("EngineCarbonCopyEventListener exec...,processInstanceId: {}", execution.getProcessInstanceId()); String currentActivityId = execution.getCurrentActivityId(); String processDefinitionId = execution.getProcessDefinitionId(); String processInstanceId = execution.getProcessInstanceId(); @@ -88,12 +90,12 @@ public class EngineCarbonCopyEventListener implements JavaDelegate { INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + currentActivityId, carbonUsers); // 发送抄送事件 - invokeCarbonCopy(carbonUsers, BpmnMetaParserHelper.getNoticeConfig(mainProcess), execution); + invokeCarbonCopy(carbonUsers, getNoticeConfig(mainProcess), getActivitySignature(serviceTask), execution); } private void invokeCarbonCopy(List carbonUsers, Optional noticeConf, - DelegateExecution execution) { + Boolean activitySignature, DelegateExecution execution) { if (CollectionUtils.isEmpty(carbonUsers)) { return; } @@ -107,10 +109,10 @@ public class EngineCarbonCopyEventListener implements JavaDelegate { } return conf; }).orElse(null); - MessagePushEventImpl event = MessagePushEventBuilder.createEvent(MessagePushEventType.CARBON_COPY, carbonUsers, bpmnNoticeConf, execution.getProcessInstanceId(), - execution.getTenantId(), getCarbonTaskId(execution)); + parseProcessDefinitionKey(execution.getProcessDefinitionId()), + execution.getTenantId(), getCarbonTaskId(execution), activitySignature); eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey()); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineDeploymentEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineDeploymentEventListener.java index 2b077c26e..d31378149 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineDeploymentEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineDeploymentEventListener.java @@ -3,7 +3,7 @@ package cn.axzo.workflow.core.engine.listener; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.utils.ContextHolder; import cn.axzo.workflow.core.service.ExtAxReProcDefService; -import com.alibaba.fastjson.JSON; +import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; @@ -42,7 +42,7 @@ public class EngineDeploymentEventListener extends AbstractFlowableEngineEventLi private void saveDeploymentInfo(ProcessDefinition definition) { log.info("thread Name: {}, hashCode: {}", Thread.currentThread().getName(), Thread.currentThread().hashCode()); BpmnTaskDelegateAssigner operatorAssigner = ContextHolder.get(); - log.info("操作人:{}", JSON.toJSONString(operatorAssigner)); + log.info("操作人:{}", JSONUtil.toJsonStr(operatorAssigner)); if (Objects.nonNull(operatorAssigner)) { extAxReProcDefService.create(definition.getId(), operatorAssigner); } 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 82b249ddf..f9b3544ad 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,22 +4,32 @@ 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 com.alibaba.fastjson.JSON; +import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceAsyncCmd; +import cn.axzo.workflow.core.engine.job.NextActivityConfigCheckJobHandler; +import cn.axzo.workflow.core.util.DingTalkUtils; +import cn.hutool.json.JSONUtil; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.ManagementService; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.ExecutionListener; +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.TimerJobService; +import org.flowable.job.service.impl.persistence.entity.TimerJobEntity; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; @@ -30,12 +40,14 @@ import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.ArrayList; -import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; 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; @@ -44,6 +56,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_R import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_BUSINESS; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_SIGN; 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.model.request.bpmn.task.BpmnTaskDelegateAssigner.buildDummyAssigner; @@ -52,6 +65,7 @@ import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprove import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecify; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNodeType; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getProcessServerVersion; +import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.getLimitedElementList; import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.removeDuplicateByPersonId; @@ -65,21 +79,16 @@ 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) { @@ -94,11 +103,39 @@ public class EngineExecutionStartListener implements ExecutionListener { // 从 version=1.2.1-SNAPSHOT 开始,才给 process 节点增加了 serverVersion 属性 Optional processServerVersion = getProcessServerVersion(mainProcess); if (processServerVersion.isPresent()) { + // 创建检查下个节点的配置 + createCheckNextActivityJob(execution.getProcessInstanceId(), currentActivityId); calcTaskAssigner121(execution, userTask, processServerVersion.get(), assigneeListVariableName, currentActivityId); } else { calcTaskAssignerDefault(execution, userTask, currentActivityId, assigneeListVariableName); } + + + } + + /** + * 提前检查下个节点的配置是否异常 + */ + private void createCheckNextActivityJob(String processInstanceId, String activityId) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + ManagementService managementService = processEngineConfiguration.getManagementService(); + + // 创建一个只执行一次的 Job + managementService.executeCommand(commandContext -> { + TimerJobService timerJobService = CommandContextUtil.getTimerJobService(); + TimerJobEntity timerJobEntity = timerJobService.createTimerJob(); + timerJobEntity.setJobType("timer"); + timerJobEntity.setJobHandlerType(NextActivityConfigCheckJobHandler.TYPE); // 这里填写你自定义的 JobHandler 类型 + timerJobEntity.setProcessInstanceId(processInstanceId); + timerJobEntity.setExecutionId(null); + timerJobEntity.setDuedate(new Date()); // 立即执行 + timerJobEntity.setRepeat(null); // 不重复 + timerJobEntity.setRetries(1); + timerJobEntity.setJobHandlerConfiguration(activityId); // 可选,传递参数 + timerJobService.scheduleTimerJob(timerJobEntity); + return null; + }); } private void calcTaskAssigner121(DelegateExecution execution, UserTask userTask, String processServerVersion, @@ -106,12 +143,14 @@ public class EngineExecutionStartListener implements ExecutionListener { Optional nodeType = getNodeType(userTask); DefaultArtifactVersion supportVersion = new DefaultArtifactVersion(FLOW_SERVER_VERSION_121); DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(processServerVersion); + + List supportedNodeType = Lists.newArrayList(NODE_TASK, NODE_BUSINESS, NODE_SIGN, NODE_STARTER); + if (currentVersion.compareTo(supportVersion) >= 0 && nodeType.isPresent() - && (Objects.equals(NODE_TASK, nodeType.get()) || Objects.equals(NODE_BUSINESS, nodeType.get()) || Objects.equals(NODE_STARTER, nodeType.get()))) { + && (supportedNodeType.contains(nodeType.get()))) { if (Objects.equals(NODE_STARTER, nodeType.get())) { // UserTask 多实例, 该变量用于引擎 - BpmnTaskDelegateAssigner initiator = execution.getVariable(INTERNAL_INITIATOR, - BpmnTaskDelegateAssigner.class); + BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(execution.getVariable(INTERNAL_INITIATOR)); // 仅用于节点人员快照 execution.setVariable(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + currentActivityId, @@ -139,27 +178,28 @@ public class EngineExecutionStartListener implements ExecutionListener { // 这里只会是 human 这一种情况, 因为 nobody 在转 BPMN 协议时,Activity 直接变成了 ReceiveTask 节点了。 List assigners = new ArrayList<>(); getApproverSpecify(userTask).ifPresent(specify -> { - if (log.isDebugEnabled()) { - log.debug("当前审批节点ID: {}, 节点名称: {}, 审批人指定方式: {}", userTask.getId(), - userTask.getName(), specify.getDesc()); - } + log.info("当前审批节点ID: {}, 节点名称: {}, 审批人指定方式: {},processInstanceId: {}", userTask.getId(), + userTask.getName(), specify.getDesc(), execution.getProcessInstanceId()); + assigners.addAll(approverSelect(specify.getType(), userTask, execution, true)); }); // 审批候选人为空时的兜底 emptyAssigneeHandle(assigners, userTask, execution); - - for (BpmnTaskDelegateAssigner user : assigners) { + List resultAssigners = getLimitedElementList(assigners, APPROVAL_ASSIGNER_LIMIT_NUMBER); + for (BpmnTaskDelegateAssigner user : resultAssigners) { assigneeIdList.add(user.buildAssigneeId()); } execution.setVariable(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + currentActivityId, - assigners); + resultAssigners); break; } // UserTask 多实例, 该变量用于引擎 execution.setVariable(assigneeListVariableName, assigneeIdList); }); + } else { + log.error("current version: {} 过低,本地运行则需先 maven package,服务端运行则需排查逻辑", currentVersion); } } @@ -178,6 +218,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: @@ -187,23 +228,40 @@ public class EngineExecutionStartListener implements ExecutionListener { case transferToAdmin: assigners.addAll(approverSelect(ApproverEmptyHandleTypeEnum.transferToAdmin.getType(), userTask, execution, true)); + + finalEmptyAssigneeHandle(assigners, userTask, execution, "未找到审批人且转交管理员失败,自动中止", "karma: " + (refreshProperties.getUseNewToAdminApi() ? "api/flow/listTaskAssignerAdmin/v2" : "api/flow/listTaskAssignerAdmin")); 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, "转交指定人员失败,自动中止", "org-gateway: api/node-user/list"); + break; default: break; } + }); } + private void finalEmptyAssigneeHandle(List assigners, UserTask userTask, DelegateExecution execution, String operationDesc, String targetUrl) { + if (CollectionUtils.isEmpty(assigners)) { + //发送钉钉消息 + if (Boolean.TRUE.equals(refreshProperties.getSendDingTalk())) { + CooperationOrgDTO orgScopes = execution.getVariable(BIZ_ORG_RELATION, CooperationOrgDTO.class); + DingTalkUtils.sendDingTalkForTransferToAdminError(profile, execution.getProcessInstanceId(), userTask.getId(), orgScopes, targetUrl); + } + BpmnProcessInstanceAbortDTO abortDTO = new BpmnProcessInstanceAbortDTO(); + abortDTO.setProcessInstanceId(execution.getProcessInstanceId()); + abortDTO.setReason(operationDesc); + CommandContextUtil.getProcessEngineConfiguration().getCommandExecutor().execute(new CustomAbortProcessInstanceAsyncCmd(abortDTO)); + } + } + + /** * 根据审批人指定类型查询审批人 * - * @param type "审批人指定"方式 {@link ApproverSpecifyEnum} + * @param type "审批人指定"方式 {@link ApproverSpecifyEnum} * @param flowElement 当前节点, 这个对象会包含配置元数据, 可以在该方法中或者基于 BpmnTaskAssigneeSelector 建一个抽象类, 做解析元数据公共方法 * @return */ @@ -233,19 +291,21 @@ public class EngineExecutionStartListener implements ExecutionListener { * \"personId\":\"89508\",\"assignee\":\"2000560\",\"assigneeType\":\"3\"}]' * }" */ + Boolean mock = refreshProperties.getMock(); + Boolean global = refreshProperties.getGlobal(); + String key = refreshProperties.getKey(); + Map assigneeMap = refreshProperties.getAssigneeMap(); if ((mock && global) || - (mock && !global && Objects.equals(category, execution.getProcessDefinitionId().split(":")[0]))) { - if (log.isDebugEnabled()) { - log.debug("当前系统 Nacos 配置中开启了 mock: {}, 将使用 mock 方式查找审批人", mock); - } - assigners.addAll(new MockTaskAssigneeSelector(assigneeMap, global, category) + (mock && !global && Objects.equals(key, execution.getProcessDefinitionId().split(":")[0]))) { + log.info("当前系统 Nacos 配置中开启了 mock: {}, 将使用 mock 方式查找审批人", mock); + assigners.addAll(new MockTaskAssigneeSelector(assigneeMap, global, key) .select(flowElement, execution, throwException)); } else { selectors.forEach(select -> { if (select.support(type)) { log.info("审批任务节点 Id:{}, Name: {}, 审批人指定枚举: {}", flowElement.getId(), flowElement.getName(), type); List selected = select.select(flowElement, execution, throwException); - log.info("审批任务节点 Id:{} 的审批人集合为: {}", flowElement.getId(), JSON.toJSONString(selected)); + log.info("审批任务节点 Id:{} 的审批人集合为: {}", flowElement.getId(), JSONUtil.toJsonStr(selected)); assigners.addAll(selected); } }); @@ -308,7 +368,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/EngineNoticeEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineNoticeEventListener.java index 0aa6b533d..9aae50069 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineNoticeEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineNoticeEventListener.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.listener; +import cn.axzo.workflow.core.common.context.NoticeOperationContext; import cn.axzo.workflow.core.engine.event.MessagePushEvent; import cn.axzo.workflow.core.engine.event.MessagePushEventType; import cn.axzo.workflow.core.listener.BpmnMessagePushEventListener; @@ -18,9 +19,11 @@ import java.util.Set; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.CARBON_COPY; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.CARBON_COPY_COMPLETE; +import static cn.axzo.workflow.core.engine.event.MessagePushEventType.IM; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.NOTICE; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.PENDING_COMPLETE; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.PENDING_PUSH; +import static cn.axzo.workflow.core.engine.event.MessagePushEventType.PENDING_ROLLBACK; import static cn.axzo.workflow.core.engine.event.MessagePushEventType.SMS; /** @@ -36,14 +39,16 @@ public class EngineNoticeEventListener extends AbstractFlowableEventListener { @Resource ObjectProvider> processEventListeners; - public static final Set NOTICE_PUSH_EVENTS = + public static final Set ACCEPT_EVENTS = ImmutableSet.builder() .add(NOTICE) .add(PENDING_PUSH) .add(PENDING_COMPLETE) + .add(PENDING_ROLLBACK) .add(CARBON_COPY) .add(CARBON_COPY_COMPLETE) .add(SMS) + .add(IM) .build(); @Override @@ -54,43 +59,55 @@ public class EngineNoticeEventListener extends AbstractFlowableEventListener { MessagePushEvent event = (MessagePushEvent) flowableEvent; MessagePushEventType pushEventType = (MessagePushEventType) flowableEvent.getType(); - if (NOTICE_PUSH_EVENTS.contains(pushEventType)) { + if (ACCEPT_EVENTS.contains(pushEventType)) { switch (pushEventType) { case NOTICE: stopWatch.start("PUSH-NOTICE Event Execution Time"); getOrderedListeners().forEach(i -> i.onNotice(event)); stopWatch.stop(); - log.info("PUSH-NOTICE StopWatch : running time = " + stopWatch.getTotalTimeSeconds() + " 's"); + log.info("PUSH-NOTICE StopWatch : running time = {} 's", stopWatch.getTotalTimeSeconds()); break; case PENDING_PUSH: stopWatch.start("PUSH-PENDING Event Execution Time"); getOrderedListeners().forEach(i -> i.onPendingPush(event)); stopWatch.stop(); - log.info("PUSH-PENDING StopWatch : running time = " + stopWatch.getTotalTimeSeconds() + " 's"); + log.info("PUSH-PENDING StopWatch : running time = {} 's", stopWatch.getTotalTimeSeconds()); break; case PENDING_COMPLETE: stopWatch.start("PUSH-PENDING-COMPLETE Event Execution Time"); getOrderedListeners().forEach(i -> i.onPendingComplete(event)); stopWatch.stop(); - log.info("PUSH-PENDING-COMPLETE StopWatch : running time = " + stopWatch.getTotalTimeSeconds() + " 's"); + log.info("PUSH-PENDING-COMPLETE StopWatch : running time = {} 's", stopWatch.getTotalTimeSeconds()); + break; + case PENDING_ROLLBACK: + stopWatch.start("PUSH-PENDING-ROLLBACK Event Execution Time"); + getOrderedListeners().forEach(i -> i.onPendingRollback(event)); + stopWatch.stop(); + log.info("PUSH-PENDING-ROLLBACK StopWatch : running time = {} 's", stopWatch.getTotalTimeSeconds()); break; case CARBON_COPY: stopWatch.start("CARBON-COPY Event Execution Time"); getOrderedListeners().forEach(i -> i.onCarbonCopy(event)); stopWatch.stop(); - log.info("CARBON-COPY StopWatch : running time = " + stopWatch.getTotalTimeSeconds() + " 's"); + log.info("CARBON-COPY StopWatch : running time = {} 's", stopWatch.getTotalTimeSeconds()); break; case CARBON_COPY_COMPLETE: stopWatch.start("CARBON-COPY-COMPLETE Event Execution Time"); getOrderedListeners().forEach(i -> i.onCarbonCopyComplete(event)); stopWatch.stop(); - log.info("CARBON-COPY-COMPLETE StopWatch : running time = " + stopWatch.getTotalTimeSeconds() + " 's"); + log.info("CARBON-COPY-COMPLETE StopWatch : running time = {} 's", stopWatch.getTotalTimeSeconds()); break; case SMS: stopWatch.start("SMS Event Execution Time"); getOrderedListeners().forEach(i -> i.onSms(event)); stopWatch.stop(); - log.info("SMS StopWatch : running time = " + stopWatch.getTotalTimeSeconds() + " 's"); + log.info("SMS StopWatch : running time = {} 's", stopWatch.getTotalTimeSeconds()); + break; + case IM: + stopWatch.start("IM Event Execution Time"); + getOrderedListeners().forEach(i -> i.onIm(event)); + stopWatch.stop(); + log.info("IM StopWatch : running time = {} 's", stopWatch.getTotalTimeSeconds()); break; default: } @@ -99,8 +116,10 @@ public class EngineNoticeEventListener extends AbstractFlowableEventListener { } private List getOrderedListeners() { + NoticeOperationContext context = new NoticeOperationContext(); List orderListeners = new ArrayList<>(); processEventListeners.ifAvailable(orderListeners::addAll); + orderListeners.forEach(i -> i.setContext(context)); return orderListeners; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineProcessInstanceEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineProcessInstanceEventListener.java index 1aa07d67f..f8c3ba7b3 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineProcessInstanceEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineProcessInstanceEventListener.java @@ -1,15 +1,17 @@ package cn.axzo.workflow.core.engine.listener; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; -import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +import cn.axzo.workflow.common.enums.WorkspaceType; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.context.ProcessOperationContext; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.cmd.CustomNoticeDestinationUserSelectorCmd; 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.listener.BpmnProcessEventListener; +import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter; +import cn.hutool.json.JSONUtil; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEvent; @@ -32,7 +34,6 @@ import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.Set; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_DELETE_PROCESS_FLAG; @@ -40,10 +41,13 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_ABORT; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_CANCEL; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_REJECT; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_WORKSPACE_TYPE; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.ABORTED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.CANCELLED; 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.event.MessagePushEventType.NOTICE; /** * 引擎全局的流程实例事件监听 @@ -53,7 +57,12 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECT public class EngineProcessInstanceEventListener extends AbstractFlowableEngineEventListener { @Resource ObjectProvider> processEventListeners; - + @Resource + private EngineExecutionStartListener engineExecutionStartListener; + @Resource + private BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter; + @Resource + private String serviceVersion; @Resource @Lazy private RuntimeService runtimeService; @@ -84,11 +93,10 @@ public class EngineProcessInstanceEventListener extends AbstractFlowableEngineEv @Override protected void processCompleted(FlowableEngineEntityEvent event) { - runtimeService.updateBusinessStatus(event.getProcessInstanceId(), - BpmnProcessInstanceResultEnum.APPROVED.getStatus()); + runtimeService.updateBusinessStatus(event.getProcessInstanceId(), APPROVED.getStatus()); getOrderedListeners().forEach(i -> i.onCompleted(event)); // 推送站内信 - pushNotice(event); + pushNotice(event, APPROVED); } @Override @@ -112,8 +120,8 @@ public class EngineProcessInstanceEventListener extends AbstractFlowableEngineEv resultEnum = DELETED; break; } - log.error("这里是不允许执行到的地方,为防止引擎一些未发现的地方,在这里输出错误日志! this current thread name: {}", - Thread.currentThread().getName()); + log.error("这里是不允许执行到的地方,为防止引擎一些未发现的地方,在这里输出错误日志! this current thread name: {},processInstanceId: {}", + Thread.currentThread().getName(), event.getProcessInstanceId()); throw new IllegalArgumentException("这里是不允许执行到的地方, 请关注程序逻辑"); } runtimeService.updateBusinessStatus(event.getProcessInstanceId(), resultEnum.getStatus()); @@ -127,34 +135,42 @@ public class EngineProcessInstanceEventListener extends AbstractFlowableEngineEv } }); // 推送站内信 - pushNotice(event); + pushNotice(event, resultEnum); } - private void pushNotice(FlowableEngineEvent event) { + private void pushNotice(FlowableEngineEvent event, BpmnProcessInstanceResultEnum resultEnum) { ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(event.getProcessInstanceId()) .includeProcessVariables().singleResult(); BpmnTaskDelegateAssigner assigner = - (BpmnTaskDelegateAssigner) processInstance.getProcessVariables().getOrDefault(INTERNAL_INITIATOR, null); + BpmnTaskDelegateAssigner.toObjectCompatible(processInstance.getProcessVariables().get(INTERNAL_INITIATOR)); if (Objects.isNull(assigner)) { return; } ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); - Optional noticeConfig = - BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId())); - MessagePushEventImpl messagePushEvent = MessagePushEventBuilder.createEvent(MessagePushEventType.NOTICE, - Lists.newArrayList(assigner), noticeConfig.orElse(null), - processInstance.getProcessInstanceId(), processInstance.getTenantId(), null); - if (log.isDebugEnabled()) { - log.debug("发送站内信: {}", com.alibaba.fastjson.JSON.toJSONString(messagePushEvent)); - } - eventDispatcher.dispatchEvent(messagePushEvent, processEngineConfiguration.getEngineCfgKey()); + + BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId())) + .ifPresent(config -> { + if (config.getNotice() != null && config.getNotice().getTriggerEvents().contains(resultEnum)) { + Integer workspaceType = (Integer) processInstance.getProcessVariables().getOrDefault(INTERNAL_PROCESS_WORKSPACE_TYPE, WorkspaceType.UN_KNOW.getCode()); + List assigners = processEngineConfiguration.getCommandExecutor() + .execute(new CustomNoticeDestinationUserSelectorCmd(engineExecutionStartListener, historicTaskInstanceConverter, + serviceVersion, workspaceType, config.getNotice(), event.getProcessInstanceId(), assigner)); + MessagePushEventImpl messagePushEvent = MessagePushEventBuilder.createEvent(NOTICE, + assigners, config, processInstance.getProcessInstanceId(), processInstance.getProcessDefinitionKey(), + processInstance.getTenantId(), null, false); + log.info("发送通知消息: {}", JSONUtil.toJsonStr(messagePushEvent)); + eventDispatcher.dispatchEvent(messagePushEvent, processEngineConfiguration.getEngineCfgKey()); + } + }); } private List getOrderedListeners() { + ProcessOperationContext context = new ProcessOperationContext(); List orderListeners = new ArrayList<>(); processEventListeners.ifAvailable(orderListeners::addAll); + orderListeners.forEach(i -> i.setContext(context)); return orderListeners; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineTaskEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineTaskEventListener.java index e872cdfaf..f48f69287 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineTaskEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/EngineTaskEventListener.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.engine.listener; +import cn.axzo.workflow.core.common.context.TaskOperationContext; import cn.axzo.workflow.core.listener.BpmnTaskEventListener; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.TaskListener; @@ -13,6 +14,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import static cn.axzo.workflow.common.constant.TaskListenerExtConstants.EVENTNAME_FALLBACK; +import static cn.axzo.workflow.common.constant.TaskListenerExtConstants.EVENTNAME_TRANSFER; + /** * 引擎用户任务事件监听, 用于 BPMN 文件中为节点设置指定类型监听 * @@ -28,8 +32,8 @@ public class EngineTaskEventListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { - log.info("Task Listener, Event: ALL , TaskId: {}, EventName: {}", delegateTask.getId(), - delegateTask.getEventName()); + log.info("Task Listener, Event: ALL , TaskId: {}, EventName: {}, ProcessInstanceId: {}", delegateTask.getId(), + delegateTask.getEventName(), delegateTask.getProcessInstanceId()); StopWatch stopWatch = new StopWatch("EngineTaskEventListener"); stopWatch.start("All Task Event Execution Time"); @@ -39,12 +43,12 @@ public class EngineTaskEventListener implements TaskListener { return; } switch (delegateTask.getEventName()) { - case EVENTNAME_CREATE: - i.onCreated(delegateTask); - break; case EVENTNAME_ASSIGNMENT: i.onAssigned(delegateTask); break; + case EVENTNAME_CREATE: + i.onCreated(delegateTask); + break; case EVENTNAME_COMPLETE: // 审批通过 i.onCompleted(delegateTask); @@ -53,11 +57,17 @@ public class EngineTaskEventListener implements TaskListener { // 审批 i.onDeleted(delegateTask); break; + case EVENTNAME_TRANSFER: + i.onTransfer(delegateTask); + break; + case EVENTNAME_FALLBACK: + i.onFallback(delegateTask); + break; default: } }); stopWatch.stop(); - log.info("StopWatch '" + stopWatch.currentTaskName() + "': running time = " + stopWatch.getTotalTimeSeconds() + " s"); + log.info("StopWatch '{}': running time = {} 's, processInstanceId:{}", stopWatch.getLastTaskName(), stopWatch.getTotalTimeSeconds(), delegateTask.getProcessInstanceId()); } @@ -67,8 +77,10 @@ public class EngineTaskEventListener implements TaskListener { * @return */ private List getOrderedListeners() { + TaskOperationContext context = new TaskOperationContext(); List orderListeners = new ArrayList<>(); taskEventListeners.ifAvailable(orderListeners::addAll); + orderListeners.forEach(i -> i.setContext(context)); return orderListeners; } 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..a8fca56e6 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/EngineEntityEventListener.java @@ -0,0 +1,76 @@ +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; + +/** + * 引擎内的 Entity 事件监听处理 + * + * @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; + 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); + } + } + }); + } + } + + + @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..9c1e71731 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/listener/entity/EntityEventHandle.java @@ -0,0 +1,33 @@ +package cn.axzo.workflow.core.engine.listener.entity; + +/** + * Entity life cycle + * + * @author wangli + * @since 2024-09-06 00:03 + */ +public interface EntityEventHandle { + + boolean support(Object entity); + + T convert(Object entity); + + default void onCreate(T entity) { + } + + default void onInitialized(T entity) { + } + + default void onUpdated(T entity) { + } + + default void onDeleted(T entity) { + } + + default void onSuspended(T entity) { + } + + default void onActivated(T entity) { + } + +} 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/model/NoticeFlowElement.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/model/NoticeFlowElement.java new file mode 100644 index 000000000..46e44ec86 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/model/NoticeFlowElement.java @@ -0,0 +1,71 @@ +package cn.axzo.workflow.core.engine.model; + +import cn.axzo.workflow.common.enums.ApproverScopeEnum; +import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; +import cn.axzo.workflow.common.enums.WorkspaceType; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import org.flowable.bpmn.model.FlowElement; + +import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_NOTICE_CUSTOM_FLOW_ELEMENT_ERROR; + +/** + * 自定义扩展流程模型中的元素 + * + * @author wangli + * @since 2024/5/11 10:32 + */ +public class NoticeFlowElement extends FlowElement { + private Integer workspaceType; + private ApproverSpecifyEnum specifyEnum; + private String customValues; + + public NoticeFlowElement() { + } + + public NoticeFlowElement(Integer workspaceType, ApproverSpecifyEnum specifyEnum, String customValues) { + this.workspaceType = workspaceType; + this.specifyEnum = specifyEnum; + this.customValues = customValues; + } + + public void setWorkspaceType(Integer workspaceType) { + this.workspaceType = workspaceType; + } + + public ApproverSpecifyEnum getSpecifyEnum() { + return specifyEnum; + } + + public void setSpecifyEnum(ApproverSpecifyEnum specifyEnum) { + this.specifyEnum = specifyEnum; + } + + public String getCustomValues() { + return customValues; + } + + public void setCustomValues(String customValues) { + this.customValues = customValues; + } + + public ApproverScopeEnum getProcessor() { + switch (WorkspaceType.getType(workspaceType)) { + case ENT: + case OMS: + return ApproverScopeEnum.entWorkspace; + case GOVERNMENT: + return ApproverScopeEnum.govWorkspace; + case PROJECT: + return ApproverScopeEnum.projectWorkspace; + default: + throw new WorkflowEngineException(ENGINE_NOTICE_CUSTOM_FLOW_ELEMENT_ERROR); + } + } + + @Override + public NoticeFlowElement clone() { + NoticeFlowElement clone = new NoticeFlowElement(); + clone.setValues(this); + return clone; + } +} 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 5f33c07c7..3fd368e85 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,20 +1,26 @@ package cn.axzo.workflow.core.engine.operation; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import lombok.extern.slf4j.Slf4j; 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.impl.agenda.AbstractOperation; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.job.api.Job; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; 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 内执行删除流程实例时的额外操作 @@ -22,15 +28,30 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID; * @author wangli * @since 2024/1/3 20:03 */ +@Slf4j 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, BpmnProcessInstanceResultEnum closingType) { + super(commandContext, null); + this.processInstanceId = processInstanceId; + this.extAxHiTaskInstService = extAxHiTaskInstService; + this.customDeleteReason = customDeleteReason; + this.closingType = closingType; } @Override @@ -53,9 +74,19 @@ public class DeleteProcessInstanceOperation extends AbstractOperation { } + ManagementService managementService = processEngineConfiguration.getManagementService(); + List timerJobs = managementService.createTimerJobQuery().processInstanceId(processInstanceId).list(); + if (!CollectionUtils.isEmpty(timerJobs)) { + log.warn("timerjobs size: {}, processInstanceId: {}", timerJobs.size(), processInstanceId); + timerJobs.forEach(e -> managementService.deleteTimerJob(e.getId())); + } + RuntimeService runtimeService = processEngineConfiguration.getRuntimeService(); - runtimeService.deleteProcessInstance(processInstanceId, HIDDEN_ASSIGNEE_ID); + 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/AddTimerJobTransactionListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AddTimerJobTransactionListener.java new file mode 100644 index 000000000..92612305f --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AddTimerJobTransactionListener.java @@ -0,0 +1,31 @@ +package cn.axzo.workflow.core.engine.tx.listener; + +import cn.axzo.workflow.common.model.dto.TermNodeAddTimerJobDTO; +import cn.axzo.workflow.core.engine.cmd.CustomAddTimerJobCmd; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.cfg.TransactionListener; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; + +/** + * 删除业务节点长时间卡住的定时处理器 + * + * @author wangli + * @since 2025-03-12 15:02 + */ +@Slf4j +@AllArgsConstructor +public class AddTimerJobTransactionListener implements TransactionListener { + private final TermNodeAddTimerJobDTO dto; + + @Override + public void execute(CommandContext commandContext) { + log.info("add timer job listener. instanceId: {}, activityId: {}, timeCycle: {}", dto.getProcessInstanceId(), dto.getActivityId(), dto.getTimeCycle()); + ProcessEngineConfigurationImpl processEngineConfiguration = + CommandContextUtil.getProcessEngineConfiguration(commandContext); + processEngineConfiguration.getCommandExecutor().execute(new CustomAddTimerJobCmd(dto)); + + } +} 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 new file mode 100644 index 000000000..7a12c474a --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java @@ -0,0 +1,73 @@ +package cn.axzo.workflow.core.engine.tx.listener; + +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.cmd.CustomApproveTaskAsyncCmd; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.cfg.TransactionListener; +import org.flowable.common.engine.impl.interceptor.CommandConfig; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.RuntimeService; +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.job.service.impl.asyncexecutor.ResetExpiredJobsCmd; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.Collections; + +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; +import static org.flowable.common.engine.impl.interceptor.EngineConfigurationConstants.KEY_PROCESS_ENGINE_CONFIG; + +/** + * 自动过审的事务监听器 + *

+ * 在节点会签模式下,业务指定审批人、加签时,由于这两个动作的内部是先进行 addMultiTask ,对当前节点加了新的实例数, + * 而该动作默认会触发多种内外部实现的监听,其中一个监听是自动过审,此时,节点的完成条件 nrOfInstance == nrOfCompleteInstances 一定不满足, + * 所以无法跳出当前节点。当 addMultiTask 执行完后,便会再执行 deleteMultiTask,但该动作只是把 nrOfInstance == nrOfCompleteInstances + * 表达式中的两个变量值变成了可以为 true 的结果。但并不会再触发节点的 leave 动作。 + * + *

+ * 使用示例:
+ *  Context.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, new AutoPassTransactionListener(delegateTask, ["advice"]));
+ * 
+ * + * @author wangli + * @since 2024-08-07 10:24 + */ +@Slf4j +@AllArgsConstructor +public class AutoPassTransactionListener implements TransactionListener { + private final DelegateTask delegateTask; + private final String advice; + private final String operationDesc; + + @Override + public void execute(CommandContext commandContext) { + log.info("exec auto pass transaction listener start, processInstanceId: {}, taskId: {}", delegateTask.getProcessInstanceId(), delegateTask.getId()); + + SpringProcessEngineConfiguration springProcessEngineConfiguration = (SpringProcessEngineConfiguration) commandContext + .getEngineConfigurations().get(KEY_PROCESS_ENGINE_CONFIG); + + RuntimeService runtimeService = springProcessEngineConfiguration.getRuntimeService(); + BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible( + runtimeService.getVariable(delegateTask.getProcessInstanceId(), INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId())); + + CommandConfig commandConfig = new CommandConfig().transactionRequired(); + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + BpmnTaskAuditDTO pass = new BpmnTaskAuditDTO(); + pass.setTaskId(delegateTask.getId()); + pass.setAdvice(advice); + pass.setApprover(assigner); + pass.setOperationDesc(operationDesc); + String jobId = commandExecutor.execute(commandConfig, new CustomApproveTaskAsyncCmd(pass)); + + // 重置任务,因为上面的 cmd 和这个 cmd 的 lock 对象不一致 + JobServiceConfiguration jobServiceConfiguration = springProcessEngineConfiguration.getJobServiceConfiguration(); + commandExecutor.execute(commandConfig, new ResetExpiredJobsCmd(Collections.singletonList(jobId), jobServiceConfiguration.getJobEntityManager(), jobServiceConfiguration)); + + log.info("exec auto pass transaction listener end"); + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoRejectTransactionListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoRejectTransactionListener.java new file mode 100644 index 000000000..4cd8bcbe6 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoRejectTransactionListener.java @@ -0,0 +1,61 @@ +package cn.axzo.workflow.core.engine.tx.listener; + +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.cmd.CustomRejectionTaskAsyncCmd; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.cfg.TransactionListener; +import org.flowable.common.engine.impl.interceptor.CommandConfig; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.job.service.JobServiceConfiguration; +import org.flowable.job.service.impl.asyncexecutor.ResetExpiredJobsCmd; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.Collections; + +import static org.flowable.common.engine.impl.interceptor.EngineConfigurationConstants.KEY_PROCESS_ENGINE_CONFIG; + +/** + * 自动拒绝的事务监听 + * + *
+ * 使用示例:
+ *  Context.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, new AutoRejectTransactionListener(delegateTask, extAxHiTaskInstService));
+ * 
+ * + * @author wangli + * @since 2024-08-07 10:26 + */ +@Slf4j +@AllArgsConstructor +public class AutoRejectTransactionListener implements TransactionListener { + private final DelegateTask delegateTask; + private final String operationDesc; + + @Override + public void execute(CommandContext commandContext) { + log.info("exec auto reject transaction listener start, processInstanceId: {}, taskId: {}", + delegateTask.getProcessInstanceId(), delegateTask.getId()); + + SpringProcessEngineConfiguration springProcessEngineConfiguration = (SpringProcessEngineConfiguration) commandContext + .getEngineConfigurations().get(KEY_PROCESS_ENGINE_CONFIG); + CommandConfig commandConfig = new CommandConfig().transactionRequired(); + + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + BpmnTaskAuditDTO reject = new BpmnTaskAuditDTO(); + reject.setTaskId(delegateTask.getId()); + reject.setApprover(new BpmnTaskDelegateAssigner("", "system", delegateTask.getTenantId())); + reject.setOperationDesc(operationDesc); + String jobId = commandExecutor.execute(commandConfig, new CustomRejectionTaskAsyncCmd(reject)); + + // 重置任务,因为上面的 cmd 和这个 cmd 的 lock 对象不一致 + JobServiceConfiguration jobServiceConfiguration = springProcessEngineConfiguration.getJobServiceConfiguration(); + commandExecutor.execute(commandConfig, + new ResetExpiredJobsCmd(Collections.singletonList(jobId), jobServiceConfiguration.getJobEntityManager(), jobServiceConfiguration)); + + log.info("exec auto reject transaction listener end"); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/DeleteTimerJobTransactionListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/DeleteTimerJobTransactionListener.java new file mode 100644 index 000000000..402ffb794 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/DeleteTimerJobTransactionListener.java @@ -0,0 +1,42 @@ +package cn.axzo.workflow.core.engine.tx.listener; + +import cn.axzo.workflow.common.model.dto.TermNodePausingDTO; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.cfg.TransactionListener; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.ManagementService; +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.cmd.DeleteTimerJobCmd; + +import java.util.Objects; + +/** + * 删除业务节点长时间卡住的定时处理器 + * + * @author wangli + * @since 2025-03-12 15:02 + */ +@Slf4j +@AllArgsConstructor +public class DeleteTimerJobTransactionListener implements TransactionListener { + private final TermNodePausingDTO dto; + + @Override + public void execute(CommandContext commandContext) { + log.info("clear timer job. instanceId: {}, activityId: {}", dto.getProcessInstanceId(), dto.getActivityId()); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + ManagementService managementService = processEngineConfiguration.getManagementService(); + Job timerJob = managementService.createTimerJobQuery() + .elementId(dto.getActivityId()) + .processInstanceId(dto.getProcessInstanceId()) + .singleResult(); + if (Objects.nonNull(timerJob)) { + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + commandExecutor.execute(new DeleteTimerJobCmd(timerJob.getId(), processEngineConfiguration.getJobServiceConfiguration())); + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/RobotTaskTransactionListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/RobotTaskTransactionListener.java new file mode 100644 index 000000000..0aee568d0 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/RobotTaskTransactionListener.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.core.engine.tx.listener; + +import cn.axzo.workflow.common.model.dto.SimpleTaskDTO; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.cfg.TransactionListener; +import org.flowable.common.engine.impl.interceptor.CommandContext; + +import java.util.List; + +/** + * 恢复创建机器人节点时暂停的审批人任务 + * + * @author wangli + * @since 2024-12-09 14:26 + */ +@Slf4j +@AllArgsConstructor +public class RobotTaskTransactionListener implements TransactionListener { + private final List tasks; + private final ExtAxProcessLogService processLogService; + + @Override + public void execute(CommandContext commandContext) { + processLogService.batchRestore(tasks); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/AbstractBpmnEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/AbstractBpmnEventListener.java new file mode 100644 index 000000000..de1d2069e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/AbstractBpmnEventListener.java @@ -0,0 +1,46 @@ +package cn.axzo.workflow.core.listener; + +import cn.axzo.workflow.core.common.context.OperationContext; +import cn.hutool.json.JSONUtil; +import org.slf4j.MDC; +import org.springframework.util.StringUtils; + +import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC; + +/** + * 操作上下文的抽象实现 + * + * @author wangli + * @since 2024/4/9 14:21 + */ +public abstract class AbstractBpmnEventListener implements OperationContext { + + private T context; + + @SuppressWarnings("unchecked") + @Override + public void setContext(OperationContext context) { + this.context = (T) context; + } + + @SuppressWarnings("unchecked") + @Override + public T getContext() { + return context; + } + + protected String getTraceId() { + return MDC.get(CTX_LOG_ID_MDC); + } + + protected String buildCacheKey(String apiUrl, Object request) { + return apiUrl + JSONUtil.toJsonStr(request); + } + + public static String parseProcessDefinitionKey(String processDefinitionId) { + if (!StringUtils.hasText(processDefinitionId)) { + return ""; + } + return processDefinitionId.split(":")[0]; + } +} 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..2b6ac9afd --- /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(Object obj); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnActivityEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnActivityEventListener.java index 46cf92619..b4b6aa6e8 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnActivityEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnActivityEventListener.java @@ -1,5 +1,7 @@ package cn.axzo.workflow.core.listener; +import cn.axzo.workflow.core.common.context.OperationContext; +import cn.axzo.workflow.core.engine.event.BizCallbackEvent; import cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEvent; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.core.Ordered; @@ -14,7 +16,7 @@ import org.springframework.core.Ordered; * @author wangli * @since 2023/7/24 17:27 */ -public interface BpmnActivityEventListener extends Ordered { +public interface BpmnActivityEventListener extends OperationContext, Ordered { /** * 节点已启动 @@ -35,6 +37,12 @@ public interface BpmnActivityEventListener extends Ordered { */ default void onTake(DelegateExecution execution) {} + /** + * 节点定时回调 + */ + default void onCallback(BizCallbackEvent event) { + } + /** * 节点已取消 * diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnAsyncJobEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnAsyncJobEventListener.java new file mode 100644 index 000000000..100bc647c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnAsyncJobEventListener.java @@ -0,0 +1,30 @@ +package cn.axzo.workflow.core.listener; + +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEvent; +import org.springframework.core.Ordered; + +/** + * 对引擎内异步任务执行异常的事件扩展监听接口 + * + * @author wangli + * @since 2024/4/29 16:57 + */ +public interface BpmnAsyncJobEventListener extends Ordered { + + /** + * 是否支持处理 + * + * @param eventType + * @return + */ + boolean support(FlowableEngineEventType eventType); + + /** + * 具体的处理逻辑 + * + * @param flowableEvent + */ + void notify(FlowableEvent flowableEvent); + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnMessagePushEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnMessagePushEventListener.java index f51af2b6d..bdc30abd7 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnMessagePushEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnMessagePushEventListener.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.listener; +import cn.axzo.workflow.core.common.context.OperationContext; import cn.axzo.workflow.core.engine.event.MessagePushEvent; import org.springframework.core.Ordered; @@ -9,14 +10,15 @@ import org.springframework.core.Ordered; * @author wangli * @since 2023/11/22 14:54 */ -public interface BpmnMessagePushEventListener extends Ordered { +public interface BpmnMessagePushEventListener extends OperationContext, Ordered { /** * 推送站内信 * * @param event */ - default void onNotice(MessagePushEvent event) {} + default void onNotice(MessagePushEvent event) { + } /** * 推送待办 @@ -34,13 +36,20 @@ public interface BpmnMessagePushEventListener extends Ordered { default void onPendingComplete(MessagePushEvent event) { } + /** + * 恢复待办 + * + * @param event + */ + default void onPendingRollback(MessagePushEvent event) { + } + /** * 抄送 * * @param event */ default void onCarbonCopy(MessagePushEvent event) { - } /** @@ -57,5 +66,14 @@ public interface BpmnMessagePushEventListener extends Ordered { * * @param event */ - default void onSms(MessagePushEvent event) {} + default void onSms(MessagePushEvent event) { + } + + /** + * 推送即时消息 + * + * @param event + */ + default void onIm(MessagePushEvent event) { + } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnProcessEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnProcessEventListener.java index 15206293a..0f5904aaf 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnProcessEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnProcessEventListener.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.listener; +import cn.axzo.workflow.core.common.context.OperationContext; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.delegate.event.FlowableProcessStartedEvent; @@ -10,7 +11,7 @@ import org.springframework.core.Ordered; *

* 微服版本使用, 统一在 Server module 中进行实现扩展, 不在该 module 中实现 */ -public interface BpmnProcessEventListener extends Ordered { +public interface BpmnProcessEventListener extends OperationContext, Ordered { /** * 流程实例创建成功后回调 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnTaskEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnTaskEventListener.java index b7c7296e6..1437a0970 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnTaskEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/BpmnTaskEventListener.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.listener; +import cn.axzo.workflow.core.common.context.OperationContext; import org.flowable.task.service.delegate.DelegateTask; import org.springframework.core.Ordered; @@ -7,35 +8,57 @@ import org.springframework.core.Ordered; * 流程审批任务相关事件, 供外部引入 jar 包方式使用的钩子 *

* 微服版本使用, 统一在 Server module 中进行实现扩展, 不在该 module 中实现. - * + *

* 特别注意: 在 Flowable Engine 中 onAssigned 执行优先级比 onCreated 更高 * * @author shao_hua */ -public interface BpmnTaskEventListener extends Ordered { +public interface BpmnTaskEventListener extends OperationContext, Ordered { /** * 用户任务已指派审核人 */ - default void onAssigned(DelegateTask delegateTask) {} + default void onAssigned(DelegateTask delegateTask) { + } /** * 用户任务已创建,未指派审核人 */ - default void onCreated(DelegateTask delegateTask) {} + default void onCreated(DelegateTask delegateTask) { + } /** * 用户任务已通过 *

* 仅审核通过一个用户任务时触发, 如果任务是驳回了, 则直接走实例撤回事件 */ - default void onCompleted(DelegateTask delegateTask) {} + default void onCompleted(DelegateTask delegateTask) { + } /** * 用户任务已删除 - * + *

* 删除不代表驳回或拒绝,因为通过也会走该事件 */ - default void onDeleted(DelegateTask delegateTask) {} + default void onDeleted(DelegateTask delegateTask) { + } + + /** + * 用户任务已转交 + * + * @param delegateTask + * @since 1.5.2 临时新增的动作类的事件 + */ + default void onTransfer(DelegateTask delegateTask) { + } + + /** + * 用户任务已回退 + * + * @param delegateTask + * @since 1.5.2 临时新增的动作类的事件 + */ + default void onFallback(DelegateTask delegateTask) { + } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/ExtTaskInstEventListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/ExtTaskInstEventListener.java index c0bcc35e8..22bb3e4a4 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/ExtTaskInstEventListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/ExtTaskInstEventListener.java @@ -4,18 +4,22 @@ import cn.axzo.workflow.core.engine.event.ExtTaskInstCreateEvent; import cn.axzo.workflow.core.engine.event.ExtTaskInstEvent; import cn.axzo.workflow.core.engine.event.ExtTaskInstUpdateEvent; import cn.axzo.workflow.core.engine.event.ReceiveTaskEventType; +import cn.axzo.workflow.core.engine.job.AsyncExtTaskInstJobHandler; import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; -import com.alibaba.fastjson.JSON; +import cn.hutool.json.JSONUtil; 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.FlowableEvent; +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.springframework.stereotype.Component; import org.springframework.util.StringUtils; -import java.util.Objects; import java.util.Set; import static cn.axzo.workflow.core.engine.event.ReceiveTaskEventType.CREATE; @@ -41,10 +45,15 @@ public class ExtTaskInstEventListener extends AbstractFlowableEventListener { .add(UPDATE) .build(); + /** + * 不要将该类的方法异步化, 否则有一连串的坑 + * + * @param flowableEvent + */ @Override public void onEvent(FlowableEvent flowableEvent) { if (flowableEvent instanceof ExtTaskInstEvent) { - log.info("Ext Task Inst Event : {}", JSON.toJSONString(flowableEvent)); + log.info("Ext Task Inst Event : {}, eventType: {}, processInstanceId: {}", JSONUtil.toJsonStr(flowableEvent), flowableEvent.getType(), ((ExtTaskInstEvent) flowableEvent).getProcessInstanceId()); ExtTaskInstEvent event = (ExtTaskInstEvent) flowableEvent; ReceiveTaskEventType eventType = (ReceiveTaskEventType) flowableEvent.getType(); if (SUPPORT_EVENTS.contains(eventType)) { @@ -79,14 +88,52 @@ public class ExtTaskInstEventListener extends AbstractFlowableEventListener { } private void updateExtTaskInst(ExtTaskInstUpdateEvent event) { - ExtAxHiTaskInst entity = extAxHiTaskInstService.getByTaskId(event.getTaskId(), event.getProcessInstanceId()); - if (Objects.isNull(entity)) { - return; - } - if (StringUtils.hasText(event.getAssignee())) { - entity.setAssignee(event.getAssignee()); - } - entity.setStatus(event.getResultEnum().getStatus()); - extAxHiTaskInstService.update(entity); + extAxHiTaskInstService.updateByTaskIdAndInstanceId(event.getTaskId(), event.getProcessInstanceId(), + event.getAssignee(), event.getResultEnum()); + // modify by wangli: 2024.6.4 移除异步任务,因为异步会导致“自动过审”的判断逻辑中,查询扩展表时,数据不一致 +// // 利用引擎异步任务的重试能力, 去正确更新扩展任务表的状态 +// createAsyncJob(event); } + + private void createAsyncJob(ExtTaskInstUpdateEvent event) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + processEngineConfiguration.getCommandExecutor().execute(commandContext -> { + JobService jobService = CommandContextUtil.getJobService(commandContext); + JobEntity job = jobService.createJob(); + job.setJobHandlerType(AsyncExtTaskInstJobHandler.TYPE); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(event)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + return null; + }); + } + + /*private void externalJob(ExtTaskInstUpdateEvent event) { + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + ManagementService managementService = processEngineConfiguration.getManagementService(); + commandExecutor.execute((Command) commandContext -> { + JobService jobService = CommandContextUtil.getJobService(commandContext); + JobServiceConfiguration jobServiceConfiguration = processEngineConfiguration.getJobServiceConfiguration(); + ExternalWorkerJobEntity workerJob = jobService.createExternalWorkerJob(); + workerJob.setJobHandlerConfiguration("extHiTaskInst"); + workerJob.setJobType(Job.JOB_TYPE_EXTERNAL_WORKER); + workerJob.setRetries(jobServiceConfiguration.getAsyncExecutorNumberOfRetries()); + workerJob.setJobHandlerType(AsyncExtTaskInstJobHandler.TYPE); + workerJob.setProcessInstanceId(event.getProcessInstanceId()); + workerJob.setCustomValues(JSONUtil.toJsonStr(event)); + jobService.insertExternalWorkerJob(workerJob); + return null; + }); + + List acquiredJobs = managementService.createExternalWorkerJobAcquireBuilder() + .topic("extHiTaskInst", Duration.ofMinutes(30)) + .acquireAndLock(3, "testWorker"); + acquiredJobs.forEach(i -> { + managementService.createExternalWorkerCompletionBuilder(i.getId(), "testWorker").complete(); + }); + }*/ } 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..a72a4a268 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalBpmnActivityEventListener_lo_Listener.java @@ -0,0 +1,130 @@ +package cn.axzo.workflow.core.listener.impl; + +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.model.dto.TermNodeAddTimerJobDTO; +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.tx.listener.AddTimerJobTransactionListener; +import cn.axzo.workflow.core.engine.tx.listener.DeleteTimerJobTransactionListener; +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.common.engine.impl.cfg.TransactionState; +import org.flowable.common.engine.impl.context.Context; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +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(), 0); + 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: + // 业务指定审批人,需要在业务设置了人后,设置定时 + String timeUnit; + switch (refreshProperties.getAlterIntervalUnit()) { + case MINUTES: + timeUnit = "M"; + break; + case HOURS: + timeUnit = "H"; + break; + default: + timeUnit = "S"; + break; + + } + + TermNodeAddTimerJobDTO addTimerJobDTO = new TermNodeAddTimerJobDTO(); + addTimerJobDTO.setProcessInstanceId(execution.getProcessInstanceId()); + addTimerJobDTO.setActivityId(execution.getCurrentActivityId()); + addTimerJobDTO.setTimeCycle("R100/PT" + refreshProperties.getAlterInterval() + timeUnit); + + Context.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, + new AddTimerJobTransactionListener(addTimerJobDTO)); + 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); +// } + TermNodePausingDTO dto = new TermNodePausingDTO(); + dto.setActivityId(execution.getCurrentActivityId()); + dto.setProcessInstanceId(execution.getProcessInstanceId()); + dto.setRetries(1); + Context.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, + new DeleteTimerJobTransactionListener(dto)); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalExtAxTaskInstEvent_min_Listener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalExtAxTaskInstEvent_lo_Listener.java similarity index 78% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalExtAxTaskInstEvent_min_Listener.java rename to workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalExtAxTaskInstEvent_lo_Listener.java index df05f5990..e34dd85fc 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalExtAxTaskInstEvent_min_Listener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/listener/impl/InternalExtAxTaskInstEvent_lo_Listener.java @@ -2,10 +2,11 @@ package cn.axzo.workflow.core.listener.impl; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.context.TaskOperationContext; import cn.axzo.workflow.core.engine.event.ExtTaskInstCreateEvent; import cn.axzo.workflow.core.engine.event.ExtTaskInstUpdateEvent; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; import cn.axzo.workflow.core.listener.BpmnTaskEventListener; -import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; @@ -13,6 +14,7 @@ import org.flowable.engine.RuntimeService; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.util.Objects; @@ -32,9 +34,9 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCES */ @Slf4j @Component +@Scope("prototype") @AllArgsConstructor -public class InternalExtAxTaskInstEvent_min_Listener implements BpmnTaskEventListener { - private final ExtAxHiTaskInstService hiTaskInstService; +public class InternalExtAxTaskInstEvent_lo_Listener extends AbstractBpmnEventListener implements BpmnTaskEventListener { private final RuntimeService runtimeService; @Override @@ -47,13 +49,13 @@ public class InternalExtAxTaskInstEvent_min_Listener implements BpmnTaskEventLis String assignee; // 记录发起人 if (Objects.equals(delegateTask.getTaskDefinitionKey(), NODE_STARTER.getType())) { - BpmnTaskDelegateAssigner assigner = - (BpmnTaskDelegateAssigner) runtimeService.getVariable(delegateTask.getProcessInstanceId(), - INTERNAL_INITIATOR); + BpmnTaskDelegateAssigner assigner = getContext().getInitiator(() -> + BpmnTaskDelegateAssigner.toObjectCompatible(runtimeService.getVariable(delegateTask.getProcessInstanceId(), + INTERNAL_INITIATOR))); if (Objects.isNull(assigner)) { // 兼容历史数据 - assigner = (BpmnTaskDelegateAssigner) runtimeService.getVariable(delegateTask.getProcessInstanceId(), - OLD_INTERNAL_INITIATOR); + assigner = getContext().getInitiator(() -> + BpmnTaskDelegateAssigner.toObjectCompatible(runtimeService.getVariable(delegateTask.getProcessInstanceId(), OLD_INTERNAL_INITIATOR))); } assignee = assigner.buildAssigneeId(); } else { @@ -71,9 +73,7 @@ public class InternalExtAxTaskInstEvent_min_Listener implements BpmnTaskEventLis @Override public void onDeleted(DelegateTask delegateTask) { Object operationType = delegateTask.getTransientVariable(TASK_COMPLETE_OPERATION_TYPE + delegateTask.getId()); - if (log.isDebugEnabled()) { - log.debug("taskId:{}, operationType:{}", delegateTask.getId(), operationType); - } + log.info("taskId:{}, operationType:{}, processInstanceId: {}", delegateTask.getId(), operationType, delegateTask.getProcessInstanceId()); // 多实例或签同意时,无法正确区分状态, 所以默认认为这种是无需展示在日志中的 BpmnProcessInstanceResultEnum resultEnum = DELETED; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/mq/CustomRocketMQEventProducer.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/mq/CustomRocketMQEventProducer.java new file mode 100644 index 000000000..7f57bb07f --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/mq/CustomRocketMQEventProducer.java @@ -0,0 +1,109 @@ +package cn.axzo.workflow.core.mq; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.RocketMQEventProducer; +import cn.axzo.framework.rocketmq.utils.TraceUtils; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.beans.BeanUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; + +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_APPLICATION; + +/** + * 默认的 RocketMQ 事件生产者的装饰器 + * + * @author wangli + * @since 2024/5/14 17:20 + */ +@Slf4j +public class CustomRocketMQEventProducer extends RocketMQEventProducer { + + private BiConsumer> sendBeforeCallback; + private BiConsumer> rollbackHandler; + private final String applicationName; + + public CustomRocketMQEventProducer(RocketMQTemplate rocketMQTemplate, String defaultModule, + String appName, Context defaultContext, + BiConsumer> sendBeforeCallback, + BiConsumer> sendAfterCallback, + BiConsumer> rollbackHandler) { + super(rocketMQTemplate, defaultModule, appName, defaultContext, sendAfterCallback); + this.applicationName = appName; + this.sendBeforeCallback = sendBeforeCallback; + this.rollbackHandler = rollbackHandler; + } + + @Override + public void send(@NonNull Event event, @NonNull Context context) { + log.info("发送事件内容:{}", event.toPrettyJsonString()); + if (sendBeforeCallback != null) { + sendBeforeCallback.accept(event, context); + } + // XXX:不要在send的时候修改event的值,有副作用。 + // 例如:当将同一个event发送到不同的topic的时候,buildSchemaHash会用不同的topic赋值两次,导致一些异常case + Event copiedEvent = Event.builder().build(); + BeanUtils.copyProperties(event, copiedEvent); + if (Strings.isNullOrEmpty(copiedEvent.getTargetId())) { + log.warn("targetId of event is black, best practice of targetId is present, event = {}", event.toJsonString()); + } + + if (copiedEvent.getData() == null) { + log.warn("data of event is empty, best practice of data must present, event = {}", event.toJsonString()); + } + Preconditions.checkArgument(!Strings.isNullOrEmpty(copiedEvent.getEventModule()), "eventModule不能为空"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(copiedEvent.getEventName()), "eventName不能为空"); + + // 复制一份 context,并加入链路跟踪信息 traceId + HashMap newHeaders = Maps.newHashMap(Optional.ofNullable(context.getHeaders()).orElse(ImmutableMap.of())); + newHeaders.put(TraceUtils.TRACE_ID, TraceUtils.getOrCreateTraceId()); + newHeaders.put(TraceUtils.CTX_LOG_ID, TraceUtils.getOrCreateTraceId()); + newHeaders.put(TraceUtils.TRACE_ID_IN_MDC, TraceUtils.getOrCreateTraceId()); + newHeaders.put(MQ_OWNERSHIP_APPLICATION, applicationName); + final Context copiedContext = context.toBuilder().headers(newHeaders).build(); + + Runnable runnable = () -> { + try { + getSender().accept(copiedEvent, copiedContext); + } catch (Exception e) { + log.error("====MQ PRODUCER ====, context={}, message = {}", copiedContext, copiedEvent.toPrettyJsonString(), e); + throw e; + } + }; + + Runnable rollbackRunnable = () -> { + try { + getRollbackHandler().accept(copiedEvent, copiedContext); + } catch (Exception e) { + // ignore + } + }; + if (copiedContext.isTransactional()) { + // https://www.jianshu.com/p/59891ede5f90 + log.info("runnable is transaction event={}", copiedEvent.toJsonString()); + runnable.run(); + } else { + // 并发会导致事件时序出现问题. 所以串行执行 + log.info("runnable not transaction event={}", copiedEvent.toJsonString()); + getAfterCommitExecutor().executeAndRollback(runnable, rollbackRunnable); + } + + List runnables = getAfterCommitExecutor().getRunnables(); + log.info("runnables.size(): {}", ListUtils.emptyIfNull(runnables).size()); + } + + @Override + public BiConsumer> getRollbackHandler() { + return rollbackHandler; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxApiLog.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxApiLog.java index df3803747..a180f272b 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxApiLog.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxApiLog.java @@ -16,7 +16,7 @@ import lombok.ToString; @TableName(value = "ext_ax_api_log", autoResultMap = true) @Data @ToString(callSuper = true) -public class ExtAxApiLog extends BaseEntity { +public class ExtAxApiLog extends BaseEntity { /** * 日志跟踪 ID @@ -28,6 +28,16 @@ public class ExtAxApiLog extends BaseEntity { */ private String apiUrl; + /** + * 请求方服务名称 + */ + private String requestApplicationName; + + /** + * 客户端 API 版本 + */ + private String clientVersion; + /** * 请求参数 */ diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxBpmnFormRelation.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxBpmnFormRelation.java new file mode 100644 index 000000000..8f1e62bf4 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxBpmnFormRelation.java @@ -0,0 +1,48 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 扩展的 bpmn 模型与表单模型的关系表 + * + * @author wangli + * @since 2024-11-04 15:35 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_bpmn_form_relation", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxBpmnFormRelation extends BaseEntity { + private static final long serialVersionUID = 1L; + /** + * 业务标识 + */ + @TableField(value = "`key`") + private String key; + /** + * bpmn 模型 ID + */ + private String bpmnDefinitionId; + /** + * 发布的定义 ID + */ + private String formDeploymentId; + /** + * 租户 ID + */ + private String tenantId; + /** + * 创建者 + */ + private Long createBy; + + /** + * 更新者 + */ + private Long updateBy; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDict.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDict.java index 6ae09bcc3..3e68f493f 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDict.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDict.java @@ -1,6 +1,7 @@ package cn.axzo.workflow.core.repository.entity; import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.enums.BusinessTypeEnum; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -12,6 +13,7 @@ import lombok.ToString; @Data @ToString(callSuper = true) public class ExtAxDict extends BaseEntity { + private static final long serialVersionUID = 720139149636794190L; /** * 编号,自增 */ @@ -62,4 +64,19 @@ public class ExtAxDict extends BaseEntity { * 工作台类型值 */ private String workspaceTypeCode; + + /** + * 图标 + */ + private String icon; + + /** + * 业务类型 + */ + private BusinessTypeEnum businessType; + + /** + * 是否展示在发起工作台 + */ + private Boolean displayInitiateMenu; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictConf.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictConf.java index 960f38a05..23604601b 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictConf.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictConf.java @@ -18,6 +18,8 @@ import lombok.ToString; @ToString(callSuper = true) public class ExtAxDictConf extends BaseEntity { + private static final long serialVersionUID = 4100924919539718197L; + /** * 字典 ID */ diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictGroup.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictGroup.java new file mode 100644 index 000000000..355944e03 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictGroup.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_dict_group", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxDictGroup extends BaseEntity { + + private static final long serialVersionUID = 7133194071098740752L; + + /** + * 对应字典id + */ + private Long dictId; + + /** + * 上级id + */ + private Long parentGroupId; + + /** + * 分组名 + */ + private String groupName; + + /** + * 顺序 + */ + private Integer ordinal; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictGroupVariable.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictGroupVariable.java new file mode 100644 index 000000000..8fce15f7a --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDictGroupVariable.java @@ -0,0 +1,47 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.enums.VarTypeEnum; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_dict_group_variable", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxDictGroupVariable extends BaseEntity { + + private static final long serialVersionUID = 2910796107844751820L; + + /** + * 字典id + */ + private Long dictId; + + /** + * 分组id + */ + private Long groupId; + + /** + * 变量类型,文本/图片 + */ + private VarTypeEnum type; + + /** + * 变量code + */ + private String code; + + /** + * 变量名称 + */ + private String name; + + /** + * 顺序 + */ + private Integer ordinal; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDocContent.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDocContent.java new file mode 100644 index 000000000..5afe0359a --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxDocContent.java @@ -0,0 +1,40 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 文件内容 + * + * @author wangli + * @since 2025-03-27 14:09 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_doc_content", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxDocContent extends BaseEntity { + + private static final long serialVersionUID = 5844913474486640969L; + + /** + * 文档ID + */ + private Long fileId; + + /** + * 文档类型 + */ + private String fileType; + + /** + * 文档内容 + */ + @TableField(insertStrategy = FieldStrategy.NOT_NULL, updateStrategy = FieldStrategy.NOT_NULL) + private String content; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxModelDoc.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxModelDoc.java new file mode 100644 index 000000000..fe00fa531 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxModelDoc.java @@ -0,0 +1,93 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 模板关联的文档 + * + * @author wangli + * @since 2025-03-27 14:09 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_model_doc", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxModelDoc extends BaseEntity { + + /** + * 文档被关联的模型 Id + */ + private String modelId; + + /** + * 文档被关联的模板对应的业务标识 + */ + private String modelKey; + + /** + * 文档关联 ID + *

+ * word/excel 对应 wps 的 Id, + * hp 对应 docContent 的 ID, + * pdf 对应 oss 中的 fileKey + */ + @TableField(updateStrategy = FieldStrategy.NOT_EMPTY) + private String fileRelationId; + + /** + * 文档名称 + */ + private String fileName; + + /** + * 模板名称 + */ + private String templateName; + + /** + * 自动归档路径 + */ + private String location; + + /** + * 文档类型 + */ + private String fileType; + + /** + * 业务标签 + */ + private String tag; + + /** + * 启用状态 + */ + private Boolean status; + + /** + * 临时文档 + */ + private Boolean tempFile; + /** + * 是否必选 + */ + @TableField(value = "`require`") + private Boolean require; + + /** + * 排序号 + */ + @TableField(value = "`order`") + private Integer order; + + /** + * 租户 ID + */ + private String tenantId; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxMqLog.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxMqLog.java new file mode 100644 index 000000000..731a6cc3b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxMqLog.java @@ -0,0 +1,57 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * MQ 发送记录表 + * + * @author wangli + * @since 2024/5/15 10:56 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_mq_log", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxMqLog extends BaseEntity { + /** + * 唯一ID + */ + private String uniqueId; + + /** + * MQ 的 MessageID + */ + private String messageId; + + /** + * MQ 的 Tag + */ + private String mqTag; + + /** + * MQ 的 Key + */ + private String mqKey; + + /** + * MQ 的 MessageBody + */ + private String messageBody; + + /** + * 链路追踪ID + */ + private String traceId; + + /** + * 线程名称 + */ + private String insertThreadName; + private String updateThreadName; + private String deleteThreadName; +} + 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..478ef04be --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProcessLog.java @@ -0,0 +1,130 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.model.dto.OrgStructureSnapshotInfo; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.form.FormPermissionMetaInfo; +import cn.axzo.workflow.core.conf.handler.ButtonConfTypeHandler; +import cn.axzo.workflow.core.conf.handler.ListAssigneeTypeHandler; +import cn.axzo.workflow.core.conf.handler.ListFormFieldPermissionTypeHandler; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler; +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 { + + private static final long serialVersionUID = 461756492937079852L; + + /** + * 流程实例 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; + /** + * 表单字段权限配置 + */ + @TableField(typeHandler = ListFormFieldPermissionTypeHandler.class) + private List formFieldPermissionConf; + /** + * 任务状态:审批中/通过/驳回/转交/加签/回退 + */ + private String status; + + /** + * 电子签名状态, true: 为开启 + */ + private Boolean signature; + /** + * 扩展字段 + */ + @TableField(typeHandler = FastjsonTypeHandler.class) + private OrgStructureSnapshotInfo extra; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProcessSign.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProcessSign.java new file mode 100644 index 000000000..2f7af6885 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProcessSign.java @@ -0,0 +1,50 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.model.dto.SignFileDTO; +import cn.axzo.workflow.core.conf.handler.ListSignFileDTOHandler; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +/** + * 审批日志 + * + * @author wangli + * @since 2024-08-30 15:29 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_process_sign", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxProcessSign extends BaseEntity { + + private static final long serialVersionUID = 461756492937079852L; + + /** + * 流程实例 ID + */ + private String processInstanceId; + /** + * 签署配置个签,还是群签 + */ + private String signType; + /** + * 签署业务待办模板 Code + */ + private String pendingMessageId; + /** + * 流程创建时,基于模型关联的模板 + */ + @TableField(typeHandler = ListSignFileDTOHandler.class) + private List docTemplate; + /** + * 文件归档 + */ + @TableField(typeHandler = ListSignFileDTOHandler.class) + private List fileArchive; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProperty.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProperty.java index 79272e41d..c0adf9e72 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProperty.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxProperty.java @@ -1,10 +1,13 @@ package cn.axzo.workflow.core.repository.entity; import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import lombok.ToString; /** @@ -17,6 +20,8 @@ import lombok.ToString; @TableName(value = "ext_ax_property", autoResultMap = true) @Data @ToString(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor public class ExtAxProperty extends BaseEntity { private static final long serialVersionUID = 1L; @@ -34,5 +39,12 @@ public class ExtAxProperty extends BaseEntity { @TableField("value") private String value; + /** + * 接入方是否使用了 manageable + */ + @TableField("manageable") + private Boolean manageable; + @TableField(value = "is_delete", fill = FieldFill.INSERT) + private Long isDelete = 0L; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxReModel.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxReModel.java index f1f655e8f..64541efdc 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxReModel.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxReModel.java @@ -1,6 +1,7 @@ package cn.axzo.workflow.core.repository.entity; import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -35,4 +36,27 @@ public class ExtAxReModel extends BaseEntity { @TableField("status") private Integer status; + /** + * 打印开关状态(1正常 0停用) + */ + @TableField("print_status") + private Integer printStatus; + + /** + * 打印文件名称 + */ + @TableField(value = "print_file_name", updateStrategy = FieldStrategy.IGNORED) + private String printFileName; + + /** + * 打印模板名称 + */ + @TableField(value = "print_template_name", updateStrategy = FieldStrategy.IGNORED) + private String printTemplateName; + + /** + * 打印模板配置内容 + */ + @TableField(value = "print_template_config", updateStrategy = FieldStrategy.IGNORED) + private String printTemplateConfig; } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxReadRecord.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxReadRecord.java new file mode 100644 index 000000000..98f66cbd9 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/entity/ExtAxReadRecord.java @@ -0,0 +1,43 @@ +package cn.axzo.workflow.core.repository.entity; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +import cn.axzo.workflow.core.conf.handler.ListSimpleDocDTOHandler; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +/** + * 审批日志 + * + * @author wangli + * @since 2024-08-30 15:29 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "ext_ax_read_record", autoResultMap = true) +@Data +@ToString(callSuper = true) +public class ExtAxReadRecord extends BaseEntity { + + private static final long serialVersionUID = 461756492937079852L; + + /** + * 流程实例 ID + */ + private String processInstanceId; + + /** + * 自热人 ID + */ + private Long personId; + + /** + * 多文档的阅读状态记录 + */ + @TableField(typeHandler = ListSimpleDocDTOHandler.class) + private List readStatus; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/CommonMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/CommonMapper.java new file mode 100644 index 000000000..e433da08c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/CommonMapper.java @@ -0,0 +1,18 @@ +package cn.axzo.workflow.core.repository.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * 适用于通用的操作 mapper + * + * @author wangli + * @since 2024/4/28 14:44 + */ +@Mapper +public interface CommonMapper { + List> executeDynamicSQL(@Param("sql") String sql); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxBpmnFormRelationMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxBpmnFormRelationMapper.java new file mode 100644 index 000000000..97f3bc648 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxBpmnFormRelationMapper.java @@ -0,0 +1,15 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * bpmn 模型与表单模型的映射 + * + * @author wangli + * @since 2024-11-04 15:38 + */ +@Mapper +public interface ExtAxBpmnFormRelationMapper extends BaseMapper { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDictGroupMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDictGroupMapper.java new file mode 100644 index 000000000..0737bd894 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDictGroupMapper.java @@ -0,0 +1,9 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxDictGroup; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ExtAxDictGroupMapper extends BaseMapper { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDictGroupVariableMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDictGroupVariableMapper.java new file mode 100644 index 000000000..81d7626f7 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDictGroupVariableMapper.java @@ -0,0 +1,9 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxDictGroupVariable; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ExtAxDictGroupVariableMapper extends BaseMapper { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDocContentMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDocContentMapper.java new file mode 100644 index 000000000..e46125e42 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxDocContentMapper.java @@ -0,0 +1,8 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxDocContent; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ExtAxDocContentMapper extends BaseMapperX { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxModelDocMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxModelDocMapper.java new file mode 100644 index 000000000..a232ef29e --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxModelDocMapper.java @@ -0,0 +1,12 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface ExtAxModelDocMapper extends BaseMapperX { + + @Select("SELECT * FROM EXT_AX_MODEL_DOC WHERE ID = #{id}") + ExtAxModelDoc getIgnoreDelete(Long id); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxMqLogMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxMqLogMapper.java new file mode 100644 index 000000000..eddfe69f3 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxMqLogMapper.java @@ -0,0 +1,8 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxMqLog; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ExtAxMqLogMapper extends BaseMapperX { +} 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..185b648ac --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessLogMapper.java @@ -0,0 +1,17 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +@Mapper +public interface ExtAxProcessLogMapper extends BaseMapperX { + + @Update("UPDATE ext_ax_process_log SET is_delete = 0, start_time = NOW() WHERE process_instance_id = #{processInstanceId} and task_id = #{taskId}") + void restore(String processInstanceId, String taskId); + + + @Select("select * from ext_ax_process_log WHERE process_instance_id = #{processInstanceId} and task_id = #{taskId}") + ExtAxProcessLog findByProcessIdAndTaskIdWithDeleted(String processInstanceId, String taskId); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessSignMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessSignMapper.java new file mode 100644 index 000000000..b5e09882c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxProcessSignMapper.java @@ -0,0 +1,9 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ExtAxProcessSignMapper extends BaseMapperX { + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxReadRecordMapper.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxReadRecordMapper.java new file mode 100644 index 000000000..c9819df54 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/repository/mapper/ExtAxReadRecordMapper.java @@ -0,0 +1,9 @@ +package cn.axzo.workflow.core.repository.mapper; + +import cn.axzo.workflow.core.repository.entity.ExtAxReadRecord; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ExtAxReadRecordMapper extends BaseMapperX { + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/AggregateModelService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/AggregateModelService.java new file mode 100644 index 000000000..d55e02489 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/AggregateModelService.java @@ -0,0 +1,22 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelUpdateDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; + +import javax.annotation.Nullable; + +/** + * 聚合审批模型和表单模型的操作服务 + * + * @author wangli + * @since 2024-11-05 16:37 + */ +public interface AggregateModelService { + + String createBpmnAndFormModel(BpmnModelCreateDTO dto); + + String updateBpmnAndFormModel(BpmnModelUpdateDTO dto); + + String deployBpmnAndFormModel(String processModelId, @Nullable String tenantId, BpmnTaskDelegateAssigner operator); +} 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 83da32776..eaad658f2 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 @@ -1,6 +1,9 @@ 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; /** * 流程活动的 Service @@ -13,9 +16,11 @@ public interface BpmnProcessActivityService { /** * 唤醒业务节点 * - * @param executionId 活动 ID + * @param dto */ - void trigger(String executionId); + void trigger(BpmnActivityTriggerDTO dto); + + /** * 给指定实例的指定节点重设审批人 @@ -23,4 +28,20 @@ public interface BpmnProcessActivityService { * @param dto */ void setAssignee(BpmnActivitySetAssigneeDTO dto); + + /** + * 设置指定业务接口继续往下流转的触发时间 + * + * @param dto + * @return + */ + Boolean setTimeoutTrigger(BpmnActivityTimeoutTriggerDTO dto); + + /** + * 设置指定业务节点定时回调(仅通过 MQ 事件广播,自身状态不做任何改变) + * + * @param dto + * @return + */ + Boolean setTimeOutCallback(BpmnActivityTimeoutCallbackDTO 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..4a4a58f6c 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; @@ -75,11 +78,19 @@ public interface BpmnProcessDefinitionService { * 获取指定模型激活的定义 ID * @return 流程定义ID */ - String getActiveProcessDefinitionId(String tenantId, String category); + String getActiveProcessDefinitionId(String tenantId, String key); void updateProcessDefinition(BpmnProcessDefinitionUpdateDTO dto); 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/BpmnProcessInstanceForEsService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceForEsService.java new file mode 100644 index 000000000..283e3e51c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessInstanceForEsService.java @@ -0,0 +1,37 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.dto.es.HistoricProcessInstanceSearchForEsDTO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.history.HistoricProcessInstance; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 专用与对接 ES 的流程实例相关操作 + * + * @author wangli + * @since 2024-09-27 14:23 + */ +public interface BpmnProcessInstanceForEsService { + + Long queryHistoricProcessInstanceTotalCount(HistoricProcessInstanceSearchForEsDTO search); + + List queryHistoricProcessInstance(HistoricProcessInstanceSearchForEsDTO search, IPage page); + + BpmnModel queryBpmnModel(String processDefinitionId); + + Map queryInstanceVariables(String processInstanceId, List variableNames); + + Long queryHistoricProcessInstanceByUnfinishedAndAlterEndTimeTotalCount(Date endTime); + + /** + * 查询指定时间点之后的完成的审批,和还未完成的审批 + * @param endTime + * @param page + * @return + */ + List queryHistoricProcessInstanceByUnfinishedAndAlterEndTime(Date endTime, IPage page); +} 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 125bf5332..5121fc0de 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 @@ -1,22 +1,33 @@ package cn.axzo.workflow.core.service; +import cn.axzo.workflow.common.model.request.bpmn.process.BeforeProcessInstanceCreateDTO; 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.request.bpmn.process.HistoricProcessInstanceSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; 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.NodesByModelVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.process.doc.DocPendingVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo; 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; @@ -25,13 +36,13 @@ import java.util.List; public interface BpmnProcessInstanceService { + List nodesBeforeCreateProcessInstance(BeforeProcessInstanceCreateDTO dto); + /** * 发起审核 */ String createProcessInstance(BpmnProcessInstanceCreateDTO processInstanceCreateDTO); - String createProcessInstanceWithForm(BpmnProcessInstanceCreateWithFormDTO createWithFormDTO); - FormInfo getStartFormModel(String processDefinitionId, String processInstanceId); /** @@ -39,7 +50,7 @@ public interface BpmnProcessInstanceService { * * @return true or false, true mean's cancelled */ - Boolean cancelProcessInstance(BpmnProcessInstanceCancelDTO processInstanceCancelDTO); + Boolean cancelProcessInstance(SuperBpmnProcessInstanceCancelDTO processInstanceCancelDTO); /** * 中止流程实例 @@ -49,6 +60,14 @@ public interface BpmnProcessInstanceService { */ Boolean abortProcessInstance(BpmnProcessInstanceAbortDTO dto); + /** + * 批量中止流程实例 + * + * @param dtos + * @return + */ + BatchOperationResultVO batchAbortProcessInstance(List dtos); + /** * 抄送流程实例 * @@ -96,8 +115,8 @@ public interface BpmnProcessInstanceService { /** * 获得流程实例 * - * @param processInstanceId 流程实例的编号 - * @param status 状态 + * @param processDefinitionId 流程实例的编号 + * @param status 状态 * @link SuspensionState.ACTIVE.getStateCode() */ Boolean updateProcessStatus(String processDefinitionId, Integer status); @@ -113,9 +132,6 @@ public interface BpmnProcessInstanceService { /** * 获得流程实例 VO 信息 * - * @param processInstanceId 流程实例的编号 - * @param businessKey 业务key - * @param tenantId 租户 ID * @return 流程实例 */ BpmnProcessInstanceVO getProcessInstanceVO(BpmnProcessInstanceQueryDTO dto); @@ -124,9 +140,65 @@ public interface BpmnProcessInstanceService { BpmPageResult historicProcessInstancePage(HistoricProcessInstanceSearchDTO dto); + /** + * 流程实例全节点推测 + * + * @param processInstanceId + * @param tenantId + * @return + */ 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); + + /** + * 根据流程实例id,任务id,查询任务状态,按钮配置 + * + * @param taskButtonsSearchDTO 请求参数 + * @return 任务按钮,状态信息 + */ + BpmnTaskButtonVo findTaskButtons(BpmnTaskButtonSearchDTO taskButtonsSearchDTO); + + /** + * 获取指定流程实例对应的模型 ID + * @param processInstanceId + * @return + */ + String getModelIdByProcessInstanceId(String processInstanceId); + + String getModelIdByProcessDefinitionId(String processInstanceId, String processDefinitionId); + + /** + * 覆盖指定流程表单的指定表单项 + * + * @param dto + */ + void overrideFormVariables(FormVariablesUpdateDTO dto); + + boolean hasPrintTemplate(String processInstanceId, String processDefinitionId); + + List processInstanceSelectDocs(ProcessDocQueryDTO 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 new file mode 100644 index 000000000..1bad2f2cd --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessJobService.java @@ -0,0 +1,12 @@ +package cn.axzo.workflow.core.service; + +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/BpmnProcessModelService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessModelService.java index aec21350e..82ab67b80 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessModelService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessModelService.java @@ -3,9 +3,11 @@ package cn.axzo.workflow.core.service; 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.print.PrintTemplateConfigUpsertDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; 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.print.PrintModelDTO; import io.swagger.annotations.ApiOperation; import javax.annotation.Nullable; @@ -65,7 +67,13 @@ public interface BpmnProcessModelService { void changeStatus(String modelId, Integer status, BpmnTaskDelegateAssigner operator); + void changePrintStatus(String modelId, Integer status, BpmnTaskDelegateAssigner assignee); + List getModelCategoryList(); List getTenantIds(); + + void printTemplateConfig(PrintTemplateConfigUpsertDTO dto); + + PrintModelDTO getPrintTemplateConfig(String modelId); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessTaskForEsService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessTaskForEsService.java new file mode 100644 index 000000000..de8ad282c --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessTaskForEsService.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import org.flowable.engine.task.Attachment; +import org.flowable.engine.task.Comment; +import org.flowable.task.api.history.HistoricTaskInstance; + +import java.util.List; + +/** + * 专用与对接 ES 的流程任务相关操作 + * + * @author wangli + * @since 2024-09-29 10:55 + */ +public interface BpmnProcessTaskForEsService { + + List queryHistoricProcessTaskByProcessInstanceId(String processInstanceId); + + List queryExtAxHiTaskInstByProcessInstanceId(String processInstanceId); + + List queryCommentByProcessInstanceId(String processInstanceId); + + List queryAttachmentByProcessInstanceId(String processInstanceId); + + List queryProcessLogByProcessInstanceId(String processInstanceId); +} 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 71f1ccad1..b03b43964 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,23 +1,13 @@ package cn.axzo.workflow.core.service; -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.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.request.bpmn.task.*; import cn.axzo.workflow.common.model.response.BpmPageResult; -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.bpmn.BatchOperationResultVO; +import cn.axzo.workflow.common.model.response.bpmn.task.*; import org.flowable.form.api.FormInfo; import java.util.List; +import java.util.Map; public interface BpmnProcessTaskService { @@ -38,11 +28,52 @@ public interface BpmnProcessTaskService { */ void approveTask(BpmnTaskAuditDTO taskAuditDTO); + void approveTaskWithForm(BpmnTaskAuditWithFormDTO taskAuditDto); + + /** + * 回退 + */ + void backTask(BpmnTaskBackAuditDTO taskAuditDTO); + + /** + * 用于系统内部回退操作 + * @param taskAuditDTO 请求参数 + */ + void systemBackTask(BpmnNodeBackSystemOperateDTO taskAuditDTO); + + /** + * 批量同意 + * + * @param taskAuditDTOS 请求参数列表 + */ + BatchOperationResultVO batchApproveTask(List taskAuditDTOS); + + /** + * 回退到指定节点,可以回退节点选项 + * @param taskId 流程任务id + */ + List getBackOptionalNodesByTaskId(String taskId); + + /** + * 回退到指定节点,可以回退节点选项 + * @param processInstanceId 流程实例id + * @param currentActivityId 当前节点定义id + */ + List getBackOptionalNodes(String processInstanceId, String currentActivityId); + /** * 驳回 */ void rejectTask(BpmnTaskAuditDTO taskAuditDTO); + /** + * 批量驳回 + * + * @param taskAuditDTOS + * @return + */ + BatchOperationResultVO batchRejectTask(List taskAuditDTOS); + /** * 获取历史已审批的列表详情 *

@@ -80,8 +111,16 @@ public interface BpmnProcessTaskService { */ void transferTask(BpmnTaskTransferDTO dto); + /** + * 批量转交 + * + * @param dtos + */ + BatchOperationResultVO batchTransferTask(List dtos); + /** * 评论 + * * @param dto */ void commentTask(BpmnTaskCommentDTO dto); @@ -97,10 +136,13 @@ public interface BpmnProcessTaskService { /** * 加签 目前加签只支持会签 + * * @param countersignDTO */ void countersignTask(BpmnTaskCountersignDTO countersignDTO); + void resetTaskApprovers(BpmnTaskResetApproversDTO dto); + void remindTask(BpmnTaskRemindDTO dto); String createRobotTask(BpmnRobotTaskCreateDTO dto); @@ -108,4 +150,7 @@ public interface BpmnProcessTaskService { void completeRobotTask(BpmnRobotTaskCompleteDTO dto); String findTaskIdByInstanceIdAndPersonId(String processInstanceId, String personId); + + Map findTaskIdByInstanceIdsAndPersonId(List processInstanceIds, String personId); + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessVariableService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessVariableService.java new file mode 100644 index 000000000..60b2f06f5 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/BpmnProcessVariableService.java @@ -0,0 +1,41 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable; + +import java.util.List; + +public interface BpmnProcessVariableService { + + /** + * 添加流程变量,如果流程变量已存在,会抛出异常 + * + * @param executionId 流程实例id + * @param restVariable 新增流程变量值 + */ + void createVariable(String executionId, RestBpmnProcessVariable restVariable); + + /** + * 更新流程变量,如果流程变量不存在,会抛出异常 + * + * @param executionId 流程实例id + * @param restVariable 需要更新的流程变量内容 + */ + void updateVariable(String executionId, RestBpmnProcessVariable restVariable); + + /** + * 批量更新流程变量,如果流程变量不存在,会抛出异常 + * + * @param executionId 流程实例id + * @param restVariables 需要更新的流程列表 + */ + void updateVariables(String executionId, List restVariables); + + /** + * 删除流程变量 + * + * @param executionId 流程实例id + * @param variableNames 需要删除流程变量名称列表 + * @param scope 删除变量的范围 + */ + void deleteVariables(String executionId, List variableNames, String scope); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryConfigService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryConfigService.java index eb0063a1a..54f3d62f1 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryConfigService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryConfigService.java @@ -2,8 +2,11 @@ package cn.axzo.workflow.core.service; 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.CategoryGroupVarSearchDto; +import cn.axzo.workflow.common.model.request.category.CategoryGroupVarUpsertDto; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; import java.util.List; diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryGroupService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryGroupService.java new file mode 100644 index 000000000..08c63adf4 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryGroupService.java @@ -0,0 +1,24 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.request.category.CategoryGroupVarSearchDto; +import cn.axzo.workflow.common.model.request.category.CategoryGroupVarUpsertDto; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; + +import java.util.List; + +public interface CategoryGroupService { + + /** + * 查询业务分类下的分组以及变量 + * @param dto 请求参数 + * @return 分组以及对应变量列表 + */ + List searchGroupAndVarList(CategoryGroupVarSearchDto dto); + + /** + * 新增/更新 分组以及变量 + * @param dto 请求桉树 + * @return 是否成功 + */ + Boolean upsertGroupAndVars(CategoryGroupVarUpsertDto dto); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryGroupVariableService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryGroupVariableService.java new file mode 100644 index 000000000..ea9e800c5 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/CategoryGroupVariableService.java @@ -0,0 +1,8 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.core.repository.entity.ExtAxDictGroupVariable; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface CategoryGroupVariableService extends IService { + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxBpmnFormRelationService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxBpmnFormRelationService.java new file mode 100644 index 000000000..b9277dd9a --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxBpmnFormRelationService.java @@ -0,0 +1,32 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.dto.BpmnFormRelationCreateDTO; +import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; + +import java.util.List; + +/** + * ext_ax_bpmn_form_relation + * + * @author wangli + * @since 2024-11-04 15:34 + */ +public interface ExtAxBpmnFormRelationService { + Long insert(BpmnFormRelationCreateDTO dto); + + ExtAxBpmnFormRelation queryByBpmnDefinitionId(String bpmnDefinitionId); + + /** + * 用于查询能被用于表单发起的审批 + * @param dto + * @return + */ + List keyQuery(BpmnFormRelationSearchDTO dto); + + List genericQuery(BpmnFormRelationSearchDTO dto); + + BpmPageResult genericPageQuery(BpmnFormRelationSearchDTO dto); + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxDocContentService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxDocContentService.java new file mode 100644 index 000000000..f3375e35f --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxDocContentService.java @@ -0,0 +1,28 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.core.repository.entity.ExtAxDocContent; + +import java.util.List; + +/** + * 文件内容表 + * + * @author wangli + * @since 2025-03-28 18:12 + */ +public interface ExtAxDocContentService { + + List getByIds(List ids); + + ExtAxDocContent createContent(String content, FileTypeEnum fileType, Long docId); + + ExtAxDocContent updateContent(String content, FileTypeEnum fileType, String docId); + + ExtAxDocContent deleteContent(String docId); + + List batchDeleteContent(List docIds); + + String getContent(Long docId); + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxHiTaskInstService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxHiTaskInstService.java index 9292aa150..6cee6a715 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxHiTaskInstService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxHiTaskInstService.java @@ -1,5 +1,6 @@ package cn.axzo.workflow.core.service; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; @@ -22,4 +23,6 @@ public interface ExtAxHiTaskInstService { ExtAxHiTaskInst getByTaskId(String taskId, String processInstanceId); void deleteStatusByTaskId(String taskId, String processInstanceId); + + void updateByTaskIdAndInstanceId(String taskId, String processInstanceId, String assignee, BpmnProcessInstanceResultEnum resultEnum); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxModelDocService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxModelDocService.java new file mode 100644 index 000000000..78d7dde32 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxModelDocService.java @@ -0,0 +1,128 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCloneDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO; +import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc; + +import java.util.List; + +/** + * 模型关联文档 + * + * @author wangli + * @since 2025-03-28 18:12 + */ +public interface ExtAxModelDocService { + + /** + * 模型文档分页搜索 + * + * @param dto + * @return + */ + BpmPageResult docPage(DocSearchDTO dto); + + List docList(DocQueryDTO dto); + + List querySetting(String key, String tenantId); + + /** + * 获取指定文档 + * + * @param docId + * @return + */ + DocBaseVO get(Long docId); + + /** + * 获取指定文档忽略删除 + * + * @param docId + * @return + */ + DocBaseVO get(Long docId, boolean ignoreDelete); + + /** + * 模型文档创建 + * + * @param dto + * @return + */ + Long createDoc(DocCreateDTO dto, Boolean fireEvent); + + /** + * 模型文档更新 + * + * @param dto + * @return + */ + Boolean updateDoc(DocUpdateDTO dto); + + /** + * 克隆文档 + * + * @param dto + * @return + */ + Long cloneDoc(DocCloneDTO dto); + + /** + * 特殊场景的克隆,一般情况下请调用上面的接口。 + *

+ * 该接口内部,会在 ext_ax_model_doc 中生成一条新的记录,但其 temp_file 标识是 true + * + * @param dto + * @param tempFile + * @return + */ + Long cloneDoc(DocCloneDTO dto, Boolean tempFile); + + /** + * 模型文档删除 + * + * @param docId + * @return + */ + Boolean deleteDoc(Long docId); + + /** + * 批量删除 + * + * @param docIds 文档列表 + * @return 是否成功 + */ + Boolean batchDeleteDoc(List docIds); + + /** + * 模型文档排序 + * + * @param dto + * @return + */ + Boolean orderDoc(DocOrderDTO dto); + + /** + * 修改文档状态 + * + * @param dto + * @return + */ + Boolean statusDoc(DocStatusDTO dto); + + /** + * 修改文档必选 + * + * @param dto + * @return + */ + Boolean requireDoc(DocStatusDTO dto); + + List getIds(List ids); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxMqLogService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxMqLogService.java new file mode 100644 index 000000000..c93af1e64 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxMqLogService.java @@ -0,0 +1,17 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.core.repository.entity.ExtAxMqLog; + +/** + * Mq Log 表操作服务 + * + * @author wangli + * @since 2024/4/3 10:40 + */ +public interface ExtAxMqLogService { + Long insert(ExtAxMqLog mqLog); + + void update(ExtAxMqLog mqLog); + + void delete(ExtAxMqLog mqLog); +} 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..a267d7e4a --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxProcessLogService.java @@ -0,0 +1,70 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.dto.OrgStructureSnapshotInfo; +import cn.axzo.workflow.common.model.dto.SimpleTaskDTO; +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 updateAssigneeAndSnapshot(ExtAxProcessLog updateLog, BpmnTaskDelegateAssigner assignee, OrgStructureSnapshotInfo snapshotInfo); + + void updateAssigneeAndSnapshot(ExtAxProcessLog updateLog, BpmnTaskDelegateAssigner assignee, OrgStructureSnapshotInfo snapshotInfo, String operationDesc); + + List genericQuery(ExtAxProcessLog query); + + ExtAxProcessLog findByProcessIdAndTaskIdWithDeleted(String processId, String taskId); + + /** + * 机器人节点会删除日志,这里仅仅为了恢复日志都删除标识,并更新时间。 + * + * @param processInstanceId + * @param taskId + */ + void restore(String processInstanceId, String taskId); + + void batchRestore(List tasks); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxProcessSignService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxProcessSignService.java new file mode 100644 index 000000000..ec221457d --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxProcessSignService.java @@ -0,0 +1,15 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * Api Log 表操作服务 + * + * @author wangli + * @since 2024/4/3 10:40 + */ +public interface ExtAxProcessSignService extends IService { + + ExtAxProcessSign findByProcessInstanceId(String processInstanceId); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxPropertyService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxPropertyService.java index 289c27d0e..caf684e89 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxPropertyService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxPropertyService.java @@ -3,6 +3,7 @@ package cn.axzo.workflow.core.service; import cn.axzo.workflow.core.repository.entity.ExtAxProperty; import java.util.Optional; +import java.util.concurrent.TimeUnit; /** * 引擎服务持久配置信息表操作 Service @@ -17,4 +18,17 @@ public interface ExtAxPropertyService { ExtAxProperty update(ExtAxProperty property); Optional getByName(String name); + + int delete(String name, String value); + + /** + * 删除指定时间段之前创建的数据 + * + * @param name + * @param timeOut + * @param timeUnit + * @return + */ + int deleteByNameWithDuration(String name, Long timeOut, TimeUnit timeUnit); + } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReModelService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReModelService.java index 9c3fa39de..5dc3301f2 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReModelService.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReModelService.java @@ -1,6 +1,9 @@ package cn.axzo.workflow.core.service; +import cn.axzo.workflow.common.enums.ExtModelStateFieldEnum; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO; import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO; +import cn.axzo.workflow.common.model.response.print.PrintModelDTO; import cn.axzo.workflow.core.repository.entity.ExtAxReModel; import java.util.List; @@ -18,7 +21,26 @@ public interface ExtAxReModelService { void update(String modelId, Integer status); - void changeStatus(String modelId, Integer status); + /** + * 修改模型扩展表的状态 + * + * @param modelId + * @param status + * @param field “status”代运营开关状态 or “printStatus”打印开关状态 + */ + void changeStatus(String modelId, Integer status, ExtModelStateFieldEnum field); BpmnModelExtVO getStatusByModelId(String modelId); + + void printTemplateConfig(PrintTemplateConfigUpsertDTO dto); + + /** + * 本方法主要判断开关以及有打印模板 + * + * @param modelId + * @return + */ + Boolean hasPrintTemplateConfig(String modelId); + + PrintModelDTO getPrintTemplateConfig(String modelId); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReadRecordService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReadRecordService.java new file mode 100644 index 000000000..cbc092036 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/ExtAxReadRecordService.java @@ -0,0 +1,22 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; +import cn.axzo.workflow.core.repository.entity.ExtAxReadRecord; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 审批人关联文档阅读记录表操作服务 + * + * @author wangli + * @since 2024/4/3 10:40 + */ +public interface ExtAxReadRecordService extends IService { + + List queryReadStatus(ApproverReadStatusDTO dto); + + Boolean changeReadStatus(ChangeApproverReadStatusDTO dto); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormCoreService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormCoreService.java new file mode 100644 index 000000000..2729776e5 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormCoreService.java @@ -0,0 +1,25 @@ +package cn.axzo.workflow.core.service; + +import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO; +import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO; +import cn.axzo.workflow.common.model.response.form.FormVO; +import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; + +import java.util.List; + +/** + * 由于 POM 的结构的问题,这个类主要解决循环依赖的问题 + * + * @author wangli + * @since 2024-11-14 10:17 + */ +public interface FormCoreService { + + List pageForm(BpmnFormRelationSearchDTO dto); + + FormDefinitionVO getStartForm(StartFormSearchDTO dto); + + FormInstanceVO getFormInstance(FormDetailDTO dto); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormDefinitionService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormDefinitionService.java deleted file mode 100644 index 44c6d36b7..000000000 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormDefinitionService.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.axzo.workflow.core.service; - - -import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionUpdateDTO; -import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; - -import javax.annotation.Nullable; - -/** - * 表单定义相关服务接口 - * - * @author wangli - * @since 2023/7/19 16:46 - */ -public interface FormDefinitionService { - - FormDefinitionVO get(String formModeId, @Nullable String tenantId); - - void updateFormDefinition(FormDefinitionUpdateDTO dto); - -} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormInstanceService.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormInstanceService.java deleted file mode 100644 index 08ab8c351..000000000 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormInstanceService.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.axzo.workflow.core.service; - - -import cn.axzo.workflow.common.model.request.form.instance.FormContentUpdateDTO; - -/** - * 表单实例相关接口 - * - * @author wangli - * @since 2023/7/21 15:19 - */ -public interface FormInstanceService { - - /** - * 更新表单填写的数据 - * - * @param dto - */ - void updateFormContent(FormContentUpdateDTO dto); -} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessDefinitionConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessDefinitionConverter.java index 872ae0bbf..ac68a7444 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessDefinitionConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessDefinitionConverter.java @@ -52,6 +52,9 @@ public interface BpmnProcessDefinitionConverter 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()); // 进行中的节点 @@ -82,10 +82,11 @@ public interface BpmnProcessInstanceAdminPageItemConverter extends EntityConvert } CategoryItemVO category = categoryMap.getOrDefault(i.getProcessDefinitionKey(), new CategoryItemVO()); + vo.setCategory(category.getValue()); 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/converter/BpmnProcessInstanceConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessInstanceConverter.java index 846da76d1..112ee9e59 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessInstanceConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/BpmnProcessInstanceConverter.java @@ -1,6 +1,7 @@ package cn.axzo.workflow.core.service.converter; 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.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; @@ -47,18 +48,24 @@ public interface BpmnProcessInstanceConverter extends EntityConverter defaultButtonConf, + Optional approveConf, BpmnTaskDelegateAssigner assigner, String version, Optional category, List runningTasks) { BpmnProcessInstanceVO vo = toVo(processInstance); - vo.setCategory(processDefinition.getCategory()); + vo.setCategory(processDefinition.getKey()); vo.setProcessDefinitionId(processDefinition.getId()); vo.setProcessDefinitionKey(processInstance.getProcessDefinitionKey()); vo.setInitiator(assigner); vo.setWorkflowEngineVersion(version); defaultButtonConf.ifPresent(vo::setDefaultButtonConf); - category.ifPresent(i -> vo.setWorkspaceType(WorkspaceType.getType(Integer.valueOf(i.getWorkspaceTypeCode())))); + approveConf.ifPresent(vo::setProcessApproveConf); + category.ifPresent(i -> { + vo.setWorkspaceType(WorkspaceType.getType(Integer.valueOf(i.getWorkspaceTypeCode()))); + vo.setCategoryDesc(i.getLabel()); + vo.setCategory(i.getValue()); + }); if (!CollectionUtils.isEmpty(runningTasks)) { vo.setCurrentDefinitionKey(runningTasks.get(0).getTaskDefinitionKey()); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/CategoryConverter.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/CategoryConverter.java index 41dceacae..579e56ac4 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/CategoryConverter.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/CategoryConverter.java @@ -7,7 +7,7 @@ import org.mapstruct.Mapping; import java.util.Arrays; -import static org.mapstruct.NullValueCheckStrategy.ALWAYS; +import static org.mapstruct.NullValueCheckStrategy.*; /** * 分类的 MapStruts 转换器 @@ -27,6 +27,9 @@ public interface CategoryConverter extends EntityConverter 的 id 和 name 决定 // 注意 2,目前该项目的设计上,需要保证 Model、Deployment、ProcessDefinition 使用相同的 key,保证关联性。 @@ -120,10 +150,18 @@ public class BpmnProcessDefinitionServiceImpl implements BpmnProcessDefinitionSe if (Objects.isNull(model)) { throw new WorkflowEngineException(MODEL_NOT_EXISTS); } + // TODO 这里需要检查 dto 中的 key 和 category 哪个数据是正确的?值期望的是processDefinitionKey + log.info("key: {}, category: {}", dto.getKey(), dto.getCategory()); + Optional optCategory = categoryService.get(BPM_MODEL_CATEGORY, dto.getKey()); + BpmnModel bpmnModel = BpmnJsonConverterUtil.convertToBpmn(dto.getJsonModel().getNode(), + ModelBizTypeEnum.valueOfType(optCategory.get().getValue()), StringUtils.hasLength(dto.getKey()) ? dto.getKey() : dto.getCategory(), dto.getName(), + dto.getFormKey(), dto.getDescription(), + dto.getJsonModel().getApproveConf(), + dto.getJsonModel().getSignConf(), dto.getJsonModel().getNoticeConf(), dto.getJsonModel().getButtonConf(), dto.getJsonModel().getFieldConf(), @@ -146,7 +184,7 @@ public class BpmnProcessDefinitionServiceImpl implements BpmnProcessDefinitionSe repositoryService.suspendProcessDefinitionById(processDefinitionId, false, null); return; } - log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", processDefinitionId, state); + log.warn("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", processDefinitionId, state); } @Override @@ -179,6 +217,21 @@ public class BpmnProcessDefinitionServiceImpl implements BpmnProcessDefinitionSe } BpmnProcessDefinitionVO vo = processDefinitionConverter.toVo(processDefinition); vo.setJsonModel(BpmnJsonConverterUtil.convertToJson(repositoryService.getBpmnModel(id))); + + ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(processDefinition.getId()); + if (Objects.isNull(relation)) { + return vo; + } + FormDefinitionSearchDTO formDefinitionSearch = new FormDefinitionSearchDTO(); + formDefinitionSearch.setKey(processDefinition.getKey()); + formDefinitionSearch.setTenantId(processDefinition.getTenantId()); + formDefinitionSearch.setParentDeploymentId(processDefinition.getDeploymentId()); + formDefinitionSearch.setDeploymentId(relation.getFormDeploymentId()); + + FormDefinitionVO formDefinitionVO = formDefinitionService.get(formDefinitionSearch); + if (Objects.nonNull(formDefinitionVO)) { + vo.setFormFields(formDefinitionVO.getFields()); + } return vo; } @@ -222,22 +275,31 @@ public class BpmnProcessDefinitionServiceImpl implements BpmnProcessDefinitionSe processDefinition = filterDefinitions.stream().filter(i -> Objects.equals(tenantId, i.getTenantId())).findFirst() .orElseThrow(() -> new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, key)); + // 判断代运营是否是关闭状态 + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + Model model = commandExecutor.execute(new CustomGetModelByDefinitionIdCmd(processDefinition.getId())); + BpmnModelExtVO modelStatus = extAxReModelService.getStatusByModelId(model.getId()); + if (Objects.equals(0, modelStatus.getStatus())) { + processDefinition = + filterDefinitions.stream().filter(i -> Objects.equals(NO_TENANT_ID, i.getTenantId())).findFirst() + .orElseThrow(() -> new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, key)); + } } return processDefinitionConverter.toVo(processDefinition); } @Override - public String getActiveProcessDefinitionId(String tenantId, String category) { + public String getActiveProcessDefinitionId(String tenantId, String key) { List list = repositoryService.createProcessDefinitionQuery() .processDefinitionTenantId(tenantId) - .processDefinitionCategory(category) + .processDefinitionKey(key) .latestVersion() .list(); if (!CollectionUtils.isEmpty(list) && list.size() != 1) { - throw new WorkflowEngineException(PROCESS_DEFINITION_RESULT_TOO_MANY, tenantId, category); + throw new WorkflowEngineException(PROCESS_DEFINITION_RESULT_TOO_MANY, tenantId, key); } else if (CollectionUtils.isEmpty(list)) { - throw new WorkflowEngineException(PROCESS_DEFINITION_HAS_DIRTY_DATA, category); + throw new WorkflowEngineException(PROCESS_DEFINITION_HAS_DIRTY_DATA, key); } else { return list.get(0).getId(); } @@ -281,4 +343,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/BpmnProcessInstanceForEsServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceForEsServiceImpl.java new file mode 100644 index 000000000..14e991b64 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceForEsServiceImpl.java @@ -0,0 +1,158 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.model.dto.es.HistoricProcessInstanceSearchForEsDTO; +import cn.axzo.workflow.core.engine.cmd.CustomGetHistoricVariablesCmd; +import cn.axzo.workflow.core.service.BpmnProcessInstanceForEsService; +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.history.HistoricProcessInstanceQuery; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.flowable.variable.api.history.HistoricVariableInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 专用与对接 ES 的流程实例相关操作 + * + * @author wangli + * @since 2024-09-27 14:23 + */ +@Service +@Slf4j +public class BpmnProcessInstanceForEsServiceImpl implements BpmnProcessInstanceForEsService { + @Resource + @Lazy + private HistoryService historyService; + @Resource + @Lazy + private RepositoryService repositoryService; + @Resource + @Lazy + private SpringProcessEngineConfiguration processEngineConfiguration; + + @Override + public Long queryHistoricProcessInstanceTotalCount(HistoricProcessInstanceSearchForEsDTO search) { + if (Objects.isNull(search)) { + return 0L; + } + return buildHistoricProcessInstanceQuery(search).count(); + } + + @Override + public List queryHistoricProcessInstance(HistoricProcessInstanceSearchForEsDTO search, IPage page) { + if (Objects.isNull(search)) { + return Collections.emptyList(); + } + HistoricProcessInstanceQuery query = buildHistoricProcessInstanceQuery(search) + .orderByProcessInstanceId().asc(); + if (Objects.nonNull(page)) { + int firstResult = Math.toIntExact((page.getCurrent() - 1) * page.getSize()); + int maxResult = Math.toIntExact(page.getSize()); + return query.listPage(firstResult, maxResult); + } + return query.list(); + } + + @Override + public BpmnModel queryBpmnModel(String processDefinitionId) { + return repositoryService.getBpmnModel(processDefinitionId); + } + + @Override + public Map queryInstanceVariables(String processInstanceId, List variableNames) { + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + List variables = commandExecutor.execute(new CustomGetHistoricVariablesCmd(processInstanceId, variableNames)); + return variables.stream().collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue, (s, t) -> s)); + } + + private HistoricProcessInstanceQuery buildHistoricProcessInstanceQuery(HistoricProcessInstanceSearchForEsDTO search) { + HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery(); + if (StringUtils.hasText(search.getProcessInstanceId())) { + historicProcessInstanceQuery.processInstanceId(search.getProcessInstanceId()); + } + if (StringUtils.hasText(search.getBusinessKey())) { + historicProcessInstanceQuery.processInstanceBusinessKey(search.getBusinessKey()); + } + if (StringUtils.hasText(search.getProcessDefinitionKey())) { + historicProcessInstanceQuery.processDefinitionKey(search.getProcessDefinitionKey()); + } + if (StringUtils.hasText(search.getProcessInstanceName())) { + historicProcessInstanceQuery.processInstanceName(search.getProcessInstanceName()); + } + if (StringUtils.hasText(search.getBusinessStatus())) { + historicProcessInstanceQuery.processInstanceBusinessStatus(search.getBusinessStatus()); + } + if (StringUtils.hasText(search.getTenantId())) { + historicProcessInstanceQuery.processInstanceTenantId(search.getTenantId()); + } + // 只有传值后, 才加入查询条件 + if (Objects.equals(Boolean.TRUE, search.getFinished())) { + historicProcessInstanceQuery.finished(); + } else if (Objects.equals(Boolean.FALSE, search.getFinished())) { + historicProcessInstanceQuery.unfinished(); + } + if (Objects.nonNull(search.getStartTime())) { + // 引擎默认仅支持两种 + switch (search.getStartTimeDirection()) { + case BEFORE: + historicProcessInstanceQuery.startedBefore(search.getStartTime()); + break; + default: + historicProcessInstanceQuery.startedAfter(search.getStartTime()); + break; + } + } + if (Objects.nonNull(search.getEndTime())) { + // 引擎默认仅支持两种 + switch (search.getEndTimeDirection()) { + case AFTER: + historicProcessInstanceQuery.finishedAfter(search.getEndTime()); + break; + default: + historicProcessInstanceQuery.finishedBefore(search.getEndTime()); + break; + } + } + if (search.getHasVariables()) { + historicProcessInstanceQuery.includeProcessVariables(); + } + return historicProcessInstanceQuery; + } + + public Long queryHistoricProcessInstanceByUnfinishedAndAlterEndTimeTotalCount(Date endTime) { + return historyService.createHistoricProcessInstanceQuery() + .or() + .unfinished() + .finishedAfter(endTime) + .endOr() + .count(); + } + + public List queryHistoricProcessInstanceByUnfinishedAndAlterEndTime(Date endTime, IPage page) { + HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery() + .or() + .unfinished() + .finishedAfter(endTime) + .endOr(); + if (Objects.nonNull(page)) { + int firstResult = Math.toIntExact((page.getCurrent() - 1) * page.getSize()); + int maxResult = Math.toIntExact(page.getSize()); + return query.listPage(firstResult, maxResult); + } + return query.list(); + } +} 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 bf37416af..8aca8215b 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,37 +1,85 @@ package cn.axzo.workflow.core.service.impl; +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.basics.common.exception.ServiceException; import cn.axzo.workflow.common.constant.BpmnConstants; +import cn.axzo.workflow.common.enums.ApprovalMethodEnum; +import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; +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.BpmnProcessTaskResultEnum; +import cn.axzo.workflow.common.enums.BusinessTypeEnum; +import cn.axzo.workflow.common.enums.ButtonVisibleScopeEnum; +import cn.axzo.workflow.common.enums.WorkspaceType; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.SignFileDTO; +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +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.BeforeProcessInstanceCreateDTO; 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.request.bpmn.process.HistoricProcessInstanceSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; import cn.axzo.workflow.common.model.request.category.CategorySearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; +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.model.doc.DocBaseVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnNodeConfigVO; 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.NodesByModelVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.process.doc.DocPendingVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo; +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; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceAsyncCmd; import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceCmd; +import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceAsyncCmd; import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd; import cn.axzo.workflow.core.engine.cmd.CustomCarbonCopyUserSelectorCmd; import cn.axzo.workflow.core.engine.cmd.CustomForecastUserTaskAssigneeCmd; +import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd; +import cn.axzo.workflow.core.engine.cmd.CustomOverrideFormVariablesByLatestInstanceCmd; import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener; +import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign; +import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper; 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.ExtAxBpmnFormRelationService; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import cn.axzo.workflow.core.service.ExtAxProcessSignService; +import cn.axzo.workflow.core.service.ExtAxReModelService; +import cn.axzo.workflow.core.service.ExtAxReadRecordService; import cn.axzo.workflow.core.service.converter.BpmnHistoricProcessInstanceConverter; import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter; import cn.axzo.workflow.core.service.converter.BpmnProcessInstanceAdminPageItemConverter; @@ -39,7 +87,9 @@ import cn.axzo.workflow.core.service.converter.BpmnProcessInstanceConverter; import cn.axzo.workflow.core.service.converter.BpmnProcessInstancePageItemConverter; import cn.axzo.workflow.core.service.support.FlowNodeForecastService; import cn.axzo.workflow.core.service.support.ProcessGraphicService; -import cn.azxo.framework.common.utils.StringUtils; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Lists; @@ -48,6 +98,7 @@ import org.apache.commons.collections4.ListUtils; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.ReceiveTask; +import org.flowable.bpmn.model.SequenceFlow; import org.flowable.bpmn.model.ServiceTask; import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.impl.db.SuspensionState; @@ -68,60 +119,112 @@ import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ActivityInstance; 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; import org.flowable.variable.api.history.HistoricVariableInstance; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Nullable; import javax.annotation.Resource; +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; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.stream.Collectors; +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.RUNNING_INSTANCE_ONLY_FORECAST; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_IS_INVALID; +import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_IS_SUSPENDED; +import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_KEY_NOT_EXISTS; 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.BPMN_FILE_SUFFIX; import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CANCEL_PROCESS_NODE_DEF_KEY_NAME; +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.INITIATOR_SPECIFY; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_AGENT; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_BIZ_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_WORKSPACE_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.PENDING_TEMPLATE_VARIABLE; +import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_OWNERSHIP_APPLICATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATORIES; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_PROCESS_ENABLE_DOC_IDS; +import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_VARIABLE; import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION; +import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE_PROCESS; import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.autoPassed; +import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.autoPassed_empty; import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.autoRejection; +import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.autoRejection_empty; import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.human; +import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.transferToAdmin; +import static cn.axzo.workflow.common.enums.BpmnButtonEnum.BPMN_UPGRADE; 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_COMMENT; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_ROBOT; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_SIGN; 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.CANCELLED; +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.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_ID_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_IS_SUSPENDED; -import static cn.axzo.workflow.core.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_KEY_NOT_EXISTS; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED; +import static cn.axzo.workflow.common.enums.WorkspaceType.GOVERNMENT; +import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.countSql; +import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.sqlConnectors; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprovalMethod; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverScope; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecify; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getButtonConfig; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCarbonCopyConfigs; import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNodeType; -import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql; -import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getProcessApproveConf; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSignApproverLimit; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getSupportInitiatorSpecified; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getUpgradeApprovalConf; @Service @Slf4j @@ -163,6 +266,21 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic private BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter; @Resource private String serviceVersion; + @Resource + @Lazy + private BpmnProcessInstanceService bpmnProcessInstanceService; + @Resource + private ExtAxProcessLogService processLogService; + @Resource + private ExtAxBpmnFormRelationService bpmnFormRelationService; + @Resource + private ExtAxReModelService extAxReModelService; + @Resource + private ExtAxModelDocMapper extAxModelDocMapper; + @Resource + private ExtAxProcessSignService extAxProcessSignService; + @Resource + private ExtAxReadRecordService extAxReadRecordService; @Override public HistoricProcessInstance getProcessInstanceByBusinessKey(String businessKey, @Nullable String tenantId, @@ -182,19 +300,19 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic public BpmPageResult getMyProcessInstancePage(@Valid BpmnProcessInstanceMyPageReqVO dto) { HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().processInstanceTenantId(dto.getTenantId()); - if (StringUtils.isNotBlank(dto.getName())) { + if (StringUtils.hasText(dto.getName())) { query.processInstanceName(dto.getName()); } - if (StringUtils.isNotBlank(dto.getProcessDefinitionId())) { + if (StringUtils.hasText(dto.getProcessDefinitionId())) { query.processDefinitionId(dto.getProcessDefinitionId()); } - if (StringUtils.isNotBlank(dto.getCategory())) { - query.processDefinitionCategory(dto.getCategory()); + if (StringUtils.hasText(dto.getKey())) { + query.processDefinitionKey(dto.getKey()); } - if (StringUtils.isNotBlank(dto.getBusinessKey())) { + if (StringUtils.hasText(dto.getBusinessKey())) { query.processInstanceBusinessKey(dto.getBusinessKey()); } - if (StringUtils.isNotBlank(dto.getUserId())) { + if (StringUtils.hasText(dto.getUserId())) { query.startedBy(dto.getUserId()); } if (Objects.nonNull(dto.getBeginCreateTime())) { @@ -238,7 +356,11 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic if (Objects.nonNull(tenantId)) { query.processInstanceTenantId(tenantId); } - return query.singleResult(); + HistoricProcessInstance processInstance = query.singleResult(); + if (Objects.isNull(processInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, id); + } + return processInstance; } @@ -261,64 +383,75 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic repositoryService.suspendProcessDefinitionById(processDefinitionId, false, null); return true; } - log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", processDefinitionId, status); + log.warn("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", processDefinitionId, status); return false; } + @Override + public List nodesBeforeCreateProcessInstance(BeforeProcessInstanceCreateDTO dto) { + BpmnProcessDefinitionVO definition = processDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(), dto.getTenantId()); + if (Objects.isNull(definition)) { + throw new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, dto.getProcessDefinitionKey()); + } + if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), definition.getSuspensionState())) { + throw new WorkflowEngineException(PROCESS_DEFINITION_IS_SUSPENDED, dto.getProcessDefinitionKey()); + } + BpmnModel bpmnModel = repositoryService.getBpmnModel(definition.getId()); + List orderedNodes = new ArrayList<>(); + forecastService.doForecasting(null, null, null, orderedNodes, bpmnModel); + List nodes = new ArrayList<>(); + orderedNodes.forEach(fe -> { + if (!(fe instanceof SequenceFlow) && !Objects.equals(fe.getId(), "startEventNode") && !Objects.equals(fe.getId(), "endEventNode")) { + BpmnFlowNodeMode nodeModel = GENERAL; + if (fe instanceof UserTask && ((UserTask) fe).getBehavior() instanceof MultiInstanceActivityBehavior) { + MultiInstanceActivityBehavior behavior = (MultiInstanceActivityBehavior) ((UserTask) fe).getBehavior(); + nodeModel = (Objects.equals(AND_SIGN_EXPRESSION, + behavior.getCompletionCondition()) ? AND : OR); + } + ApproverSpecifyEnum approverSpecifyEnum = getApproverSpecify(fe).orElse(null); + nodes.add(new NodesByModelVO() + .setActivityId(fe.getId()) + .setActivityName(fe.getName()) + .setAssigners(Objects.equals(NODE_STARTER.getType(), fe.getId()) ? Lists.newArrayList(dto.getInitiator()) : calcSpecifyAssigners(approverSpecifyEnum, fe)) + .setSupportSpecify(getSupportInitiatorSpecified(fe)) + .setNodeModel(nodeModel) + .setNodeType(getNodeType(fe).orElse(null)) + .setNodeConfig(BpmnNodeConfigVO.builder() + .approvalMethodEnum(getApprovalMethod(fe).orElse(null)) + .approverScopeEnum(getApproverScope(fe).orElse(null)) + .approverSpecifyEnum(approverSpecifyEnum) + .approverEmptyHandleTypeEnum(getApproverEmptyHandleType(fe).orElse(null)) + .signature(getActivitySignature(fe)) + .signApproverLimit(getSignApproverLimit(fe).orElse(null)) + .build()) + ); + } + }); + if (CollectionUtils.isEmpty(dto.getFilter())) { + return nodes; + } + return nodes.stream().filter(i -> dto.getFilter().contains(i.getNodeType())).collect(Collectors.toList()); + } + + private List calcSpecifyAssigners(ApproverSpecifyEnum approverSpecifyEnum, FlowElement fe) { + List assigners = new ArrayList<>(); + if (Objects.nonNull(approverSpecifyEnum) && Objects.equals(approverSpecifyEnum, ApproverSpecifyEnum.fixedPerson)) { + BpmnMetaParserHelper.getApproverSpecifyValue(fe).ifPresent(s -> + assigners.addAll(JSON.parseArray(s, BpmnTaskDelegateAssigner.class))); + } + return assigners; + } + + // TODO 该接口有个问题,如果模型没有关联表单,但是传入参数中含有表单变量,则不能正确创建流程 @Override public String createProcessInstance(BpmnProcessInstanceCreateDTO dto) { BpmnProcessDefinitionVO definition = null; - if (StringUtils.isBlank(dto.getProcessDefinitionId())) { - definition = processDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(), - dto.getTenantId()); - // 校验流程定义 - if (definition == null) { - throw new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, dto.getProcessDefinitionKey()); - } - if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), definition.getSuspensionState())) { - throw new WorkflowEngineException(PROCESS_DEFINITION_IS_SUSPENDED, dto.getProcessDefinitionKey()); - } + if (!StringUtils.hasText(dto.getProcessDefinitionId())) { + definition = processDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(), dto.getTenantId()); + } else { + definition = processDefinitionService.getProcessDefinition(dto.getProcessDefinitionId()); } - - dto.getVariables().put(INTERNAL_INITIATOR, dto.getInitiator()); - dto.getVariables().put(BIZ_ORG_RELATION, dto.getCooperationOrg()); - dto.getVariables().put(WORKFLOW_ENGINE_VERSION, serviceVersion); - dto.getVariables().put(PENDING_TEMPLATE_VARIABLE, dto.getPendingVariables()); - dto.getVariables().put(INTERNAL_PROCESS_AGENT, - !Objects.isNull(definition) && StringUtils.isNotBlank(definition.getTenantId())); - // if (Objects.nonNull(dto.getNextApprover())) { - // BpmnTaskDelegateAssigner nextApprover = dto.getNextApprover(); - // nextApprover.setTenantId(Objects.nonNull(nextApprover.getTenantId()) ? nextApprover.getTenantId - // () : - // dto.getInitiator().getTenantId()); - // dto.getVariables().put(INTERNAL_SPECIFY_NEXT_APPROVER, nextApprover); - // } - - // 创建流程实例 - // 设置流程实例的开始人,参考 https://wenku.baidu.com/view/5538062e7a563c1ec5da50e2524de518964bd3f9.html - Authentication.setAuthenticatedUserId(dto.getInitiator().buildAssigneeId()); - String name = StringUtils.isNotBlank(dto.getCustomProcessInstanceName()) ? - dto.getCustomProcessInstanceName() - : Objects.isNull(definition) ? "" : definition.getName(); - - // 设置流程名字 - ProcessInstance instance = runtimeService.createProcessInstanceBuilder() - .processDefinitionId(Objects.isNull(definition) ? dto.getProcessDefinitionId() : definition.getId()) - .businessKey(dto.getBusinessKey()) - .variables(dto.getVariables()) - .name(name) - .overrideProcessDefinitionTenantId(dto.getTenantId()) - .start(); - Authentication.setAuthenticatedUserId(null); - return instance.getProcessInstanceId(); - } - - @Override - public String createProcessInstanceWithForm(BpmnProcessInstanceCreateWithFormDTO dto) { - BpmnProcessDefinitionVO definition = - processDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(), - dto.getInitiator().getTenantId()); // 校验流程定义 if (definition == null) { throw new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, dto.getProcessDefinitionKey()); @@ -326,17 +459,70 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), definition.getSuspensionState())) { throw new WorkflowEngineException(PROCESS_DEFINITION_IS_SUSPENDED, dto.getProcessDefinitionKey()); } - dto.getVariables().put(INTERNAL_INITIATOR, dto.getInitiator()); + Optional categoryItemVO = categoryService.get(BPM_MODEL_CATEGORY, definition.getKey()); + //政务只能使用代运营的流程定义 + if (categoryItemVO.isPresent() && categoryItemVO.get().getWorkspaceTypeCode().equals(String.valueOf(GOVERNMENT.getCode()))) { + if (!StringUtils.hasText(definition.getTenantId())) { + throw new WorkflowEngineException(PROCESS_DEFINITION_IS_INVALID, dto.getProcessDefinitionKey()); + } + } + + categoryItemVO.ifPresent(itemVO -> { + dto.getVariables().put(INTERNAL_PROCESS_WORKSPACE_TYPE, WorkspaceType.getType(Integer.valueOf(itemVO.getWorkspaceTypeCode())).getCode()); + dto.getVariables().put(INTERNAL_PROCESS_BIZ_TYPE, itemVO.getBusinessType().getType()); + if (Objects.equals(BusinessTypeEnum.SIGN, itemVO.getBusinessType())) { + // 签署业务 + dto.getVariables().put(SIGN_PROCESS_ENABLE_DOC_IDS, dto.getDocIds()); + dto.getVariables().put(SIGN_VARIABLE, dto.getBizCustomVariables()); + dto.getVariables().put(SIGNATORIES, dto.getSignatories()); + } + }); + dto.getVariables().put(INTERNAL_INITIATOR, dto.getInitiator().toJson()); + dto.getVariables().put(BIZ_ORG_RELATION, dto.getCooperationOrg()); + dto.getVariables().put(WORKFLOW_ENGINE_VERSION, serviceVersion); + dto.getVariables().put(PENDING_TEMPLATE_VARIABLE, dto.getPendingVariables()); + dto.getVariables().put(INTERNAL_PROCESS_AGENT, StringUtils.hasText(definition.getTenantId())); + + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + dto.getVariables().put(PROCESS_OWNERSHIP_APPLICATION, request.getHeader(HEADER_SERVER_NAME)); + // 发起人指定节点的审批人集合,此处不校验集合,如果人员为空,就走节点的审批人为空的逻辑 + dto.getVariables().put(INITIATOR_SPECIFY, dto.getSpecifyAssignerMap()); // 创建流程实例 - // 设置流程实例的开始人,参考https://wenku.baidu.com/view/5538062e7a563c1ec5da50e2524de518964bd3f9.html + // 设置流程实例的开始人,参考 https://wenku.baidu.com/view/5538062e7a563c1ec5da50e2524de518964bd3f9.html Authentication.setAuthenticatedUserId(dto.getInitiator().buildAssigneeId()); - String name = StringUtils.isNotBlank(dto.getCustomProcessInstanceName()) ? - dto.getCustomProcessInstanceName() - : definition.getName(); + String name = StringUtils.hasText(dto.getCustomProcessInstanceName()) ? dto.getCustomProcessInstanceName() : definition.getName(); - ProcessInstance instance = runtimeService.startProcessInstanceWithForm(definition.getId(), - dto.getOutcome(), dto.getVariables(), name); + // 设置流程名字 + ProcessInstanceBuilder instanceBuilder = runtimeService.createProcessInstanceBuilder() + .processDefinitionId(definition.getId()) + .businessKey(dto.getBusinessKey()) + .name(name) + .tenantId(definition.getTenantId()) + .overrideProcessDefinitionTenantId(dto.getTenantId()); + + ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(definition.getId()); + if (Objects.isNull(relation)) { + // 如果模型没有绑定表单,则强制清空表单相关属性,避免异常 + instanceBuilder.outcome(null); + } else { + if (!CollectionUtils.isEmpty(dto.getStartFormVariables())) { + instanceBuilder.startFormVariables(dto.getStartFormVariables()); + } + if (StringUtils.hasText(dto.getOutcome())) { + instanceBuilder.outcome(dto.getOutcome()); + } + } + dto.getVariables().put(CREATE_INSTANCE_PARAMS, JSONUtil.toJsonStr(dto)); + instanceBuilder.variables(dto.getVariables()); + ProcessInstance instance; + if (Boolean.TRUE.equals(dto.getAsync())) { + // 异步开始 + instance = instanceBuilder.startAsync(); + } else { + // 同步开始 + instance = instanceBuilder.start(); + } Authentication.setAuthenticatedUserId(null); return instance.getProcessInstanceId(); } @@ -347,10 +533,13 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic } @Override - public Boolean cancelProcessInstance(BpmnProcessInstanceCancelDTO dto) { + public Boolean cancelProcessInstance(SuperBpmnProcessInstanceCancelDTO dto) { CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); - commandExecutor.execute(new CustomCancelProcessInstanceCmd(dto.getProcessInstanceId(), dto.getTenantId(), - dto.getReason(), dto.getInitiator(), extAxHiTaskInstService)); + if (dto.getAsync() != null && dto.getAsync()) { + commandExecutor.execute(new CustomCancelProcessInstanceAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomCancelProcessInstanceCmd(dto, extAxHiTaskInstService)); + } return true; } @@ -358,11 +547,39 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public Boolean abortProcessInstance(BpmnProcessInstanceAbortDTO dto) { CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); - commandExecutor.execute(new CustomAbortProcessInstanceCmd(dto.getProcessInstanceId(), dto.getTenantId(), - dto.getReason(), extAxHiTaskInstService)); + if (dto.getAsync() != null && dto.getAsync()) { + commandExecutor.execute(new CustomAbortProcessInstanceAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomAbortProcessInstanceCmd(dto, extAxHiTaskInstService)); + } return true; } + @Override + public BatchOperationResultVO batchAbortProcessInstance(List dtos) { + BatchOperationResultVO result = new BatchOperationResultVO(); + result.setRequestCount(dtos.size()); + if (CollectionUtils.isEmpty(dtos)) { + return result; + } + List details = new ArrayList<>(); + result.setDetails(details); + dtos.forEach(dto -> { + BatchOperationItemResultVO itemVO = new BatchOperationItemResultVO(); + details.add(itemVO); + itemVO.setType("流程"); + try { + itemVO.setId(dto.getProcessInstanceId()); + bpmnProcessInstanceService.abortProcessInstance(dto); + } catch (Exception e) { + itemVO.setHasError(true); + itemVO.setErrorMessage(e.getMessage()); + } + }); + result.setFailCount((int) result.getDetails().stream().filter(BatchOperationItemResultVO::getHasError).count()); + return result; + } + @Override public Boolean carbonCopyProcessInstance(BpmnProcessInstanceCarbonCopyDTO dto) { // TODO 暴露给二方 API 的抄送功能, 需求被砍 @@ -372,12 +589,13 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic } @Override - public BpmPageResult getAdminProcessInstancePage(BpmnProcessInstanceAdminPageReqVO dto) { + public BpmPageResult getAdminProcessInstancePage + (BpmnProcessInstanceAdminPageReqVO dto) { List dictValues = new ArrayList<>(); - if (StringUtils.isNotBlank(dto.getCategory())) { + if (StringUtils.hasText(dto.getKey())) { CategorySearchDTO searchDTO = new CategorySearchDTO(); searchDTO.setDictType(BpmnConstants.BPM_MODEL_CATEGORY); - searchDTO.setLabel(dto.getCategory()); + searchDTO.setLabel(dto.getKey()); dictValues.addAll(categoryService.list(searchDTO).stream().map(CategoryItemVO::getValue).distinct().collect(Collectors.toList())); } NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery(); @@ -390,7 +608,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic .append(" FROM ").append(tableName) .append(" RES LEFT OUTER JOIN ACT_RE_PROCDEF DEF ON RES.PROC_DEF_ID_ = DEF.ID_") .append(" WHERE 1 = 1"); - if (StringUtils.isNotBlank(dto.getProcessInstanceId())) { + if (StringUtils.hasText(dto.getProcessInstanceId())) { List ids = Lists.newArrayList(dto.getProcessInstanceId().replaceAll(" ", "").split(",")); baseQuerySql.append(" AND RES.PROC_INST_ID_ IN ("); for (int i = 0; i < ids.size(); i++) { @@ -402,18 +620,18 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic } baseQuerySql.append(")"); } - if (StringUtils.isNotBlank(dto.getName())) { + if (StringUtils.hasText(dto.getName())) { baseQuerySql.append(" AND RES.NAME_ LIKE #{name}"); query.parameter("name", "%" + dto.getName() + "%"); } if (!CollectionUtils.isEmpty(dictValues)) { - baseQuerySql.append(" AND DEF.CATEGORY_ IN ("); + baseQuerySql.append(" AND DEF.KEY_ IN ("); for (int i = 0; i < dictValues.size(); i++) { - baseQuerySql.append("#{category").append(i).append("}"); + baseQuerySql.append("#{key").append(i).append("}"); if (i < dictValues.size() - 1) { baseQuerySql.append(","); } - query.parameter("category" + i, dictValues.get(i)); + query.parameter("key" + i, dictValues.get(i)); } baseQuerySql.append(")"); } @@ -428,7 +646,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic } baseQuerySql.append(")"); } - if (StringUtils.isNotBlank(dto.getBusinessStatus())) { + if (StringUtils.hasText(dto.getBusinessStatus())) { baseQuerySql.append(" AND RES.BUSINESS_STATUS_ = #{businessStatus}"); query.parameter("businessStatus", dto.getBusinessStatus()); } @@ -514,7 +732,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic @Override public HistoricProcessInstance getHistoricProcessInstance(String id, String tenantId) { HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().processInstanceId(id); - if (StringUtils.isNotBlank(tenantId)) { + if (StringUtils.hasText(tenantId)) { query.processInstanceTenantId(tenantId); } return query.singleResult(); @@ -524,7 +742,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic public HistoricProcessInstance getHistoricProcessInstanceByBusinessKey(String businessKey, String tenantId) { HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey); - if (StringUtils.isNotBlank(tenantId)) { + if (StringUtils.hasText(tenantId)) { query.processInstanceTenantId(tenantId); } return query.singleResult(); @@ -533,9 +751,9 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic @Override public BpmnProcessInstanceVO getProcessInstanceVO(BpmnProcessInstanceQueryDTO dto) { HistoricProcessInstance processInstance = null; - if (StringUtils.isNotBlank(dto.getProcessInstanceId())) { + if (StringUtils.hasText(dto.getProcessInstanceId())) { processInstance = getProcessInstance(dto.getProcessInstanceId(), dto.getTenantId(), dto.getHasVariable()); - } else if (StringUtils.isNotBlank(dto.getBusinessKey())) { + } else if (StringUtils.hasText(dto.getBusinessKey())) { processInstance = getProcessInstanceByBusinessKey(dto.getBusinessKey(), dto.getTenantId(), dto.getHasVariable()); } @@ -552,12 +770,12 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic processInstance.getProcessDefinitionId()); } - BpmnTaskDelegateAssigner assigner = null; + Object tempAssigner = null; String version = null; if (dto.getHasVariable()) { - assigner = (BpmnTaskDelegateAssigner) processInstance.getProcessVariables().get(INTERNAL_INITIATOR); - if (Objects.isNull(assigner)) { - assigner = (BpmnTaskDelegateAssigner) processInstance.getProcessVariables().get(OLD_INTERNAL_INITIATOR); + tempAssigner = processInstance.getProcessVariables().get(INTERNAL_INITIATOR); + if (Objects.isNull(tempAssigner)) { + tempAssigner = processInstance.getProcessVariables().get(OLD_INTERNAL_INITIATOR); } version = (String) processInstance.getProcessVariables().get(WORKFLOW_ENGINE_VERSION); @@ -567,13 +785,14 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic for (HistoricVariableInstance i : variableInstances) { if (Objects.equals(i.getVariableName(), INTERNAL_INITIATOR) || Objects.equals(i.getVariableName(), OLD_INTERNAL_INITIATOR)) { - assigner = (BpmnTaskDelegateAssigner) i.getValue(); + tempAssigner = i.getValue(); } if (Objects.equals(i.getVariableName(), WORKFLOW_ENGINE_VERSION)) { version = (String) i.getValue(); } } } + if (Objects.isNull(version)) { version = FLOW_SERVER_VERSION_121; } @@ -585,8 +804,10 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic .list(); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); - return instanceConverter.toVo(processInstance, processDefinition, getButtonConfig(bpmnModel.getMainProcess()) - , assigner, version, categoryService.get(BPM_MODEL_CATEGORY, + return instanceConverter.toVo(processInstance, processDefinition, + getButtonConfig(bpmnModel.getMainProcess()), + getProcessApproveConf(bpmnModel.getMainProcess()), + BpmnTaskDelegateAssigner.toObjectCompatible(tempAssigner), version, categoryService.get(BPM_MODEL_CATEGORY, processInstance.getProcessDefinitionKey()), runningTasks); } @@ -623,7 +844,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic public ObjectNode getProcessInstanceGraphical(String processInstanceId, @Nullable String tenantId) { HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId); - if (StringUtils.isNotBlank(tenantId)) { + if (StringUtils.hasText(tenantId)) { query.processInstanceTenantId(tenantId); } HistoricProcessInstance processInstance = query.singleResult(); @@ -679,24 +900,25 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic } @Override - public BpmPageResult historicProcessInstancePage(HistoricProcessInstanceSearchDTO dto) { + public BpmPageResult historicProcessInstancePage(HistoricProcessInstanceSearchDTO + dto) { NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery(); String tableName = managementService.getTableName(HistoricProcessInstance.class); StringBuilder baseQuerySql = new StringBuilder("SELECT a.* FROM ").append(tableName).append(" a"); - if (StringUtils.isNotBlank(dto.getCategory())) { + if (StringUtils.hasText(dto.getKey())) { baseQuerySql.append(" LEFT JOIN ACT_RE_PROCDEF b ON a.PROC_DEF_ID_ = b.ID_ ") .append(sqlConnectors(baseQuerySql)) - .append(" b.CATEGORY_ = #{category}"); - query.parameter("category", dto.getCategory()); + .append(" b.KEY_ = #{key}"); + query.parameter("key", dto.getKey()); } - if (StringUtils.isNotBlank(dto.getSearchKey())) { + if (StringUtils.hasText(dto.getSearchKey())) { baseQuerySql.append(sqlConnectors(baseQuerySql)) .append(" (a.BUSINESS_KEY_ LIKE #{searchKey}") .append(" or") .append(" a.NAME_ LIKE #{searchKey})"); query.parameter("searchKey", "%" + dto.getSearchKey() + "%"); } - if (StringUtils.isNotBlank(dto.getResult())) { + if (StringUtils.hasText(dto.getResult())) { baseQuerySql.append(sqlConnectors(baseQuerySql)) .append(" a.BUSINESS_STATUS_ = #{result}"); query.parameter("result", dto.getResult()); @@ -739,8 +961,8 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic } NativeHistoricProcessInstanceQuery countSqlQuery = query.sql(countSql(baseQuerySql)); - if (StringUtils.isNotBlank(dto.getCategory())) { - return new BpmPageResult(historicProcessInstanceConverter.toVos(instances, dto.getCategory()), + if (StringUtils.hasText(dto.getKey())) { + return new BpmPageResult<>(historicProcessInstanceConverter.toVos(instances, dto.getKey()), countSqlQuery.count()); } else { Set procDefIds = new HashSet<>(); @@ -748,32 +970,73 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic Map defCategoryMap = repositoryService.createProcessDefinitionQuery().processDefinitionIds(procDefIds).list() .stream().collect(Collectors.toMap(ProcessDefinition::getId, - ProcessDefinition::getCategory, (s, t) -> s)); + ProcessDefinition::getKey, (s, t) -> s)); List vos = new ArrayList<>(); instances.forEach(i -> vos.add(historicProcessInstanceConverter.toVo(i, defCategoryMap.getOrDefault(i.getProcessDefinitionId(), "")))); - return new BpmPageResult(vos, countSqlQuery.count()); + return new BpmPageResult<>(vos, countSqlQuery.count()); } } @Override public List getProcessInstanceNodeForecast(String processInstanceId, @Nullable String tenantId) { + + 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 (Boolean.TRUE.equals(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, startNodeDefinitionKey, containSelf); + } + + + @Override + public List getProcessInstanceNodeFilterForecast(String processInstanceId, String + tenantId, List nodeDefinitionKeys) { ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId); - if (StringUtils.isNotBlank(tenantId)) { + if (StringUtils.hasText(tenantId)) { query.processInstanceTenantId(tenantId); } ProcessInstance instance = query.singleResult(); if (Objects.isNull(instance)) { - throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId); + throw new WorkflowEngineException(RUNNING_INSTANCE_ONLY_FORECAST); } List flowElements = forecastService.performProcessForecasting(processInstanceId, instance); + return buildNodeDetailVos(processInstanceId, nodeDefinitionKeys, instance, flowElements, null, false); + } + + private List buildNodeDetailVos(String processInstanceId, + List nodeDefinitionKeys, + ProcessInstance instance, + List flowElements, + String startNodeDefinitionKey, + Boolean containSelf) { BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId()); List resultList = new ArrayList<>(flowElements.size()); // 发起人节点,也是一个 UserTask 节点, 所以这里默认就包含了发起人节点 - flowElements.stream().filter(i -> (i instanceof UserTask || i instanceof ReceiveTask || i instanceof ServiceTask)) + flowElements.stream() + .filter(i -> (i instanceof UserTask || i instanceof ReceiveTask || i instanceof ServiceTask)) .forEach(i -> { ProcessNodeDetailVO node = new ProcessNodeDetailVO(); node.setForecastAssigners(Collections.emptyList()); @@ -781,6 +1044,9 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic getButtonConfig(bpmnModel.getMainProcess(), i.getId()).ifPresent(node::setButtonConf); // 设置审批方式 getApprovalMethod(i).ifPresent(node::setApprovalMethod); + node.setFutureNode(Objects.equals(Boolean.TRUE, containSelf) && !Objects.equals(i.getId(), startNodeDefinitionKey)); + // 提级审批配置 + getUpgradeApprovalConf(i).ifPresent(node::setUpgradeApprovalConf); getNodeType(i).ifPresent(node::setNodeType); if (Objects.equals(NODE_STARTER.getType(), i.getId())) { node.setNodeType(NODE_STARTER); @@ -788,33 +1054,36 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic node.setNodeMode(GENERAL); node.setId(i.getId()).setName(i.getName()); if (i instanceof UserTask) { - parseUserTask(processInstanceId, (UserTask) i, node); + parseUserTask(processInstanceId, (UserTask) i, node, nodeDefinitionKeys); } else if (i instanceof ServiceTask) { - parseServiceTask(processInstanceId, (ServiceTask) i, node); + parseServiceTask(processInstanceId, (ServiceTask) i, node, nodeDefinitionKeys); } resultList.add(node); }); return resultList; } - private void parseServiceTask(String processInstanceId, ServiceTask i, ProcessNodeDetailVO node) { + private void parseServiceTask(String processInstanceId, ServiceTask i, ProcessNodeDetailVO + node, List skipTaskDefinitionKeys) { // ServiceTask 主要作用于抄送 - ServiceTask serviceTask = i; - node.setId(serviceTask.getId()).setName(serviceTask.getName()); - getCarbonCopyConfigs(serviceTask).ifPresent(carbons -> + node.setId(i.getId()).setName(i.getName()); + if (skipTaskDefinitionKeys.contains(i.getId())) { + return; + } + getCarbonCopyConfigs(i).ifPresent(carbons -> node.setForecastAssigners(springProcessEngineConfiguration.getCommandExecutor() .execute(new CustomCarbonCopyUserSelectorCmd(processInstanceId, carbons, - serviceTask, engineExecutionStartListener, + i, engineExecutionStartListener, historicTaskInstanceConverter, serviceVersion)))); } - private void parseUserTask(String processInstanceId, UserTask i, ProcessNodeDetailVO node) { - UserTask userTask = i; - node.setFormKey(userTask.getFormKey()); + private void parseUserTask(String processInstanceId, UserTask i, ProcessNodeDetailVO + node, List skipTaskDefinitionKeys) { + node.setFormKey(i.getFormKey()); // 设置审批模式, - if (userTask.getBehavior() instanceof MultiInstanceActivityBehavior) { + if (i.getBehavior() instanceof MultiInstanceActivityBehavior) { MultiInstanceActivityBehavior behavior = - (MultiInstanceActivityBehavior) userTask.getBehavior(); + (MultiInstanceActivityBehavior) i.getBehavior(); node.setNodeMode(Objects.equals(AND_SIGN_EXPRESSION, behavior.getCompletionCondition()) ? AND : OR); @@ -822,21 +1091,27 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic node.setNodeMode(BpmnFlowNodeMode.GENERAL); } + if (skipTaskDefinitionKeys.contains(i.getId())) { + return; + } if (Objects.equals(node.getApprovalMethod(), human)) { // 推测当前节点的审批人,并且如果审批人为空,也会根据审批人为空的策略去找人 List forecastAssigners = springProcessEngineConfiguration.getCommandExecutor() .execute(new CustomForecastUserTaskAssigneeCmd(processInstanceId, - userTask, engineExecutionStartListener)); + i, engineExecutionStartListener)); node.setForecastAssigners(forecastAssigners); if (CollectionUtils.isEmpty(forecastAssigners)) { - getApproverEmptyHandleType(userTask).ifPresent(emptyHandleType -> { + getApproverEmptyHandleType(i).ifPresent(emptyHandleType -> { switch (emptyHandleType) { case autoPassed: - node.setApprovalMethod(autoPassed); + node.setApprovalMethod(autoPassed_empty); break; case autoRejection: - node.setApprovalMethod(autoRejection); + node.setApprovalMethod(autoRejection_empty); + break; + case transferToAdmin: + node.setApprovalMethod(transferToAdmin); break; default: node.setNodeMode(EXCEPTIONAL); @@ -856,4 +1131,685 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic NativeHistoricProcessInstanceQuery dataSqlQuery = query.sql(baseQuerySql); return ListUtils.emptyIfNull(dataSqlQuery.list()).stream().map(HistoricProcessInstance::getTenantId).distinct().collect(Collectors.toList()); } + + @Override + public Boolean checkInstanceApprover(BpmnProcessInstanceCheckApproverDTO dto) { + if (Boolean.TRUE.equals(dto.getOnlyPersonId())) { + dto.getApprover().setOuId(null); + } + List list = taskService.createTaskQuery() + .processInstanceId(dto.getProcessInstanceId()) + .taskAssigneeLikeIgnoreCase(dto.getApprover().buildAssigneeId()) + .active() + .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) -> Objects.equals(s.getActivityId(), NODE_ROBOT.getType()) || Objects.equals(s.getActivityId(), NODE_COMMENT.getType()) ? f : s) + .ifPresent(e -> forecasting.addAll( + getProcessInstanceNodeForecastWithSpecifyTaskDefinitionKey(dto.getProcessInstanceId(), instance, e.getActivityId(), true, 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)) + .catPrint(hasPrintTemplate(null, historicProcessInstance.getProcessDefinitionId())) + .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; + } + + + @Override + public BpmnTaskButtonVo findTaskButtons(BpmnTaskButtonSearchDTO taskButtonsSearchDTO) { + BpmnTaskButtonVo bpmnTaskButtonVo = new BpmnTaskButtonVo(); + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(taskButtonsSearchDTO.getProcessInstanceId()) + .includeProcessVariables() + .singleResult(); + if (Objects.isNull(historicProcessInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, taskButtonsSearchDTO.getProcessInstanceId()); + } + BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId()); + ExtAxHiTaskInst extAxHiTaskInst = extAxHiTaskInstService.getByTaskId(taskButtonsSearchDTO.getTaskId(), taskButtonsSearchDTO.getProcessInstanceId()); + if (extAxHiTaskInst == null) { + log.warn("extAxHiTaskInst is null,request param:{}", JSON.toJSONString(taskButtonsSearchDTO)); + throw new ServiceException("can find extAxHiTaskInst"); + } + if (Objects.equals(extAxHiTaskInst.getStatus(), DELETED.getStatus())) { + ExtHiTaskSearchDTO extHiTaskSearchDTO = ExtHiTaskSearchDTO.builder() + .processInstanceId(taskButtonsSearchDTO.getProcessInstanceId()) + .taskDefinitionKey(extAxHiTaskInst.getTaskDefinitionKey()) + .assignee(extAxHiTaskInst.getAssignee()) + .excludeIds(Collections.singletonList(extAxHiTaskInst.getId())) + .build(); + List extAxHiTaskInsts = extAxHiTaskInstService.queryList(extHiTaskSearchDTO); + log.info("extAxHiTaskInst status is DELETED, find another task list in same assignee and task def key,result is:{}", JSON.toJSONString(extAxHiTaskInsts)); + if (!CollectionUtils.isEmpty(extAxHiTaskInsts)) { + extAxHiTaskInst = extAxHiTaskInsts.get(0); + } + } + //判断节点类型 + FlowElement flowElement = bpmnModel.getMainProcess().getFlowElement(extAxHiTaskInst.getTaskDefinitionKey()); + BpmnFlowNodeType nodeType = getNodeType(flowElement).orElse(null); + //设置任务状态 + bpmnTaskButtonVo.setExecutorTaskResult(getTaskStatus(nodeType, extAxHiTaskInst)); + String buttonConfigName = null; + BpmnButtonConf buttonConf = getButtonConfig(bpmnModel.getMainProcess(), extAxHiTaskInst.getTaskDefinitionKey()).orElse(null); + BpmnProcessInstanceLogVO logVO = BpmnProcessInstanceLogVO.builder() + .id(historicProcessInstance.getId()) + .defaultButtonConf(getButtonConfig(bpmnModel.getMainProcess()).orElse(new BpmnButtonConf())) + .calculatingButtonConf(buttonConf) + .build(); + List carbonCopyButtons = chooseButtons(logVO, CONFIG_BUTTON_TYPE_CARBON_COPY); + List initiatorButtons; + List executorButtons; + if (Objects.equals(historicProcessInstance.getBusinessStatus(), PROCESSING.getStatus())) { + //发起人按钮 + initiatorButtons = chooseButtons(logVO, CONFIG_BUTTON_TYPE_INITIATOR); + if (Objects.equals(nodeType, BpmnFlowNodeType.NODE_CARBON_COPY)) { + buttonConfigName = CONFIG_BUTTON_TYPE_CARBON_COPY; + } else if (Objects.equals(nodeType, BpmnFlowNodeType.NODE_TASK) || //审批节点加业务节点指定审批人 + (Objects.equals(nodeType, BpmnFlowNodeType.NODE_BUSINESS) && flowElement.getClass().isAssignableFrom(UserTask.class))) { + // 待审批的节点 + if ((Objects.equals(PROCESSING.getStatus(), extAxHiTaskInst.getStatus()))) { + buttonConfigName = CONFIG_BUTTON_TYPE_CURRENT; + } + //已审批节点 + if (!Objects.equals(PROCESSING.getStatus(), extAxHiTaskInst.getStatus())) { + buttonConfigName = CONFIG_BUTTON_TYPE_HISTORY; + } + } else if (Objects.equals(nodeType, BpmnFlowNodeType.NODE_STARTER)) { + buttonConfigName = CONFIG_BUTTON_TYPE_INITIATOR; + } + if (!StringUtils.hasText(buttonConfigName)) { + log.warn("can't find button config name ,req:{},extAxHiTaskInst:{}", JSON.toJSONString(taskButtonsSearchDTO), JSON.toJSONString(extAxHiTaskInst)); + throw new ServiceException("can't find button config name"); + } + executorButtons = chooseButtons(logVO, buttonConfigName); + } else { + //需要判断流程是否撤回,撤回需要设置发起人状态 + if (Objects.equals(historicProcessInstance.getBusinessStatus(), CANCELLED.getStatus())) { + Object object = historicProcessInstance.getProcessVariables().get(CANCEL_PROCESS_NODE_DEF_KEY_NAME); + if (object instanceof String) { + String cancelNodeKey = (String) object; + if (StringUtils.hasText(cancelNodeKey) && cancelNodeKey.equals(extAxHiTaskInst.getTaskDefinitionKey())) { + bpmnTaskButtonVo.setInitiatorTaskResult(BpmnProcessTaskResultEnum.CANCELED); + } + } + } + initiatorButtons = new ArrayList<>(carbonCopyButtons); + executorButtons = new ArrayList<>(carbonCopyButtons); + } + + bpmnTaskButtonVo.setCustomHiddenButtons(getHiddenCustomButtons(logVO, executorButtons)); + //设置allConfigButtons + List allConfigButtons = new ArrayList<>(executorButtons); + bpmnTaskButtonVo.setAllConfigButtons(allConfigButtons); + Map> metaInfoListMap = new HashMap<>(); + if (!CollectionUtils.isEmpty(executorButtons)) { + for (BpmnButtonMetaInfo metaInfo : filterValuableButtons(executorButtons)) { + metaInfoListMap.computeIfAbsent(metaInfo, k -> new HashSet<>()).add(ButtonVisibleScopeEnum.EXECUTOR); + } + } + if (!CollectionUtils.isEmpty(initiatorButtons)) { + for (BpmnButtonMetaInfo metaInfo : filterValuableButtons(initiatorButtons)) { + metaInfoListMap.computeIfAbsent(metaInfo, k -> new HashSet<>()).add(ButtonVisibleScopeEnum.INITIATOR); + } + } + + List scopeButtons = metaInfoListMap.entrySet().stream() + .map(m -> { + BpmnTaskButtonVo.BpmnButtonMetaInfoWithVisibleScope scopeButton = BeanUtil.copyProperties(m.getKey(), BpmnTaskButtonVo.BpmnButtonMetaInfoWithVisibleScope.class); + scopeButton.setVisibleScopes(new ArrayList<>(m.getValue())); + return scopeButton; + }) + .collect(Collectors.toList()); + bpmnTaskButtonVo.setButtons(scopeButtons); + return bpmnTaskButtonVo; + } + + private BpmnProcessTaskResultEnum getTaskStatus(BpmnFlowNodeType nodeType, ExtAxHiTaskInst axHiTaskInst) { + String logResultStatus = axHiTaskInst.getStatus(); + if (!StringUtils.hasText(logResultStatus)) { + log.warn("log result status is blank!"); + return null; + } + BpmnProcessTaskResultEnum taskResult = null; + BpmnProcessInstanceResultEnum instanceResultEnum = BpmnProcessInstanceResultEnum.valueOf(logResultStatus); + switch (instanceResultEnum) { + case CANCELLED: + taskResult = BpmnProcessTaskResultEnum.CANCELED; + break; + case APPROVED: + if (Objects.equals(nodeType, BpmnFlowNodeType.NODE_CARBON_COPY)) { + taskResult = BpmnProcessTaskResultEnum.NONE; + } else { + taskResult = BpmnProcessTaskResultEnum.APPROVED; + } + break; + case REJECTED: + taskResult = BpmnProcessTaskResultEnum.REJECTED; + break; + case TRANSFER: + taskResult = BpmnProcessTaskResultEnum.TRANSFERRED; + break; + case PROCESSING: + taskResult = BpmnProcessTaskResultEnum.PENDING; + break; + case DELETED: + taskResult = BpmnProcessTaskResultEnum.DELETED; + break; + default: + break; + } + return taskResult; + } + + @Override + public boolean hasPrintTemplate(String processInstanceId, String processDefinitionId) { + try { + return extAxReModelService.hasPrintTemplateConfig(getModelIdByProcessDefinitionId(processInstanceId, processDefinitionId)); + } catch (Exception e) { + log.warn("hasPrintTemplate error:{}", e.getMessage(), e); + return false; + } + } + + 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()); + } + + AtomicBoolean supportUpgrade = new AtomicBoolean(false); + if (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.getResult(), PROCESSING) && + (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)); + supportUpgrade.set(Objects.equals(Boolean.TRUE,i.getSupportUpgradeApproval())); + }); + + + // 比对历史审批人 + logVO.getTaskDetails().stream() + .filter(i -> Objects.equals(i.getNodeType(), NODE_TASK) + || Objects.equals(i.getNodeType(), NODE_BUSINESS) + || Objects.equals(i.getNodeType(), NODE_SIGN)) + .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))); + } + // 提级审批按钮特殊处理 + + if (!supportUpgrade.get()) { + authorizedButtons.removeIf(button -> Objects.equals(BPMN_UPGRADE.getBtnKey(), button.getBtnKey())); + } + + + logVO.setCurrentUserButtons(authorizedButtons); + // 有权限访问的自定义按钮 + logVO.setCustomHiddenButtons(getHiddenCustomButtons(logVO, authorizedButtons)); + } + + private List getHiddenCustomButtons(BpmnProcessInstanceLogVO logVO, List bpmnButtonMetaInfos) { + // 有权限访问的自定义按钮 + List customButtonKeys = bpmnButtonMetaInfos.stream() + .filter(i -> Objects.equals(i.getType(), BpmnButtonMetaInfo.BUTTON_TYPE_CUSTOM)) + .filter(i -> Boolean.FALSE.equals(i.getDisabled()) && Boolean.TRUE.equals(i.getChecked())) + .map(BpmnButtonMetaInfo::getBtnKey) + .distinct() + .collect(Collectors.toList()); + return logVO.getDefaultButtonConf().getInitiator() + .stream() + .filter(i -> Objects.equals(i.getType(), BpmnButtonMetaInfo.BUTTON_TYPE_CUSTOM)) + .filter(i -> !customButtonKeys.contains(i.getBtnKey())) + .collect(Collectors.toList()); + } + + /** + * 按钮的通用处理, 有限使用节点的按钮配置,如果没有则按兜底按钮配置 + * + * @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; + } + + /** + * 过滤Disabled=false && Checked=true + * + * @param bpmnButtonMetaInfos 按钮列表 + * @return 有效的按钮列表 + */ + private List filterValuableButtons(List bpmnButtonMetaInfos) { + if (CollectionUtils.isEmpty(bpmnButtonMetaInfos)) { + return Collections.emptyList(); + } + return bpmnButtonMetaInfos.stream() + .filter(i -> !i.getDisabled()) + .filter(BpmnButtonMetaInfo::getChecked) + .distinct() + .collect(Collectors.toList()); + } + + 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, forecasting, 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).stream() + .filter(i -> Objects.equals(Boolean.TRUE, i.getFutureNode())) + .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: + build.setOperationDesc("动态审批人"); + break; + case nobody: + build.setOperationDesc("系统处理"); + break; + case autoPassed: + build.setOperationDesc("无需审批人,自动同意"); + break; + case autoRejection: + build.setOperationDesc("无需审批人,自动驳回"); + break; + case autoPassed_empty: + build.setOperationDesc("未找到审批人,自动同意"); + break; + case autoRejection_empty: + build.setOperationDesc("未找到审批人,自动驳回"); + break; + case transferToAdmin: + build.setOperationDesc("找不到审批人且转交管理员失败,自动中止"); + 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 + "人或签,仅一人同意即可"); + } + if (Objects.equals(countPerson, 1)) { + // 如果未来节点是单人,则按单人节点展示 + build.setAssigneeSnapshot(build.getForecastAssignees().get(0)); + build.setOperationDesc(build.getAssigneeSnapshot().getAssignerName()); + build.setForecastAssignees(null); + } + } + break; + } + } + if (Objects.equals(e.getNodeType(), NODE_CARBON_COPY)) { + build.setOperationDesc("抄送" + e.getForecastAssigners().size() + "人"); + } + build.setUpgradeApprovalConf(e.getUpgradeApprovalConf()); + tasks.add(build); + }); + } + + private void getHistoricTasks(List logs, + List forecasting, + 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(); + + BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(CollectionUtils.isEmpty(ListUtils.emptyIfNull(e.getAssigneeFull())) ? null : e.getAssigneeFull().get(0)); + 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()); + } + } + if (Objects.nonNull(assigner)) { + assigners.add(assigner); + } + 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 + if (Objects.nonNull(visitor)) { + 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()) + .signature(e.getSignature()) + .assigneeSnapshot(Objects.equals(e.getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()) ? null : + assigner) + .forecastAssignees(Objects.equals(e.getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()) ? ListUtils.emptyIfNull(e.getAssigneeFull()) : Collections.emptyList()) + .upgradeApprovalConf(forecasting.stream().filter(i -> Objects.equals(i.getId(), e.getActivityId())).findAny().orElse(new ProcessNodeDetailVO()).getUpgradeApprovalConf()) + .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()); + } + + @Override + public String getModelIdByProcessInstanceId(String processInstanceId) { + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + if (Objects.isNull(historicProcessInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId); + } + return getModelIdByProcessDefinitionId(null, historicProcessInstance.getProcessDefinitionId()); + } + + @Override + public String getModelIdByProcessDefinitionId(String processInstanceId, String processDefinitionId) { + String key, tenantId; + if (StringUtils.hasText(processInstanceId)) { + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + if (Objects.isNull(historicProcessInstance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId); + } + key = historicProcessInstance.getProcessDefinitionKey(); + tenantId = historicProcessInstance.getTenantId(); + } else { + ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId); + if (Objects.isNull(processDefinition)) { + throw new WorkflowEngineException(PROCESS_DEFINITION_ID_NOT_EXISTS, processDefinitionId); + } + key = processDefinition.getKey(); + tenantId = processDefinition.getTenantId(); + } + return repositoryService.createModelQuery() + .modelKey(key) + .list().stream() + .filter(i -> (Objects.equals(i.getTenantId(), tenantId) || Objects.equals(i.getTenantId(), NO_TENANT_ID)) + && (Objects.equals(i.getCategory(), BPMN_FILE_SUFFIX) || Objects.equals(i.getCategory(), i.getKey())) + && i.getMetaInfo().contains(MODEL_TYPE_PROCESS)) + .sorted((a, b) -> { + if (Objects.equals(a.getTenantId(), tenantId)) { + return -1; + } else if (Objects.equals(b.getTenantId(), tenantId)) { + return 1; + } + return 0; + }) + .findFirst() + .orElseThrow(() -> new WorkflowEngineException(MODEL_NOT_EXISTS)) + .getId(); + } + + @Override + public void overrideFormVariables(FormVariablesUpdateDTO dto) { + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + commandExecutor.execute(new CustomOverrideFormVariablesByLatestInstanceCmd(dto.getProcessInstanceId(), dto.getFormVariables())); + } + + @Override + public List processInstanceSelectDocs(ProcessDocQueryDTO dto) { + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + List docs = commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(), true, extAxModelDocMapper, extAxReModelService)); + + Map readStatusMap = new HashMap<>(); + if (Objects.nonNull(dto.getAssigner())) { + readStatusMap.putAll(extAxReadRecordService.queryReadStatus(ApproverReadStatusDTO.builder() + .processInstanceId(dto.getProcessInstanceId()) + .assigner(dto.getAssigner()) + .build()).stream().collect(Collectors.toMap(SimpleDocDTO::getId, SimpleDocDTO::getReadStatus, (s, t) -> s))); + } + ExtAxProcessSign processSign = extAxProcessSignService.findByProcessInstanceId(dto.getProcessInstanceId()); + Map archiveFileMap = new HashMap<>(); + if (Objects.nonNull(processSign)) { + archiveFileMap.putAll(processSign.getFileArchive().stream().collect(Collectors.toMap(SignFileDTO::getId, Function.identity()))); + } + + return BeanMapper.copyList(docs, DocPendingVO.class, (s, t) -> { + SignFileDTO archive = archiveFileMap.getOrDefault(t.getId(), null); + if (Objects.nonNull(archive)) { + t.setFileRelationId(archive.getFileCode()); + t.setFileKey(archive.getFileKey()); + } + t.setReadStatus(readStatusMap.getOrDefault(t.getId(), false)); + }); + } } 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 new file mode 100644 index 000000000..ed6dd0804 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessJobServiceImp.java @@ -0,0 +1,84 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.service.BpmnProcessJobService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +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; + +import static cn.axzo.workflow.common.code.AsyncJobRespCode.JOB_NOT_EXISTS_JOB_ID; +import static cn.axzo.workflow.common.code.AsyncJobRespCode.JOB_NOT_EXISTS_PROC_INST_ID; + +@Service +@Slf4j +public class BpmnProcessJobServiceImp implements BpmnProcessJobService { + + @Resource + private ManagementService managementService; + + @Resource + private ProcessEngineConfigurationImpl processEngineConfiguration; + + @Override + public void executeDeadLetterJobActionByJobId(String jobId) { + Job deadLetterJob = getDeadLetterJobById(jobId); + managementService.moveDeadLetterJobToExecutableJob(deadLetterJob.getId(), processEngineConfiguration.getAsyncExecutorNumberOfRetries()); + } + + @Override + public void executeDeadLetterJobActionByProcInstId(String processInstanceId) { + List jobs = getDeadLetterProcInstId(processInstanceId); + for (Job deadLetterJob : jobs) { + managementService.moveDeadLetterJobToExecutableJob(deadLetterJob.getId(), processEngineConfiguration.getAsyncExecutorNumberOfRetries()); + } + } + + @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) { + throw new WorkflowEngineException(JOB_NOT_EXISTS_JOB_ID, jobId); + } + return job; + } + + protected List getDeadLetterProcInstId(String procInstId) { + List jobs = managementService.createDeadLetterJobQuery().processInstanceId(procInstId).list(); + if (CollectionUtils.isEmpty(jobs)) { + throw new WorkflowEngineException(JOB_NOT_EXISTS_PROC_INST_ID, procInstId); + } + return jobs; + } + +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessModelServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessModelServiceImpl.java index cc45d13ac..64819f436 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessModelServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessModelServiceImpl.java @@ -1,20 +1,33 @@ package cn.axzo.workflow.core.service.impl; import cn.axzo.framework.jackson.utility.JSON; +import cn.axzo.workflow.common.enums.ExtModelStateFieldEnum; +import cn.axzo.workflow.common.enums.ModelBizTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel; 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.print.PrintTemplateConfigUpsertDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelDetailVO; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.response.category.CategoryItemVO; +import cn.axzo.workflow.common.model.response.print.PrintModelDTO; import cn.axzo.workflow.core.common.utils.BpmnJsonConverterUtil; import cn.axzo.workflow.core.common.utils.ContextHolder; +import cn.axzo.workflow.core.conf.CustomEventManager; +import cn.axzo.workflow.core.engine.event.DocChangeEventImpl; +import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc; +import cn.axzo.workflow.core.service.AggregateModelService; import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; import cn.axzo.workflow.core.service.BpmnProcessModelService; +import cn.axzo.workflow.core.service.CategoryService; +import cn.axzo.workflow.core.service.ExtAxModelDocService; import cn.axzo.workflow.core.service.ExtAxReModelService; import cn.axzo.workflow.core.service.converter.BpmnModelConverter; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.BpmnModel; import org.flowable.common.engine.impl.db.SuspensionState; @@ -42,24 +55,28 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_KEY_EXISTS; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_KEY_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_BPMN_NOT_EXISTS; +import static cn.axzo.workflow.common.constant.BpmnConstants.BPMN_FILE_SUFFIX; +import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; import static cn.axzo.workflow.common.constant.BpmnConstants.DISABLED; import static cn.axzo.workflow.common.constant.BpmnConstants.ENABLED; import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_DESCRIPTION; import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE; import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE_PROCESS; -import static cn.axzo.workflow.core.common.code.BpmnModelRespCode.MODEL_ID_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.BpmnModelRespCode.MODEL_KEY_EXISTS; -import static cn.axzo.workflow.core.common.code.BpmnModelRespCode.MODEL_KEY_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.BpmnModelRespCode.MODEL_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_BPMN_NOT_EXISTS; -import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql; -import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors; +import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.countSql; +import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.sqlConnectors; @Service +@Slf4j public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { @Resource @@ -74,7 +91,15 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { @Resource private BpmnModelConverter bpmnModelConverter; @Resource + private CategoryService categoryService; + @Resource private String serviceVersion; + @Resource + private ExtAxModelDocService extAxModelDocService; + @Resource + private CustomEventManager eventPublisher; + @Resource + private AggregateModelService aggregateModelService; @Override public BpmPageResult getModelPage(BpmnModelSearchDTO dto) { @@ -94,10 +119,17 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { .append(" b.status = #{status}"); query.parameter("status", dto.getStatus() == 1); } - if (StringUtils.hasLength(dto.getKey())) { + if (!CollectionUtils.isEmpty(dto.getKeys())) { baseQuerySql.append(sqlConnectors(baseQuerySql)) - .append(" a.KEY_ = #{key}"); - query.parameter("key", dto.getKey()); + .append(" a.KEY_ in ("); + for (int i = 0; i < dto.getKeys().size(); i++) { + baseQuerySql.append("#{KEY_").append(i).append("}"); + if (i < dto.getKeys().size() - 1) { + baseQuerySql.append(","); + } + query.parameter("KEY_" + i, dto.getKeys().get(i)); + } + baseQuerySql.append(")"); } if (StringUtils.hasLength(dto.getName())) { baseQuerySql.append(sqlConnectors(baseQuerySql)) @@ -117,7 +149,7 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { baseQuerySql.append(")"); } if (Objects.nonNull(dto.getAgent())) { - if (dto.getAgent()) { + if (Boolean.TRUE.equals(dto.getAgent())) { baseQuerySql.append(sqlConnectors(baseQuerySql)) .append(" TENANT_ID_ != '' "); if (!CollectionUtils.isEmpty(dto.getTenantIds())) { @@ -172,16 +204,20 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { } })); - if (Objects.isNull(dto.getStatus())) { - extAxReModelService.listByModelIds(vos.stream().map(BpmnModelDetailVO::getId).collect(Collectors.toList())) - .forEach(i -> vos.forEach(j -> { - if (Objects.equals(i.getModelId(), j.getId())) { - j.setStatus(i.getStatus()); - } - })); - } else { - vos.forEach(i -> i.setStatus(dto.getStatus())); - } +// if (Objects.isNull(dto.getStatus())) { + extAxReModelService.listByModelIds(vos.stream().map(BpmnModelDetailVO::getId).collect(Collectors.toList())) + .forEach(i -> vos.forEach(j -> { + if (Objects.equals(i.getModelId(), j.getId())) { + j.setStatus(i.getStatus()); + j.setPrintStatus(i.getPrintStatus()); + j.setPrintFileName(i.getPrintFileName()); + j.setPrintTemplateName(i.getPrintTemplateName()); + j.setPrintTemplateConfig(i.getPrintTemplateConfig()); + } + })); +// } else { +// vos.forEach(i -> i.setStatus(dto.getStatus())); +// } return new BpmPageResult<>(vos, countSqlQuery.count()); } @@ -209,11 +245,15 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { public String createBpmModel(@Valid BpmnModelCreateDTO dto) { ModelQuery modelQuery = repositoryService.createModelQuery() .modelTenantId(dto.getTenantId()) - .modelKey(dto.getCategory()); + .modelKey(dto.getKey()); - Model existModel = modelQuery.singleResult(); - if (!ObjectUtils.isEmpty(existModel)) { - throw new WorkflowEngineException(MODEL_KEY_EXISTS, dto.getCategory()); +// Model existModel = modelQuery.list(); +// if (!ObjectUtils.isEmpty(existModel)) { +// throw new WorkflowEngineException(MODEL_KEY_EXISTS, dto.getKey()); +// } + List list = modelQuery.list(); + if (!CollectionUtils.isEmpty(list)) { + throw new WorkflowEngineException(MODEL_KEY_EXISTS, dto.getKey()); } Map metaInfoMap = new HashMap<>(); @@ -222,22 +262,28 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { Model model = repositoryService.newModel(); model.setName(dto.getName()); - model.setMetaInfo(JSON.toJSONString(metaInfoMap)); - model.setCategory(dto.getCategory()); - model.setKey(StringUtils.hasLength(dto.getKey()) ? dto.getKey() : dto.getCategory()); + model.setMetaInfo(JSONUtil.toJsonStr(metaInfoMap)); + model.setCategory(BPMN_FILE_SUFFIX); + model.setKey(dto.getKey()); model.setTenantId(dto.getTenantId()); repositoryService.saveModel(model); + + Optional optCategory = categoryService.get(BPM_MODEL_CATEGORY, dto.getKey()); //存储Bpmn协议 - if (Objects.nonNull(dto.getJsonModel())) { + if (Objects.nonNull(dto.getJsonModel()) && optCategory.isPresent()) { BpmnModel bpmnModel = BpmnJsonConverterUtil.convertToBpmn(dto.getJsonModel().getNode(), - StringUtils.hasLength(dto.getKey()) ? dto.getKey() : dto.getCategory(), + ModelBizTypeEnum.valueOfType(optCategory.get().getRemark()), + dto.getKey(), dto.getName(), + Objects.nonNull(dto.getFormJsonModel()) ? dto.getKey() : null, dto.getDescription(), + dto.getJsonModel().getApproveConf(), + dto.getJsonModel().getSignConf(), dto.getJsonModel().getNoticeConf(), dto.getJsonModel().getButtonConf(), dto.getJsonModel().getFieldConf(), serviceVersion); - BpmnJsonConverterUtil.setCategory(bpmnModel, dto.getCategory()); + BpmnJsonConverterUtil.setCategory(bpmnModel, dto.getKey()); byte[] bpmn = BpmnJsonConverterUtil.transformBytes(bpmnModel); processDefinitionService.updateProcessDefinition(model.getId(), bpmn); } @@ -294,11 +340,12 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { } @Override + @Transactional(rollbackFor = Exception.class) public void updateBpmModel(BpmnModelUpdateDTO dto) { ModelQuery query = repositoryService.createModelQuery() .modelId(dto.getProcessModelId()) .modelTenantId(dto.getTenantId()) - .modelKey(dto.getCategory()); + .modelKey(dto.getKey()); Model originModel = query.singleResult(); if (ObjectUtils.isEmpty(originModel)) { @@ -309,23 +356,28 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { metaInfoMap.put(MODEL_DESCRIPTION, dto.getDescription()); originModel.setName(dto.getName()); - originModel.setMetaInfo(JSON.toJSONString(metaInfoMap)); - originModel.setCategory(dto.getCategory()); - originModel.setKey(StringUtils.hasLength(dto.getKey()) ? dto.getKey() : dto.getCategory()); + originModel.setMetaInfo(JSONUtil.toJsonStr(metaInfoMap)); + originModel.setCategory(BPMN_FILE_SUFFIX); + originModel.setKey(dto.getKey()); originModel.setTenantId(dto.getTenantId()); repositoryService.saveModel(originModel); + Optional optCategory = categoryService.get(BPM_MODEL_CATEGORY, dto.getKey()); //存储Bpmn协议 - if (Objects.nonNull(dto.getJsonModel())) { + if (Objects.nonNull(dto.getJsonModel()) && optCategory.isPresent()) { BpmnModel bpmnModel = BpmnJsonConverterUtil.convertToBpmn(dto.getJsonModel().getNode(), - StringUtils.hasLength(dto.getKey()) ? dto.getKey() : dto.getCategory(), + ModelBizTypeEnum.valueOfType(optCategory.get().getRemark()), + dto.getKey(), dto.getName(), + Objects.nonNull(dto.getFormJsonModel()) ? dto.getKey() : null, dto.getDescription(), + dto.getJsonModel().getApproveConf(), + dto.getJsonModel().getSignConf(), dto.getJsonModel().getNoticeConf(), dto.getJsonModel().getButtonConf(), dto.getJsonModel().getFieldConf(), serviceVersion); - BpmnJsonConverterUtil.setCategory(bpmnModel, dto.getCategory()); + BpmnJsonConverterUtil.setCategory(bpmnModel, dto.getKey()); byte[] bpmn = BpmnJsonConverterUtil.transformBytes(bpmnModel); processDefinitionService.updateProcessDefinition(originModel.getId(), bpmn); } @@ -344,6 +396,21 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { throw new WorkflowEngineException(MODEL_ID_NOT_EXISTS, processModelId); } + List modelIds = repositoryService.createModelQuery() + .modelTenantId(model.getTenantId()) + .modelKey(model.getKey()) + .list().stream() + .map(Model::getId).distinct() + .collect(Collectors.toList()); + + long count = extAxReModelService.listByModelIds(modelIds).stream() + .filter(e -> Objects.equals(e.getStatus(), 1)) + .filter(e -> !Objects.equals(e.getModelId(), processModelId)) + .count(); + if (count > 0) { + throw new WorkflowEngineException(MODEL_KEY_EXISTS, model.getKey()); + } + byte[] bpmnBytes = this.repositoryService.getModelEditorSource(model.getId()); if (bpmnBytes == null) { throw new WorkflowEngineException(PROCESS_DEFINITION_BPMN_NOT_EXISTS, model.getId()); @@ -359,8 +426,9 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { } ProcessDefinition definition = definitionQuery.singleResult(); model.setDeploymentId(definition.getDeploymentId()); + model.setCategory(BPMN_FILE_SUFFIX); this.repositoryService.saveModel(model); - extAxReModelService.changeStatus(model.getId(), ENABLED); + extAxReModelService.changeStatus(model.getId(), ENABLED, ExtModelStateFieldEnum.status); return definition.getId(); } @@ -419,31 +487,57 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { throw new WorkflowEngineException(MODEL_ID_NOT_EXISTS, processModelId); } updateProcessDefinitionSuspended(model.getDeploymentId()); - extAxReModelService.changeStatus(model.getId(), DISABLED); + extAxReModelService.changeStatus(model.getId(), DISABLED, ExtModelStateFieldEnum.status); } @Override @Transactional(rollbackFor = Throwable.class) public void changeStatus(String modelId, Integer status, BpmnTaskDelegateAssigner operator) { Model model = this.repositoryService.getModel(modelId); + if (Objects.isNull(model)) { + return; + } // 公共模型不会真实的取消部署 if (!Objects.equals("", model.getTenantId())) { if (Objects.equals(ENABLED, status)) { - deployBpmModelById(modelId, null, operator); + aggregateModelService.deployBpmnAndFormModel(modelId, null, operator); +// deployBpmModelById(modelId, null, operator); } else { unDeployBpmModelById(modelId, null, operator); } } - extAxReModelService.changeStatus(modelId, status); + extAxReModelService.changeStatus(modelId, status, ExtModelStateFieldEnum.status); + + List oldSettings = extAxModelDocService.querySetting(model.getKey(), model.getTenantId()); + List newSettings = extAxModelDocService.querySetting(model.getKey(), ""); + DocChangeEventImpl event; + if (Objects.equals(ENABLED, status)) { + event = new DocChangeEventImpl(model.getKey(), model.getTenantId(), newSettings, oldSettings); + } else { + event = new DocChangeEventImpl(model.getKey(), model.getTenantId(), oldSettings, newSettings); + } + // 发送文档变更事件 + eventPublisher.publishEvent(event); + } + @Override + public void changePrintStatus(String modelId, Integer status, BpmnTaskDelegateAssigner assignee) { + Model model = this.repositoryService.getModel(modelId); + if (Objects.isNull(model)) { + return; + } + extAxReModelService.changeStatus(modelId, status, ExtModelStateFieldEnum.printStatus); + } + + @Override public List getModelCategoryList() { List list = repositoryService.createModelQuery().deployed().list(); if (CollectionUtils.isEmpty(list)) { return Collections.emptyList(); } - return list.stream().map(Model::getCategory).collect(Collectors.toList()); + return list.stream().map(Model::getKey).distinct().collect(Collectors.toList()); } @Override @@ -452,7 +546,17 @@ public class BpmnProcessModelServiceImpl implements BpmnProcessModelService { if (CollectionUtils.isEmpty(list)) { return Collections.emptyList(); } - return list.stream().map(Model::getTenantId).collect(Collectors.toList()); + return list.stream().map(Model::getTenantId).distinct().collect(Collectors.toList()); + } + + @Override + public void printTemplateConfig(PrintTemplateConfigUpsertDTO dto) { + extAxReModelService.printTemplateConfig(dto); + } + + @Override + public PrintModelDTO getPrintTemplateConfig(String modelId) { + return extAxReModelService.getPrintTemplateConfig(modelId); } private void updateProcessDefinitionSuspended(String deploymentId) { diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskForEsServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskForEsServiceImpl.java new file mode 100644 index 000000000..cca82ba97 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessTaskForEsServiceImpl.java @@ -0,0 +1,88 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.service.BpmnProcessTaskForEsService; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.HistoryService; +import org.flowable.engine.TaskService; +import org.flowable.engine.task.Attachment; +import org.flowable.engine.task.Comment; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; + +/** + * 专用与对接 ES 的流程任务相关操作 + * + * @author wangli + * @since 2024-09-29 10:55 + */ +@Service +@Slf4j +public class BpmnProcessTaskForEsServiceImpl implements BpmnProcessTaskForEsService { + @Resource + @Lazy + private HistoryService historyService; + @Resource + @Lazy + private TaskService taskService; + @Resource + private ExtAxHiTaskInstService extAxHiTaskInstService; + @Resource + private ExtAxProcessLogService extAxProcessLogService; + + @Override + public List queryHistoricProcessTaskByProcessInstanceId(String processInstanceId) { +// List historicTaskListByProcessInstanceId = bpmnProcessTaskService.getHistoricTaskListByProcessInstanceId(processInstanceId, null); + if (!StringUtils.hasText(processInstanceId)) { + return Collections.emptyList(); + } + return historyService.createHistoricTaskInstanceQuery() + .processInstanceId(processInstanceId) + .list(); + } + + public List queryExtAxHiTaskInstByProcessInstanceId(String processInstanceId) { + if (!StringUtils.hasText(processInstanceId)) { + return Collections.emptyList(); + } + ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); + searchDTO.setProcessInstanceId(processInstanceId); + return extAxHiTaskInstService.queryList(searchDTO); + } + + @Override + public List queryCommentByProcessInstanceId(String processInstanceId) { + if (!StringUtils.hasText(processInstanceId)) { + return Collections.emptyList(); + } + return taskService.getProcessInstanceComments(processInstanceId); + } + + @Override + public List queryAttachmentByProcessInstanceId(String processInstanceId) { + if (!StringUtils.hasText(processInstanceId)) { + return Collections.emptyList(); + } + return taskService.getProcessInstanceAttachments(processInstanceId); + } + + @Override + public List queryProcessLogByProcessInstanceId(String processInstanceId) { + if(!StringUtils.hasText(processInstanceId)){ + return Collections.emptyList(); + } + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(processInstanceId); + return extAxProcessLogService.genericQuery(queryLog); + } +} 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 9698fea00..2fb5ac129 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,39 +1,64 @@ 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.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnNodeBackSystemOperateDTO; +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.BpmnTaskAuditWithFormDTO; +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.BpmnTaskResetApproversDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; +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.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.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskAsyncCmd; import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd; +import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskWithFormAsyncCmd; +import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskWithFormCmd; +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; import cn.axzo.workflow.core.engine.cmd.CustomCountersignUserTaskCmd; import cn.axzo.workflow.core.engine.cmd.CustomCreateDummyTaskCmd; +import cn.axzo.workflow.core.engine.cmd.CustomRejectionTaskAsyncCmd; import cn.axzo.workflow.core.engine.cmd.CustomRejectionTaskCmd; +import cn.axzo.workflow.core.engine.cmd.CustomRemindTaskAsyncCmd; +import cn.axzo.workflow.core.engine.cmd.CustomRemindTaskCmd; +import cn.axzo.workflow.core.engine.cmd.CustomResetTaskApproversAsyncCmd; +import cn.axzo.workflow.core.engine.cmd.CustomResetTaskApproversCmd; +import cn.axzo.workflow.core.engine.cmd.CustomTransferUserTaskAsyncCmd; import cn.axzo.workflow.core.engine.cmd.CustomTransferUserTaskCmd; 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.ExtAxProcessLogService; import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter; import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter; import cn.axzo.workflow.core.service.converter.BpmnTaskConverter; @@ -42,7 +67,12 @@ 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.bpmn.model.Process; 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; @@ -62,6 +92,7 @@ import org.flowable.engine.task.Comment; import org.flowable.form.api.FormInfo; import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.task.api.Task; +import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskQuery; import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstanceQuery; @@ -70,9 +101,9 @@ import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEnt import org.flowable.variable.api.history.HistoricVariableInstance; import org.flowable.variable.api.persistence.entity.VariableInstance; import org.flowable.variable.service.impl.persistence.entity.HistoricVariableInstanceEntityImpl; +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; @@ -80,33 +111,52 @@ import java.util.ArrayList; 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; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_TASK_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.FIND_TASK_BY_PERSON_ID_ERROR; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.REACHED_BACKED_MAXIMUM_NUM; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_REMIND_ERROR_NOT_EXISTS; 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.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_SIGN; +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.BpmnTaskRespCode.FIND_TASK_BY_PERSON_ID_ERROR; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_REMIND_ERROR_NOT_EXISTS; +import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.countSql; +import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.sqlConnectors; import static cn.axzo.workflow.core.common.utils.BpmnCollectionUtils.convertSet; -import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql; -import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature; @Service @Slf4j @@ -138,6 +188,17 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { private ExtAxHiTaskInstService extAxHiTaskInstService; @Resource private RepositoryService repositoryService; + @Resource + @Lazy + private BpmnProcessTaskService bpmnProcessTaskService; + @Resource + private BpmnProcessDefinitionService bpmnProcessModelService; + @Resource + private ExtAxProcessLogService extAxProcessLogService; + @Resource + private ExtAxProcessLogService processLogService; + @Resource + private SupportRefreshProperties refreshProperties; @Override public BpmPageResult getTodoTaskPage(BpmnTaskPageSearchDTO dto) { @@ -290,16 +351,259 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { @Transactional(rollbackFor = Exception.class) public void approveTask(BpmnTaskAuditDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); - commandExecutor.execute(new CustomApproveTaskCmd(dto.getTaskId(), dto.getAdvice(), dto.getAttachmentList(), - dto.getApprover(), dto.getNextApprover())); + if (Boolean.TRUE.equals(dto.getAsync())) { + commandExecutor.execute(new CustomApproveTaskAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomApproveTaskCmd(dto)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void approveTaskWithForm(BpmnTaskAuditWithFormDTO dto) { + // 表单级别暂时只允许同步 + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + if (Boolean.TRUE.equals(dto.getAsync())) { + commandExecutor.execute(new CustomApproveTaskWithFormAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomApproveTaskWithFormCmd(dto)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void backTask(BpmnTaskBackAuditDTO dto) { + Task task = processEngineConfiguration.getTaskService().createTaskQuery().taskId(dto.getTaskId()).singleResult(); + if (task == null) { + throw new WorkflowEngineException(PROCESS_TASK_NOT_EXISTS); + } + List backOptionalNodes = getBackOptionalNodes(task.getProcessInstanceId(), task.getTaskDefinitionKey()); + 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(CustomBackTaskCmd.CustomBackParamsDto.builder() + .targetTaskIds(Collections.singletonList(dto.getTaskId())) + .advice(dto.getAdvice()) + .operationDesc(dto.getOperationDesc()) + .attachmentList(dto.getAttachmentList()) + .operator(dto.getApprover()) + .processInstanceId(task.getProcessInstanceId()) + .currentActivityId(task.getTaskDefinitionKey()) + .toActivityId(dto.getToActivityId()) + .validateApprover(true) + .build())); + } + } + + @Override + public void systemBackTask(BpmnNodeBackSystemOperateDTO dto) { + //需要查询当前流程是否停留在当前节点 + //需要查询退回的节点是否可达 + //然后进行退回操作 + List taskList = processEngineConfiguration.getHistoryService() + .createHistoricTaskInstanceQuery() + .taskDefinitionKey(dto.getCurrentActivityId()) + .processInstanceId(dto.getProcessInstanceId()) + .list(); + if (CollectionUtils.isEmpty(taskList)) { + throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS); + } + List valuableTasks = taskList.stream() + .filter(t -> Objects.isNull(t.getEndTime())) + .filter(t -> { + if (CollectionUtils.isNotEmpty(dto.getTargetTaskIds())) { + return dto.getTargetTaskIds().contains(t.getId()); + } + return true; + }) + .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(valuableTasks)) { + throw new WorkflowEngineException(TASK_HAS_BEEN_COMPLETE); + } + List backOptionalNodes = getBackOptionalNodes(valuableTasks.get(0).getProcessInstanceId(), valuableTasks.get(0).getTaskDefinitionKey()); + 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)); + } + } + } + BpmnTaskDelegateAssigner operator = dto.getOperator(); + if (operator == null) { + operator = new BpmnTaskDelegateAssigner("系统", "system", taskList.get(0).getTenantId()); + } + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + commandExecutor.execute(new CustomBackTaskCmd(CustomBackTaskCmd.CustomBackParamsDto.builder() + .targetTaskIds(valuableTasks.stream().map(TaskInfo::getId).collect(Collectors.toList())) + .advice(dto.getAdvice()) + .operationDesc(dto.getOperationDesc()) + .attachmentList(dto.getAttachmentList()) + .operator(operator) + .processInstanceId(valuableTasks.get(0).getProcessInstanceId()) + .currentActivityId(dto.getCurrentActivityId()) + .toActivityId(dto.getToActivityId()) + .build())); + } + + @Override + public List getBackOptionalNodesByTaskId(String taskId) { + Task task = processEngineConfiguration.getTaskService().createTaskQuery().taskId(taskId).singleResult(); + if (task == null) { + throw new WorkflowEngineException(PROCESS_TASK_NOT_EXISTS); + } + return this.getBackOptionalNodes(task.getProcessInstanceId(), task.getTaskDefinitionKey()); + } + + @Override + public List getBackOptionalNodes(String processInstanceId, String currentActivityId) { + //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 + || t.getNodeType() == NODE_SIGN) + .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 -> !currentActivityId.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 || currentNodeType == NODE_SIGN; + }) + .filter(flowElement -> !NODE_STARTER.getType().equals(flowElement.getId())) + .map(flowElement -> BpmnOptionalNodeDTO + .builder() + .processInstanceId(processInstanceId) + .processDefinitionId(processInstance.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); } @Override @Transactional(rollbackFor = Exception.class) public void rejectTask(BpmnTaskAuditDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); - commandExecutor.execute(new CustomRejectionTaskCmd(dto.getTaskId(), dto.getAdvice(), dto.getAttachmentList(), - dto.getApprover(), extAxHiTaskInstService)); + if (Boolean.TRUE.equals(dto.getAsync())) { + commandExecutor.execute(new CustomRejectionTaskAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomRejectionTaskCmd(dto, extAxHiTaskInstService)); + } + } + + @Override + public BatchOperationResultVO batchRejectTask(List dtos) { + return batchOperation(dtos, bpmnProcessTaskService::rejectTask); } @Override @@ -354,8 +658,13 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { .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()); @@ -386,8 +695,10 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { variableInstanceMap.getOrDefault(OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + vo.getTaskId(), null); } - BpmnTaskDelegateAssigner assigner = Objects.nonNull(assginerSnapshot) ? - (BpmnTaskDelegateAssigner) assginerSnapshot.getValue() : null; + BpmnTaskDelegateAssigner assigner = null; + if (Objects.nonNull(assginerSnapshot)) { + assigner = BpmnTaskDelegateAssigner.toObjectCompatible(assginerSnapshot.getValue()); + } if (Objects.nonNull(assigner) && !Objects.equals(assigner.buildAssigneeId(), NO_ASSIGNEE)) { vo.setAssigneeSnapshot(assigner); } @@ -405,7 +716,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { BpmnMetaParserHelper.getApprovalMethod(bpmnModel.getFlowElement(vo.getTaskDefinitionKey())) .ifPresent(vo::setApprovalMethod); } - return vos; + return resultList; } /** @@ -462,7 +773,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { vos.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).collect(Collectors.toList()); Map instanceMap = runtimeService.getVariableInstances(processInstanceId, snapshotTaskIds); - vos.forEach(i -> i.setAssigner((BpmnTaskDelegateAssigner) instanceMap.get(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).getValue())); + vos.forEach(i -> i.setAssigner(BpmnTaskDelegateAssigner.toObjectCompatible(instanceMap.get(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).getValue()))); return vos; } @@ -491,10 +802,47 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { @Transactional(rollbackFor = Exception.class) public void transferTask(BpmnTaskTransferDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); - commandExecutor.execute(new CustomTransferUserTaskCmd(dto.getTaskId(), - dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssigner())); + if (dto.getAsync() != null && dto.getAsync()) { + commandExecutor.execute(new CustomTransferUserTaskAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomTransferUserTaskCmd(dto.getTaskId(), + dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssigner(), dto.getAdditionalOpeDesc())); + } } + @Override + public BatchOperationResultVO batchTransferTask(List dtos) { + return batchOperation(dtos, bpmnProcessTaskService::transferTask); + } + + private BatchOperationResultVO batchOperation(List dtos, Consumer consumer) { + BatchOperationResultVO result = new BatchOperationResultVO(); + result.setRequestCount(dtos.size()); + if (CollectionUtils.isEmpty(dtos)) { + return result; + } + List details = new ArrayList<>(); + result.setDetails(details); + dtos.forEach(dto -> { + BatchOperationItemResultVO itemVO = new BatchOperationItemResultVO(); + details.add(itemVO); + itemVO.setType("流程"); + try { + if (dto instanceof BpmnTaskTransferDTO) { + itemVO.setId(((BpmnTaskTransferDTO) dto).getTaskId()); + } + if (dto instanceof BpmnTaskAuditDTO) { + itemVO.setId(((BpmnTaskAuditDTO) dto).getTaskId()); + } + consumer.accept(dto); + } catch (Exception e) { + itemVO.setHasError(true); + itemVO.setErrorMessage(e.getMessage()); + } + }); + result.setFailCount((int) result.getDetails().stream().filter(BatchOperationItemResultVO::getHasError).count()); + return result; + } @Override @Transactional(rollbackFor = Exception.class) @@ -520,67 +868,50 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { @Transactional(rollbackFor = Exception.class) public void countersignTask(BpmnTaskCountersignDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); - commandExecutor.execute(new CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum.valueOfType(dto.getCountersignType()), dto.getTaskId(), - dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(), - extAxHiTaskInstService)); + if (dto.getAsync() != null && dto.getAsync()) { + //异步处理 + 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)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void resetTaskApprovers(BpmnTaskResetApproversDTO dto) { + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + if(dto.getAsync() != null && dto.getAsync()) { + commandExecutor.execute(new CustomResetTaskApproversAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomResetTaskApproversCmd(dto.getTaskId(), dto.getAdvice(), + dto.getAttachmentList(), dto.getOriginAssigner(), dto.getTargetAssignerList(), extAxHiTaskInstService)); + } } @Override public void remindTask(BpmnTaskRemindDTO dto) { - TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(dto.getProcessInstanceId()); - if (StringUtils.hasLength(dto.getTaskDefinitionKey())) { - taskQuery.taskDefinitionKey(dto.getTaskDefinitionKey()); + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + if (Boolean.TRUE.equals(dto.getAsync())) { + commandExecutor.execute(new CustomRemindTaskAsyncCmd(dto)); + } else { + commandExecutor.execute(new CustomRemindTaskCmd(dto.getTerminalType(), dto.getProcessInstanceId(), dto.getTaskDefinitionKey(), dto.getRemindTypes(), refreshProperties)); } - List list = taskQuery.active().list(); - if (CollectionUtils.isEmpty(list)) { - throw new WorkflowEngineException(TASK_REMIND_ERROR_NOT_EXISTS); - } - - if (CollectionUtils.isEmpty(dto.getRemindTypes())) { - return; - } - ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); - FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); - ProcessInstance processInstance = - runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult(); - Optional noticeConfig = - BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId())); - List assigners = - (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.getTenantId(), task.getId()); - event.setProcessInstanceId(processInstance.getProcessInstanceId()); - event.setTenantId(processInstance.getTenantId()); - event.setTaskId(task.getId()); - eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey()); - }); - }); - }); } @Override @Transactional(rollbackFor = Exception.class) 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)); + return commandExecutor.execute(new CustomCreateDummyTaskCmd(dto, extAxHiTaskInstService)); } @Override @Transactional(rollbackFor = Exception.class) public void completeRobotTask(BpmnRobotTaskCompleteDTO dto) { CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); - commandExecutor.execute(new CustomCompleteDummyTaskCmd(dto.getProcessInstanceId(), dto.getTaskId(), - dto.getRobotNode().getFlowNodeName(), dto.getRobotNode().getOperationDesc(), extAxHiTaskInstService)); + commandExecutor.execute(new CustomCompleteDummyTaskCmd(dto, extAxHiTaskInstService, extAxProcessLogService)); } @Override @@ -594,4 +925,16 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService { } return list.get(0).getId(); } + + @Override + public Map findTaskIdByInstanceIdsAndPersonId(List processInstanceIds, String personId) { + List tasks = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds) + .taskAssigneeLike("%" + personId) + .active() + .list(); + if (CollectionUtils.isEmpty(tasks)) { + return new HashMap<>(); + } + return tasks.stream().collect(Collectors.toMap(TaskInfo::getProcessInstanceId, TaskInfo::getId)); + } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessVariableServiceImp.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessVariableServiceImp.java new file mode 100644 index 000000000..b198a3f32 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessVariableServiceImp.java @@ -0,0 +1,131 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.service.BpmnProcessVariableService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.runtime.Execution; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_EXIST; +import static cn.axzo.workflow.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_NAME_MUST_BE_NOT_NULL; +import static cn.axzo.workflow.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_NOT_EXIST; +import static cn.axzo.workflow.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_SCOPE_ERROR; +import static cn.axzo.workflow.common.code.BpmnVariablesRespCode.PROCESS_VARIABLE_VARS_NOT_NULL; + +@Service +@Slf4j +public class BpmnProcessVariableServiceImp implements BpmnProcessVariableService { + + @Resource + protected RuntimeService runtimeService; + + @Override + public void createVariable(String executionId, RestBpmnProcessVariable restVariable) { + Execution execution = getExecutionFromRequest(executionId); + setSimpleVariable(restVariable, execution, true); + } + + @Override + public void updateVariable(String executionId, RestBpmnProcessVariable restVariable) { + Execution execution = getExecutionFromRequest(executionId); + setSimpleVariable(restVariable, execution, false); + } + + @Override + public void updateVariables(String executionId, List restVariables) { + if (CollectionUtils.isEmpty(restVariables)) { + throw new WorkflowEngineException(PROCESS_VARIABLE_VARS_NOT_NULL); + } + Execution execution = getExecutionFromRequest(executionId); + for (RestBpmnProcessVariable restVariable : restVariables) { + setSimpleVariable(restVariable, execution, false); + } + } + + @Override + public void deleteVariables(String executionId, List variableNames, String scope) { + Execution execution = getExecutionFromRequest(executionId); + RestBpmnProcessVariable.RestVariableScope variableScope = RestBpmnProcessVariable.RestVariableScope.GLOBAL; + if (scope != null) { + variableScope = getScopeFromString(scope); + } + if (variableScope == RestBpmnProcessVariable.RestVariableScope.LOCAL) { + runtimeService.removeVariablesLocal(execution.getId(), variableNames); + } else { + runtimeService.removeVariables(execution.getParentId(), variableNames); + } + } + + protected Execution getExecutionFromRequest(String executionId) { + Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult(); + if (execution == null) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, executionId); + } + return execution; + } + + protected void setSimpleVariable(RestBpmnProcessVariable restVariable, Execution execution, boolean isNew) { + if (restVariable.getName() == null) { + throw new WorkflowEngineException(PROCESS_VARIABLE_NAME_MUST_BE_NOT_NULL); + } + RestBpmnProcessVariable.RestVariableScope scope = restVariable.getScope(); + if (scope == null) { + scope = RestBpmnProcessVariable.RestVariableScope.GLOBAL; + } + setVariable(execution, restVariable.getName(), restVariable.getValue(), scope, isNew); + } + + protected void setVariable(Execution execution, String name, Object value, RestBpmnProcessVariable.RestVariableScope scope, boolean isNew) { + boolean hasVariable = hasVariableOnScope(execution, name, scope); + if (isNew && hasVariable) { + throw new WorkflowEngineException(PROCESS_VARIABLE_EXIST, name, execution.getId()); + } + if (!isNew && !hasVariable) { + throw new WorkflowEngineException(PROCESS_VARIABLE_NOT_EXIST, execution.getId(), name); + } + + if (scope == RestBpmnProcessVariable.RestVariableScope.LOCAL) { + runtimeService.setVariableLocal(execution.getId(), name, value); + } else { + if (execution.getParentId() != null) { + runtimeService.setVariable(execution.getParentId(), name, value); + } else { + runtimeService.setVariable(execution.getId(), name, value); + } + } + } + + protected boolean hasVariableOnScope(Execution execution, String variableName, RestBpmnProcessVariable.RestVariableScope scope) { + boolean variableFound = false; + if (scope == RestBpmnProcessVariable.RestVariableScope.GLOBAL) { + if (execution.getParentId() != null && runtimeService.hasVariable(execution.getParentId(), variableName)) { + variableFound = true; + } + } else if (scope == RestBpmnProcessVariable.RestVariableScope.LOCAL) { + if (runtimeService.hasVariableLocal(execution.getId(), variableName)) { + variableFound = true; + } + } + return variableFound; + } + + public static RestBpmnProcessVariable.RestVariableScope getScopeFromString(String scope) { + if (scope != null) { + for (RestBpmnProcessVariable.RestVariableScope s : RestBpmnProcessVariable.RestVariableScope.values()) { + if (s.name().equalsIgnoreCase(scope)) { + return s; + } + } + throw new WorkflowEngineException(PROCESS_VARIABLE_SCOPE_ERROR, scope); + } else { + return RestBpmnProcessVariable.RestVariableScope.GLOBAL; + } + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryConfigServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryConfigServiceImpl.java index ee2ed61d2..dd65cf121 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryConfigServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryConfigServiceImpl.java @@ -1,31 +1,45 @@ package cn.axzo.workflow.core.service.impl; +import cn.axzo.basics.common.constant.enums.TableIsDeleteEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; 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.CategoryGroupVarSearchDto; +import cn.axzo.workflow.common.model.request.category.CategoryGroupVarUpsertDto; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; import cn.axzo.workflow.core.repository.entity.ExtAxDict; import cn.axzo.workflow.core.repository.entity.ExtAxDictConf; +import cn.axzo.workflow.core.repository.entity.ExtAxDictGroup; +import cn.axzo.workflow.core.repository.entity.ExtAxDictGroupVariable; import cn.axzo.workflow.core.repository.mapper.ExtAxDictConfMapper; +import cn.axzo.workflow.core.repository.mapper.ExtAxDictGroupMapper; +import cn.axzo.workflow.core.repository.mapper.ExtAxDictGroupVariableMapper; import cn.axzo.workflow.core.repository.mapper.ExtAxDictMapper; import cn.axzo.workflow.core.service.CategoryConfigService; import cn.axzo.workflow.core.service.converter.CategoryConfigConverter; +import cn.hutool.core.bean.BeanUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import javax.annotation.Resource; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; -import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_CONFIG_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.*; +import static cn.axzo.workflow.common.code.CategoryRespCode.*; /** * 分类黑白名单配置服务 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryGroupServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryGroupServiceImpl.java new file mode 100644 index 000000000..1bec90c76 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryGroupServiceImpl.java @@ -0,0 +1,175 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.basics.common.constant.enums.TableIsDeleteEnum; +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.framework.domain.ServiceException; +import cn.axzo.workflow.common.model.request.category.CategoryGroupVarSearchDto; +import cn.axzo.workflow.common.model.request.category.CategoryGroupVarUpsertDto; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; +import cn.axzo.workflow.core.repository.entity.ExtAxDictGroup; +import cn.axzo.workflow.core.repository.entity.ExtAxDictGroupVariable; +import cn.axzo.workflow.core.repository.mapper.ExtAxDictGroupMapper; +import cn.axzo.workflow.core.service.CategoryGroupService; +import cn.axzo.workflow.core.service.CategoryGroupVariableService; +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CategoryGroupServiceImpl extends ServiceImpl implements CategoryGroupService { + + private final CategoryGroupVariableService categoryGroupVariableService; + + @Override + public List searchGroupAndVarList(CategoryGroupVarSearchDto dto) { + if ((dto.getDictId() == null || dto.getDictId() <= 0) && StringUtils.isBlank(dto.getCategory())) { + throw new ServiceException("dictId和category不能同时为空"); + } + List extAxDictGroups = this.lambdaQuery() + .eq(Objects.nonNull(dto.getDictId()) && dto.getDictId() > 0, ExtAxDictGroup::getDictId, dto.getDictId()) + .inSql((Objects.isNull(dto.getDictId()) || dto.getDictId() <= 0) && StringUtils.isNotBlank(dto.getCategory()), ExtAxDictGroup::getDictId, + String.format("SELECT id FROM ext_ax_dict WHERE value = '%s'", dto.getCategory())) + .orderByAsc(ExtAxDictGroup::getOrdinal) + .list(); + if (CollectionUtils.isEmpty(extAxDictGroups)) { + return Collections.emptyList(); + } + List groupVars = categoryGroupVariableService.lambdaQuery() + .eq(Objects.nonNull(dto.getDictId()), ExtAxDictGroupVariable::getDictId, dto.getDictId()) + .eq(ExtAxDictGroupVariable::getIsDelete, TableIsDeleteEnum.NORMAL.value) + .list(); + Map> varMap = ListUtils.emptyIfNull(groupVars).stream().collect(Collectors.groupingBy(ExtAxDictGroupVariable::getGroupId)); + return extAxDictGroups.stream() + .map(group -> { + CategoryGroupVarItemVo categoryGroupVarItemVo = BeanUtil.copyProperties(group, CategoryGroupVarItemVo.class); + List categoryVarItemVos = BeanUtil.copyToList(ListUtils.emptyIfNull(varMap.get(group.getId())), CategoryGroupVarItemVo.CategoryVarItemVo.class); + categoryGroupVarItemVo.setVars(categoryVarItemVos); + return categoryGroupVarItemVo; + }).collect(Collectors.toList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean upsertGroupAndVars(CategoryGroupVarUpsertDto dto) { + if (dto.getDictId() == null || dto.getDictId() <= 0) { + throw new ServiceException("dictId 不能为空且必须大于0"); + } + if (ListUtils.emptyIfNull(dto.getGroupVos()).stream() + .collect(Collectors.groupingBy(CategoryGroupVarUpsertDto.CategoryGroupUpsertVo::getOrdinal)) + .values() + .stream() + .anyMatch(gl -> gl.size() > 1)) { + throw new ServiceException("分组 ordinal 不允许重复"); + } + List existGroups = this.lambdaQuery() + .eq(ExtAxDictGroup::getDictId, dto.getDictId()) + .eq(ExtAxDictGroup::getIsDelete, TableIsDeleteEnum.NORMAL.value) + .list(); + Map> existGroupMap = ListUtils.emptyIfNull(existGroups).stream().collect(Collectors.groupingBy(BaseEntity::getId)); + List existVars = categoryGroupVariableService.lambdaQuery() + .eq(ExtAxDictGroupVariable::getDictId, dto.getDictId()) + .eq(ExtAxDictGroupVariable::getIsDelete, TableIsDeleteEnum.NORMAL.value) + .list(); + if (CollectionUtils.isEmpty(dto.getGroupVos())) { + if (!CollectionUtils.isEmpty(existGroups)) { + this.removeByIds(existGroups.stream().map(BaseEntity::getId).collect(Collectors.toList())); + } + if (!CollectionUtils.isEmpty(existVars)) { + categoryGroupVariableService.removeByIds(existVars.stream().map(BaseEntity::getId).collect(Collectors.toList())); + } + return Boolean.TRUE; + } + Map> existVarMap = ListUtils.emptyIfNull(existVars).stream().collect(Collectors.groupingBy(BaseEntity::getId)); + List saveOrUpdateGroups = new ArrayList<>(); + List saveOrUpdateVars = new ArrayList<>(); + Map> createVarMap = new HashMap<>(); + for (CategoryGroupVarUpsertDto.CategoryGroupUpsertVo groupVo : dto.getGroupVos()) { + ExtAxDictGroup extAxDictGroup = new ExtAxDictGroup(); + extAxDictGroup.setGroupName(groupVo.getGroupName()); + extAxDictGroup.setDictId(dto.getDictId()); + extAxDictGroup.setParentGroupId(groupVo.getParentGroupId()); + extAxDictGroup.setOrdinal(groupVo.getOrdinal()); + if (groupVo.getId() != null && groupVo.getId() > 0) { + extAxDictGroup.setId(groupVo.getId()); + saveOrUpdateGroups.add(extAxDictGroup); + List vars = groupVo.getVars(); + if (!CollectionUtils.isEmpty(vars)) { + for (CategoryGroupVarUpsertDto.CategoryVarUpsertVo v : vars) { + ExtAxDictGroupVariable extAxDictGroupVariable = BeanUtil.copyProperties(v, ExtAxDictGroupVariable.class); + if (v.getId() != null && v.getId() > 0) { + saveOrUpdateVars.add(extAxDictGroupVariable); + } else { + extAxDictGroupVariable.setDictId(dto.getDictId()); + extAxDictGroupVariable.setGroupId(groupVo.getId()); + extAxDictGroupVariable.setOrdinal(groupVo.getOrdinal()); + extAxDictGroupVariable.setDictId(dto.getDictId()); + createVarMap.computeIfAbsent(groupVo.getOrdinal(), c -> new ArrayList<>()).add(extAxDictGroupVariable); + } + } + } + } else { + saveOrUpdateGroups.add(extAxDictGroup); + List vars = groupVo.getVars(); + if (!CollectionUtils.isEmpty(vars)) { + vars.forEach(v -> { + ExtAxDictGroupVariable extAxDictGroupVariable = BeanUtil.copyProperties(v, ExtAxDictGroupVariable.class); + extAxDictGroupVariable.setOrdinal(v.getOrdinal()); + extAxDictGroupVariable.setDictId(dto.getDictId()); + createVarMap.computeIfAbsent(groupVo.getOrdinal(), c -> new ArrayList<>()).add(extAxDictGroupVariable); + }); + } + } + } + if (!existGroupMap.isEmpty()) { + saveOrUpdateGroups.stream() + .filter(g -> g.getId() != null && g.getId() > 0) + .forEach(g -> existGroupMap.remove(g.getId())); + } + if (!existVarMap.isEmpty()) { + saveOrUpdateVars.stream() + .filter(v -> v.getId() != null && v.getId() > 0) + .forEach(v -> existVarMap.remove(v.getId())); + } + boolean groupSaveResult = this.saveOrUpdateBatch(saveOrUpdateGroups); + if (!groupSaveResult) { + throw new ServiceException("保存失败"); + } + Map> ordinalGroupMap = saveOrUpdateGroups.stream().collect(Collectors.groupingBy(ExtAxDictGroup::getOrdinal)); + createVarMap.forEach((key, value) -> value.forEach(v -> v.setGroupId(ordinalGroupMap.get(key).get(0).getId()))); + saveOrUpdateVars.addAll(createVarMap.values().stream().flatMap(Collection::stream).collect(Collectors.toList())); + //保存变量 + categoryGroupVariableService.saveOrUpdateBatch(saveOrUpdateVars); + //删除多余的group和变量 + if (!existGroupMap.isEmpty()) { + //删除分组 + this.removeByIds(existGroupMap.keySet()); + //删除分组下的变量 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("group_id", existGroupMap.keySet()); + categoryGroupVariableService.remove(queryWrapper); + } + //删除多余变量 + if (!existVarMap.isEmpty()) { + categoryGroupVariableService.removeByIds(existVarMap.keySet()); + } + return true; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryGroupVariableServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryGroupVariableServiceImpl.java new file mode 100644 index 000000000..4e20fdf01 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryGroupVariableServiceImpl.java @@ -0,0 +1,15 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.core.repository.entity.ExtAxDictGroupVariable; +import cn.axzo.workflow.core.repository.mapper.ExtAxDictGroupVariableMapper; +import cn.axzo.workflow.core.service.CategoryGroupVariableService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CategoryGroupVariableServiceImpl extends ServiceImpl implements CategoryGroupVariableService { +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryServiceImpl.java index cec24e07f..3de68514c 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/CategoryServiceImpl.java @@ -1,13 +1,13 @@ package cn.axzo.workflow.core.service.impl; import cn.axzo.workflow.common.constant.BpmnConstants; +import cn.axzo.workflow.common.exception.WorkflowEngineException; 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.category.CategoryConfigItemVO; import cn.axzo.workflow.common.model.response.category.CategoryItemVO; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.repository.entity.ExtAxDict; import cn.axzo.workflow.core.repository.mapper.ExtAxDictMapper; import cn.axzo.workflow.core.service.CategoryConfigService; @@ -30,11 +30,11 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; +import static cn.axzo.workflow.common.code.CategoryRespCode.CATEGORY_DATA_ERROR; +import static cn.axzo.workflow.common.code.CategoryRespCode.CATEGORY_ID_NOT_EXISTS; +import static cn.axzo.workflow.common.code.CategoryRespCode.CATEGORY_NAME_EXISTS; +import static cn.axzo.workflow.common.code.CategoryRespCode.CATEGORY_VALUE_EXISTS; import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; -import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_DATA_ERROR; -import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_ID_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_NAME_EXISTS; -import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_VALUE_EXISTS; @Service @@ -115,6 +115,9 @@ public class CategoryServiceImpl extends ServiceImpl dict.setTenantId(dto.getTenantId()); dict.setOperatorName(dto.getOperatorName()); dict.setWorkspaceTypeCode(dto.getWorkspaceCodeType()); + dict.setBusinessType(dto.getBusinessType()); + dict.setIcon(dto.getIcon()); + dict.setDisplayInitiateMenu(dto.getDisplayInitiateMenu()); } @Override @@ -156,6 +159,7 @@ public class CategoryServiceImpl extends ServiceImpl .eq(StringUtils.isNotBlank(dto.getWorkspaceTypeCode()), ExtAxDict::getWorkspaceTypeCode, dto.getWorkspaceTypeCode()) .eq(ExtAxDict::getTenantId, dto.getTenantId()) + .eq(dto.getBusinessType() != null, ExtAxDict::getBusinessType, dto.getBusinessType()) .orderByDesc(ExtAxDict::getCreateAt); Page page = dictMapper.selectPage(new Page<>(dto.getPageNo(), dto.getPageSize()), @@ -195,9 +199,7 @@ public class CategoryServiceImpl extends ServiceImpl if (!dictDO.isPresent()) { throw new WorkflowEngineException(CATEGORY_ID_NOT_EXISTS, String.valueOf(id)); } - dictDO.ifPresent(dict -> { - dict.setStatus(state ? 1 : 0); - }); + dictDO.ifPresent(dict -> dict.setStatus(Boolean.TRUE.equals(state) ? 1 : 0)); dictMapper.updateById(dictDO.get()); return true; } @@ -208,9 +210,7 @@ public class CategoryServiceImpl extends ServiceImpl if (!dictDO.isPresent()) { throw new WorkflowEngineException(CATEGORY_ID_NOT_EXISTS, String.valueOf(id)); } - dictDO.ifPresent(dict -> { - dict.setRemark(configType); - }); + dictDO.ifPresent(dict -> dict.setRemark(configType)); dictMapper.updateById(dictDO.get()); return true; } @@ -225,7 +225,10 @@ public class CategoryServiceImpl extends ServiceImpl .eq(StringUtils.isNotBlank(dto.getWorkspaceTypeCode()), ExtAxDict::getWorkspaceTypeCode, dto.getWorkspaceTypeCode()) .eq(ExtAxDict::getTenantId, dto.getTenantId()) - .eq(ExtAxDict::getIsDelete, 0); + .eq(dto.getBusinessType() != null, ExtAxDict::getBusinessType, dto.getBusinessType()) + .eq(ExtAxDict::getIsDelete, 0) + .orderByDesc(Objects.equals(dto.getOrderCreateAt(), "desc"), ExtAxDict::getCreateAt); + ; List extAxDicts = dictMapper.selectList(queryWrapper); return categoryConverter.toVos(extAxDicts); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxAxReModelServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxAxReModelServiceImpl.java deleted file mode 100644 index d825a654b..000000000 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxAxReModelServiceImpl.java +++ /dev/null @@ -1,67 +0,0 @@ -package cn.axzo.workflow.core.service.impl; - -import cn.axzo.basics.common.BeanMapper; -import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; -import cn.axzo.workflow.core.repository.entity.ExtAxReModel; -import cn.axzo.workflow.core.repository.mapper.ExtAxReModelMapper; -import cn.axzo.workflow.core.service.ExtAxReModelService; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Objects; - -import static cn.axzo.workflow.core.common.code.BpmnModelRespCode.MODEL_ID_NOT_EXISTS; - - -/** - * 模型扩展表操作服务实现 - * - * @author wangli - * @since 2023/11/18 15:09 - */ -@Service -@RequiredArgsConstructor -@Slf4j -public class ExtAxAxReModelServiceImpl implements ExtAxReModelService { - @Resource - private ExtAxReModelMapper extAxReModelMapper; - - @Override - public List listByModelIds(List modelIds) { - return extAxReModelMapper.selectList(new QueryWrapper().in("model_id", modelIds)); - } - - @Override - public void create(String modeId) { - ExtAxReModel extAxReModel = new ExtAxReModel(); - extAxReModel.setModelId(modeId); - extAxReModelMapper.insert(extAxReModel); - } - - public void update(String modelId, Integer status) { - ExtAxReModel extAxReModel = new ExtAxReModel(); - extAxReModel.setStatus(status); - extAxReModelMapper.update(extAxReModel, new QueryWrapper().eq("model_id", modelId)); - } - - @Override - public void changeStatus(String modelId, Integer status) { - ExtAxReModel reModel = extAxReModelMapper.selectOne(new QueryWrapper().eq("model_id", modelId)); - if (Objects.isNull(reModel)) { - throw new WorkflowEngineException(MODEL_ID_NOT_EXISTS, reModel.getModelId()); - } - reModel.setStatus(status); - extAxReModelMapper.updateById(reModel); - } - - @Override - public BpmnModelExtVO getStatusByModelId(String modelId) { - ExtAxReModel model = extAxReModelMapper.selectOne(new QueryWrapper().eq("model_id", modelId)); - return BeanMapper.copyBean(model, BpmnModelExtVO.class); - } -} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxBpmnFormRelationServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxBpmnFormRelationServiceImpl.java new file mode 100644 index 000000000..56754b2e9 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxBpmnFormRelationServiceImpl.java @@ -0,0 +1,80 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.model.dto.BpmnFormRelationCreateDTO; +import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; +import cn.axzo.workflow.core.repository.mapper.ExtAxBpmnFormRelationMapper; +import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; + +/** + * bpmn 模型与表单模型的关联关系 + * + * @author wangli + * @since 2024-11-04 15:39 + */ +@Slf4j +@Service +public class ExtAxBpmnFormRelationServiceImpl implements ExtAxBpmnFormRelationService { + @Resource + private ExtAxBpmnFormRelationMapper bpmnFormRelationMapper; + + @Override + public Long insert(BpmnFormRelationCreateDTO dto) { + ExtAxBpmnFormRelation relation = new ExtAxBpmnFormRelation(); + relation.setKey(dto.getKey()); + relation.setBpmnDefinitionId(dto.getBpmnDefinitionId()); + relation.setFormDeploymentId(dto.getFormDeploymentId()); + relation.setTenantId(dto.getTenantId()); + bpmnFormRelationMapper.insert(relation); + return relation.getId(); + } + + @Override + public ExtAxBpmnFormRelation queryByBpmnDefinitionId(String bpmnDefinitionId) { + BpmnFormRelationSearchDTO searchDTO = new BpmnFormRelationSearchDTO(); + searchDTO.setBpmnDefinitionId(bpmnDefinitionId); + return bpmnFormRelationMapper.selectOne(buildQueryWrapper(searchDTO)); + } + + @Override + public List keyQuery(BpmnFormRelationSearchDTO dto) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(dto.getTenantId())) { + queryWrapper.eq(ExtAxBpmnFormRelation::getTenantId, dto.getTenantId()).or().eq(ExtAxBpmnFormRelation::getTenantId, NO_TENANT_ID); + } + + return bpmnFormRelationMapper.selectList(queryWrapper); + } + + @Override + public List genericQuery(BpmnFormRelationSearchDTO dto) { + return bpmnFormRelationMapper.selectList(buildQueryWrapper(dto)); + } + + public BpmPageResult genericPageQuery(BpmnFormRelationSearchDTO dto) { + Page page = bpmnFormRelationMapper.selectPage(new Page<>(dto.getPageNo(), dto.getPageSize()), buildQueryWrapper(dto)); + return new BpmPageResult<>(page.getRecords(), page.getTotal()); + } + + LambdaQueryWrapper buildQueryWrapper(BpmnFormRelationSearchDTO dto) { + return new LambdaQueryWrapper() + .eq(StringUtils.hasText(dto.getKey()), ExtAxBpmnFormRelation::getKey, dto.getKey()) + .eq(StringUtils.hasText(dto.getBpmnDefinitionId()), ExtAxBpmnFormRelation::getBpmnDefinitionId, dto.getBpmnDefinitionId()) + .eq(StringUtils.hasText(dto.getFormDeploymentId()), ExtAxBpmnFormRelation::getFormDeploymentId, dto.getFormDeploymentId()) +// .eq(!StringUtils.hasText(dto.getTenantId()), ExtAxBpmnFormRelation::getTenantId, NO_TENANT_ID) + .eq(StringUtils.hasText(dto.getTenantId()), ExtAxBpmnFormRelation::getTenantId, dto.getTenantId()) + .eq(ExtAxBpmnFormRelation::getIsDelete, 0) + ; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxDocContentServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxDocContentServiceImpl.java new file mode 100644 index 000000000..9fefbf808 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxDocContentServiceImpl.java @@ -0,0 +1,84 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.repository.entity.ExtAxDocContent; +import cn.axzo.workflow.core.repository.mapper.ExtAxDocContentMapper; +import cn.axzo.workflow.core.service.ExtAxDocContentService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +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 java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_CONTENT_DATA_ERROR; + +/** + * 文档模板内容 + * + * @author wangli + * @since 2025-03-28 18:47 + */ +@Service +@Slf4j +public class ExtAxDocContentServiceImpl implements ExtAxDocContentService { + @Resource + private ExtAxDocContentMapper extAxDocContentMapper; + + @Override + public List getByIds(List ids) { + return extAxDocContentMapper.selectBatchIds(ids); + } + + @Override + public ExtAxDocContent createContent(String content, FileTypeEnum fileType, Long docId) { + ExtAxDocContent entity = new ExtAxDocContent(); + entity.setContent(StringUtils.hasText(content) ? content : ""); + entity.setFileType(fileType.name()); + entity.setFileId(docId); + extAxDocContentMapper.insert(entity); + return entity; + } + + @Override + public ExtAxDocContent updateContent(String content, FileTypeEnum fileType, String docId) { + ExtAxDocContent entity = extAxDocContentMapper.selectOne(new LambdaQueryWrapper() + .eq(ExtAxDocContent::getId, Long.valueOf(docId))); + if (Objects.nonNull(content)) { + entity.setContent(content); + } + entity.setFileType(fileType.name()); + extAxDocContentMapper.updateById(entity); + return entity; + } + + @Override + public ExtAxDocContent deleteContent(String docId) { + ExtAxDocContent entity = extAxDocContentMapper.selectOne(new LambdaQueryWrapper() + .eq(ExtAxDocContent::getId, docId)); + extAxDocContentMapper.deleteById(entity); + return entity; + } + + @Override + public List batchDeleteContent(List docIds) { + List entities = extAxDocContentMapper.selectList(new LambdaQueryWrapper() + .in(ExtAxDocContent::getId, docIds)); + extAxDocContentMapper.deleteBatchIds(entities.stream().map(BaseEntity::getId).collect(Collectors.toList())); + return entities; + } + + @Override + public String getContent(Long docId) { + ExtAxDocContent content = extAxDocContentMapper.selectOne(ExtAxDocContent::getId, docId); + if (Objects.isNull(content)) { + throw new WorkflowEngineException(MODEL_FILE_CONTENT_DATA_ERROR); + } + return content.getContent(); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxHiTaskInstServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxHiTaskInstServiceImpl.java index 758e136ad..89bfb729c 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxHiTaskInstServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxHiTaskInstServiceImpl.java @@ -6,14 +6,18 @@ import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; import cn.axzo.workflow.core.repository.mapper.ExtAxHiTaskInstMapper; import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.List; import java.util.Objects; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; + /** * 流程定义扩展表操作服务实现 * @@ -44,7 +48,8 @@ public class ExtAxHiTaskInstServiceImpl implements ExtAxHiTaskInstService { .eq(StringUtils.hasLength(dto.getTaskId()), "task_id", dto.getTaskId()) .eq(Objects.nonNull(dto.getStatus()), "status", Objects.isNull(dto.getStatus()) ? "" : dto.getStatus().getStatus()) - .eq(StringUtils.hasLength(dto.getAssignee()), "assignee", dto.getAssignee()); + .eq(StringUtils.hasLength(dto.getAssignee()), "assignee", dto.getAssignee()) + .notIn(!CollectionUtils.isEmpty(dto.getExcludeIds()), "id", dto.getExcludeIds()); return extAxHiTaskInstMapper.selectList(queryWrapper); } @@ -62,4 +67,28 @@ public class ExtAxHiTaskInstServiceImpl implements ExtAxHiTaskInstService { taskInst.setStatus(BpmnProcessInstanceResultEnum.DELETED.getStatus()); extAxHiTaskInstMapper.updateById(taskInst); } + + @Override + public void updateByTaskIdAndInstanceId(String taskId, String processInstanceId, String assignee, BpmnProcessInstanceResultEnum resultEnum) { + ExtAxHiTaskInst existence = getByTaskId(taskId, processInstanceId); + if (Objects.nonNull(existence)) { + log.info("更新扩展任务实例表数据: taskId:{}, instanceId:{}, currentStatus:{}, anticipateStatus: {}", + taskId, processInstanceId, existence.getStatus(), resultEnum.getStatus()); + } else { + log.warn("无法正确更新扩展任务表数据: taskId:{}, instanceId:{}, anticipateStatus: {}", + taskId, processInstanceId, resultEnum.getStatus()); + } + ExtAxHiTaskInst entity = new ExtAxHiTaskInst(); + if (StringUtils.hasText(assignee)) { + entity.setAssignee(assignee); + } + entity.setStatus(resultEnum.getStatus()); + + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper() + .eq(ExtAxHiTaskInst::getTaskId, taskId) + .eq(ExtAxHiTaskInst::getProcInstId, processInstanceId) + // 只能变更还是处理中的状态 + .eq(ExtAxHiTaskInst::getStatus, PROCESSING.getStatus()); + extAxHiTaskInstMapper.update(entity, wrapper); + } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxModelDocServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxModelDocServiceImpl.java new file mode 100644 index 000000000..750eb912f --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxModelDocServiceImpl.java @@ -0,0 +1,392 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.enums.OrderEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCloneDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO; +import cn.axzo.workflow.core.conf.CustomEventManager; +import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd; +import cn.axzo.workflow.core.engine.event.DocChangeEventImpl; +import cn.axzo.workflow.core.repository.entity.ExtAxDocContent; +import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc; +import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper; +import cn.axzo.workflow.core.service.ExtAxDocContentService; +import cn.axzo.workflow.core.service.ExtAxModelDocService; +import cn.axzo.workflow.core.service.ExtAxReModelService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.beans.BeanUtils; +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; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TAG_DUPLICATE; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TAG_EXISTS; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TYPE_CLONE_NOT_SUPPORT; + +/** + * 模型关联的文档 + * + * @author wangli + * @since 2025-03-28 18:22 + */ +@Service +@Slf4j +public class ExtAxModelDocServiceImpl implements ExtAxModelDocService { + @Resource + private SpringProcessEngineConfiguration springProcessEngineConfiguration; + @Resource + private ExtAxModelDocMapper extAxModelDocMapper; + @Resource + private ExtAxDocContentService extAxDocContentService; + @Resource + private ExtAxReModelService extAxReModelService; + @Resource + private CustomEventManager eventPublisher; + + @Override + public BpmPageResult docPage(DocSearchDTO dto) { + Page page = extAxModelDocMapper.selectPage(new Page<>(dto.getPageNo(), dto.getPageSize()), + buildQueryWrapper(BeanMapper.copyBean(dto, ExtAxModelDoc.class))); + List list = BeanMapper.copyList(page.getRecords(), DocBaseVO.class, + (s, t) -> t.setFileType(FileTypeEnum.valueOfType(s.getFileType()))); + return new BpmPageResult<>(list, page.getTotal()); + } + + @Override + public List docList(DocQueryDTO dto) { + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + return commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(), + dto.getProcessDefinitionKey(), dto.getTenantId(), extAxModelDocMapper, extAxReModelService)); + } + + @Override + public List querySetting(String key, String tenantId) { + return extAxModelDocMapper.selectList(new LambdaQueryWrapper() + .eq(StringUtils.hasText(key), ExtAxModelDoc::getModelKey, key) + .eq(Objects.nonNull(tenantId), ExtAxModelDoc::getTenantId, tenantId) + .eq(ExtAxModelDoc::getTempFile, false) + ); + } + + + @Override + public DocBaseVO get(Long docId) { + return get(docId, false); + } + + @Override + public DocBaseVO get(Long docId, boolean ignoreDelete) { + ExtAxModelDoc doc; + if (ignoreDelete) { + doc = extAxModelDocMapper.getIgnoreDelete(docId); + } else { + doc = extAxModelDocMapper.selectById(docId); + if (Objects.isNull(doc)) { + throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS); + } + } + return BeanMapper.copyBean(doc, DocBaseVO.class, (s, t) -> { + t.setFileType(FileTypeEnum.valueOfType(s.getFileType())); + }); + } + + public static LambdaQueryWrapper buildQueryWrapper(ExtAxModelDoc entity) { + return new LambdaQueryWrapper() + .eq(StringUtils.hasText(entity.getModelId()), ExtAxModelDoc::getModelId, entity.getModelId()) + .eq(StringUtils.hasText(entity.getModelKey()), ExtAxModelDoc::getModelKey, entity.getModelKey()) + .eq(StringUtils.hasText(entity.getFileRelationId()), ExtAxModelDoc::getFileRelationId, + entity.getFileRelationId()) + .eq(StringUtils.hasText(entity.getFileType()), ExtAxModelDoc::getFileType, entity.getFileType()) + .eq(StringUtils.hasText(entity.getTag()), ExtAxModelDoc::getTag, entity.getTag()) + .eq(Objects.nonNull(entity.getStatus()), ExtAxModelDoc::getStatus, entity.getStatus()) + .eq(Objects.nonNull(entity.getTempFile()), ExtAxModelDoc::getTempFile, entity.getTempFile()) + .eq(Objects.isNull(entity.getTempFile()), ExtAxModelDoc::getTempFile, false) + .eq(Objects.nonNull(entity.getRequire()), ExtAxModelDoc::getRequire, entity.getRequire()) + .eq(Objects.nonNull(entity.getTenantId()), ExtAxModelDoc::getTenantId, entity.getTenantId()) + .orderByAsc(ExtAxModelDoc::getOrder) + ; + } + + public Integer maxOrder(DocCreateDTO dto) { + if (Objects.equals(Boolean.TRUE, dto.getTempFile())) { + return 0; + } + ExtAxModelDoc orderQuery = new ExtAxModelDoc(); + orderQuery.setModelId(dto.getModelId()); + List docs = extAxModelDocMapper.selectList(buildQueryWrapper(orderQuery)); + return docs.stream().mapToInt(ExtAxModelDoc::getOrder).max().orElse(0) + 1; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createDoc(DocCreateDTO dto, Boolean fireEvent) { + // 用于发送文档变更事件 + List oldSettings = Collections.emptyList(); + if (fireEvent) { + oldSettings = querySetting(dto.getModelKey(), dto.getTenantId()); + } + + if (StringUtils.hasText(dto.getTag()) && !Objects.equals(Boolean.TRUE, dto.getTempFile())) { + ExtAxModelDoc old = new ExtAxModelDoc(); + old.setModelId(dto.getModelId()); + old.setTag(dto.getTag()); + old.setTenantId(dto.getTenantId()); + Integer count = extAxModelDocMapper.selectCount(buildQueryWrapper(old)); + if (count > 0) { + throw new WorkflowEngineException(MODEL_FILE_TAG_DUPLICATE); + } + } + ExtAxModelDoc entity = BeanMapper.copyBean(dto, ExtAxModelDoc.class); + entity.setFileType(dto.getFileType().name()); + entity.setOrder(maxOrder(dto)); + extAxModelDocMapper.insert(entity); + if (Objects.equals(FileTypeEnum.HIPRINT, dto.getFileType())) { + ExtAxDocContent content = extAxDocContentService.createContent(dto.getContent(), dto.getFileType(), + entity.getId()); + entity.setFileRelationId(String.valueOf(content.getId())); + extAxModelDocMapper.updateById(entity); + } + + if (fireEvent) { + // 发送文档变更事件 + eventPublisher.publishEvent(new DocChangeEventImpl(dto.getModelKey(), dto.getTenantId(), + querySetting(dto.getModelKey(), dto.getTenantId()), oldSettings)); + } + return entity.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateDoc(DocUpdateDTO dto) { + // 用于发送文档变更事件 + List oldSettings = querySetting(dto.getModelKey(), dto.getTenantId()); + + if (StringUtils.hasText(dto.getTag())) { + ExtAxModelDoc query = new ExtAxModelDoc(); + query.setModelId(dto.getModelId()); + query.setTag(dto.getTag()); + query.setTenantId(dto.getTenantId()); + ExtAxModelDoc old = extAxModelDocMapper.selectOne(buildQueryWrapper(query)); + if (Objects.nonNull(old) && !Objects.equals(old.getId(), dto.getId())) { + throw new WorkflowEngineException(MODEL_FILE_TAG_EXISTS); + } + } + ExtAxModelDoc entity = extAxModelDocMapper.selectById(dto.getId()); + if (Objects.isNull(entity)) { + return false; + } + if (Objects.equals(FileTypeEnum.HIPRINT, dto.getFileType())) { + extAxDocContentService.updateContent(dto.getContent(), dto.getFileType(), entity.getFileRelationId()); + } + BeanUtils.copyProperties(dto, entity); + entity.setFileType(dto.getFileType().name()); + int flat = extAxModelDocMapper.updateById(entity); + + // 发送文档变更事件 + eventPublisher.publishEvent(new DocChangeEventImpl(dto.getModelKey(), dto.getTenantId(), + querySetting(dto.getModelKey(), dto.getTenantId()), oldSettings)); + return flat > 0; + } + + /** + * 该功能,只能处理 hiprint 类型 + * + * @param dto + * @return + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Long cloneDoc(DocCloneDTO dto) { + return cloneDoc(dto, false); + } + + @Override + public Long cloneDoc(DocCloneDTO dto, Boolean tempFile) { + ExtAxModelDoc origin = extAxModelDocMapper.selectById(dto.getDocId()); + if (Objects.isNull(origin)) { + throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS); + } + + List oldSettings = new ArrayList<>(); + if (!Objects.equals(Boolean.TRUE, tempFile)) { + // 用于发送文档变更事件 + oldSettings.addAll(querySetting(origin.getModelKey(), origin.getTenantId())); + } + + if (Objects.equals(FileTypeEnum.HIPRINT, FileTypeEnum.valueOfType(origin.getFileType()))) { + DocCreateDTO newDoc = BeanMapper.copyBean(origin, DocCreateDTO.class, (s, t) -> { + t.setFileType(FileTypeEnum.valueOfType(s.getFileType())); + t.setTempFile(Objects.equals(Boolean.TRUE, tempFile)); + if (StringUtils.hasText(dto.getTargetTenantId())) { + // 没设置租户,则使用原来文档的租户 ID + t.setTenantId(dto.getTargetTenantId()); + } + if (StringUtils.hasText(dto.getTargetModelId())) { + // 没设置模型,则使用原来文档的模型 ID + t.setModelId(dto.getTargetModelId()); + } + if (StringUtils.hasText(dto.getTargetFileTag())) { + t.setTag(dto.getTargetFileTag()); + } + }); + newDoc.setContent(extAxDocContentService.getContent(Long.valueOf(origin.getFileRelationId()))); + Long newDocId = createDoc(newDoc, false); + + if (!Objects.equals(Boolean.TRUE, tempFile)) { + // 发送文档变更事件 + eventPublisher.publishEvent(new DocChangeEventImpl(origin.getModelKey(), origin.getTenantId(), + querySetting(origin.getModelKey(), origin.getTenantId()), oldSettings)); + } + return newDocId; + } else { + throw new WorkflowEngineException(MODEL_FILE_TYPE_CLONE_NOT_SUPPORT); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteDoc(Long docId) { + ExtAxModelDoc origin = extAxModelDocMapper.selectById(docId); + if(Objects.isNull(origin)) { + return false; + } + // 用于发送文档变更事件 + List oldSettings = querySetting(origin.getModelKey(), origin.getTenantId()); + + if (Objects.equals(FileTypeEnum.HIPRINT, FileTypeEnum.valueOfType(origin.getFileType()))) { + extAxDocContentService.deleteContent(origin.getFileRelationId()); + } + int flat = extAxModelDocMapper.deleteById(docId); + + // 发送文档变更事件 + eventPublisher.publishEvent(new DocChangeEventImpl(origin.getModelKey(), origin.getTenantId(), + querySetting(origin.getModelKey(), origin.getTenantId()), oldSettings)); + return flat > 0; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean batchDeleteDoc(List docIds) { + if (CollectionUtils.isEmpty(docIds)) { + return true; + } + List entities = extAxModelDocMapper.selectBatchIds(docIds); + if (CollectionUtils.isEmpty(entities)) { + return true; + } + List hiprintList = entities.stream() + .filter(entity -> Objects.equals(FileTypeEnum.HIPRINT, FileTypeEnum.valueOfType(entity.getFileType()))) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(hiprintList)) { + extAxDocContentService.batchDeleteContent(hiprintList.stream() + .map(ExtAxModelDoc::getFileRelationId) + .collect(Collectors.toList())); + } + int flat = extAxModelDocMapper.deleteBatchIds(docIds); + + // 发送文档变更事件 + eventPublisher.publishEvent(new DocChangeEventImpl(entities.get(0).getModelKey(), + entities.get(0).getTenantId(), Collections.emptyList(), entities)); + return flat > 0; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean orderDoc(DocOrderDTO dto) { + ExtAxModelDoc doc = extAxModelDocMapper.selectById(dto.getId()); + if (Objects.isNull(doc)) { + throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS); + } + + LambdaQueryWrapper orderWrapper = new LambdaQueryWrapper<>(); + orderWrapper.eq(ExtAxModelDoc::getModelId, doc.getModelId()) + .eq(ExtAxModelDoc::getTempFile, false); + Integer currentOrder = doc.getOrder(); + if (Objects.equals(dto.getOrder(), OrderEnum.UP)) { + orderWrapper.eq(ExtAxModelDoc::getOrder, currentOrder - 1); + ExtAxModelDoc upDoc = extAxModelDocMapper.selectOne(orderWrapper); + if (Objects.isNull(upDoc)) { + return false; + } + doc.setOrder(currentOrder - 1); + upDoc.setOrder(currentOrder); + extAxModelDocMapper.updateById(upDoc); + } else { + orderWrapper.eq(ExtAxModelDoc::getOrder, currentOrder + 1); + ExtAxModelDoc downDoc = extAxModelDocMapper.selectOne(orderWrapper); + if (Objects.isNull(downDoc)) { + return false; + } + doc.setOrder(currentOrder + 1); + downDoc.setOrder(currentOrder); + extAxModelDocMapper.updateById(downDoc); + } + extAxModelDocMapper.updateById(doc); + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean statusDoc(DocStatusDTO dto) { + ExtAxModelDoc origin = extAxModelDocMapper.selectById(dto.getId()); + if (Objects.isNull(origin)) { + throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS); + } + // 用于发送文档变更事件 + List oldSettings = querySetting(origin.getModelKey(), origin.getTenantId()); + + origin.setStatus(Objects.equals(Boolean.TRUE, dto.getStatus())); + int flat = extAxModelDocMapper.updateById(origin); + + // 发送文档变更事件 + eventPublisher.publishEvent(new DocChangeEventImpl(origin.getModelKey(), origin.getTenantId(), + querySetting(origin.getModelKey(), origin.getTenantId()), oldSettings)); + return flat > 0; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean requireDoc(DocStatusDTO dto) { + ExtAxModelDoc origin = extAxModelDocMapper.selectById(dto.getId()); + if (Objects.isNull(origin)) { + throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS); + } + origin.setRequire(Objects.equals(Boolean.TRUE, dto.getStatus())); + return extAxModelDocMapper.updateById(origin) > 0; + } + + @Override + public List getIds(List ids) { + if (CollectionUtils.isEmpty(ids)) { + return Collections.emptyList(); + } + List docs = extAxModelDocMapper.selectList(new LambdaQueryWrapper() + .in(ExtAxModelDoc::getId, ids) + .eq(ExtAxModelDoc::getTempFile, false) + .eq(ExtAxModelDoc::getIsDelete, 0)); + return BeanMapper.copyList(docs, DocBaseVO.class, + (s, t) -> t.setFileType(FileTypeEnum.valueOfType(s.getFileType()))); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxMqLogServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxMqLogServiceImpl.java new file mode 100644 index 000000000..cd0cbfc23 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxMqLogServiceImpl.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.core.repository.entity.ExtAxMqLog; +import cn.axzo.workflow.core.repository.mapper.ExtAxMqLogMapper; +import cn.axzo.workflow.core.service.ExtAxMqLogService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * Api Log 表操服务实现 + * + * @author wangli + * @since 2024/4/3 10:41 + */ +@Service +@Slf4j +public class ExtAxMqLogServiceImpl implements ExtAxMqLogService { + @Resource + private ExtAxMqLogMapper mqLogMapper; + + @Override + public Long insert(ExtAxMqLog apiLog) { + mqLogMapper.insert(apiLog); + return apiLog.getId(); + } + + @Override + public void update(ExtAxMqLog mqLog) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("unique_id", mqLog.getUniqueId()); + mqLogMapper.update(mqLog, updateWrapper); + } + + @Override + public void delete(ExtAxMqLog mqLog) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("unique_id", mqLog.getUniqueId()); + queryWrapper.eq("trace_id", mqLog.getTraceId()); + mqLogMapper.delete(queryWrapper); + } +} 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..9e2924515 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxProcessLogServiceImpl.java @@ -0,0 +1,123 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.model.dto.OrgStructureSnapshotInfo; +import cn.axzo.workflow.common.model.dto.SimpleTaskDTO; +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.alibaba.fastjson.JSON; +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.CollectionUtils; +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; +import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.HANDLING; + +/** + * 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 updateAssigneeAndSnapshot(ExtAxProcessLog queryLog, BpmnTaskDelegateAssigner assignee, OrgStructureSnapshotInfo snapshotInfo) { + updateAssigneeAndSnapshot(queryLog, assignee, snapshotInfo, assignee.getAssignerName() + HANDLING.getDesc()); + + } + + @Override + public void updateAssigneeAndSnapshot(ExtAxProcessLog queryLog, BpmnTaskDelegateAssigner assignee, OrgStructureSnapshotInfo snapshotInfo, String operationDesc) { + List filterAssignee = Lists.newArrayList(NO_ASSIGNEE, HIDDEN_ASSIGNEE_ID, ROBOT_ASSIGNEE_ID, + DUMMY_ASSIGNEE_ID, "system"); + if (Objects.isNull(assignee) || filterAssignee.contains(assignee.buildAssigneeId())) { + return; + } + ExtAxProcessLog update = new ExtAxProcessLog(); + update.setOperationDesc(StringUtils.hasText(operationDesc) ? operationDesc : assignee.getAssignerName() + HANDLING.getDesc()); + update.setAssigneeFull(Lists.newArrayList(assignee)); + update.setAssigneeId(Long.valueOf(assignee.getPersonId())); + update.setAssigneeTenantId(assignee.getTenantId()); + update.setAssigneeOuId(assignee.getOuId()); + update.setAssigneeName(assignee.getAssignerName()); + update.setExtra(snapshotInfo); + extAxProcessLogMapper.update(update, buildQueryWrapper(queryLog)); + } + + @Override + public List genericQuery(ExtAxProcessLog query) { + return extAxProcessLogMapper.selectList(buildQueryWrapper(query)); + } + + @Override + public ExtAxProcessLog findByProcessIdAndTaskIdWithDeleted(String processId, String taskId) { + return extAxProcessLogMapper.findByProcessIdAndTaskIdWithDeleted(processId, taskId); + } + + @Override + public void restore(String processInstanceId, String taskId) { + batchRestore(Lists.newArrayList(new SimpleTaskDTO(processInstanceId, taskId))); + } + + @Override + public void batchRestore(List tasks) { + if (CollectionUtils.isEmpty(tasks)) { + return; + } + tasks.forEach(task -> { + if (StringUtils.hasText(task.getProcessInstanceId()) && StringUtils.hasText(task.getTaskId())) { + extAxProcessLogMapper.restore(task.getProcessInstanceId(), task.getTaskId()); + } + }); + } + + 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(StringUtils.hasText(log.getStatus()), ExtAxProcessLog::getStatus, log.getStatus()) + .eq(Objects.nonNull(log.getAssigneeId()), ExtAxProcessLog::getAssigneeId, log.getAssigneeId()) + .eq(StringUtils.hasText(log.getAssigneeTenantId()), ExtAxProcessLog::getAssigneeTenantId, log.getAssigneeTenantId()) + .eq(StringUtils.hasText(log.getAssigneeOuId()), ExtAxProcessLog::getAssigneeOuId, log.getAssigneeOuId()) + .eq(ExtAxProcessLog::getIsDelete, log.getIsDelete()); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxProcessSignServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxProcessSignServiceImpl.java new file mode 100644 index 000000000..cc6b79ef1 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxProcessSignServiceImpl.java @@ -0,0 +1,29 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign; +import cn.axzo.workflow.core.repository.mapper.ExtAxProcessSignMapper; +import cn.axzo.workflow.core.service.ExtAxProcessSignService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 流程签署业务信息记录 + * + * @author wangli + * @since 2025-04-02 10:06 + */ +@Service +@Slf4j +public class ExtAxProcessSignServiceImpl extends ServiceImpl implements ExtAxProcessSignService { + + @Resource + private ExtAxProcessSignMapper extAxProcessSignMapper; + + @Override + public ExtAxProcessSign findByProcessInstanceId(String processInstanceId) { + return extAxProcessSignMapper.selectOne(ExtAxProcessSign::getProcessInstanceId, processInstanceId); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxPropertyServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxPropertyServiceImpl.java index f496a7208..1b4e671d5 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxPropertyServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxPropertyServiceImpl.java @@ -5,11 +5,21 @@ import cn.axzo.workflow.core.repository.mapper.ExtAxPropertyMapper; import cn.axzo.workflow.core.service.ExtAxPropertyService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.TimeUnit; /** * 引擎服务持久配置信息表操作 Service 实现 @@ -23,18 +33,22 @@ public class ExtAxPropertyServiceImpl implements ExtAxPropertyService { @Resource private ExtAxPropertyMapper mapper; + @CacheEvict(value = "property", key = "#property.name") @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) public ExtAxProperty add(ExtAxProperty property) { mapper.insert(property); return property; } + @CachePut(value = "property", key = "#property.name") @Override public ExtAxProperty update(ExtAxProperty property) { mapper.updateById(property); return property; } + @Cacheable(value = "property", key = "#name") @Override public Optional getByName(String name) { if (!StringUtils.hasText(name)) { @@ -42,6 +56,35 @@ public class ExtAxPropertyServiceImpl implements ExtAxPropertyService { } QueryWrapper queryWrapper = new QueryWrapper() .eq("name", name); - return Optional.ofNullable(mapper.selectOne(queryWrapper)); + Optional extAxProperty = Optional.ofNullable(mapper.selectOne(queryWrapper)); + return extAxProperty; + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public int delete(String name, String value) { + if (!StringUtils.hasText(name)) { + return 0; + } + Map deleteMap = new HashMap<>(); + deleteMap.put("name", name); + if (StringUtils.hasText(value)) { + deleteMap.put("value", value); + } + return mapper.deleteByMap(deleteMap); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public int deleteByNameWithDuration(String name, Long timeOut, TimeUnit timeUnit) { + if (!StringUtils.hasText(name) || timeOut == null || timeUnit == null) { + log.warn("argument not valid,name:{},timeOut:{},timeUnit:{}", name, timeOut, timeUnit); + throw new IllegalArgumentException("argument not valid"); + } + LocalDateTime startTime = LocalDateTime.now().minus(timeUnit.toMillis(timeOut), ChronoUnit.MILLIS); + QueryWrapper queryWrapper = new QueryWrapper() + .eq("name", name) + .le("create_at", startTime); + return mapper.delete(queryWrapper); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReModelServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReModelServiceImpl.java new file mode 100644 index 000000000..fed6ae284 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReModelServiceImpl.java @@ -0,0 +1,113 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.workflow.common.enums.ExtModelStateFieldEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO; +import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO; +import cn.axzo.workflow.common.model.response.print.PrintModelDTO; +import cn.axzo.workflow.core.repository.entity.ExtAxReModel; +import cn.axzo.workflow.core.repository.mapper.ExtAxReModelMapper; +import cn.axzo.workflow.core.service.ExtAxReModelService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.RequiredArgsConstructor; +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.code.BpmnModelRespCode.MODEL_NOT_EXISTS; + + +/** + * 模型扩展表操作服务实现 + * + * @author wangli + * @since 2023/11/18 15:09 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class ExtAxReModelServiceImpl implements ExtAxReModelService { + @Resource + private ExtAxReModelMapper extAxReModelMapper; + + @Override + public List listByModelIds(List modelIds) { + return extAxReModelMapper.selectList(new QueryWrapper().in("model_id", modelIds)); + } + + @Override + public void create(String modeId) { + ExtAxReModel extAxReModel = new ExtAxReModel(); + extAxReModel.setModelId(modeId); + extAxReModelMapper.insert(extAxReModel); + } + + public void update(String modelId, Integer status) { + ExtAxReModel extAxReModel = new ExtAxReModel(); + extAxReModel.setStatus(status); + extAxReModelMapper.update(extAxReModel, new QueryWrapper().eq("model_id", modelId)); + } + + @Override + public void changeStatus(String modelId, Integer status, ExtModelStateFieldEnum field) { + ExtAxReModel reModel = extAxReModelMapper.selectOne(new QueryWrapper().eq("model_id", modelId)); + if (Objects.isNull(reModel)) { + throw new WorkflowEngineException(MODEL_NOT_EXISTS); + } + if (Objects.equals(field, ExtModelStateFieldEnum.status)) { + reModel.setStatus(status); + } + if (Objects.equals(field, ExtModelStateFieldEnum.printStatus)) { + reModel.setPrintStatus(status); + } + extAxReModelMapper.updateById(reModel); + } + + @Override + public BpmnModelExtVO getStatusByModelId(String modelId) { + ExtAxReModel model = extAxReModelMapper.selectOne(new QueryWrapper().eq("model_id", modelId)); + return BeanMapper.copyBean(model, BpmnModelExtVO.class); + } + + @Override + public void printTemplateConfig(PrintTemplateConfigUpsertDTO dto) { + ExtAxReModel model = extAxReModelMapper.selectOne(new QueryWrapper().eq("model_id", dto.getModelId())); + if (Objects.isNull(model)) { + throw new WorkflowEngineException(MODEL_NOT_EXISTS); + } + if (Objects.nonNull(dto.getPrintFileName())) { + model.setPrintFileName(dto.getPrintFileName()); + } + if (Objects.nonNull(dto.getPrintTemplateName())) { + model.setPrintTemplateName(dto.getPrintTemplateName()); + } + if (Objects.nonNull(dto.getPrintTemplateConfig())) { + model.setPrintTemplateConfig(dto.getPrintTemplateConfig()); + } + extAxReModelMapper.updateById(model); + } + + @Override + public Boolean hasPrintTemplateConfig(String modelId) { + ExtAxReModel model = extAxReModelMapper.selectOne(new QueryWrapper().eq("model_id", modelId)); + return Objects.equals(1, model.getPrintStatus()) && StringUtils.hasText(model.getPrintTemplateConfig()); + } + + @Override + public PrintModelDTO getPrintTemplateConfig(String modelId) { + ExtAxReModel model = extAxReModelMapper.selectOne(new QueryWrapper().eq("model_id", modelId)); + if (Objects.isNull(model)) { + throw new WorkflowEngineException(MODEL_NOT_EXISTS); + } + return PrintModelDTO.builder() + .printFileName(model.getPrintFileName()) + .printTemplateName(model.getPrintTemplateName()) + .printTemplateConfig(model.getPrintTemplateConfig()) + .build(); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReProcDefServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReProcDefServiceImpl.java index f7eabecd0..635e65665 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReProcDefServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReProcDefServiceImpl.java @@ -4,7 +4,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.repository.entity.ExtAxReProcDef; import cn.axzo.workflow.core.repository.mapper.ExtAxReProcDefMapper; import cn.axzo.workflow.core.service.ExtAxReProcDefService; -import com.alibaba.fastjson.JSON; +import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,7 +40,7 @@ public class ExtAxReProcDefServiceImpl implements ExtAxReProcDefService { extAxReProcDef.setOperatorName(operatorAssigner.getAssignerName()); extAxReProcDef.setOperatorType(operatorAssigner.getAssigneeType()); extAxReProcDef.setTenantId(operatorAssigner.getTenantId()); - extAxReProcDef.setOperatorMetaData(JSON.toJSONString(operatorAssigner)); + extAxReProcDef.setOperatorMetaData(JSONUtil.toJsonStr(operatorAssigner)); extAxReProcDefMapper.insert(extAxReProcDef); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReadRecordServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReadRecordServiceImpl.java new file mode 100644 index 000000000..cdeec873f --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/ExtAxReadRecordServiceImpl.java @@ -0,0 +1,95 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; +import cn.axzo.workflow.core.repository.entity.ExtAxReadRecord; +import cn.axzo.workflow.core.repository.mapper.ExtAxReadRecordMapper; +import cn.axzo.workflow.core.service.ExtAxModelDocService; +import cn.axzo.workflow.core.service.ExtAxReadRecordService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.TaskService; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * 审批人关联文档阅读记录表服务实现 + * + * @author wangli + * @since 2025-04-02 10:06 + */ +@Service +@Slf4j +public class ExtAxReadRecordServiceImpl extends ServiceImpl implements ExtAxReadRecordService { + private static final Object lock = new Object(); + @Resource + private ExtAxReadRecordMapper extAxReadRecordMapper; + @Resource + private TaskService taskService; + @Resource + private ExtAxModelDocService modelDocService; + + @Override + public List queryReadStatus(ApproverReadStatusDTO dto) { + ExtAxReadRecord entity = new ExtAxReadRecord(); + entity.setProcessInstanceId(dto.getProcessInstanceId()); + entity.setPersonId(Long.valueOf(dto.getAssigner().getPersonId())); + ExtAxReadRecord record = extAxReadRecordMapper.selectOne(buildWrapper(entity)); + return Objects.isNull(record) ? Collections.emptyList() : record.getReadStatus(); + } + + @Override + public Boolean changeReadStatus(ChangeApproverReadStatusDTO dto) { + ExtAxReadRecord entity = new ExtAxReadRecord(); + entity.setProcessInstanceId(dto.getProcessInstanceId()); + entity.setPersonId(Long.valueOf(dto.getAssigner().getPersonId())); + + ExtAxReadRecord record; + synchronized (lock) { + record = extAxReadRecordMapper.selectOne(buildWrapper(entity)); + if (Objects.isNull(record)) { + // 新增全新阅读记录 + record = new ExtAxReadRecord(); + record.setProcessInstanceId(dto.getProcessInstanceId()); + record.setPersonId(Long.valueOf(dto.getAssigner().getPersonId())); + record.setReadStatus(Lists.newArrayList(SimpleDocDTO.builder() + .id(dto.getDocId()) + .tag(modelDocService.get(dto.getDocId()).getTag()) + .readStatus(dto.getReadStatus()) + .build())); + extAxReadRecordMapper.insert(record); + return true; + } else { + // 更新现有的阅读记录 + Optional opt = record.getReadStatus().stream().filter(i -> Objects.equals(i.getId(), dto.getDocId())).findFirst(); + if (opt.isPresent()) { + opt.ifPresent(i -> i.setReadStatus(dto.getReadStatus())); + } else { + record.getReadStatus().add(SimpleDocDTO.builder() + .id(dto.getDocId()) + .tag(modelDocService.get(dto.getDocId()).getTag()) + .readStatus(dto.getReadStatus()) + .build()); + } + extAxReadRecordMapper.updateById(record); + return true; + } + } + } + + private LambdaQueryWrapper buildWrapper(ExtAxReadRecord entity) { + return new LambdaQueryWrapper() + .eq(StringUtils.hasText(entity.getProcessInstanceId()), ExtAxReadRecord::getProcessInstanceId, entity.getProcessInstanceId()) + .eq(Objects.nonNull(entity.getPersonId()), ExtAxReadRecord::getPersonId, entity.getPersonId()) + ; + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormCoreServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormCoreServiceImpl.java new file mode 100644 index 000000000..bc7c8b9b1 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormCoreServiceImpl.java @@ -0,0 +1,133 @@ +package cn.axzo.workflow.core.service.impl; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO; +import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; +import cn.axzo.workflow.common.model.response.category.CategoryItemVO; +import cn.axzo.workflow.common.model.response.form.FormVO; +import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.common.utils.FormHelper; +import cn.axzo.workflow.core.engine.cmd.GetFormInstanceAndPermissionCmd; +import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; +import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; +import cn.axzo.workflow.core.service.BpmnProcessTaskForEsService; +import cn.axzo.workflow.core.service.CategoryService; +import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService; +import cn.axzo.workflow.core.service.FormCoreService; +import cn.axzo.workflow.form.service.converter.FormFieldConverter; +import cn.axzo.workflow.form.service.converter.FormInstanceConverter; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.impl.cmd.GetBpmnModelCmd; +import org.flowable.form.api.FormInfo; +import org.flowable.form.api.FormInstanceInfo; +import org.flowable.form.api.FormRepositoryService; +import org.flowable.form.model.FormField; +import org.flowable.form.model.SimpleFormModel; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS; +import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; + +/** + * @author wangli + * @since 2024-11-14 10:18 + */ +@Slf4j +@Service +public class FormCoreServiceImpl implements FormCoreService { + @Resource + private SpringProcessEngineConfiguration springProcessEngineConfiguration; + @Resource + private ExtAxBpmnFormRelationService bpmnFormRelationService; + @Resource + private BpmnProcessTaskForEsService bpmnProcessTaskForEsService; + @Resource + private FormInstanceConverter formInstanceConverter; + @Resource + private FormRepositoryService formRepositoryService; + @Resource + private BpmnProcessDefinitionService bpmnProcessDefinitionService; + @Resource + private FormFieldConverter formFieldConverter; + @Resource + private CategoryService categoryService; + + @Override + public List pageForm(BpmnFormRelationSearchDTO searchDTO) { + List list = bpmnFormRelationService.keyQuery(searchDTO); + + Set uniqueKeys = new HashSet<>(); + List uniqueRecords = list.stream() + .filter(record -> uniqueKeys.add(record.getKey() + record.getTenantId())) + .collect(Collectors.toList()); + List keys = list.stream().map(ExtAxBpmnFormRelation::getKey).distinct().collect(Collectors.toList()); + Map categoryMap = categoryService.listByValue(BPM_MODEL_CATEGORY, keys).stream().collect(Collectors.toMap(CategoryItemVO::getValue, Function.identity(), (s, t) -> s)); + return uniqueRecords.stream() + .map(e -> FormVO.builder() + .key(e.getKey()) + .name(categoryMap.getOrDefault(e.getKey(), new CategoryItemVO()).getLabel()) + .tenantId(e.getTenantId()) + .build()) + .collect(Collectors.toList()); + } + + @Override + public FormDefinitionVO getStartForm(StartFormSearchDTO dto) { + FormInfo formModel; + try { + BpmnProcessDefinitionVO definitionVO = bpmnProcessDefinitionService.getActiveProcessDefinitionByKey(dto.getKey(), dto.getTenantId()); + formModel = formRepositoryService.getFormModelByKey(dto.getKey(), definitionVO.getTenantId(), false); + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + BpmnModel bpmnModel = commandExecutor.execute(new GetBpmnModelCmd(definitionVO.getId())); + BpmnMetaParserHelper.getFormFieldPermissionConf(bpmnModel.getFlowElement(NODE_STARTER.getType())) + .ifPresent(permission -> { + if (formModel.getFormModel() instanceof SimpleFormModel) { + SimpleFormModel simpleFormModel = (SimpleFormModel) formModel.getFormModel(); + FormHelper.populateFormModel(simpleFormModel, permission, new HashMap<>(), dto.getShowOriginDefaultValue()); + } + }); + FormDefinitionVO formDefinitionVO = new FormDefinitionVO(); + formDefinitionVO.setFormDefinitionId(formModel.getId()); + formDefinitionVO.setName(formModel.getName()); + formDefinitionVO.setKey(formModel.getKey()); + formDefinitionVO.setVersion(formModel.getVersion()); + formDefinitionVO.setTenantId(dto.getTenantId()); + List fields = ((SimpleFormModel) formModel.getFormModel()).getFields(); + formDefinitionVO.setFields(formFieldConverter.toVos(fields)); + return formDefinitionVO; + } catch (FlowableObjectNotFoundException e) { + log.warn("can't found form model"); + if (Objects.equals(Boolean.TRUE, dto.getThrowException())) { + throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); + } + } + return null; + } + + @Override + public FormInstanceVO getFormInstance(FormDetailDTO dto) { + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + FormInstanceInfo formInstanceInfo = commandExecutor.execute(new GetFormInstanceAndPermissionCmd(bpmnFormRelationService, + bpmnProcessTaskForEsService, dto.getAssigner(), dto.getProcessInstanceId(), dto.getTaskId(), dto.getShowOriginDefaultValue())); + return formInstanceConverter.toVo(formInstanceInfo); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormDefinitionServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormDefinitionServiceImpl.java deleted file mode 100644 index c68cf46c8..000000000 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormDefinitionServiceImpl.java +++ /dev/null @@ -1,93 +0,0 @@ -package cn.axzo.workflow.core.service.impl; - -import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionDTO; -import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionUpdateDTO; -import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; -import cn.axzo.workflow.core.service.FormDefinitionService; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.flowable.engine.RepositoryService; -import org.flowable.engine.repository.Model; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.annotation.Nullable; -import javax.annotation.Resource; -import java.util.Objects; - -import static cn.axzo.workflow.core.common.code.FormDefinitionRespCode.FORM_DEFINITION_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.FormDefinitionRespCode.FORM_DEFINITION_PARSER_ERROR; -import static cn.axzo.workflow.core.common.code.FormModelRespCode.FORM_MODEL_ID_NOT_EXISTS; -import static cn.axzo.workflow.core.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS; - - -/** - * 表单定义 Service 实现 - * - * @author wangli - * @since 2023/7/19 16:47 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class FormDefinitionServiceImpl implements FormDefinitionService { - - @Resource - private RepositoryService repositoryService; - @Resource - private ObjectMapper objectMapper; - - @Override - public FormDefinitionVO get(String formModelId, @Nullable String tenantId) { - Model model = repositoryService.getModel(formModelId); - - if (Objects.isNull(model)) { - throw new WorkflowEngineException(FORM_MODEL_ID_NOT_EXISTS, formModelId); - } - if (Objects.nonNull(tenantId) && !Objects.equals(model.getTenantId(), tenantId)) { - throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); - } - FormDefinitionVO vo = null; - try { - vo = objectMapper.readValue(repositoryService.getModelEditorSource(model.getId()), - FormDefinitionVO.class); - } catch (Exception e) { - e.printStackTrace(); - throw new WorkflowEngineException(FORM_DEFINITION_NOT_EXISTS); - } - vo.setKey(model.getKey()); - vo.setName(model.getName()); - vo.setCategory(model.getCategory()); - vo.setVersion(model.getVersion()); - vo.setTenantId(model.getTenantId()); - return vo; - } - - - @Override - @Transactional(rollbackFor = Exception.class) - public void updateFormDefinition(FormDefinitionUpdateDTO dto) { - Model model = repositoryService.getModel(dto.getFormModelId()); - if (Objects.isNull(model)) { - throw new WorkflowEngineException(FORM_MODEL_ID_NOT_EXISTS, dto.getFormModelId()); - } - if (Objects.nonNull(dto.getTenantId()) && !Objects.equals(model.getTenantId(), dto.getTenantId())) { - throw new WorkflowEngineException(FORM_MODEL_ID_NOT_EXISTS, dto.getFormModelId()); - } - FormDefinitionDTO formDefinition = dto.getFormDefinition(); - formDefinition.setKey(model.getKey()); - formDefinition.setName(model.getName()); - formDefinition.setVersion(model.getVersion()); - - try { - repositoryService.addModelEditorSource(model.getId(), - objectMapper.writeValueAsString(formDefinition).getBytes()); - } catch (Exception e) { - throw new WorkflowEngineException(FORM_DEFINITION_PARSER_ERROR); - } - repositoryService.saveModel(model); - } - -} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/ExpressionConditionCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/ExpressionConditionCmd.java index 672db4e0a..3af6ccafa 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/ExpressionConditionCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/ExpressionConditionCmd.java @@ -1,17 +1,20 @@ package cn.axzo.workflow.core.service.support; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.engine.cmd.AbstractCommand; import cn.azxo.framework.common.utils.StringUtils; +import com.alibaba.fastjson.JSON; import org.flowable.common.engine.api.delegate.Expression; -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.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; -import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_EXECUTION_LOST_ID_ERROR; +import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_EXECUTION_LOST_ID_ERROR; /** @@ -20,7 +23,7 @@ import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_EX * @author wangli * @since 2023/10/9 19:30 */ -public class ExpressionConditionCmd implements Command, Serializable { +public class ExpressionConditionCmd extends AbstractCommand implements Serializable { protected final RuntimeService runtimeService; protected final ProcessEngineConfigurationImpl processEngineConfiguration; protected final String processInstanceId; @@ -35,6 +38,14 @@ public class ExpressionConditionCmd implements Command, Serializable { this.exp = exp; } + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("processInstanceId", processInstanceId); + params.put("exp", exp); + return JSON.toJSONString(params); + } + @Override public Boolean execute(CommandContext commandContext) { Expression expression = processEngineConfiguration.getExpressionManager().createExpression(this.exp); 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 037037a7c..c1c3d6535 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 @@ -1,15 +1,13 @@ package cn.axzo.workflow.core.service.support; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.service.support.forecast.AbstractForecast; 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.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.ResolvableType; @@ -27,7 +25,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.RUNNING_INSTANCE_ONLY_FORECAST; /** @@ -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,6 +111,23 @@ 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)) { @@ -121,32 +135,59 @@ public class FlowNodeForecastService implements InitializingBean { } else { instance = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId) - // .includeProcessVariables() .singleResult(); } if (Objects.isNull(instance)) { - HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery() - .processInstanceId(processInstanceId) - .singleResult(); - processInstanceId = processInstance.getId(); - } else { - processInstanceId = instance.getProcessInstanceId(); + throw new WorkflowEngineException(RUNNING_INSTANCE_ONLY_FORECAST); } - BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId()); // 保持推测出来的节点执行顺序的容器 List orderedNodes = new ArrayList<>(); + + doForecasting(processInstanceId, taskDefinitionKey, containSelf, orderedNodes, bpmnModel); + return orderedNodes; + } + + /** + * 根据 BpmnModel 推测节点运行顺序 + * + * @param processInstanceId 如果有值,则用该实例内的变量进行推测 + * @param taskDefinitionKey 如果有值,则从 BpmnModel 中的该节点往下推测 + * @param containSelf 配合第二个参数,是否包含第二个参数的节点 + * @param orderedNodes 结果集 + * @param bpmnModel 模型 + */ + public void doForecasting(String processInstanceId, + String taskDefinitionKey, + Boolean containSelf, + List orderedNodes, + BpmnModel bpmnModel) { // 流程定义中所有的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; } private void startForecasting(List orderNodes, String processInstanceId) { diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/AbstractForecast.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/AbstractForecast.java index fecb833c5..0e35f812b 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/AbstractForecast.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/AbstractForecast.java @@ -3,7 +3,6 @@ package cn.axzo.workflow.core.service.support.forecast; import cn.axzo.workflow.core.service.support.forecast.impl.SequenceFlowForecasting; import cn.axzo.workflow.core.service.support.spring.FlowableConditionEvaluatorAware; import org.flowable.bpmn.model.FlowElement; -import org.flowable.engine.runtime.ProcessInstance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; @@ -30,7 +29,7 @@ public abstract class AbstractForecast implements Forecas @Override public final List nextFlowElement(FlowElement sourceFlowElement, String processInstanceId) { - return forecastNextNodes(getOutgoing((T) sourceFlowElement), processInstanceId); + return forecastNextNodes(sourceFlowElement, getOutgoing((T) sourceFlowElement), processInstanceId); } protected abstract List getOutgoing(T flowElement); @@ -39,10 +38,11 @@ public abstract class AbstractForecast implements Forecas * 计算出口节点,此时的入参 sourceFlowElement 已经不等于泛型类 * * @param sourceFlowElements 当前节点的出口节点集合 - * @param instance 流程实例 + * @param processInstanceId 流程实例 * @return */ - private List forecastNextNodes(List sourceFlowElements, + private List forecastNextNodes(FlowElement sourceFlowElement, + List sourceFlowElements, String processInstanceId) { if (CollectionUtils.isEmpty(sourceFlowElements)) { return Collections.emptyList(); @@ -54,7 +54,7 @@ public abstract class AbstractForecast implements Forecas elementGroup.forEach((k, v) -> { AbstractForecast forecast = FORECAST_MAP.get(k); if (Objects.nonNull(forecast)) { - result.addAll(forecast.calcRealOutgoingNodes(v, processInstanceId)); + result.addAll(forecast.calcRealOutgoingNodes(v, sourceFlowElement, processInstanceId)); } }); return result; @@ -64,13 +64,15 @@ public abstract class AbstractForecast implements Forecas * 由于每个节点的下级节点是多个,且有可能是不同类型, 在预测下级节点时,需要将同类型的节点传递给对应类型的预测器来计算 * * @param flowElements - * @param instance + * @param processInstanceId * @return */ - public List calcRealOutgoingNodes(List flowElements, String processInstanceId) { + public List calcRealOutgoingNodes(List flowElements, FlowElement sourceFlowElement, String processInstanceId) { if (!CollectionUtils.isEmpty(flowElements) && flowElements.size() == 1) { return flowElements; } + log.warn("流程实例未找到下一个节点,或者由于协议转换异常,出现多个下级节点, instanceId:{}, sourceNode: {}", + processInstanceId, sourceFlowElement.getId()); return Collections.emptyList(); } @@ -85,7 +87,7 @@ public abstract class AbstractForecast implements Forecas * @param exp 表达式 * @param processInstanceId 实例 ID * @return - * @see SequenceFlowForecasting#calcRealOutgoingNodes(List, ProcessInstance) 参考这里的用法 + * @see SequenceFlowForecasting#calcRealOutgoingNodes(List, String) 参考这里的用法 */ protected final Boolean conditionOn(String exp, String processInstanceId) { try { diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/impl/EndEventForecasting.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/impl/EndEventForecasting.java index 8a4d85a92..dc713cc22 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/impl/EndEventForecasting.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/impl/EndEventForecasting.java @@ -28,10 +28,12 @@ public class EndEventForecasting extends AbstractForecast { } @Override - public List calcRealOutgoingNodes(List flowElements, String processInstanceId) { + public List calcRealOutgoingNodes(List flowElements, + FlowElement sourceFlowElement, + String processInstanceId) { if (CollectionUtils.isEmpty(flowElements)) { return Collections.emptyList(); } - return super.calcRealOutgoingNodes(flowElements, processInstanceId); + return super.calcRealOutgoingNodes(flowElements, sourceFlowElement, processInstanceId); } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/impl/SequenceFlowForecasting.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/impl/SequenceFlowForecasting.java index f28f807ca..c74b4b21d 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/impl/SequenceFlowForecasting.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/support/forecast/impl/SequenceFlowForecasting.java @@ -3,11 +3,13 @@ package cn.axzo.workflow.core.service.support.forecast.impl; import cn.axzo.workflow.core.service.support.forecast.AbstractForecast; import com.google.common.collect.Lists; import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.Gateway; import org.flowable.bpmn.model.SequenceFlow; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -34,25 +36,36 @@ public class SequenceFlowForecasting extends AbstractForecast { @Override public List calcRealOutgoingNodes(List flowElements, + FlowElement sourceFlowElement, String processInstanceId) { if (CollectionUtils.isEmpty(flowElements)) { return Collections.emptyList(); } - // 评估顺序流集合中条件为 true 的顺序流 - List executableFlows = - flowElements.stream().filter(i -> StringUtils.hasLength(i.getConditionExpression())) - .filter(i -> conditionOn(i.getConditionExpression(), processInstanceId)).collect(Collectors.toList()); - if (!CollectionUtils.isEmpty(executableFlows) && executableFlows.size() == 1) { - return Lists.newArrayList(executableFlows.get(0)); + // 如果流程实例 ID 为空,则全走默认分支 + if (StringUtils.hasText(processInstanceId)) { + // 评估顺序流集合中条件为 true 的顺序流 + List executableFlows = + flowElements.stream().filter(i -> StringUtils.hasLength(i.getConditionExpression())) + .filter(i -> conditionOn(i.getConditionExpression(), processInstanceId)).collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(executableFlows) && executableFlows.size() == 1) { + return Lists.newArrayList(executableFlows.get(0)); + } } - // 如果 conditionExpression 为空, 则认为是默认流 - List defaultFlows = - flowElements.stream().filter(i -> Objects.isNull(i.getConditionExpression())).collect(Collectors.toList()); + List defaultFlows = new ArrayList<>(); + if (sourceFlowElement instanceof Gateway) { + Gateway gateway = (Gateway) sourceFlowElement; + flowElements.stream() + .filter(i -> Objects.equals(i.getId(), gateway.getDefaultFlow())) + .findAny().ifPresent(defaultFlows::add); + } else { + // 如果 conditionExpression 为空, 则认为是默认流 + defaultFlows.addAll(flowElements.stream().filter(i -> Objects.isNull(i.getConditionExpression())).collect(Collectors.toList())); + } if (!CollectionUtils.isEmpty(defaultFlows) && defaultFlows.size() == 1) { return Lists.newArrayList(defaultFlows.get(0)); } - return super.calcRealOutgoingNodes(flowElements, processInstanceId); + return super.calcRealOutgoingNodes(flowElements, sourceFlowElement, processInstanceId); } } 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..2d096f46b --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/util/DingTalkUtils.java @@ -0,0 +1,205 @@ +package cn.axzo.workflow.core.util; + +import cn.axzo.workflow.common.model.NextNodePreCheckAlterDTO; +import cn.axzo.workflow.common.model.dto.AlterDTO; +import cn.hutool.core.date.DateUtil; +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.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" + + "> 时间: " + DateUtil.now() + "\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 中手动查询 \n\n" + + mobiles(atMobiles)); + request.setMarkdown(markdown); + OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); + at.setAtMobiles(atMobiles); + at.setIsAtAll(false); + request.setAt(at); + sendDingTalk(request); + } + + public static String mobiles(List atMobiles) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < atMobiles.size(); i++) { + if (i != 0) { + sb.append(","); + } + sb.append("@").append(atMobiles.get(i)); + } + return sb.toString(); + } + + public static void sendDingTalkForNodePreCheck(String profile, NextNodePreCheckAlterDTO alterDTO, List alterMobiles) { + 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" + + "> 实例 ID:" + alterDTO.getProcessInstanceId() + "\n\n" + + "> 检测节点 ID:" + alterDTO.getActivityId() + "\n\n" + + "> ##### 错误信息:" + alterDTO.getErrorMsg() + "\n\n" + + mobiles(alterMobiles)); + request.setMarkdown(markdown); + OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); + at.setAtMobiles(alterMobiles); + at.setIsAtAll(false); + request.setAt(at); + sendDingTalk(request); + + } + + public static void sendDingTalkForTransferToAdminError(String profile, String processInstanceId, String taskDefinitionKey, Object orgScopes, String targetUrl) { + 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" + + "> 目标接口: " + targetUrl + "\n\n" + + "> ##### 提示:仅作为提示信息,不代表是异常"); + request.setMarkdown(markdown); + sendDingTalk(request); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/version/MultiVersionBeanUtils.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/version/MultiVersionBeanUtils.java new file mode 100644 index 000000000..a6393917d --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/version/MultiVersionBeanUtils.java @@ -0,0 +1,53 @@ +package cn.axzo.workflow.core.version; + + +import cn.axzo.workflow.core.common.utils.SpringContextUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.core.version.Versioned.UNKNOWN_VERSION; + +public class MultiVersionBeanUtils { + + /** + * 获取支持指定版本的bean + * + * @param clazz 类型 + * @param version 版本号 + * @param + * @return + */ + public static T getSpecifiedVersionBean(Class clazz, String version) { + Map beans = SpringContextUtils.getBeansOfType(clazz); + if (CollectionUtils.isEmpty(beans)) { + throw new NullPointerException("no beans of type " + clazz.getName()); + } + if (StringUtils.isEmpty(version)) { + T t = beans.values().stream().filter(bean -> bean.getVersion() == null || bean.getVersion() == UNKNOWN_VERSION).findFirst().orElse(null); + if (t != null) { + return t; + } + throw new IllegalArgumentException("no default version bean found for version: " + version); + } + //根据版本号排序 + List sortedList = beans.values().stream().sorted((o1, o2) -> { + DefaultArtifactVersion version1 = o1.getVersion() == null ? UNKNOWN_VERSION : o1.getVersion(); + DefaultArtifactVersion version2 = o2.getVersion() == null ? UNKNOWN_VERSION : o2.getVersion(); + return version1.compareTo(version2); + }).collect(Collectors.toList()); + DefaultArtifactVersion targetVersion = new DefaultArtifactVersion(version); + for (int i = sortedList.size() - 1; i >= 0; i--) { + DefaultArtifactVersion classVersion = sortedList.get(i).getVersion() == null ? UNKNOWN_VERSION : sortedList.get(i).getVersion(); + int flag = classVersion.compareTo(targetVersion); + if (flag <= 0) { + return sortedList.get(i); + } + } + throw new NullPointerException("no beans of type " + clazz.getName() + " and version " + version); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/version/Versioned.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/version/Versioned.java new file mode 100644 index 000000000..b024b21d3 --- /dev/null +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/version/Versioned.java @@ -0,0 +1,10 @@ +package cn.axzo.workflow.core.version; + +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; + +public interface Versioned { + + DefaultArtifactVersion getVersion(); + + DefaultArtifactVersion UNKNOWN_VERSION = new DefaultArtifactVersion("0.0.0"); +} diff --git a/workflow-engine-core/src/main/java/org/flowable/engine/impl/cmd/CreateAttachmentCmd.java b/workflow-engine-core/src/main/java/org/flowable/engine/impl/cmd/CreateAttachmentCmd.java index aa30f0396..debe1e8ff 100644 --- a/workflow-engine-core/src/main/java/org/flowable/engine/impl/cmd/CreateAttachmentCmd.java +++ b/workflow-engine-core/src/main/java/org/flowable/engine/impl/cmd/CreateAttachmentCmd.java @@ -1,11 +1,12 @@ package org.flowable.engine.impl.cmd; +import cn.axzo.workflow.core.engine.cmd.AbstractCommand; +import com.alibaba.fastjson.JSON; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableObjectNotFoundException; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; 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.common.engine.impl.persistence.entity.ByteArrayEntity; import org.flowable.common.engine.impl.util.IoUtil; @@ -22,6 +23,9 @@ import org.flowable.task.api.Task; import org.flowable.task.service.impl.persistence.entity.TaskEntity; import java.io.InputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; /** * 覆盖 Flowable 自带的 CreateAttachmentCmd, 由于 Flowable 5 的实现导致异常,需要覆盖 @@ -29,8 +33,9 @@ import java.io.InputStream; * @author Tom Baeyens * @author Joram Barrez */ -public class CreateAttachmentCmd implements Command { +public class CreateAttachmentCmd extends AbstractCommand implements Serializable { + private static final long serialVersionUID = -2901013696126749407L; protected String attachmentType; protected String taskId; protected String processInstanceId; @@ -50,6 +55,19 @@ public class CreateAttachmentCmd implements Command { this.url = url; } + @Override + public String paramToJsonString() { + Map params = new HashMap<>(); + params.put("attachmentType", attachmentType); + params.put("taskId", taskId); + params.put("processInstanceId", processInstanceId); + params.put("attachmentName", attachmentName); + params.put("attachmentDescription", attachmentDescription); +// params.put("content", content); + params.put("url", url); + return JSON.toJSONString(params); + } + @Override public Attachment execute(CommandContext commandContext) { diff --git a/workflow-engine-core/src/main/java/org/flowable/engine/impl/variable/ParallelMultiInstanceLoopVariableType.java b/workflow-engine-core/src/main/java/org/flowable/engine/impl/variable/ParallelMultiInstanceLoopVariableType.java new file mode 100644 index 000000000..d08627502 --- /dev/null +++ b/workflow-engine-core/src/main/java/org/flowable/engine/impl/variable/ParallelMultiInstanceLoopVariableType.java @@ -0,0 +1,108 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.engine.impl.variable; + +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.variable.api.types.ValueFields; +import org.flowable.variable.api.types.VariableType; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class ParallelMultiInstanceLoopVariableType implements VariableType { + + public static final String TYPE_NAME = "bpmnParallelMultiInstanceCompleted"; + protected static final String NUMBER_OF_INSTANCES = "nrOfInstances"; + + protected final ProcessEngineConfigurationImpl processEngineConfiguration; + + public ParallelMultiInstanceLoopVariableType(ProcessEngineConfigurationImpl processEngineConfiguration) { + this.processEngineConfiguration = processEngineConfiguration; + } + + @Override + public String getTypeName() { + return TYPE_NAME; + } + + @Override + public boolean isCachable() { + return false; + } + + @Override + public boolean isAbleToStore(Object value) { + return value instanceof ParallelMultiInstanceLoopVariable; + } + + @Override + public void setValue(Object value, ValueFields valueFields) { + if (value instanceof ParallelMultiInstanceLoopVariable) { + valueFields.setTextValue(((ParallelMultiInstanceLoopVariable) value).getExecutionId()); + valueFields.setTextValue2(((ParallelMultiInstanceLoopVariable) value).getType()); + } else { + valueFields.setTextValue(null); + valueFields.setTextValue2(null); + } + } + + @Override + public Object getValue(ValueFields valueFields) { + CommandContext commandContext = CommandContextUtil.getCommandContext(); + if (commandContext != null) { + return getValue(valueFields, commandContext); + } else { + return processEngineConfiguration.getCommandExecutor() + .execute(context -> getValue(valueFields, context)); + } + } + + protected Object getValue(ValueFields valueFields, CommandContext commandContext) { + String multiInstanceRootId = valueFields.getTextValue(); + String type = valueFields.getTextValue2(); + + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext); + ExecutionEntityManager executionEntityManager = processEngineConfiguration.getExecutionEntityManager(); + ExecutionEntity multiInstanceRootExecution = executionEntityManager.findById(multiInstanceRootId); + if (multiInstanceRootExecution == null) { + /** + * @see https://github.com/flowable/flowable-engine/blob/main/modules/flowable-engine/src/main/java/org/flowable/engine/impl/variable/ParallelMultiInstanceLoopVariableType.java + */ + return 0; + } + List childExecutions = multiInstanceRootExecution.getExecutions(); + int nrOfActiveInstances = (int) childExecutions.stream().filter(execution -> execution.isActive() + && !(execution.getCurrentFlowElement() instanceof BoundaryEvent)).count(); + if (ParallelMultiInstanceLoopVariable.COMPLETED_INSTANCES.equals(type)) { + Object nrOfInstancesValue = multiInstanceRootExecution.getVariable(NUMBER_OF_INSTANCES); + int nrOfInstances = (Integer) (nrOfInstancesValue != null ? nrOfInstancesValue : 0); + return nrOfInstances - nrOfActiveInstances; + } else if (ParallelMultiInstanceLoopVariable.ACTIVE_INSTANCES.equals(type)) { + return nrOfActiveInstances; + } else { + return 0; + } + } + + @Override + public boolean isReadOnly() { + return true; + } +} diff --git a/workflow-engine-core/src/main/resources/mapper/common.xml b/workflow-engine-core/src/main/resources/mapper/common.xml new file mode 100644 index 000000000..5f60c52e2 --- /dev/null +++ b/workflow-engine-core/src/main/resources/mapper/common.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.1.1.sql b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.1.1.sql index b31d751cf..b8795121e 100644 --- a/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.1.1.sql +++ b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.1.1.sql @@ -1,3 +1,11 @@ alter table ext_ax_api_log modify response_body longblob null comment '响应参数'; +create index ACT_IDX_HI_COMMENT_PROC_INST + on act_hi_comment (PROC_INST_ID_); +create index ACT_IDX_HI_COMMENT_TASK_ID + on act_hi_comment (TASK_ID_); +create index ACT_IDX_HI_ATTACH_PROC_INST + on act_hi_attachment (PROC_INST_ID_); +create index ACT_IDX_HI_ATTACH_TASK_ID + on act_hi_attachment (TASK_ID_); diff --git a/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.3.1.sql b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.3.1.sql new file mode 100644 index 000000000..3e9d73185 --- /dev/null +++ b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.3.1.sql @@ -0,0 +1,9 @@ +alter table ext_ax_api_log + modify request_body longblob null comment '请求参数'; + +alter table ext_ax_api_log + add request_application_name varchar(20) default '' null comment '请求方服务名称' after type; + +alter table ext_ax_api_log + add client_version varchar(20) default '' null comment '客户端 API 版本' after request_application_name; + diff --git a/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.3.sql b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.3.sql new file mode 100644 index 000000000..d65168734 --- /dev/null +++ b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.3.3.sql @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS `EXT_AX_MQ_LOG`; +CREATE TABLE IF NOT EXISTS EXT_AX_MQ_LOG +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `unique_id` varchar(50) NOT NULL DEFAULT '' COMMENT '唯一ID', + `message_id` varchar(100) NOT NULL DEFAULT '' COMMENT 'MQ 的 Message ID', + `mq_tag` varchar(50) NOT NULL DEFAULT '' COMMENT 'MQ 的 TAG', + `mq_key` varchar(50) NOT NULL DEFAULT '' COMMENT 'MQ 的 KEY', + `message_body` text COMMENT 'MQ 的 Message Body', + `trace_id` varchar(50) NOT NULL DEFAULT '' COMMENT 'traceId', + `create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `is_delete` bigint NOT NULL DEFAULT '0' COMMENT '是否删除', + `insert_thread_name` varchar(100) NOT NULL DEFAULT '' COMMENT '插入线程名', + `update_thread_name` varchar(100) NOT NULL DEFAULT '' COMMENT '更新线程名', + `delete_thread_name` varchar(100) NOT NULL DEFAULT '' COMMENT '删除线程名', + PRIMARY KEY (`id`) +) ENGINE = InnoDB COMMENT 'MQ发送记录表'; + +create unique index idx_unique on EXT_AX_MQ_LOG (unique_id); + + diff --git a/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.0.sql b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.0.sql new file mode 100644 index 000000000..4905c4e7d --- /dev/null +++ b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.0.sql @@ -0,0 +1,3 @@ +alter table ext_ax_property + add manageable boolean default false null comment '接入方是否使用了 manageable'; + diff --git a/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.1.sql b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.1.sql new file mode 100644 index 000000000..3d304a1ab --- /dev/null +++ b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.4.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE ext_ax_property ADD UNIQUE INDEX name_unique_index (name); + 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-core/src/main/resources/sql/upgrade_to_1.5.0.sql b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.5.0.sql new file mode 100644 index 000000000..00e89af83 --- /dev/null +++ b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.5.0.sql @@ -0,0 +1,17 @@ +create table ext_ax_process_admin +( + id bigint auto_increment comment '主键' primary key, + person_id bigint default 0 not null comment '自然人id', + organizational_unit_id bigint default 0 not null comment '单位id', + workspace_id bigint default 0 not null comment '工作台ID', + workspace_type tinyint default 0 not null comment '工作台类型,1-企业, 2-项目, 3-政务监管平台, 6-oms工作台,参考 WorkspaceType枚举', + admin_type varchar(15) default 'COMMON_ADMIN' not null comment '管理员类型, SUPER_ADMIN-超级管理员, COMMON_ADMIN-普通管理员', + role_type varchar(20) default 'OTHER' not null comment '角色类型, ORGANIZATION_ADMIN-单位超管, ORG_WORKSPACE_ADMIN-项目内单位负责人, WORKSPACE_ADMIN-项目超管,OTHER-其他用户', + data_source varchar(15) default 'USER_ENTRY' not null comment '数据来源, SYSTEM_ENTRY-系统录入, USER_ENTRY-用户手动录入', + update_by bigint default 0 not null comment '更新人', + update_at datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间', + create_by bigint default 0 not null comment '创建人', + is_delete bigint default 0 not null comment '是否删除:0否,1是' +); +create index EXT_IDX_PROCESS_ADMIN_CONFIG_USER on ext_ax_process_admin (person_id,organizational_unit_id,workspace_id); \ No newline at end of file diff --git a/workflow-engine-core/src/main/resources/sql/upgrade_to_1.5.1.sql b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.5.1.sql new file mode 100644 index 000000000..d5daea388 --- /dev/null +++ b/workflow-engine-core/src/main/resources/sql/upgrade_to_1.5.1.sql @@ -0,0 +1,25 @@ +create table ext_ax_bpmn_form_relation +( + id bigint auto_increment comment '主键' primary key, + `key` varchar(64) default '' not null comment '业务标识', + bpmn_definition_id varchar(64) default '' not null comment 'BPMN定义 ID', + form_deployment_id varchar(64) default '' not null comment '表单部署 ID', + tenant_id varchar(64) default '' not null comment '租户 ID', + update_by bigint default 0 not null comment '更新人', + update_at datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间', + create_by bigint default 0 not null comment '创建人', + is_delete bigint default 0 not null comment '是否删除:0否,1是' +); + +create index idx_key_def_form + on ext_ax_bpmn_form_relation (bpmn_definition_id desc, `key` asc, tenant_id asc, is_delete asc); + +-- 优化 oms 审批数据查询功能 +create index idx_start_time_proc_def + on act_hi_procinst (START_TIME_ desc, PROC_DEF_ID_); + +-- 流程日志表增加字段权限配置 +alter table ext_ax_process_log + add form_field_permission_conf json null comment '表单字段权限' after button_conf; + diff --git a/workflow-engine-elasticsearch/pom.xml b/workflow-engine-elasticsearch/pom.xml new file mode 100644 index 000000000..588687a96 --- /dev/null +++ b/workflow-engine-elasticsearch/pom.xml @@ -0,0 +1,43 @@ + + 4.0.0 + + workflow-engine + cn.axzo.workflow + ${revision} + + workflow-engine-elasticsearch + jar + Workflow Engine ElasticSearch + + + 8 + 8 + + + + + cn.axzo.workflow + workflow-engine-core + + + cn.axzo.framework.data + axzo-data-mybatis-plus + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + + + + org.elasticsearch + elasticsearch + + + + org.dromara.easy-es + easy-es-boot-starter + + + diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/common/code/ElasticSearchRespCode.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/common/code/ElasticSearchRespCode.java new file mode 100644 index 000000000..90dafa865 --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/common/code/ElasticSearchRespCode.java @@ -0,0 +1,42 @@ +package cn.axzo.workflow.es.common.code; + +import cn.axzo.framework.domain.web.code.IModuleRespCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * ES 相关操作响应码 + * + * @author wangli + * @since 2023/12/14 16:25 + */ +@Getter +@AllArgsConstructor +public enum ElasticSearchRespCode implements IModuleRespCode { + ES_SEARCH_ERROR("001", "ES 搜索时发生未知异常,信息: {}"), + /** + * 该异常的解决方式 + * PUT _cluster/settings + * { + * "transient": { + * "search.allow_expensive_queries": "true" + * } + * } + */ + ES_EXPENSIVE_QUERIES_NOT_SUPPORTED("002", "ES 集群设置不允许 expensive 查询."), + ; + + private final String code; + private final String message; + + @Override + public String getModuleCode() { + return "13"; + } + + @Override + public String getProjectCode() { + return "998"; + } + +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/mapper/EsProcessInstanceMapper.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/mapper/EsProcessInstanceMapper.java new file mode 100644 index 000000000..1e200c59f --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/mapper/EsProcessInstanceMapper.java @@ -0,0 +1,13 @@ +package cn.axzo.workflow.es.mapper; + +import cn.axzo.workflow.es.model.ProcessInstanceDocument; +import org.dromara.easyes.core.kernel.BaseEsMapper; + +/** + * 流程实例的 EsMapper + * + * @author wangli + * @since 2024-09-23 10:52 + */ +public interface EsProcessInstanceMapper extends BaseEsMapper { +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/mapper/EsProcessTaskMapper.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/mapper/EsProcessTaskMapper.java new file mode 100644 index 000000000..aacf0dc2f --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/mapper/EsProcessTaskMapper.java @@ -0,0 +1,13 @@ +package cn.axzo.workflow.es.mapper; + +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import org.dromara.easyes.core.kernel.BaseEsMapper; + +/** + * 流程任务的 EsMapper + * + * @author wangli + * @since 2024-09-23 10:52 + */ +public interface EsProcessTaskMapper extends BaseEsMapper { +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/model/ProcessInstanceDocument.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/model/ProcessInstanceDocument.java new file mode 100644 index 000000000..100d07c73 --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/model/ProcessInstanceDocument.java @@ -0,0 +1,123 @@ +package cn.axzo.workflow.es.model; + +import cn.axzo.workflow.es.setting.CustomIndexSettingProvider; +import lombok.Data; +import org.dromara.easyes.annotation.IndexField; +import org.dromara.easyes.annotation.IndexId; +import org.dromara.easyes.annotation.IndexName; +import org.dromara.easyes.annotation.Join; +import org.dromara.easyes.annotation.Node; +import org.dromara.easyes.annotation.Settings; +import org.dromara.easyes.annotation.rely.FieldType; +import org.dromara.easyes.annotation.rely.IdType; +import org.dromara.easyes.annotation.rely.RefreshPolicy; + +import java.util.Date; + +import static org.dromara.easyes.annotation.rely.Analyzer.IK_MAX_WORD; + +/** + * 流程实例文档模型 + * + * @author wangli + * @since 2024-09-25 20:32 + */ +@Data +@Settings(settingsProvider = CustomIndexSettingProvider.class) +@IndexName(value = "process_instance_document", keepGlobalPrefix = true, refreshPolicy = RefreshPolicy.IMMEDIATE) +@Join(nodes = {@Node(parentClass = ProcessInstanceDocument.class, parentAlias = "process_instance_document", childClasses = {ProcessTaskDocument.class}, childAliases = {"process_task_document"})}) +public class ProcessInstanceDocument { + + /** + * 流程实例 ID + */ + @IndexId(type = IdType.CUSTOMIZE) + private String id; + + @IndexField(fieldType = FieldType.KEYWORD) + private String instanceId; + + /** + * 流程实例名称 + */ + @IndexField(fieldType = FieldType.KEYWORD_TEXT, analyzer = IK_MAX_WORD, searchAnalyzer = IK_MAX_WORD) + private String processInstanceName; + + /** + * 业务传入业务信息 + */ + private String businessKey; + + /** + * 流程实例使用的定义 ID + */ + private String processDefinitionId; + + /** + * 流程对应业务分类的类型,项目/单位/监管/OMS + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String processCategoryType; + + /** + * 流程实例的发起时间 + */ + @IndexField(dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis") + private Date instanceStartTime; + + /** + * 流程实例的结束时间 + */ + @IndexField(dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis") + private Date instanceEndTime; + + /** + * 流程实例的持续时间 ms + */ + private Long durationInMillis; + + /** + * 该流程中上次操作完成的时间, 如果是已完成的实例, 就清空该值 + */ + @IndexField(dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis") + private Date lastOperationTime; + + /** + * 租户 ID + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String instanceTenantId; + + /** + * 流程实例业务状态 + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String businessStatus; + + /** + * 发起人姓名 + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String initiatorName; + + /** + * 实例对应的流程引擎服务端迭代版本 + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String workflowEngineVersion; + + /** + * 是否代运营 + */ + private Boolean agent; + + /** + * 是否支持批量操作任务 + */ + private Boolean supportBatch; + + /** + * 是否开启同意操作时的签名 + */ + private Boolean userAgreeSignature; +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/model/ProcessTaskDocument.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/model/ProcessTaskDocument.java new file mode 100644 index 000000000..dd6104082 --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/model/ProcessTaskDocument.java @@ -0,0 +1,132 @@ +package cn.axzo.workflow.es.model; + +import lombok.Data; +import org.dromara.easyes.annotation.IndexField; +import org.dromara.easyes.annotation.IndexId; +import org.dromara.easyes.annotation.rely.FieldType; +import org.dromara.easyes.annotation.rely.IdType; + +import java.util.Date; + +/** + * 流程任务文档模型 + * + * @author wangli + * @since 2024-09-25 20:32 + */ +@Data +//@IndexName("process_task_document") +public class ProcessTaskDocument { + + /** + * 任务 ID + */ + @IndexId(type = IdType.CUSTOMIZE) + private String taskId; + + /** + * 任务归属的实例 ID + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String processInstanceId; + + /** + * 任务定义 KEY,对应节点 ID + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String taskDefinitionKey; + + /** + * 任务名称 + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String taskName; + + /** + * 任务状态:审批中/通过/驳回/转交/加签... + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String taskStatus; + + /** + * 操作建议 + */ + private String advice; + + /** + * 操作描述 + */ + private String operationDesc; + + /** + * 流程实例的发起时间 + */ + @IndexField(dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis") + private Date taskStartTime; + + /** + * 流程实例的结束时间 + */ + @IndexField(dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis") + private Date taskEndTime; + + /** + * 流程实例的持续时间 ms + */ + private Long duration; + + /** + * 归属租户,与 processInstance#tenantId 一致 + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String taskTenantId; + + /** + * 审批人姓名 + */ + @IndexField(fieldType = FieldType.KEYWORD_TEXT, analyzer = "ngram_analyzer", searchAnalyzer = "ngram_analyzer") + private String assigneeName; + + /** + * 审批人自然人 ID + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String assigneePersonId; + + /** + * 审批人所属单位 ID + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String assigneeOuId; + + /** + * 审批人所属租户 ID + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String assigneeTenantId; + + /** + * 任务关联的附件(含文件,图片,签名) + */ +// @IndexField(fieldType = FieldType.NESTED, nestedClass = AttachmentDTO.class) +// private List attachments; + + /** + * 审批方式:配置审批人/业务指定/业务触发(不含人) + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String approvalMethod; + + /** + * 节点类型:审批节点/业务节点/评论节点/抄送节点 + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String nodeType; + + /** + * 节点模式:会签/或签 + */ + @IndexField(fieldType = FieldType.KEYWORD) + private String nodeMode; + +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/EsProcessInstanceService.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/EsProcessInstanceService.java new file mode 100644 index 000000000..d7491d6ee --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/EsProcessInstanceService.java @@ -0,0 +1,66 @@ +package cn.axzo.workflow.es.service; + +import cn.axzo.workflow.common.model.request.es.InstanceSearchReqDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.es.model.ProcessInstanceDocument; + +import java.util.Collection; + +/** + * 操作流程实例的 ES Service + * + * @author wangli + * @since 2024-09-27 11:06 + */ +public interface EsProcessInstanceService { + + /** + * 创建索引 + *

+ * 由于采用的是父子文档, 所以该接口会一并创建 + * + * @return + */ + Boolean createIndex(); + + /** + * 删除流程实例索引 + * + * @return + */ + Boolean deleteIndex(); + + /** + * 新增流程实例文档 + * + * @param processInstanceDocument + * @return 成功的条数 + */ + Integer insert(String routing, ProcessInstanceDocument processInstanceDocument); + + /** + * 批量新增流程实例文档 + * + * @param processInstanceDocuments + * @return + */ + Integer insertBatch(String routing, Collection processInstanceDocuments); + + /** + * 更新流程实例文档 + * + * @param routing + * @param processInstanceDocument + */ + Integer update(String routing, ProcessInstanceDocument processInstanceDocument); + + /** + * 删除指定文档 + * + * @param processInstanceId + * @return + */ + Integer delete(String processInstanceId); + + BpmPageResult search(InstanceSearchReqDTO dto); +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/EsProcessTaskService.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/EsProcessTaskService.java new file mode 100644 index 000000000..2ac2e315a --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/EsProcessTaskService.java @@ -0,0 +1,47 @@ +package cn.axzo.workflow.es.service; + +import cn.axzo.workflow.common.model.request.es.TaskSearchReqDTO; +import cn.axzo.workflow.es.model.ProcessTaskDocument; + +import java.util.Collection; +import java.util.List; + +/** + * 操作流程任务的 ES Service + * + * @author wangli + * @since 2024-09-27 11:07 + */ +public interface EsProcessTaskService { + + /** + * 删除流程实例索引 + * + * @return + */ + Boolean deleteIndex(); + + /** + * 新增子文档(重复调用等于更新) + * + * @param routing + * @param parentId + * @param processTaskDocument + * @return + */ + Integer insert(String routing, String parentId, ProcessTaskDocument processTaskDocument); + + /** + * 批量新增子文档(重复调用等于更新) + * + * @param routing + * @param parentId + * @param processTaskDocuments + * @return + */ + Integer insertBatch(String routing, String parentId, Collection processTaskDocuments); + + List search(TaskSearchReqDTO dto); + + Integer delete(String routing, String processInstanceId); +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/aggregation/AggregateProcessInstanceService.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/aggregation/AggregateProcessInstanceService.java new file mode 100644 index 000000000..6fe1c088e --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/aggregation/AggregateProcessInstanceService.java @@ -0,0 +1,165 @@ +package cn.axzo.workflow.es.service.aggregation; + +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.workflow.common.enums.WorkspaceType; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.es.InstanceSearchReqDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.model.response.es.ProcessInstanceDocumentVO; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.service.BpmnProcessInstanceForEsService; +import cn.axzo.workflow.es.model.ProcessInstanceDocument; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.EsProcessInstanceService; +import cn.hutool.core.util.NumberUtil; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.history.HistoricProcessInstance; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +import static cn.axzo.workflow.common.constant.BpmnConstants.ES_FIXED_ROUTING; +import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121; +import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_142; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_AGENT; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_WORKSPACE_TYPE; +import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; + +/** + * 实例纬度的聚合操作 + * + * @author wangli + * @since 2024-09-30 11:46 + */ +@Component +@AllArgsConstructor +@Slf4j +public class AggregateProcessInstanceService { + private final BpmnProcessInstanceForEsService bpmnProcessInstanceForEsService; + private final EsProcessInstanceService esProcessInstanceService; + private final AggregateProcessTaskService aggregateProcessTaskService; + + /** + * 同步结束审批的数据至 ES + */ + public List syncProcessInstance(HistoricProcessInstance hpi, Function, Map> function) { + if (Objects.isNull(hpi)) { + return Collections.emptyList(); + } + ProcessInstanceDocument processInstanceDocument = new ProcessInstanceDocument(); + processInstanceDocument.setId(hpi.getId()); + processInstanceDocument.setInstanceId(hpi.getId()); + processInstanceDocument.setProcessInstanceName(hpi.getName()); + processInstanceDocument.setBusinessKey(hpi.getBusinessKey()); + processInstanceDocument.setProcessDefinitionId(hpi.getProcessDefinitionId()); + processInstanceDocument.setInstanceStartTime(hpi.getStartTime()); + processInstanceDocument.setInstanceEndTime(hpi.getEndTime()); + processInstanceDocument.setDurationInMillis(hpi.getDurationInMillis()); + processInstanceDocument.setInstanceTenantId(hpi.getTenantId()); + processInstanceDocument.setBusinessStatus(hpi.getBusinessStatus()); + + Map variables = hpi.getProcessVariables(); + if (CollectionUtils.isEmpty(variables)) { + variables.putAll(bpmnProcessInstanceForEsService.queryInstanceVariables(hpi.getId(), + Lists.newArrayList(WORKFLOW_ENGINE_VERSION, INTERNAL_PROCESS_WORKSPACE_TYPE, INTERNAL_PROCESS_AGENT, INTERNAL_INITIATOR, OLD_INTERNAL_INITIATOR))); + } + + BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(variables.getOrDefault(INTERNAL_INITIATOR, variables.getOrDefault(OLD_INTERNAL_INITIATOR, null))); + if (Objects.nonNull(initiator)) { + BpmnTaskDelegateAssigner fullAssigner = null; + if (Objects.nonNull(function) && !StringUtils.hasText(initiator.getAssignerName()) && NumberUtil.isNumber(initiator.getPersonId())) { + fullAssigner = function.apply(Lists.newArrayList(initiator)).getOrDefault(Long.parseLong(initiator.getPersonId()), null); + } + processInstanceDocument.setInitiatorName(Objects.nonNull(fullAssigner) ? fullAssigner.getAssignerName() : StringUtils.hasText(initiator.getAssignerName()) ? initiator.getAssignerName() : "未知"); + } + processInstanceDocument.setWorkflowEngineVersion(String.valueOf(variables.getOrDefault(WORKFLOW_ENGINE_VERSION, FLOW_SERVER_VERSION_121))); + processInstanceDocument.setProcessCategoryType(WorkspaceType.getType((Integer) variables.getOrDefault(INTERNAL_PROCESS_WORKSPACE_TYPE, WorkspaceType.UN_KNOW.getCode())).getDesc()); + processInstanceDocument.setAgent((Boolean) variables.getOrDefault(INTERNAL_PROCESS_AGENT, false)); + + BpmnModel bpmnModel = bpmnProcessInstanceForEsService.queryBpmnModel(hpi.getProcessDefinitionId()); + BpmnMetaParserHelper.getProcessApproveConf(bpmnModel.getMainProcess()).ifPresent(cfg -> { + processInstanceDocument.setSupportBatch(cfg.getSupportBatchOperation()); + processInstanceDocument.setUserAgreeSignature(cfg.getUserAgreeSignature()); + }); + + // 实例纬度数据同步 ES + esProcessInstanceService.insert(ES_FIXED_ROUTING, processInstanceDocument); + + String instanceVersion = String.valueOf(variables.getOrDefault(WORKFLOW_ENGINE_VERSION, FLOW_SERVER_VERSION_121)) + .replaceAll("-SNAPSHOT","") + .replaceAll("-RELEASE", ""); + DefaultArtifactVersion version = new DefaultArtifactVersion(instanceVersion); + DefaultArtifactVersion supportVersion = new DefaultArtifactVersion(FLOW_SERVER_VERSION_142); + List toEsProcessTaskDocuments = new ArrayList<>(); + if (version.compareTo(supportVersion) >= 0) { + log.info("通过新日志表进行任务纬度的数据同步"); + // 用新日志表数据任务同步 + toEsProcessTaskDocuments.addAll(aggregateProcessTaskService.syncProcessTaskForNew(hpi, instanceVersion)); + } else { + log.info("通过引擎的任务表进行任务纬度的数据同步"); + // 用引擎的任务表处理任务同步 + toEsProcessTaskDocuments.addAll(aggregateProcessTaskService.syncProcessTaskForOld(hpi, instanceVersion, bpmnModel, function)); + } + + //更新实例上的最后操作时间 + if (Objects.equals(PROCESSING.getStatus(), hpi.getBusinessStatus())) { + toEsProcessTaskDocuments.stream() + .filter(i -> StringUtils.hasText(i.getTaskStatus()) && !Objects.equals(i.getTaskStatus(), PROCESSING.getStatus())) + .filter(i -> Objects.nonNull(i.getTaskEndTime())) + .max(Comparator.comparing(ProcessTaskDocument::getTaskEndTime)) + .ifPresent(processTaskDocument -> { + processInstanceDocument.setLastOperationTime(processTaskDocument.getTaskEndTime()); + }); + } else { + processInstanceDocument.setLastOperationTime(null); + } + + // 更新实例维度的 LastOperationTime 字段为 null + esProcessInstanceService.update(ES_FIXED_ROUTING, processInstanceDocument); + return toEsProcessTaskDocuments; + } + + /** + * 从 ES 中搜索数据, 并转换响应模型 + * + * @param dto + * @return + */ + public BpmPageResult search(InstanceSearchReqDTO dto) { + BpmPageResult searchResult = esProcessInstanceService.search(dto); + List vos = BeanMapper.copyList(searchResult.getList(), ProcessInstanceDocumentVO.class); + return new BpmPageResult<>(vos, searchResult.getTotal()); + } + + /** + * 删除指定流程实例的父子文档 + * + * @param processInstanceId + * @return + */ + public Boolean deleteDocumentParentAndChild(String processInstanceId) { + try { + esProcessInstanceService.delete(processInstanceId); + aggregateProcessTaskService.deleteByParentId(processInstanceId); + } catch (Exception e) { + log.warn("删除文档失败:{}", e.getMessage(), e); + return false; + } + return true; + } +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/aggregation/AggregateProcessTaskService.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/aggregation/AggregateProcessTaskService.java new file mode 100644 index 000000000..89bf90f72 --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/aggregation/AggregateProcessTaskService.java @@ -0,0 +1,254 @@ +package cn.axzo.workflow.es.service.aggregation; + +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.es.TaskSearchReqDTO; +import cn.axzo.workflow.common.model.response.es.ProcessTaskDocumentVO; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.service.BpmnProcessInstanceForEsService; +import cn.axzo.workflow.core.service.BpmnProcessTaskForEsService; +import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.EsProcessTaskService; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.NumberUtil; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.persistence.entity.CommentEntityImpl; +import org.flowable.engine.task.Comment; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +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.ES_FIXED_ROUTING; +import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_130; +import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_142; +import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; +import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_TASK_ASSIGNEE_SKIP_FLAT; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CARBON_COPY; +import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.MI_END; +import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.REJECTION_AUTO_COMPLETED; + +/** + * 任务纬度的聚合操作 + * + * @author wangli + * @since 2024-09-30 11:46 + */ +@Component +@AllArgsConstructor +@Slf4j +public class AggregateProcessTaskService { + private final static DefaultArtifactVersion SUPPORT_VERSION = new DefaultArtifactVersion(FLOW_SERVER_VERSION_142); + private final EsProcessTaskService esProcessTaskService; + private final BpmnProcessInstanceForEsService bpmnProcessInstanceForEsService; + private final BpmnProcessTaskForEsService bpmnProcessTaskForEsService; + private final BpmnHistoricAttachmentConverter attachmentConverter; + + /** + * 通过新的日志表进行任务纬度的数据同步至 ES + */ + public List syncProcessTaskForNew(HistoricProcessInstance hpi, String instanceVersion) { + DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(instanceVersion); + if (currentVersion.compareTo(SUPPORT_VERSION) < 0) { + return Collections.emptyList(); + } + + List logs = bpmnProcessTaskForEsService.queryProcessLogByProcessInstanceId(hpi.getId()); +// Map> attachmentMap = bpmnProcessTaskForEsService.queryAttachmentByProcessInstanceId(hpi.getId()) +// .stream().collect(Collectors.groupingBy(Attachment::getTaskId, Collectors.mapping(Function.identity(), Collectors.toList()))); + + List toEsProcessTaskDocuments = new ArrayList<>(); + logs.stream() + .filter(i -> !Objects.equals(NODE_CARBON_COPY.getType(), i.getNodeType())) + .forEach(log -> { + ProcessTaskDocument processTaskDocument = new ProcessTaskDocument(); + processTaskDocument.setTaskId(log.getTaskId()); + processTaskDocument.setProcessInstanceId(hpi.getId()); + processTaskDocument.setTaskDefinitionKey(log.getActivityId()); + processTaskDocument.setTaskName(log.getActivityName()); + processTaskDocument.setTaskStatus(log.getStatus()); + processTaskDocument.setAdvice(log.getAdvice()); + processTaskDocument.setOperationDesc(log.getOperationDesc()); + processTaskDocument.setTaskStartTime(log.getStartTime()); + processTaskDocument.setTaskEndTime(log.getEndTime()); + if (Objects.nonNull(log.getEndTime())) { + processTaskDocument.setDuration(DateUtil.betweenMs(log.getEndTime(), log.getStartTime())); + } + processTaskDocument.setTaskTenantId(log.getTenantId()); + BpmnTaskDelegateAssigner assigner = CollectionUtils.isEmpty(log.getAssigneeFull()) ? null : log.getAssigneeFull().get(0); + if (Objects.nonNull(assigner)) { + processTaskDocument.setAssigneeName(assigner.getAssignerName()); + processTaskDocument.setAssigneeOuId(assigner.getOuId()); + processTaskDocument.setAssigneePersonId(assigner.getPersonId()); + processTaskDocument.setAssigneeTenantId(assigner.getTenantId()); + } +// esProcessTask.setAttachments(attachmentConverter.toVos(attachmentMap.getOrDefault(log.getTaskId(), Collections.emptyList()))); + processTaskDocument.setApprovalMethod(log.getApprovalMethod()); + processTaskDocument.setNodeType(log.getNodeType()); + processTaskDocument.setNodeMode(log.getNodeMode()); + toEsProcessTaskDocuments.add(processTaskDocument); + }); + + esProcessTaskService.insertBatch(ES_FIXED_ROUTING, hpi.getId(), toEsProcessTaskDocuments); + return toEsProcessTaskDocuments; + } + + /** + * 优先使用三个参数的同步方法. + *

+ * 通过原本引擎表进行任务纬度的数据同步至 ES + */ + public List syncProcessTaskForOld(HistoricProcessInstance hpi, String instanceVersion) { + BpmnModel bpmnModel = bpmnProcessInstanceForEsService.queryBpmnModel(hpi.getProcessDefinitionId()); + return syncProcessTaskForOld(hpi, instanceVersion, bpmnModel, null); + } + + /** + * 通过原本引擎表进行任务纬度的数据同步至 ES + * + * @param hpi + * @param instanceVersion + * @param bpmnModel + * @param function + */ + public List syncProcessTaskForOld(HistoricProcessInstance hpi, String instanceVersion, + BpmnModel bpmnModel, + Function, Map> function) { + + List tasks = bpmnProcessTaskForEsService.queryHistoricProcessTaskByProcessInstanceId(hpi.getId()); + + Map extTaskMap = bpmnProcessTaskForEsService.queryExtAxHiTaskInstByProcessInstanceId(hpi.getId()) + .stream().collect(Collectors.toMap(ExtAxHiTaskInst::getTaskId, Function.identity(), (s, t) -> s)); + + Map> commentMap = bpmnProcessTaskForEsService.queryCommentByProcessInstanceId(hpi.getId()) + .stream().collect(Collectors.groupingBy(Comment::getTaskId, Collectors.mapping(Function.identity(), Collectors.toList()))); + + // 父子文档中子文档不支持 Nested 类型字段 +// Map> attachmentMap = bpmnProcessTaskForEsService.queryAttachmentByProcessInstanceId(hpi.getId()) +// .stream().collect(Collectors.groupingBy(Attachment::getTaskId, Collectors.mapping(Function.identity(), Collectors.toList()))); + + // 过滤出有效任务, 比如同节点的多实例下或签, 一个人操作了同节点的一个任务,会把同节点的其他任务处理了,此时被处理的任务认为非人工处理 + List filteredEffectiveTasks = filterEffectiveTasks(tasks, instanceVersion); + + List taskIds = filteredEffectiveTasks.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getId()).distinct().collect(Collectors.toList()); + Map taskAssigneeMap = bpmnProcessInstanceForEsService.queryInstanceVariables(hpi.getId(), taskIds); + + List toEsProcessTaskDocuments = new ArrayList<>(); + for (HistoricTaskInstance task : filteredEffectiveTasks) { + ExtAxHiTaskInst extTask = extTaskMap.getOrDefault(task.getId(), new ExtAxHiTaskInst()); + List comments = commentMap.getOrDefault(task.getId(), Collections.emptyList()); +// List attachments = attachmentMap.getOrDefault(task.getId(), Collections.emptyList()); + + ProcessTaskDocument processTaskDocument = new ProcessTaskDocument(); + processTaskDocument.setTaskId(task.getId()); + processTaskDocument.setProcessInstanceId(hpi.getId()); + processTaskDocument.setTaskDefinitionKey(task.getTaskDefinitionKey()); + processTaskDocument.setTaskName(task.getName()); + processTaskDocument.setTaskStatus(extTask.getStatus()); + // 处理 advice + processTaskDocument.setAdvice(comments.stream().filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_ADVICE)) + .findFirst().orElse(new CommentEntityImpl()).getFullMessage()); + // 处理 operationDesc + processTaskDocument.setOperationDesc(comments.stream().filter(i -> Objects.equals(COMMENT_TYPE_OPERATION_DESC, i.getType())) + .max(Comparator.comparing(Comment::getTime)) + .orElse(new CommentEntityImpl()).getFullMessage()); + processTaskDocument.setTaskStartTime(task.getCreateTime()); + processTaskDocument.setTaskEndTime(task.getEndTime()); + processTaskDocument.setDuration(task.getDurationInMillis()); + processTaskDocument.setTaskTenantId(task.getTenantId()); + BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(taskAssigneeMap.getOrDefault(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + task.getId(), null)); + if (Objects.nonNull(assigner)) { + BpmnTaskDelegateAssigner fullAssigner = null; + if (Objects.nonNull(function) && !StringUtils.hasText(assigner.getAssignerName()) && NumberUtil.isNumber(assigner.getPersonId())) { + fullAssigner = function.apply(Lists.newArrayList(assigner)).getOrDefault(Long.parseLong(assigner.getPersonId()), null); + } + processTaskDocument.setAssigneeName(Objects.nonNull(fullAssigner) ? fullAssigner.getAssignerName() : StringUtils.hasText(assigner.getAssignerName()) ? assigner.getAssignerName() : "未知"); + processTaskDocument.setAssigneeOuId(assigner.getOuId()); + processTaskDocument.setAssigneePersonId(assigner.getPersonId()); + processTaskDocument.setAssigneeTenantId(assigner.getTenantId()); + } + if (Objects.nonNull(bpmnModel)) { + FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); + BpmnMetaParserHelper.getApprovalMethod(flowElement) + .ifPresent(e -> processTaskDocument.setApprovalMethod(e.getType())); + BpmnMetaParserHelper.getNodeType(flowElement) + .ifPresent(e -> processTaskDocument.setNodeType(e.getType())); + } + if (!Objects.equals(processTaskDocument.getNodeType(), NODE_CARBON_COPY.getType())) { + toEsProcessTaskDocuments.add(processTaskDocument); + } + } + esProcessTaskService.insertBatch(ES_FIXED_ROUTING, hpi.getId(), toEsProcessTaskDocuments); + return toEsProcessTaskDocuments; + } + + private List filterEffectiveTasks(List tasks, String instanceVersion) { + List effectiveTasks = new ArrayList<>(); + if (CollectionUtils.isEmpty(tasks)) { + return effectiveTasks; + } + Stream taskInstanceStream = tasks.stream() + .filter(i -> !Objects.equals(i.getAssignee(), HIDDEN_ASSIGNEE_ID)) + .filter(i -> !Objects.equals(i.getDeleteReason(), HIDDEN_ASSIGNEE_ID)) + .filter(i -> !Objects.equals(i.getDeleteReason(), MI_END.getStatus())); + if (Objects.isNull(instanceVersion)) { + compatibleVersion(taskInstanceStream).forEach(effectiveTasks::add); + } else { + if (StringUtils.hasText(instanceVersion)) { + DefaultArtifactVersion version = new DefaultArtifactVersion(instanceVersion); + DefaultArtifactVersion supportVersion = new DefaultArtifactVersion(FLOW_SERVER_VERSION_130); + if (version.compareTo(supportVersion) < 0) { + compatibleVersion(taskInstanceStream).forEach(effectiveTasks::add); + } else { + taskInstanceStream.forEach(effectiveTasks::add); + } + } else { + taskInstanceStream.forEach(effectiveTasks::add); + } + } + return effectiveTasks; + } + + + /** + * 兼容 1.2.1 版本的审批日志 + * + * @param stream + * @return + */ + private Stream compatibleVersion(Stream stream) { + return stream.filter(i -> (!Objects.equals(REJECTION_AUTO_COMPLETED.getDesc(), i.getDeleteReason())) + || (Objects.equals(i.getAssignee(), OLD_TASK_ASSIGNEE_SKIP_FLAT) && Objects.equals(REJECTION_AUTO_COMPLETED.getDesc(), i.getDeleteReason())) + ).filter(i -> !(!Objects.equals(i.getAssignee(), OLD_TASK_ASSIGNEE_SKIP_FLAT) && Objects.equals(MI_END.getStatus(), i.getDeleteReason()))); + } + + public List search(TaskSearchReqDTO dto) { + return BeanMapper.copyList(esProcessTaskService.search(dto), ProcessTaskDocumentVO.class); + } + + public Integer deleteByParentId(String processInstanceId) { + return esProcessTaskService.delete(ES_FIXED_ROUTING, processInstanceId); + } +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/impl/EsProcessInstanceServiceImpl.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/impl/EsProcessInstanceServiceImpl.java new file mode 100644 index 000000000..d90490176 --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/impl/EsProcessInstanceServiceImpl.java @@ -0,0 +1,157 @@ +package cn.axzo.workflow.es.service.impl; + +import cn.axzo.workflow.common.model.request.es.InstanceSearchReqDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.es.mapper.EsProcessInstanceMapper; +import cn.axzo.workflow.es.model.ProcessInstanceDocument; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.EsProcessInstanceService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.easyes.annotation.IndexName; +import org.dromara.easyes.core.biz.EsPageInfo; +import org.dromara.easyes.core.cache.GlobalConfigCache; +import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper; +import org.dromara.easyes.core.config.GlobalConfig; +import org.dromara.easyes.core.toolkit.FieldUtils; +import org.elasticsearch.ElasticsearchException; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.BpmnConstants.ES_FIXED_ROUTING; +import static cn.axzo.workflow.es.common.code.ElasticSearchRespCode.ES_EXPENSIVE_QUERIES_NOT_SUPPORTED; +import static cn.axzo.workflow.es.common.code.ElasticSearchRespCode.ES_SEARCH_ERROR; + +/** + * 操作流程实例的 ES Service 实现 + * + * @author wangli + * @since 2024-09-27 11:09 + */ +@Service +@AllArgsConstructor +@Slf4j +public class EsProcessInstanceServiceImpl implements EsProcessInstanceService { + + private final EsProcessInstanceMapper esProcessInstanceMapper; + + /** + * 创建索引 + *

+ * 由于采用的是父子文档, 所以该接口会一并创建 + * + * @return + */ + @Override + public Boolean createIndex() { + IndexName annotation = AnnotationUtils.findAnnotation(ProcessInstanceDocument.class, IndexName.class); + if (esProcessInstanceMapper.existsIndex(annotation.value())) { + return false; + } + return esProcessInstanceMapper.createIndex(); + } + + /** + * 删除流程实例索引 + * + * @return + */ + @Override + public Boolean deleteIndex() { + GlobalConfig globalConfig = GlobalConfigCache.getGlobalConfig(); + IndexName annotation = AnnotationUtils.findAnnotation(ProcessInstanceDocument.class, IndexName.class); + return esProcessInstanceMapper.deleteIndex(globalConfig.getDbConfig().getIndexPrefix() + annotation.value()); + } + + /** + * 新增流程实例文档文档 + * + * @param processInstanceDocument + * @return 成功的条数 + */ + @Override + public Integer insert(String routing, ProcessInstanceDocument processInstanceDocument) { + return esProcessInstanceMapper.insert(routing, processInstanceDocument); + } + + @Override + public Integer insertBatch(String routing, Collection processInstanceDocuments) { + return esProcessInstanceMapper.insertBatch(routing, processInstanceDocuments); + } + + /** + * 更新流程实例文档 + * + * @param routing + * @param processInstanceDocument + */ + @Override + public Integer update(String routing, ProcessInstanceDocument processInstanceDocument) { + return esProcessInstanceMapper.updateById(ES_FIXED_ROUTING, processInstanceDocument); + } + + @Override + public Integer delete(String processInstanceId) { + return esProcessInstanceMapper.deleteById(ES_FIXED_ROUTING, processInstanceId); + } + + @Override + public BpmPageResult search(InstanceSearchReqDTO dto) { + if (!CollectionUtils.isEmpty(dto.getProcessInstanceIds())) { + List processInstanceDocuments = esProcessInstanceMapper + .selectList(new LambdaEsQueryWrapper() + .in(FieldUtils.val(ProcessInstanceDocument::getId), dto.getProcessInstanceIds())); + return new BpmPageResult<>(processInstanceDocuments, (long) processInstanceDocuments.size()); + } + + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); + wrapper.hasChild("process_task_document", + w -> w.matchPhrase(StringUtils.hasText(dto.getAssigneeName()), FieldUtils.val(ProcessTaskDocument::getAssigneeName), dto.getAssigneeName()) + .eq(StringUtils.hasText(dto.getTenantId()), FieldUtils.val(ProcessTaskDocument::getAssigneeTenantId), dto.getTenantId()) + .eq(StringUtils.hasText(dto.getOuId()), FieldUtils.val(ProcessTaskDocument::getAssigneeOuId), dto.getOuId()) + .eq(StringUtils.hasText(dto.getPersonId()), FieldUtils.val(ProcessTaskDocument::getAssigneePersonId), dto.getPersonId()) + ) + .eq(StringUtils.hasText(dto.getBusinessStatus()), FieldUtils.val(ProcessInstanceDocument::getBusinessStatus), dto.getBusinessStatus()) + .and(StringUtils.hasText(dto.getProcessInstanceName()), + w -> w.or(i -> i.eq(FieldUtils.val(ProcessInstanceDocument::getProcessInstanceName), dto.getProcessInstanceName(), 1.2F)) + .or(j->j.match(FieldUtils.val(ProcessInstanceDocument::getProcessInstanceName), dto.getProcessInstanceName())) + ) + .in(CollectionUtils.isEmpty(dto.getProcessInstanceIds()), FieldUtils.val(ProcessInstanceDocument::getId), dto.getProcessInstanceIds()) + .ge(Objects.nonNull(dto.getBeginStartTime()), ProcessInstanceDocument::getInstanceStartTime, dto.getBeginStartTime()) + .le(Objects.nonNull(dto.getOverStartTime()), ProcessInstanceDocument::getInstanceStartTime, dto.getOverStartTime()) + .ge(Objects.nonNull(dto.getBeginEndTime()), ProcessInstanceDocument::getInstanceEndTime, dto.getBeginEndTime()) + .le(Objects.nonNull(dto.getOverEndTime()), ProcessInstanceDocument::getInstanceEndTime, dto.getOverEndTime()) + .sortByScore() + .orderByDesc(FieldUtils.val(ProcessInstanceDocument::getInstanceStartTime)) + .trackScores() + .routing(ES_FIXED_ROUTING) + ; + EsPageInfo pageInfo; + try { + pageInfo = esProcessInstanceMapper.pageQuery(wrapper, dto.getPageNo(), dto.getPageSize()); + } catch (Exception e) { + Throwable rootCause = getRootCause(e); + if (rootCause instanceof ElasticsearchException && rootCause.getMessage().contains("search.allow_expensive_queries")) { + // 该问题涉及到集群参数调整,调整方法参考异常 CODE 中的说明 + throw new WorkflowEngineException(ES_EXPENSIVE_QUERIES_NOT_SUPPORTED); + } + log.warn("ES 搜索时发生异常:{}", e.getMessage(), e); + throw new WorkflowEngineException(ES_SEARCH_ERROR, e.getMessage()); + } + return new BpmPageResult<>(pageInfo.getList(), pageInfo.getTotal()); + } + + private Throwable getRootCause(Throwable throwable) { + if (Objects.nonNull(throwable) && Objects.nonNull(throwable.getCause())) { + return getRootCause(throwable.getCause()); + } + return throwable; + } +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/impl/EsProcessTaskServiceImpl.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/impl/EsProcessTaskServiceImpl.java new file mode 100644 index 000000000..977d6b1e0 --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/service/impl/EsProcessTaskServiceImpl.java @@ -0,0 +1,83 @@ +package cn.axzo.workflow.es.service.impl; + +import cn.axzo.workflow.common.model.request.es.TaskSearchReqDTO; +import cn.axzo.workflow.es.mapper.EsProcessTaskMapper; +import cn.axzo.workflow.es.model.ProcessInstanceDocument; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.EsProcessTaskService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.easyes.annotation.IndexName; +import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper; +import org.dromara.easyes.core.toolkit.FieldUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.List; + +import static cn.axzo.workflow.common.constant.BpmnConstants.ES_FIXED_ROUTING; + +/** + * 操作流程任务的 ES Service 实现 + * + * @author wangli + * @since 2024-09-27 11:09 + */ +@Service +@AllArgsConstructor +@Slf4j +public class EsProcessTaskServiceImpl implements EsProcessTaskService { + + private final EsProcessTaskMapper esProcessTaskMapper; + + /** + * 删除流程实例索引 + * + * @return + */ + @Override + public Boolean deleteIndex() { + IndexName annotation = AnnotationUtils.findAnnotation(ProcessTaskDocument.class, IndexName.class); + return esProcessTaskMapper.deleteIndex(annotation.value()); + } + + /** + * 新增流程实例文档文档 + * + * @param processTaskDocument + * @return 成功的条数 + */ + public Integer insert(String routing, String parentId, ProcessTaskDocument processTaskDocument) { + return esProcessTaskMapper.insert(routing, parentId, processTaskDocument); + } + + @Override + public Integer insertBatch(String routing, String parentId, Collection processTaskDocuments) { + return esProcessTaskMapper.insertBatch(routing, parentId, processTaskDocuments); + } + + @Override + public List search(TaskSearchReqDTO dto) { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper() + .eq(StringUtils.hasText(dto.getAssigneeOuId()), ProcessTaskDocument::getAssigneeOuId, dto.getAssigneeOuId()) + .eq(StringUtils.hasText(dto.getAssigneeTenantId()), ProcessTaskDocument::getAssigneeTenantId, dto.getAssigneeTenantId()) + .eq(StringUtils.hasText(dto.getAssigneePersonId()), ProcessTaskDocument::getAssigneePersonId, dto.getAssigneePersonId()) + .matchPhrase(StringUtils.hasText(dto.getAssigneeName()), ProcessTaskDocument::getAssigneeName, dto.getAssigneeName()) + .hasParent(StringUtils.hasText(dto.getProcessInstanceId()), "process_instance_document", + w-> w.eq(FieldUtils.val(ProcessInstanceDocument::getInstanceId), dto.getProcessInstanceId())) +// .parentId(StringUtils.hasText(dto.getProcessInstanceId()), dto.getProcessInstanceId(), "process_instance_document") + .routing(ES_FIXED_ROUTING) + ; + return esProcessTaskMapper.selectList(wrapper); + } + + @Override + public Integer delete(String routing, String processInstanceId) { + LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper() + .eq(FieldUtils.val(ProcessTaskDocument::getProcessInstanceId), processInstanceId) + .routing(ES_FIXED_ROUTING); + return esProcessTaskMapper.delete(wrapper); + } +} diff --git a/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/setting/CustomIndexSettingProvider.java b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/setting/CustomIndexSettingProvider.java new file mode 100644 index 000000000..9170b327c --- /dev/null +++ b/workflow-engine-elasticsearch/src/main/java/cn/axzo/workflow/es/setting/CustomIndexSettingProvider.java @@ -0,0 +1,24 @@ +package cn.axzo.workflow.es.setting; + +import com.google.common.collect.Lists; +import org.dromara.easyes.annotation.rely.DefaultSettingsProvider; + +import java.util.HashMap; +import java.util.Map; + +/** + * 对索引这是自定义的 Settings + * + * @author wangli + * @since 2024-10-12 10:00 + */ +public class CustomIndexSettingProvider extends DefaultSettingsProvider { + @Override + public Map getSettings() { + Map settings = new HashMap<>(); + settings.put("analysis.analyzer.ngram_analyzer.tokenizer", "ngram_tokenizer"); + settings.put("analysis.tokenizer.ngram_tokenizer.token_chars", Lists.newArrayList("letter", "digit")); + settings.put("analysis.tokenizer.ngram_tokenizer.type", "ngram"); + return settings; + } +} diff --git a/workflow-engine-form/pom.xml b/workflow-engine-form/pom.xml new file mode 100644 index 000000000..4560e570b --- /dev/null +++ b/workflow-engine-form/pom.xml @@ -0,0 +1,52 @@ + + 4.0.0 + + cn.axzo.workflow + workflow-engine + ${revision} + + workflow-engine-form + jar + Workflow Engine Form + + 6.7.2 + + + + + + org.flowable + flowable-spring-boot-starter-basic + ${flowable.version} + + + + + org.flowable + flowable-form-spring + ${flowable.version} + + + + org.flowable + flowable-form-spring-configurator + ${flowable.version} + + + + cn.axzo.workflow + workflow-engine-api + + + + cn.hutool + hutool-all + + + + cn.axzo.workflow + workflow-engine-common + + + diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/conf/FormFlowableConfiguration.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/conf/FormFlowableConfiguration.java new file mode 100644 index 000000000..5e2623d4c --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/conf/FormFlowableConfiguration.java @@ -0,0 +1,27 @@ +package cn.axzo.workflow.form.conf; + +import cn.axzo.workflow.form.engine.api.CustomFormServiceImpl; +import org.flowable.form.spring.SpringFormEngineConfiguration; +import org.flowable.spring.boot.EngineConfigurationConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.flowable.common.engine.impl.AbstractEngineConfiguration.DB_SCHEMA_UPDATE_TRUE; + +/** + * 表单引擎配置 + * + * @author wangli + * @since 2024-11-04 13:38 + */ +@Configuration +public class FormFlowableConfiguration { + + @Bean + public EngineConfigurationConfigurer formEngineConfigurer() { + return configuration -> { + configuration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_TRUE); + configuration.setFormService(new CustomFormServiceImpl(configuration)); + }; + } +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/api/CustomFormServiceImpl.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/api/CustomFormServiceImpl.java new file mode 100644 index 000000000..ee3f8bb1a --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/api/CustomFormServiceImpl.java @@ -0,0 +1,43 @@ +package cn.axzo.workflow.form.engine.api; + +import cn.axzo.workflow.form.engine.cmd.CustomCreateFormInstanceCmd; +import cn.axzo.workflow.form.engine.cmd.CustomGetVariablesFromFormSubmissionCmd; +import org.flowable.form.api.FormInfo; +import org.flowable.form.api.FormInstance; +import org.flowable.form.engine.FormEngineConfiguration; +import org.flowable.form.engine.impl.FormServiceImpl; + +import java.util.Map; + +/** + * 自定义扩展表单引擎中的 FormService + * + * @author wangli + * @since 2025-01-22 11:19 + */ +public class CustomFormServiceImpl extends FormServiceImpl { + public CustomFormServiceImpl(FormEngineConfiguration engineConfiguration) { + super(engineConfiguration); + } + + @Override + public void validateFormFields(FormInfo formInfo, Map values) { + // StartEvent Form Fields Validation + super.validateFormFields(formInfo, values); + } + + @Override + public FormInstance createFormInstance(Map variables, FormInfo formInfo, String taskId, String processInstanceId, String processDefinitionId, String tenantId, String outcome) { + return commandExecutor.execute(new CustomCreateFormInstanceCmd(formInfo, variables, taskId, processInstanceId, processDefinitionId, tenantId, outcome)); + } + + @Override + public Map getVariablesFromFormSubmission(FormInfo formInfo, Map values) { + return commandExecutor.execute(new CustomGetVariablesFromFormSubmissionCmd(formInfo, values)); + } + + @Override + public Map getVariablesFromFormSubmission(FormInfo formInfo, Map values, String outcome) { + return commandExecutor.execute(new CustomGetVariablesFromFormSubmissionCmd(formInfo, values, outcome)); + } +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/cmd/CustomCreateFormInstanceCmd.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/cmd/CustomCreateFormInstanceCmd.java new file mode 100644 index 000000000..d66daee7c --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/cmd/CustomCreateFormInstanceCmd.java @@ -0,0 +1,168 @@ +package cn.axzo.workflow.form.engine.cmd; + +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.form.api.FormInfo; +import org.flowable.form.api.FormInstance; +import org.flowable.form.engine.FormEngineConfiguration; +import org.flowable.form.engine.impl.cmd.CreateFormInstanceCmd; +import org.flowable.form.engine.impl.persistence.entity.FormInstanceEntity; +import org.flowable.form.engine.impl.persistence.entity.FormInstanceEntityManager; +import org.flowable.form.engine.impl.util.CommandContextUtil; +import org.flowable.form.model.FormField; +import org.flowable.form.model.FormFieldTypes; +import org.flowable.form.model.SimpleFormModel; +import org.joda.time.LocalDate; +import org.springframework.util.CollectionUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_AMOUNT; +import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_UPLOAD; + +/** + * 覆盖表单引擎的创建表单实例的命令 + * + * @author wangli + * @since 2025-01-22 16:52 + */ +public class CustomCreateFormInstanceCmd extends CreateFormInstanceCmd { + public CustomCreateFormInstanceCmd(FormInfo formInfo, Map variables, String taskId, String processInstanceId, String processDefinitionId, String tenantId, String outcome) { + super(formInfo, variables, taskId, processInstanceId, processDefinitionId, tenantId, outcome); + } + + public CustomCreateFormInstanceCmd(String formModelId, Map variables, String taskId, String processInstanceId, String processDefinitionId, String tenantId, String outcome) { + super(formModelId, variables, taskId, processInstanceId, processDefinitionId, tenantId, outcome); + } + + public CustomCreateFormInstanceCmd(FormInfo formInfo, Map variables, String taskId, String scopeId, String scopeType, String scopeDefinitionId, String tenantId, String outcome) { + super(formInfo, variables, taskId, scopeId, scopeType, scopeDefinitionId, tenantId, outcome); + } + + public CustomCreateFormInstanceCmd(String formModelId, Map variables, String taskId, String scopeId, String scopeType, String scopeDefinitionId, String tenantId, String outcome) { + super(formModelId, variables, taskId, scopeId, scopeType, scopeDefinitionId, tenantId, outcome); + } + + @Override + public FormInstance execute(CommandContext commandContext) { + FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(); + if (formInfo == null) { + if (formModelId == null) { + throw new FlowableException("Invalid form model and no form model Id provided"); + } + formInfo = CommandContextUtil.getFormEngineConfiguration().getFormRepositoryService().getFormModelById(formModelId); + } + + if (formInfo == null || formInfo.getId() == null) { + throw new FlowableException("Invalid form model provided"); + } + + ObjectMapper objectMapper = formEngineConfiguration.getObjectMapper(); + ObjectNode submittedFormValuesJson = objectMapper.createObjectNode(); + + ObjectNode valuesNode = submittedFormValuesJson.putObject("values"); + + // Loop over all form fields and see if a value was provided + + SimpleFormModel formModel = (SimpleFormModel) formInfo.getFormModel(); + Map fieldMap = formModel.allFieldsAsMap(); + for (String fieldId : fieldMap.keySet()) { + FormField formField = fieldMap.get(fieldId); + + if (FormFieldTypes.EXPRESSION.equals(formField.getType()) || FormFieldTypes.CONTAINER.equals(formField.getType())) { + continue; + } + + if (variables != null && variables.containsKey(fieldId)) { + Object variableValue = variables.get(fieldId); + if (variableValue == null) { + valuesNode.putNull(fieldId); + } else if (variableValue instanceof Long) { + valuesNode.put(fieldId, (Long) variables.get(fieldId)); + + } else if (variableValue instanceof Double) { + valuesNode.put(fieldId, (Double) variables.get(fieldId)); + + } else if (variableValue instanceof Boolean) { + valuesNode.put(fieldId, (Boolean) variables.get(fieldId)); + + } else if (variableValue instanceof LocalDate) { + valuesNode.put(fieldId, ((LocalDate) variableValue).toString()); + } else if (variableValue instanceof Collection) { + if (CollectionUtils.isEmpty((Collection) variableValue)) { + variableValue = Collections.emptyList(); + } + try { + valuesNode.put(fieldId, objectMapper.writeValueAsString(variableValue)); + } catch (JsonProcessingException e) { + throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_UPLOAD); + } + } else if (variableValue instanceof Map) { + if (CollectionUtils.isEmpty((Map) variableValue)) { + variableValue = Collections.emptyMap(); + } + try { + valuesNode.put(fieldId, objectMapper.writeValueAsString(variableValue)); + } catch (JsonProcessingException e) { + throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_AMOUNT); + } + } else { + valuesNode.put(fieldId, variableValue.toString()); + } + } + } + + if (outcome != null) { + submittedFormValuesJson.put("flowable_form_outcome", outcome); + } + + FormInstanceEntityManager formInstanceEntityManager = CommandContextUtil.getFormInstanceEntityManager(commandContext); + FormInstanceEntity formInstanceEntity = findExistingFormInstance(formEngineConfiguration); + + if (formInstanceEntity == null) { + formInstanceEntity = formInstanceEntityManager.create(); + } + + formInstanceEntity.setFormDefinitionId(formInfo.getId()); + formInstanceEntity.setTaskId(taskId); + + if (processInstanceId != null) { + formInstanceEntity.setProcessInstanceId(processInstanceId); + formInstanceEntity.setProcessDefinitionId(processDefinitionId); + + } else { + formInstanceEntity.setScopeId(scopeId); + formInstanceEntity.setScopeType(scopeType); + formInstanceEntity.setScopeDefinitionId(scopeDefinitionId); + } + + formInstanceEntity.setSubmittedDate(formEngineConfiguration.getClock().getCurrentTime()); + formInstanceEntity.setSubmittedBy(Authentication.getAuthenticatedUserId()); + + if (tenantId != null) { + formInstanceEntity.setTenantId(tenantId); + } + + try { + formInstanceEntity.setFormValueBytes(objectMapper.writeValueAsBytes(submittedFormValuesJson)); + } catch (Exception e) { + throw new FlowableException("Error setting form values JSON", e); + } + + if (formInstanceEntity.getId() == null) { + formInstanceEntityManager.insert(formInstanceEntity); + } else { + formInstanceEntityManager.update(formInstanceEntity); + } + + + return formInstanceEntity; + } +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/cmd/CustomGetVariablesFromFormSubmissionCmd.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/cmd/CustomGetVariablesFromFormSubmissionCmd.java new file mode 100644 index 000000000..11f35e163 --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/cmd/CustomGetVariablesFromFormSubmissionCmd.java @@ -0,0 +1,143 @@ +package cn.axzo.workflow.form.engine.cmd; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.form.api.FormInfo; +import org.flowable.form.engine.FormEngineConfiguration; +import org.flowable.form.engine.impl.cmd.GetVariablesFromFormSubmissionCmd; +import org.flowable.form.engine.impl.util.CommandContextUtil; +import org.flowable.form.model.FormField; +import org.flowable.form.model.FormFieldTypes; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Date; +import java.util.Map; + +/** + * 覆盖表单引擎自带的表单提交处理命令 + * + * @author wangli + * @since 2025-01-22 15:20 + */ +public class CustomGetVariablesFromFormSubmissionCmd extends GetVariablesFromFormSubmissionCmd { + + public CustomGetVariablesFromFormSubmissionCmd(FormInfo formInfo, Map values) { + super(formInfo, values); + } + + public CustomGetVariablesFromFormSubmissionCmd(FormInfo formInfo, Map values, String outcome) { + super(formInfo, values, outcome); + } + + @Override + public Map execute(CommandContext commandContext) { + return super.execute(commandContext); + } + + @Override + protected Object transformFormFieldValueToVariableValue(FormField formField, Object formFieldValue) { + Object result = formFieldValue; + if (formField.getType().equals(FormFieldTypes.DATE) && formFieldValue instanceof String) { + if (StringUtils.isNotEmpty((String) formFieldValue)) { + try { +// result = LocalDate.parse((String) formFieldValue); + result = parseToLocalDateTime((String) formFieldValue, fmtConvert(formField.getParam("fmt").toString())); + } catch (Exception e) { + e.printStackTrace(); + result = null; + } + } + + } else if (formField.getType().equals(FormFieldTypes.DATE) && formFieldValue instanceof Date) { + result = parseToLocalDateTime((String) formFieldValue, fmtConvert(formField.getParam("fmt").toString())); +// result = new LocalDate(formFieldValue); + } else if (formField.getType().equals(FormFieldTypes.INTEGER) && formFieldValue instanceof String) { + String strFieldValue = (String) formFieldValue; + if (StringUtils.isNotEmpty(strFieldValue) && NumberUtils.isCreatable(strFieldValue)) { + result = Long.valueOf(strFieldValue); + + } else { + result = null; + } + + } else if (formField.getType().equals(FormFieldTypes.DECIMAL) && formFieldValue instanceof String) { + String strFieldValue = (String) formFieldValue; + if (StringUtils.isNotEmpty(strFieldValue) && NumberUtils.isCreatable(strFieldValue)) { + result = Double.valueOf(strFieldValue); + + } else { + result = null; + } + + } else if (formField.getType().equals(FormFieldTypes.AMOUNT) && formFieldValue instanceof String) { + try { + result = Double.parseDouble((String) formFieldValue); + + } catch (NumberFormatException e) { + result = null; + } + + } else if (formField.getType().equals(FormFieldTypes.DROPDOWN) || formField.getType().equals(FormFieldTypes.RADIO_BUTTONS)) { + if (formFieldValue instanceof Map) { + result = ((Map) formFieldValue).get("id"); + if (result == null) { + // fallback to name for manual config options + result = ((Map) formFieldValue).get("name"); + } + } + + } else if (formField.getType().equals(FormFieldTypes.UPLOAD)) { + FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(); + try { + result = formEngineConfiguration.getObjectMapper().writeValueAsString(formFieldValue); + } catch (JsonProcessingException e) { + result = null; + } + + } else if (formField.getType().equals(FormFieldTypes.PEOPLE) || formField.getType().equals(FormFieldTypes.FUNCTIONAL_GROUP)) { + if (formFieldValue instanceof Map) { + Map value = (Map) formFieldValue; + result = value.get("id").toString(); + + } else { + // Incorrect or empty map, ignore + result = null; + } + } + + // Default: no processing needs to be done, can be stored as-is + return result; + } + + + public static LocalDateTime parseToLocalDateTime(String dateStr, String format) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format.replace("YYYY", "yyyy").replace("DD", "dd")); + if (format.contains("HH:mm")) { + return LocalDateTime.parse(dateStr, formatter); + } else { + LocalDate localDate = LocalDate.parse(dateStr, formatter); + return localDate.atStartOfDay(); + } + } catch (DateTimeParseException e) { + System.err.println("解析日期字符串时出错: " + dateStr + " ,使用格式: " + format); + return null; + } + } + + public static String fmtConvert(String fmt) { + switch (fmt) { + case "YYYY-MM-DD HH:mm:ss": + return "yyyy-MM-dd HH:mm:ss"; + case "YYYY-MM-DD HH:mm": + return "yyyy-MM-dd HH:mm"; + default: + return "yyyy-MM-dd"; + } + } +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/model/CustomSimpleFormModel.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/model/CustomSimpleFormModel.java new file mode 100644 index 000000000..6e5052e56 --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/engine/model/CustomSimpleFormModel.java @@ -0,0 +1,47 @@ +package cn.axzo.workflow.form.engine.model; + +import org.flowable.form.model.FormContainer; +import org.flowable.form.model.FormField; +import org.flowable.form.model.SimpleFormModel; + +import java.util.List; + +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_CUSTOM_COMPONENT; + +/** + * @author wangli + * @since 2025-01-24 15:49 + */ +public class CustomSimpleFormModel extends SimpleFormModel { + public CustomSimpleFormModel(SimpleFormModel simpleFormModel) { + this.setName(simpleFormModel.getName()); + this.setKey(simpleFormModel.getKey()); + this.setVersion(simpleFormModel.getVersion()); + this.setDescription(simpleFormModel.getDescription()); + this.setFields(simpleFormModel.getFields()); + this.setOutcomes(simpleFormModel.getOutcomes()); + this.setOutcomeVariableName(simpleFormModel.getOutcomeVariableName()); + } + + private static final long serialVersionUID = 1L; + + @Override + protected void collectSubFields(List fields, List listOfAllFields) { + if (fields != null && fields.size() > 0) { + for (FormField field : fields) { + listOfAllFields.add(field); + if (field instanceof FormContainer && !FORM_FIELD_TYPE_CUSTOM_COMPONENT.equals(field.getType())) { + FormContainer container = (FormContainer) field; + List> subFields = container.getFields(); + if (subFields != null) { + for (List subFieldDefinitions : subFields) { + if (subFieldDefinitions != null) { + collectSubFields(subFieldDefinitions, listOfAllFields); + } + } + } + } + } + } + } +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormDefinitionService.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormDefinitionService.java new file mode 100644 index 000000000..44898ec56 --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormDefinitionService.java @@ -0,0 +1,25 @@ +package cn.axzo.workflow.form.service; + + +import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionSearchDTO; +import cn.axzo.workflow.common.model.request.form.definition.FormContentSearchDTO; +import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import org.flowable.form.api.FormInfo; + +/** + * 表单定义相关服务接口 + * + * @author wangli + * @since 2023/7/19 16:46 + */ +public interface FormDefinitionService { + + FormDefinitionVO get(FormDefinitionSearchDTO dto); + + /** + * 表单内容,适用于发起时打开原始表单页面 + * @param dto + * @return + */ + FormInfo getFormInfo(FormContentSearchDTO dto); +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormInstanceService.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormInstanceService.java new file mode 100644 index 000000000..de4745766 --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormInstanceService.java @@ -0,0 +1,29 @@ +package cn.axzo.workflow.form.service; + + +import cn.axzo.workflow.common.model.request.form.instance.FormContentUpdateDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO; +import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; + +/** + * 表单实例相关接口 + * + * @author wangli + * @since 2023/7/21 15:19 + */ +public interface FormInstanceService { + + /** + * 更新表单填写的数据 + * + * @param dto + */ + void updateFormContent(FormContentUpdateDTO dto); + + /** + * 查询指定审批的表单已经填入的最新的内容 + * @param dto + * @return + */ + FormInstanceVO getFormInstanceInfo(FormInstanceSearchDTO dto); +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormModelService.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormModelService.java similarity index 93% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormModelService.java rename to workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormModelService.java index e12bf37bf..64d816b23 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/FormModelService.java +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/FormModelService.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.service; +package cn.axzo.workflow.form.service; import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO; @@ -27,7 +27,7 @@ public interface FormModelService { FormModelBaseVO getByKey(String formModelKey, @Nullable String tenantId); - String deployFormModelById(String formModelId, @Nullable String tenantId); + String deployFormModelById(String formModelId, @Nullable String tenantId, String parentDeploymentId); String deployFormModelByKey(String formModelKey, @Nullable String tenantId); diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/ConversionUtils.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/ConversionUtils.java new file mode 100644 index 000000000..e6cf5c63c --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/ConversionUtils.java @@ -0,0 +1,33 @@ +package cn.axzo.workflow.form.service.converter; + +/** + * Object to List + * + * @author wangli + * @since 2024-11-11 13:42 + */ + +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.List; + +public class ConversionUtils { + public static Object convertObject(Object obj) { + if (obj instanceof List) { + List list = (List) obj; + if (CollectionUtils.isEmpty(list)) { + return null; + } + return list; + } else if (obj instanceof String) { + if (StringUtils.pathEquals(obj.toString(), "[]")) { + return null; + } + // 如果输入不是列表类型,返回空列表或进行其他处理 + return obj; + } else { + return obj; + } + } +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/EntityConverter.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/EntityConverter.java new file mode 100644 index 000000000..844007034 --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/EntityConverter.java @@ -0,0 +1,21 @@ +package cn.axzo.workflow.form.service.converter; + +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public interface EntityConverter { + + V toVo(E entity); + + default List toVos(List entities) { + if (CollectionUtils.isEmpty(entities)) { + return Collections.emptyList(); + } + List vos = new ArrayList<>(); + entities.forEach(i -> vos.add(toVo(i))); + return vos; + } +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormFieldConverter.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormFieldConverter.java new file mode 100644 index 000000000..e9d46e2fa --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormFieldConverter.java @@ -0,0 +1,59 @@ +package cn.axzo.workflow.form.service.converter; + +import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO; +import org.flowable.form.model.FormContainer; +import org.flowable.form.model.FormField; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +/** + * Converter + * + * @author wangli + * @since 2024-11-11 11:27 + */ +@Mapper( + componentModel = "spring", + nullValueCheckStrategy = ALWAYS, + imports = {Arrays.class, ConversionUtils.class} +) +public interface FormFieldConverter extends EntityConverter { + + + @Mapping(target = "id", source = "entity.id") + @Mapping(target = "name", source = "entity.name") + @Mapping(target = "type", source = "entity.type") + @Mapping(target = "value", expression = "java(ConversionUtils.convertObject(entity.getValue()))") + @Mapping(target = "placeholder", source = "entity.placeholder") + @Mapping(target = "params", source = "entity.params") + FormFieldDTO toVo(FormField entity); + + @Mapping(target = "fieldType", expression = "java(entity.getClass().getSimpleName())") + @Mapping(target = "id", source = "entity.id") + @Mapping(target = "name", source = "entity.name") + @Mapping(target = "type", source = "entity.type") + @Mapping(target = "value", expression = "java(ConversionUtils.convertObject(entity.getValue()))") + @Mapping(target = "placeholder", source = "entity.placeholder") + @Mapping(target = "params", source = "entity.params") + @Mapping(target = "fields", source = "entity.fields") + FormFieldDTO toVo(FormContainer entity); + + @Override + default List toVos(List entities) { + List dtos = new ArrayList<>(); + for (FormField entity : entities) { + if(entity instanceof FormContainer) { + dtos.add(toVo((FormContainer) entity)); + } else{ + dtos.add(toVo((FormField) entity)); + } + } + return dtos; + } +} diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormInstanceConverter.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormInstanceConverter.java new file mode 100644 index 000000000..4d99a007e --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormInstanceConverter.java @@ -0,0 +1,95 @@ +package cn.axzo.workflow.form.service.converter; + +import cn.axzo.workflow.common.model.request.form.definition.FormFieldDTO; +import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; +import cn.axzo.workflow.common.model.response.form.model.FormModelVO; +import org.flowable.form.api.FormInstanceInfo; +import org.flowable.form.api.FormModel; +import org.flowable.form.model.FormContainer; +import org.flowable.form.model.FormField; +import org.flowable.form.model.SimpleFormModel; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +/** + * 表单实例模型的 MapStruts 转换器 + * + * @author wangli + * @since 2024-11-14 10:31 + */ +@Mapper( + componentModel = "spring", + nullValueCheckStrategy = ALWAYS, + imports = Arrays.class +) +public interface FormInstanceConverter extends EntityConverter { + + @Mapping(target = "id", source = "entity.id") + @Mapping(target = "name", source = "entity.name") + @Mapping(target = "key", source = "entity.key") + @Mapping(target = "version", source = "entity.version") + @Mapping(target = "formModel", expression = "java(formModelConverter(entity.getFormModel()))") + @Mapping(target = "formInstanceId", source = "entity.formInstanceId") + @Mapping(target = "submittedBy", source = "entity.submittedBy") + @Mapping(target = "submittedDate", source = "entity.submittedDate") + @Mapping(target = "taskId", source = "entity.taskId") + @Mapping(target = "processInstanceId", source = "entity.processInstanceId") + @Mapping(target = "processDefinitionId", source = "entity.processDefinitionId") + @Mapping(target = "tenantId", source = "entity.tenantId") + FormInstanceVO toVo(FormInstanceInfo entity); + + default FormModelVO formModelConverter(FormModel formModel) { + FormModelVO formModelVO = new FormModelVO(); + if (formModel instanceof SimpleFormModel) { + SimpleFormModel simpleFormModel = (SimpleFormModel) formModel; + formModelVO.setName(simpleFormModel.getName()); + formModelVO.setKey(simpleFormModel.getKey()); + formModelVO.setVersion(simpleFormModel.getVersion()); + formModelVO.setFields(formContainersToFormFieldDTOs(simpleFormModel.getFields())); + } + return formModelVO; + } + + default FormFieldDTO formFieldConverter(FormField formField) { + FormFieldDTO formFieldDTO = new FormFieldDTO(); + formFieldDTO.setType(formField.getType()); + formFieldDTO.setId(formField.getId()); + formFieldDTO.setName(formField.getName()); + formFieldDTO.setValue(ConversionUtils.convertObject(formField.getValue())); + formFieldDTO.setPlaceholder(formField.getPlaceholder()); + formFieldDTO.setParams(formField.getParams()); + if (formField instanceof FormContainer) { + FormContainer formContainer = (FormContainer) formField; + formFieldDTO.setFieldType(formContainer.getClass().getSimpleName()); + formFieldDTO.setFields(formContainerListsToFormFieldDTOLists(formContainer.getFields())); + } + return formFieldDTO; + } + + + // 辅助方法,用于处理List到List的转换,调用上面自定义的转换方法 + default List formContainersToFormFieldDTOs(List formContainers) { + if (formContainers == null) { + return null; + } + return formContainers.stream() + .map(this::formFieldConverter) + .collect(Collectors.toList()); + } + + // 辅助方法,用于处理List>到List>的转换,调用上面的辅助方法 + default List> formContainerListsToFormFieldDTOLists(List> formContainerLists) { + if (formContainerLists == null) { + return null; + } + return formContainerLists.stream() + .map(this::formContainersToFormFieldDTOs) + .collect(Collectors.toList()); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/FormModelConverter.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormModelConverter.java similarity index 95% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/FormModelConverter.java rename to workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormModelConverter.java index 2a7b9368c..76e4daa12 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/converter/FormModelConverter.java +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/converter/FormModelConverter.java @@ -1,4 +1,4 @@ -package cn.axzo.workflow.core.service.converter; +package cn.axzo.workflow.form.service.converter; import cn.axzo.workflow.common.model.response.form.model.FormModelBaseVO; import org.flowable.engine.repository.Model; diff --git a/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormDefinitionServiceImpl.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormDefinitionServiceImpl.java new file mode 100644 index 000000000..f8b2ba4c2 --- /dev/null +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormDefinitionServiceImpl.java @@ -0,0 +1,82 @@ +package cn.axzo.workflow.form.service.impl; + +import cn.axzo.workflow.common.model.request.form.definition.FormContentSearchDTO; +import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionSearchDTO; +import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import cn.axzo.workflow.form.service.FormDefinitionService; +import cn.axzo.workflow.form.service.converter.FormFieldConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.engine.HistoryService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.form.api.FormInfo; +import org.flowable.form.api.FormRepositoryService; +import org.flowable.form.api.FormService; +import org.flowable.form.model.FormField; +import org.flowable.form.model.SimpleFormModel; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; + + +/** + * 表单定义 Service 实现 + * + * @author wangli + * @since 2023/7/19 16:47 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class FormDefinitionServiceImpl implements FormDefinitionService { + @Resource + private FormRepositoryService formRepositoryService; + @Resource + private FormFieldConverter formFieldConverter; + @Resource + private HistoryService historyService; + @Resource + private FormService formService; + + @Override + public FormDefinitionVO get(FormDefinitionSearchDTO dto) { + FormInfo formModel; + try { + formModel = formRepositoryService.getFormModelByKeyAndParentDeploymentId(dto.getKey(),dto.getParentDeploymentId(), dto.getTenantId(),false ); + }catch (FlowableObjectNotFoundException e){ + return null; + } + + FormDefinitionVO formDefinitionVO = new FormDefinitionVO(); + formDefinitionVO.setFormDefinitionId(formModel.getId()); + formDefinitionVO.setName(formModel.getName()); + formDefinitionVO.setKey(formModel.getKey()); + formDefinitionVO.setVersion(formModel.getVersion()); + formDefinitionVO.setTenantId(dto.getTenantId()); + List fields = ((SimpleFormModel) formModel.getFormModel()).getFields(); + formDefinitionVO.setFields(formFieldConverter.toVos(fields)); + return formDefinitionVO; + } + + @Override + public FormInfo getFormInfo(FormContentSearchDTO dto) { + if (StringUtils.hasText(dto.getProcessInstanceId()) && !StringUtils.hasText(dto.getKey())) { + HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery() + .includeProcessVariables() + .processInstanceId(dto.getProcessInstanceId()) + .processInstanceTenantId(dto.getTenantId()) + .singleResult(); + if (Objects.nonNull(instance)) { + dto.setKey(instance.getProcessDefinitionKey()); + dto.setVariables(instance.getProcessVariables()); + } else { + return null; + } + } + return formService.getFormModelWithVariablesByKey(dto.getKey(), dto.getTaskId(), dto.getVariables(), dto.getTenantId(), true); + } +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormInstanceServiceImpl.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormInstanceServiceImpl.java similarity index 51% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormInstanceServiceImpl.java rename to workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormInstanceServiceImpl.java index fe2c73743..9f3a70bb3 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormInstanceServiceImpl.java +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormInstanceServiceImpl.java @@ -1,12 +1,18 @@ -package cn.axzo.workflow.core.service.impl; +package cn.axzo.workflow.form.service.impl; +import cn.axzo.workflow.common.code.BpmnTaskRespCode; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.form.instance.FormContentUpdateDTO; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; -import cn.axzo.workflow.core.service.FormInstanceService; +import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO; +import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; +import cn.axzo.workflow.form.service.FormInstanceService; +import cn.axzo.workflow.form.service.converter.FormInstanceConverter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.HistoryService; import org.flowable.engine.TaskService; -import org.flowable.form.api.FormInfo; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.form.api.FormInstanceInfo; import org.flowable.form.api.FormRepositoryService; import org.flowable.form.api.FormService; import org.flowable.task.api.Task; @@ -16,8 +22,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Objects; -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.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; /** @@ -31,12 +36,16 @@ import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_COMPLETE_F @RequiredArgsConstructor public class FormInstanceServiceImpl implements FormInstanceService { - @Resource - private FormRepositoryService formRepositoryService; @Resource private FormService formService; @Resource private TaskService taskService; + @Resource + private FormInstanceConverter formInstanceConverter; + @Resource + private HistoryService historyService; + @Resource + private FormRepositoryService formRepositoryService; @Override public void updateFormContent(FormContentUpdateDTO dto) { @@ -46,13 +55,12 @@ public class FormInstanceServiceImpl implements FormInstanceService { } Task task = query.singleResult(); if (Objects.isNull(task)) { - throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS); + throw new WorkflowEngineException(BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS); } if (!Objects.equals(dto.getUserId(), task.getAssignee())) { - throw new WorkflowEngineException(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF); + throw new WorkflowEngineException(BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF); } - FormInfo formInfo = formRepositoryService.getFormModelById(dto.getFormDefinitionId()); if (Objects.nonNull(task.getProcessInstanceId())) { formService.saveFormInstanceByFormDefinitionId(dto.getFormVariables(), dto.getFormDefinitionId(), dto.getTaskId(), @@ -63,4 +71,17 @@ public class FormInstanceServiceImpl implements FormInstanceService { task.getScopeId(), task.getScopeType(), task.getScopeDefinitionId(), task.getTenantId(), null); } } + + @Override + public FormInstanceVO getFormInstanceInfo(FormInstanceSearchDTO dto) { + HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult(); + if (Objects.isNull(instance)) { + throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, dto.getProcessInstanceId()); + } + + String processDefinitionKey = instance.getProcessDefinitionKey(); + + FormInstanceInfo formInstanceInfo = formService.getFormInstanceModelByKey(processDefinitionKey, dto.getTaskId(), dto.getProcessInstanceId(), dto.getVariables(), instance.getTenantId(), true); + return formInstanceConverter.toVo(formInstanceInfo); + } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormModelServiceImpl.java b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormModelServiceImpl.java similarity index 70% rename from workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormModelServiceImpl.java rename to workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormModelServiceImpl.java index dfa5cda22..8bbdeb2cf 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/FormModelServiceImpl.java +++ b/workflow-engine-form/src/main/java/cn/axzo/workflow/form/service/impl/FormModelServiceImpl.java @@ -1,14 +1,17 @@ -package cn.axzo.workflow.core.service.impl; +package cn.axzo.workflow.form.service.impl; import cn.axzo.framework.jackson.utility.JSON; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO; +import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionDTO; import cn.axzo.workflow.common.model.request.form.model.FormModelCreateDTO; import cn.axzo.workflow.common.model.request.form.model.FormModelUpdateDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.form.model.FormModelBaseVO; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; -import cn.axzo.workflow.core.service.FormModelService; -import cn.axzo.workflow.core.service.converter.FormModelConverter; +import cn.axzo.workflow.form.service.FormModelService; +import cn.axzo.workflow.form.service.converter.FormModelConverter; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; import org.flowable.engine.ManagementService; import org.flowable.engine.RepositoryService; import org.flowable.engine.repository.Model; @@ -23,17 +26,18 @@ import org.springframework.util.StringUtils; import javax.annotation.Nullable; import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS; import static cn.axzo.workflow.common.constant.BpmnConstants.FORM_FILE_SUFFIX; import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE; import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE_FORM; -import static cn.axzo.workflow.core.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS; -import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql; -import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors; +import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.countSql; +import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.sqlConnectors; /** * 表单模型 Service @@ -41,6 +45,7 @@ import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnecto * @author wangli * @since 2023/7/25 10:13 */ +@Slf4j @Service public class FormModelServiceImpl implements FormModelService { @@ -57,22 +62,29 @@ public class FormModelServiceImpl implements FormModelService { public String createFormModel(FormModelCreateDTO dto) { Model persistModel = repositoryService.createModelQuery() .modelKey(dto.getKey()) + .modelCategory(FORM_FILE_SUFFIX) .modelTenantId(dto.getTenantId()) .singleResult(); - if (Objects.nonNull(persistModel)) { - throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); - } + + persistModel = repositoryService.newModel(); + persistModel.setKey(dto.getKey()); + persistModel.setCategory(FORM_FILE_SUFFIX); + persistModel.setName(dto.getName()); + persistModel.setTenantId(dto.getTenantId()); Map metaInfoMap = new HashMap<>(); metaInfoMap.put(MODEL_TYPE, MODEL_TYPE_FORM); - Model model = repositoryService.newModel(); - model.setKey(dto.getKey()); - model.setCategory(dto.getCategory()); - model.setName(dto.getName()); - model.setTenantId(dto.getTenantId()); - model.setMetaInfo(JSON.toJSONString(metaInfoMap)); - repositoryService.saveModel(model); - return model.getId(); + persistModel.setMetaInfo(JSONUtil.toJsonStr(metaInfoMap)); + repositoryService.saveModel(persistModel); + + FormDefinitionDTO formDefinition = new FormDefinitionDTO(); + formDefinition.setKey(dto.getKey()); + formDefinition.setName(dto.getName()); + formDefinition.setVersion(persistModel.getVersion()); + formDefinition.setFields(dto.getFormFields()); + + repositoryService.addModelEditorSource(persistModel.getId(), JSON.toJSONString(formDefinition).getBytes(StandardCharsets.UTF_8)); + return persistModel.getId(); } @Override @@ -81,14 +93,30 @@ public class FormModelServiceImpl implements FormModelService { Model model = repositoryService.createModelQuery() .modelId(dto.getFormModelId()) .modelKey(dto.getKey()) + .modelCategory(FORM_FILE_SUFFIX) .modelTenantId(dto.getTenantId()) .singleResult(); if (Objects.isNull(model)) { - throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); + model = repositoryService.newModel(); } model.setName(dto.getName()); - model.setCategory(dto.getCategory()); + model.setKey(dto.getKey()); + model.setCategory(FORM_FILE_SUFFIX); + + Map metaInfoMap = new HashMap<>(); + metaInfoMap.put(MODEL_TYPE, MODEL_TYPE_FORM); + + model.setMetaInfo(JSONUtil.toJsonStr(metaInfoMap)); + model.setTenantId(dto.getTenantId()); repositoryService.saveModel(model); + + FormDefinitionDTO formDefinition = new FormDefinitionDTO(); + formDefinition.setKey(model.getKey()); + formDefinition.setName(dto.getName()); + formDefinition.setVersion(model.getVersion()); + formDefinition.setFields(dto.getFormFields()); + + repositoryService.addModelEditorSource(model.getId(), JSON.toJSONString(formDefinition).getBytes(StandardCharsets.UTF_8)); } @Override @@ -97,10 +125,17 @@ public class FormModelServiceImpl implements FormModelService { StringBuilder baseQuerySql = new StringBuilder("SELECT * FROM ") .append(tableName); NativeModelQuery query = repositoryService.createNativeModelQuery(); - if (StringUtils.hasLength(dto.getKey())) { + if (!CollectionUtils.isEmpty(dto.getKeys())) { baseQuerySql.append(sqlConnectors(baseQuerySql)) - .append(" KEY_ = #{key}"); - query.parameter("key", dto.getKey()); + .append(" a.KEY_ in ("); + for (int i = 0; i < dto.getKeys().size(); i++) { + baseQuerySql.append("#{KEY_").append(i).append("}"); + if (i < dto.getKeys().size() - 1) { + baseQuerySql.append(","); + } + query.parameter("KEY_" + i, dto.getKeys().get(i)); + } + baseQuerySql.append(")"); } if (StringUtils.hasLength(dto.getName())) { baseQuerySql.append(sqlConnectors(baseQuerySql)) @@ -174,7 +209,7 @@ public class FormModelServiceImpl implements FormModelService { @Override @Transactional(rollbackFor = Exception.class) - public String deployFormModelById(String formModelId, String tenantId) { + public String deployFormModelById(String formModelId, String tenantId, String parentDeploymentId) { Model model = repositoryService.getModel(formModelId); if (Objects.isNull(model) || !model.getMetaInfo().contains(MODEL_TYPE_FORM)) { throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); @@ -182,14 +217,28 @@ public class FormModelServiceImpl implements FormModelService { if (Objects.nonNull(tenantId) && !Objects.equals(model.getTenantId(), tenantId)) { throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); } + byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId()); + String modelStr; + if (StringUtils.hasText(modelStr = new String(modelEditorSource))) { + try { + FormDefinitionDTO formDefinitionDTO = JSON.parseObject(modelStr, FormDefinitionDTO.class); + if (Objects.isNull(formDefinitionDTO) || CollectionUtils.isEmpty(formDefinitionDTO.getFields())) { + return null; + } + } catch (Exception e) { + log.warn("form model parse error: {}", e.getMessage(), e); + return null; + } + } FormDeployment deploy = formRepositoryService.createDeployment() - .addFormDefinition(model.getKey() + FORM_FILE_SUFFIX, - new String(repositoryService.getModelEditorSource(model.getId()))) + .addFormDefinition(model.getKey() + FORM_FILE_SUFFIX, modelStr) .name(model.getName()) - .category(model.getCategory()) + .category(model.getKey()) .tenantId(model.getTenantId()) + .parentDeploymentId(parentDeploymentId) .deploy(); + model.setDeploymentId(deploy.getId()); return deploy.getId(); } @@ -205,7 +254,7 @@ public class FormModelServiceImpl implements FormModelService { throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); } - return deployFormModelById(model.getId(), tenantId); + return deployFormModelById(model.getId(), tenantId, null); } @Override diff --git a/workflow-engine-server/pom.xml b/workflow-engine-server/pom.xml index dab288f14..10cae3ff8 100644 --- a/workflow-engine-server/pom.xml +++ b/workflow-engine-server/pom.xml @@ -10,6 +10,7 @@ 4.0.0 workflow-engine-server + Workflow Engine Server 8 @@ -23,6 +24,20 @@ cn.axzo.framework axzo-web-spring-boot-starter + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + + + org.elasticsearch.client + elasticsearch-rest-client + + + org.elasticsearch + elasticsearch + + org.springframework.boot @@ -52,6 +67,18 @@ ${project.groupId} workflow-engine-core + + ${project.groupId} + workflow-engine-form + + + ${project.groupId} + workflow-engine-elasticsearch + + + ${project.groupId} + workflow-engine-axzo-ext + org.projectlombok lombok @@ -70,7 +97,6 @@ mysql mysql-connector-java - cn.axzo.framework axzo-processor-spring-boot-starter @@ -95,18 +121,15 @@ cn.axzo.maokai maokai-api - - cn.axzo.tyr - tyr-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 @@ -115,6 +138,22 @@ org.apache.maven maven-artifact + + com.xuxueli + xxl-job-core + + + cn.axzo + riven-api + + + cn.axzo.org + org-api + + + cn.axzo.nanopart + doc-api + 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/WorkflowEnginApplication.java deleted file mode 100644 index 7a626b7bf..000000000 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEnginApplication.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.axzo.workflow.server; - -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.context.annotation.ComponentScan; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.transaction.annotation.EnableTransactionManagement; - - -@MapperScan({"cn.axzo.workflow.core.**.mapper"}) -@ComponentScan({"cn.axzo.workflow", "cn.axzo.maokai"}) -@SpringBootApplication(exclude = RabbitAutoConfiguration.class) -@EnableTransactionManagement -@EnableAsync -public class WorkflowEnginApplication { - - public static void main(String[] args) { - SpringApplication.run(WorkflowEnginApplication.class, args); - } -} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java new file mode 100644 index 000000000..31e42d3b1 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEngineApplication.java @@ -0,0 +1,26 @@ +package cn.axzo.workflow.server; + +import org.dromara.easyes.starter.register.EsMapperScan; +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", "cn.axzo.workflow.admin.**.mapper"}) +@ComponentScan({"cn.axzo.workflow"}) +@EnableFeignClients({"cn.axzo.oss", "cn.axzo.riven.client.feign", "cn.axzo.msg.center", "cn.axzo.basics.profiles"}) +@SpringBootApplication(exclude = RabbitAutoConfiguration.class) +@EnableTransactionManagement +@EnableCaching +@EsMapperScan("cn.axzo.workflow.es.**.mapper") +public class WorkflowEngineApplication { + + public static void main(String[] args) { + SpringApplication.run(WorkflowEngineApplication.class, args); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/FlowableExceptionResultHandlerAdvice.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/FlowableExceptionResultHandlerAdvice.java index 00b8820cb..e34900841 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/FlowableExceptionResultHandlerAdvice.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/FlowableExceptionResultHandlerAdvice.java @@ -2,17 +2,16 @@ package cn.axzo.workflow.server.advice; import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties; import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler; +import cn.axzo.framework.domain.web.code.BaseCode; import cn.axzo.framework.domain.web.code.IRespCode; import cn.axzo.framework.domain.web.code.RespCode; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import lombok.extern.slf4j.Slf4j; import org.flowable.common.engine.api.FlowableException; import org.springframework.stereotype.Component; import java.util.Objects; -import static cn.axzo.framework.domain.web.code.BaseCode.UNAVAILABLE_FOR_LEGAL_REASONS; - /** * 针对 Flowable 引擎内部异常直接抛出给调用方的异常处理器 * @@ -30,8 +29,8 @@ public class FlowableExceptionResultHandlerAdvice extends AbstractExceptionApiRe protected IRespCode decode(FlowableException ex, IRespCode fallbackCode) { log.warn("发现 FlowableException Error: ", ex); if (Objects.nonNull(ex.getCause()) && ex.getCause() instanceof WorkflowEngineException) { - return new RespCode(UNAVAILABLE_FOR_LEGAL_REASONS.getCode(), ex.getCause().getMessage()); + return new RespCode(BaseCode.SUCCESS.getCode(), ex.getCause().getMessage()); } - return new RespCode(UNAVAILABLE_FOR_LEGAL_REASONS.getCode(), ex.getMessage()); + return new RespCode(BaseCode.SUCCESS.getCode(), ex.getMessage()); } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/FlowableOptimisticLockingExceptionHandlerAdvice.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/FlowableOptimisticLockingExceptionHandlerAdvice.java new file mode 100644 index 000000000..6f97e8c35 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/FlowableOptimisticLockingExceptionHandlerAdvice.java @@ -0,0 +1,29 @@ +package cn.axzo.workflow.server.advice; + +import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties; +import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler; +import cn.axzo.framework.domain.web.code.BaseCode; +import cn.axzo.framework.domain.web.code.IRespCode; +import cn.axzo.framework.domain.web.code.RespCode; +import org.flowable.common.engine.api.FlowableOptimisticLockingException; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.springframework.stereotype.Component; + +/** + * 降级 FlowableOptimisticLockingException 异常,该异常在 Flowable 框架中是可以忽略的 + * + * @author wangli + * @see CommandContext#logException() + * @since 2024/6/19 17:32 + */ +@Component +public class FlowableOptimisticLockingExceptionHandlerAdvice extends AbstractExceptionApiResultHandler { + public FlowableOptimisticLockingExceptionHandlerAdvice(RespErrorCodeMappingProperties properties) { + super(properties); + } + + @Override + protected IRespCode decode(FlowableOptimisticLockingException ex, IRespCode fallbackCode) { + return new RespCode(BaseCode.SUCCESS.getCode(), ex.getMessage()); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/SQLIntegrityConstraintViolationExceptionHandlerAdvice.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/SQLIntegrityConstraintViolationExceptionHandlerAdvice.java new file mode 100644 index 000000000..0a618d5fd --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/SQLIntegrityConstraintViolationExceptionHandlerAdvice.java @@ -0,0 +1,36 @@ +package cn.axzo.workflow.server.advice; + +import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingProperties; +import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler; +import cn.axzo.framework.domain.web.code.BaseCode; +import cn.axzo.framework.domain.web.code.IRespCode; +import cn.axzo.framework.domain.web.code.RespCode; +import cn.axzo.workflow.client.feign.bpmn.ProcessActivityApi; +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.task.BpmnActivitySetAssigneeDTO; +import org.springframework.stereotype.Component; + +import java.sql.SQLIntegrityConstraintViolationException; + + +/** + * 处理多个接口同时操作一个流程实例时,可能会抛出数据库的异常,这种异常是可以被忽略的 + *

+ * 例如: {@link ProcessActivityApi#setAssignee(BpmnActivitySetAssigneeDTO)} 于 {@link ProcessInstanceApi#abortProcessInstance(BpmnProcessInstanceAbortDTO)} + * 两个接口并发访问时,由于 abort 先执行完,并提交事务后,setAssignee 方法内虽然有判断实例状态,但最后事务提交时突然发现实例状态被中止了就会抛出异常。 + * + * @author wangli + * @since 2024/6/20 09:42 + */ +@Component +public class SQLIntegrityConstraintViolationExceptionHandlerAdvice extends AbstractExceptionApiResultHandler { + public SQLIntegrityConstraintViolationExceptionHandlerAdvice(RespErrorCodeMappingProperties properties) { + super(properties); + } + + @Override + protected IRespCode decode(SQLIntegrityConstraintViolationException ex, IRespCode fallbackCode) { + return new RespCode(BaseCode.SUCCESS.getCode(), ex.getMessage()); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/WorkflowExceptionResultHandlerAdvice.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/WorkflowExceptionResultHandlerAdvice.java index d09ce6c9c..cf878b537 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/WorkflowExceptionResultHandlerAdvice.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/advice/WorkflowExceptionResultHandlerAdvice.java @@ -4,9 +4,15 @@ import cn.axzo.framework.autoconfigure.web.exception.RespErrorCodeMappingPropert import cn.axzo.framework.autoconfigure.web.exception.handler.AbstractExceptionApiResultHandler; import cn.axzo.framework.domain.web.code.IRespCode; import cn.axzo.framework.domain.web.code.RespCode; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.server.common.config.property.WorkflowProperties; import org.springframework.stereotype.Component; +import javax.annotation.Resource; +import java.util.List; + +import static org.springframework.http.HttpStatus.OK; + /** * 基于 Flowable 框架在业务功能开发过程中,抛出的异常处理器 * @@ -15,12 +21,20 @@ import org.springframework.stereotype.Component; */ @Component public class WorkflowExceptionResultHandlerAdvice extends AbstractExceptionApiResultHandler { + + @Resource + private WorkflowProperties workflowProperties; + public WorkflowExceptionResultHandlerAdvice(RespErrorCodeMappingProperties properties) { super(properties); } @Override protected IRespCode decode(WorkflowEngineException ex, IRespCode fallbackCode) { + List filterExceptionCodes = workflowProperties.getFilterExceptionCode(); + if (filterExceptionCodes.contains(ex.getCode())) { + return new RespCode(String.valueOf(OK.value()), ex.getMessage()); + } return new RespCode(ex.getCode(), ex.getMessage()); } } 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..dc348e2be --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/alter/DingTalkAlter.java @@ -0,0 +1,108 @@ +package cn.axzo.workflow.server.alter; + +import cn.axzo.framework.jackson.utility.JSON; +import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum; +import cn.axzo.riven.client.feign.DingDingMsgApi; +import cn.axzo.riven.client.model.SampleMarkdown; +import cn.axzo.riven.client.req.DingDingSendRebootGroupMsgReq; +import cn.axzo.workflow.common.model.NextNodePreCheckAlterDTO; +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 cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Objects; + +import static cn.axzo.workflow.core.util.DingTalkUtils.getWebUrl; +import static cn.axzo.workflow.core.util.DingTalkUtils.mobiles; + +/** + * 钉钉告警实现 + * + * @author wangli + * @since 2024-09-13 11:40 + */ +@Slf4j +@Component +public class DingTalkAlter implements Alter { + + @Value("${spring.profiles.active}") + private String profile; + @Resource + private SupportRefreshProperties refreshProperties; + @Resource + private DingDingMsgApi dingDingMsgApi; + + @Override + public void invoke(Object obj) { + log.info("send biz node alter : {}", JSON.toJSONString(obj)); + if (obj instanceof AlterDTO) { + AlterDTO alterDTO = (AlterDTO) obj; + if (Objects.equals(profile, "master")) { + DingTalkUtils.sendDingTalkForBizNodeAlter(profile, alterDTO, refreshProperties.getAlterMobiles()); + } else { + rivenDingtalk(alterDTO); + } + } + if (obj instanceof NextNodePreCheckAlterDTO) { + NextNodePreCheckAlterDTO alterDTO = (NextNodePreCheckAlterDTO) obj; + if(Objects.equals(profile, "master")){ + DingTalkUtils.sendDingTalkForNodePreCheck(profile, alterDTO, refreshProperties.getAlterMobiles()); + } else { + rivenDingtalkForNodePreCheck(alterDTO); + } + } + } + + private void rivenDingtalkForNodePreCheck(NextNodePreCheckAlterDTO alterDTO) { + DingDingSendRebootGroupMsgReq req = new DingDingSendRebootGroupMsgReq(); + req.setDingDingScene("WORKFLOW_ENGINE_BIZNODE_ALTER"); + String processInstanceId = alterDTO.getProcessInstanceId(); + String title = "Notice 审批模板节点预检查告警, Env: " + profile; + String text = "#### [" + profile + "]审批模板节点预检查告警\n" + +// "> 相关信息: " + JSONUtil.toJsonStr(alterDTO) + "\n\n" + + "> 实例 ID:" + alterDTO.getProcessInstanceId() + "\n\n" + + "> 检测节点 ID:" + alterDTO.getActivityId() + "\n\n" + + "> ##### 错误信息:" + alterDTO.getErrorMsg() + "\n\n" + + mobiles(refreshProperties.getAlterMobiles()); + SampleMarkdown markdown = new SampleMarkdown(title, text); + + JSONObject markdownJson = JSONObject.parseObject(markdown.toJson()); + JSONObject atMobiles = new JSONObject(); + atMobiles.put("atMobiles", refreshProperties.getAlterMobiles()); + markdownJson.put("at", atMobiles); + markdownJson.put("isAtAll", false); + req.setDingDingJson(markdownJson.toJSONString()); + req.setMsgType(DingTalkMsgTypeEnum.sampleMarkdown); + dingDingMsgApi.sendRebootGroupMsg(req); + } + + private void rivenDingtalk(AlterDTO alterDTO) { + DingDingSendRebootGroupMsgReq req = new DingDingSendRebootGroupMsgReq(); + req.setDingDingScene("WORKFLOW_ENGINE_BIZNODE_ALTER"); + String processInstanceId = alterDTO.getProcessInstanceId(); + String title = "Notice 业务节点长时间停止告警, Env: " + profile; + String text = "#### [" + profile + "]业务节点长时间停止\n" + + "> 节点相关信息: " + JSONUtil.toJsonStr(alterDTO) + "\n\n" + + "> ##### [点击查看审批日志](" + getWebUrl(profile) + "/#/workflow/examples?processInstanceId=" + processInstanceId + ") \n\n" + + "> ##### 提示:如果以上地址提示未登录,请在对应环境 OMS 系统登录后并点击审批管理的功能后,再使用。或者复制实例 ID 到 OMS 中手动查询 \n\n " + + mobiles(refreshProperties.getAlterMobiles()); + SampleMarkdown markdown = new SampleMarkdown(title, text); + + JSONObject markdownJson = JSONObject.parseObject(markdown.toJson()); + JSONObject atMobiles = new JSONObject(); + atMobiles.put("atMobiles", refreshProperties.getAlterMobiles()); + markdownJson.put("at", atMobiles); + markdownJson.put("isAtAll", false); + req.setDingDingJson(markdownJson.toJSONString()); + req.setMsgType(DingTalkMsgTypeEnum.sampleMarkdown); + dingDingMsgApi.sendRebootGroupMsg(req); + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/EnvConfig.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/EnvConfig.java index 5c662bf34..82b820ad7 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/EnvConfig.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/EnvConfig.java @@ -1,11 +1,20 @@ package cn.axzo.workflow.server.common.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * 用于 ErrorReporter 注解的属性配置 * * @author wangli * @since 2024/4/2 10:39 */ +@Target({ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented public @interface EnvConfig { String[] profiles(); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/ErrorReporter.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/ErrorReporter.java index c6835a9f8..6db75470f 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/ErrorReporter.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/annotation/ErrorReporter.java @@ -28,5 +28,9 @@ public @interface ErrorReporter { @AliasFor("value") String title() default ""; - EnvConfig[] envConfig() default {@EnvConfig(profiles = {"dev", "test", "pre"}, type = ReporterType.BOTH), @EnvConfig(profiles = {"live", "master"}, type = ReporterType.ONLY_LOG)}; + EnvConfig[] envConfig() default { + @EnvConfig(profiles = {"local"}, type = ReporterType.ONLY_LOG), + @EnvConfig(profiles = {"dev", "test", "pre"}, type = ReporterType.BOTH), + @EnvConfig(profiles = {"live", "master"}, type = ReporterType.ONLY_LOG) + }; } 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 be4d22380..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,16 +1,18 @@ package cn.axzo.workflow.server.common.annotation; -import cn.axzo.workflow.server.common.util.DingTalkUtils; -import cn.azxo.framework.common.utils.LogUtil; +import cn.axzo.workflow.core.util.DingTalkUtils; +import lombok.extern.slf4j.Slf4j; import static cn.axzo.workflow.server.common.aspectj.RepeatSubmitAspect.argsArrayToString; + /** * 用于 ErrorReporter 注解的报告类型枚举 * * @author wangli * @since 2024/4/2 10:36 */ +@Slf4j public enum ReporterType { /** @@ -18,9 +20,9 @@ public enum ReporterType { */ ONLY_DING_TALK { @Override - public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, Exception e) { + 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); } } @@ -30,8 +32,13 @@ public enum ReporterType { */ ONLY_LOG { @Override - public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, Exception e) { - LogUtil.error(LogUtil.ErrorType.ERROR_BUSINESS, shortString, e.getMessage(), e); + 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 { +// LogUtil.error(LogUtil.ErrorType.ERROR_BUSINESS, shortString, e.getMessage(), e); + logWarn(e, shortString); + } } }, /** @@ -39,13 +46,34 @@ public enum ReporterType { */ BOTH { @Override - public void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, Exception e) { + 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()); + } else { + logWarn(e, shortString); } - LogUtil.error(LogUtil.ErrorType.ERROR_BUSINESS, shortString, e.getMessage(), e); } }; - public abstract void executeAction(String profile, String title, Boolean sendDingTalk, Object[] args, String shortString, Exception e); + /** + * @param profile 当前应用激活的环境 + * @param title 告警消息标题 + * @param sendDingTalk 是否调用钉钉 + * @param args 请求所有的入参 + * @param shortString 用于日志输出的关键信息 + * @param e 异常对象 + */ + 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 默认会打印,所以此处都只是降级打印 +// if (throwable instanceof WorkflowEngineException) { + log.warn("引擎内部正常业务异常类型,日志降级: " + throwable.getMessage(), throwable); +// } else { +// LogUtil.error(LogUtil.ErrorType.ERROR_BUSINESS, shortString, throwable.getMessage(), throwable); +// } + } } 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 d26cc7873..9619ce85a 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 @@ -1,8 +1,11 @@ package cn.axzo.workflow.server.common.aspectj; import cn.axzo.workflow.core.common.event.ApiLogEvent; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; import cn.axzo.workflow.server.common.annotation.EnvConfig; import cn.axzo.workflow.server.common.annotation.ErrorReporter; +import cn.axzo.workflow.server.common.config.property.WorkflowProperties; +import com.google.common.collect.Lists; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; @@ -14,15 +17,23 @@ import org.aspectj.lang.annotation.Aspect; import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; +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; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import java.util.Arrays; +import java.util.List; import java.util.Objects; +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_API_VERSION; +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME; import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC; /** @@ -34,40 +45,63 @@ import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC; @Component @Aspect @Slf4j -public class ErrorReportAspect { +public class ErrorReportAspect implements Ordered { @Value("${spring.profiles.active}") private String profile; - @Value("${workflow.sendDingTalk:true}") - private Boolean sendDingTalk; - @Value("${workflow.api.timeout:10}") - private Long apiTimeout; + @Resource + private SupportRefreshProperties refreshProperties; @Resource private ApplicationEventPublisher applicationEventPublisher; + @Resource + private WorkflowProperties workflowProperties; + private static List methodNames = Lists.newArrayList("HealthCheckController.checkDeath()"); + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 3; + } /** * 处理完请求后执行 * * @param joinPoint 切点 */ - @Around("@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Service)") + @Around(value = "@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Service)") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { + if (log.isDebugEnabled()) { + log.debug("{}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); + } Signature signature = joinPoint.getSignature(); StopWatch watch = new StopWatch("running controller time"); watch.start(signature.toShortString()); - Object result = joinPoint.proceed(); - watch.stop(); - log.info("StopWatch '" + watch.getLastTaskName() + "': running time = " + watch.getTotalTimeSeconds() + " s"); + Object result = null; + try { + result = joinPoint.proceed(); + } finally { + watch.stop(); + if (log.isDebugEnabled()) { + log.debug("StopWatch '{}': running time = {} 's", watch.getLastTaskName(), watch.getTotalTimeSeconds()); + } - if (!signature.toShortString().contains("ExtAxApiLogServiceImpl")) { - String type = getType(joinPoint); - ApiLogEvent event = new ApiLogEvent(MDC.get(CTX_LOG_ID_MDC), - signature.toShortString(), - Objects.equals(type, "Controller") ? joinPoint.getArgs() : null, - Objects.equals(type, "Controller") ? result : null, - watch.getTotalTimeSeconds(), - type); + if (!methodNames.contains(watch.getLastTaskName())) { + if (log.isDebugEnabled()) { + log.debug("StopWatch '{}': running time = {} 's", watch.getLastTaskName(), watch.getTotalTimeSeconds()); + } + } - applicationEventPublisher.publishEvent(event); + 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); + + applicationEventPublisher.publishEvent(event); + } } return result; } @@ -100,13 +134,51 @@ public class ErrorReportAspect { * @param joinPoint 切点 * @param e 异常 */ - @AfterThrowing(pointcut = "@within(errorReporter) && @within(operation)", throwing = "e", argNames = "joinPoint,errorReporter,operation,e") + @AfterThrowing(pointcut = "@within(errorReporter) && @annotation(operation)", throwing = "e", argNames = "joinPoint,errorReporter,operation,e") public void doAfterThrowing(JoinPoint joinPoint, ErrorReporter errorReporter, Operation operation, Exception e) { + if(log.isDebugEnabled()) { + log.debug("ErrorReportAspect 记录异常信息: {}", e.getMessage()); + } EnvConfig[] envConfigs = errorReporter.envConfig(); for (EnvConfig envConfig : envConfigs) { if (Arrays.asList(envConfig.profiles()).contains(profile)) { - envConfig.type().executeAction(profile, operation.summary(), sendDingTalk, joinPoint.getArgs(), joinPoint.getSignature().toShortString(), e); + boolean filterSendDingTalk = refreshProperties.getSendDingTalk(); + if (workflowProperties.getFilterSendDingTalk().contains(operation.summary())) { + filterSendDingTalk = false; + } + + 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(); + return Objects.requireNonNull(requestAttributes).getRequest(); + } catch (Exception e) { + return null; + } + } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/aspectj/RepeatSubmitAspect.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/aspectj/RepeatSubmitAspect.java index 7afba1b2e..13a3505cc 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/aspectj/RepeatSubmitAspect.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/aspectj/RepeatSubmitAspect.java @@ -1,6 +1,7 @@ package cn.axzo.workflow.server.common.aspectj; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.core.common.utils.TraceUtil; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; import cn.axzo.workflow.server.common.config.RepeatSubmitResolver; import cn.axzo.workflow.server.common.util.RedisUtils; @@ -9,14 +10,14 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.http.HttpStatus; -import com.alibaba.fastjson.JSON; +import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; -import org.springframework.stereotype.Component; +import org.springframework.core.Ordered; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.web.context.request.RequestContextHolder; @@ -30,17 +31,23 @@ import java.util.Collection; import java.util.Map; import java.util.StringJoiner; -import static cn.axzo.workflow.core.common.code.OtherRespCode.REPEAT_SUBMIT_TIME_ERROR_TIPS; +import static cn.axzo.workflow.common.code.OtherRespCode.REPEAT_SUBMIT_ERROR_TIPS; +import static cn.axzo.workflow.common.code.OtherRespCode.REPEAT_SUBMIT_TIME_ERROR_TIPS; /** * 防止重复提交拦截器 * * @author wangli */ -@Component @Aspect @Slf4j -public class RepeatSubmitAspect { +public class RepeatSubmitAspect implements Ordered { + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 2; + } + private final RepeatSubmitResolver repeatSubmitResolver; private static final ThreadLocal KEY_CACHE = new ThreadLocal<>(); private static final String REPEAT_SUBMIT_KEY = "global:repeat_submit:"; @@ -71,14 +78,15 @@ public class RepeatSubmitAspect { String paramsKey = SecureUtil.md5(nowParams); // 唯一标识(指定key + url + 消息头) String cacheRepeatKey = REPEAT_SUBMIT_KEY + url + ":" + paramsKey; + log.info("repeatSubmit key: {}", cacheRepeatKey); String key = RedisUtils.getCacheObject(cacheRepeatKey); if (key == null) { - RedisUtils.setCacheObject(cacheRepeatKey, "", Duration.ofMillis(interval)); + RedisUtils.setCacheObject(cacheRepeatKey, TraceUtil.traceId(), Duration.ofMillis(interval)); KEY_CACHE.set(cacheRepeatKey); } else { log.warn("{}", repeatSubmit.message()); -// throw new WorkflowEngineException(REPEAT_SUBMIT_ERROR_TIPS, repeatSubmit.message()); + throw new WorkflowEngineException(REPEAT_SUBMIT_ERROR_TIPS, repeatSubmit.message()); } } @@ -110,7 +118,7 @@ public class RepeatSubmitAspect { * @param e 异常 */ @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e") - public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) { + public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) throws Exception { if (StringUtils.hasText(KEY_CACHE.get())) { RedisUtils.deleteObject(KEY_CACHE.get()); KEY_CACHE.remove(); @@ -127,7 +135,7 @@ public class RepeatSubmitAspect { } for (Object o : paramsArray) { if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { - params.add(JSON.toJSONString(o)); + params.add(JSONUtil.toJsonStr(o)); } } return params.toString(); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/WebMvcConfig.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/WebMvcConfiguration.java similarity index 92% rename from workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/WebMvcConfig.java rename to workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/WebMvcConfiguration.java index 40d54f391..b13b9074f 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/WebMvcConfig.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/WebMvcConfiguration.java @@ -13,7 +13,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * @since 2024/2/6 11:35 */ @Configuration(proxyBeanMethods = false) -public class WebMvcConfig implements WebMvcConfigurer { +public class WebMvcConfiguration implements WebMvcConfigurer { @Autowired private RequestHeaderContextInterceptor requestHeaderContextInterceptor; diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/XxlJobConfiguration.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/XxlJobConfiguration.java new file mode 100644 index 000000000..42c4500b3 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/XxlJobConfiguration.java @@ -0,0 +1,56 @@ +package cn.axzo.workflow.server.common.config; + +import cn.azxo.framework.common.annotation.OnlyPodsEnvironment; +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * XXL-JOB + * + * @author wangli + * @since 2024/4/28 14:02 + */ +@OnlyPodsEnvironment +@Configuration +public class XxlJobConfiguration { + Logger logger = LoggerFactory.getLogger(XxlJobConfiguration.class); + + @Value("${xxl.job.admin.addresses}") + private String adminAddresses; + + @Value("${xxl.job.executor.appname}") + private String appName; + + @Value("") + private String ip; + + @Value("${xxl.job.executor.port}") + private int port; + + @Value("") + private String accessToken; + + @Value("${xxl.job.executor.logpath:}") + private String logPath; + + @Value("-1") + private int logRetentionDays; + + @Bean + public XxlJobSpringExecutor xxlJobExecutor() { + logger.info(">>>>>>>>>>> xxl-job config init."); + XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); + xxlJobSpringExecutor.setAdminAddresses(adminAddresses); + xxlJobSpringExecutor.setAppname(appName); + xxlJobSpringExecutor.setIp(ip); + xxlJobSpringExecutor.setPort(port); + xxlJobSpringExecutor.setAccessToken(accessToken); + xxlJobSpringExecutor.setLogPath(logPath); + xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); + return xxlJobSpringExecutor; + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/property/WorkflowProperties.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/property/WorkflowProperties.java new file mode 100644 index 000000000..a13183966 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/config/property/WorkflowProperties.java @@ -0,0 +1,41 @@ +package cn.axzo.workflow.server.common.config.property; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + * 本服务的一些配置,可通过 @RefreshScope 刷新 + *

+ * 可全局搜索 {@code workflow.} 查看有哪些, 这里仅简单的支持需要刷新的 + * + * @author wangli + * @since 2024/4/22 14:09 + */ + +@RefreshScope +@Configuration +@ConfigurationProperties("workflow") +@Data +public class WorkflowProperties { + + /** + * 用于控制当前服务的控制器异常的日志输出级别从 ERROR 改成 WARN + */ + private List filterOperations = new ArrayList<>(); + + /** + * 将指定的 998 开头的响应码统一转成 200, 目前暂时没用了 + */ + private List filterExceptionCode = new ArrayList<>(); + + /** + * 用于过滤哪些 Controller 方法的异常,不发送钉钉 + */ + private List filterSendDingTalk = new ArrayList<>(); + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/filter/HttpTraceLogFilter.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/filter/HttpTraceLogFilter.java index cd1d0021f..d8452d23b 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/filter/HttpTraceLogFilter.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/filter/HttpTraceLogFilter.java @@ -1,6 +1,6 @@ package cn.axzo.workflow.server.common.filter; -import cn.azxo.framework.common.constatns.Constants; +import cn.hutool.core.util.IdUtil; import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import org.apache.skywalking.apm.toolkit.trace.Trace; @@ -8,6 +8,7 @@ import org.apache.skywalking.apm.toolkit.trace.TraceContext; import org.slf4j.MDC; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; @@ -15,7 +16,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.UUID; + +import static cn.axzo.workflow.common.constant.LogFieldConstants.X_REQUEST_ID; +import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC; /** * Http接口日志记录 @@ -26,7 +29,6 @@ import java.util.UUID; @Slf4j @Component public class HttpTraceLogFilter extends OncePerRequestFilter implements Ordered { - private static final String X_REQUEST_ID = "x-request-id"; @Override public int getOrder() { @@ -43,25 +45,30 @@ public class HttpTraceLogFilter extends OncePerRequestFilter implements Ordered } else { MDC.put(X_REQUEST_ID, requestId); } - String ctxLogId = request.getHeader(Constants.CTX_LOG_ID_MDC); + String ctxLogId = request.getHeader(CTX_LOG_ID_MDC); if (Strings.isNullOrEmpty(ctxLogId)) { - MDC.put(Constants.CTX_LOG_ID_MDC, getTraceId()); + MDC.put(CTX_LOG_ID_MDC, getTraceId()); } else { - MDC.put(Constants.CTX_LOG_ID_MDC, ctxLogId); + MDC.put(CTX_LOG_ID_MDC, ctxLogId); } try { filterChain.doFilter(request, response); } finally { - response.setHeader(Constants.CTX_LOG_ID_MDC, MDC.get(Constants.CTX_LOG_ID_MDC)); - response.setHeader(X_REQUEST_ID, MDC.get(Constants.CTX_LOG_ID_MDC)); + response.setHeader(CTX_LOG_ID_MDC, MDC.get(CTX_LOG_ID_MDC)); + response.setHeader(X_REQUEST_ID, MDC.get(CTX_LOG_ID_MDC)); MDC.clear(); } } private String getTraceId() { String contextTraceId = TraceContext.traceId(); - return Strings.isNullOrEmpty(contextTraceId) - ? UUID.randomUUID().toString().replaceAll("-", "") : contextTraceId; + return Strings.isNullOrEmpty(contextTraceId) ? IdUtil.fastSimpleUUID() : contextTraceId; + } + + public static String traceId() { + String xRequestId = MDC.get(X_REQUEST_ID); + return StringUtils.hasText(xRequestId) ? xRequestId : MDC.get(CTX_LOG_ID_MDC); + } } 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 5acff1c44..177a83971 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 @@ -1,8 +1,9 @@ package cn.axzo.workflow.server.common.interceptor; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.core.repository.entity.ExtAxProperty; import cn.axzo.workflow.core.service.ExtAxPropertyService; +import cn.axzo.workflow.server.common.util.RedisUtils; import lombok.extern.slf4j.Slf4j; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.springframework.beans.factory.annotation.Autowired; @@ -12,16 +13,19 @@ import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.time.Duration; import java.util.Enumeration; import java.util.Objects; +import java.util.Optional; import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_API_VERSION; import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_HTTP_CLIENT; import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_HTTP_CLIENT_VALUE; import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME; import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_130; -import static cn.axzo.workflow.core.common.code.OtherRespCode.CLIENT_VERSION_SUPPORT; -import static cn.axzo.workflow.core.common.code.OtherRespCode.MICRO_SERVER_NEED_REBUILD; +import static cn.axzo.workflow.common.constant.StarterConstants.ENABLE_MANAGEABLE; +import static cn.axzo.workflow.common.code.OtherRespCode.CLIENT_VERSION_SUPPORT; +import static cn.axzo.workflow.common.code.OtherRespCode.MICRO_SERVER_NEED_REBUILD; /** * 客户端与服务端的版本比较 @@ -36,16 +40,18 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor { private String serviceVersion; @Autowired private ExtAxPropertyService extAxPropertyService; + private static final ThreadLocal KEY_CACHE = new ThreadLocal<>(); + private static final String REPEAT_KEY = "global:api_application:"; @Override 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); @@ -55,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); @@ -74,27 +79,52 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor { private void recordClientInfo(HttpServletRequest request, String headerClientVersion, DefaultArtifactVersion clientVersion) { - if (!StringUtils.hasText(request.getHeader(HEADER_SERVER_NAME))) { + String applicationName = request.getHeader(HEADER_SERVER_NAME); + log.info("HEADER_SERVER_NAME : {}", applicationName); + if (!StringUtils.hasText(applicationName)) { return; } - ExtAxProperty property = extAxPropertyService.getByName(request.getHeader(HEADER_SERVER_NAME)).map(entity -> { - entity.setCreated(!Objects.equals(entity.getValue(), serviceVersion)); - entity.setValue(headerClientVersion); - return entity; - }).orElseGet(() -> { - ExtAxProperty extAxProperty = new ExtAxProperty(); - extAxProperty.setCreated(true); - extAxProperty.setName(request.getHeader(HEADER_SERVER_NAME)); - extAxProperty.setValue(clientVersion.toString()); - return extAxProperty; - }); + String manageableStatus = request.getHeader(ENABLE_MANAGEABLE); - if (property.getCreated()) { - if (Objects.isNull(property.getId())) { - extAxPropertyService.add(property); - } else { - extAxPropertyService.update(property); - } + Optional extAxProperty = extAxPropertyService.getByName(applicationName); + String cacheRepeatKey = REPEAT_KEY + applicationName; + log.info("repeatApi key: {}", cacheRepeatKey); + + //success为true表示key不存在,执行成功,false表示key存在,执行失败 + Boolean success = RedisUtils.trySetObject(cacheRepeatKey, "", Duration.ofSeconds(60)); + if (success) { + KEY_CACHE.set(cacheRepeatKey); + insert(extAxProperty, applicationName, clientVersion, manageableStatus); + } + } + + private void update(Optional extAxProperty, String requestApplicationName, DefaultArtifactVersion clientVersion, String manageableStatus) { + if (!extAxProperty.isPresent()) { + return; + } + ExtAxProperty property = extAxProperty.get(); + if (Objects.equals(property.getValue(), clientVersion.toString()) + && Objects.equals(property.getManageable().toString(), manageableStatus)) { + return; + } + property.setName(requestApplicationName); + property.setValue(clientVersion.toString()); + property.setManageable(Boolean.valueOf(manageableStatus)); + extAxPropertyService.update(property); + } + + private void insert(Optional extAxProperty, String requestApplicationName, DefaultArtifactVersion clientVersion, String manageableStatus) { + if (extAxProperty.isPresent()) { + update(extAxProperty, requestApplicationName, clientVersion, manageableStatus); + } else { + extAxPropertyService.add(extAxProperty.orElseGet(() -> { + ExtAxProperty property = new ExtAxProperty(); + property.setCreated(true); + property.setName(requestApplicationName); + property.setValue(clientVersion.toString()); + property.setManageable(Boolean.valueOf(manageableStatus)); + return property; + })); } } 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 3163ead6e..000000000 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/DingTalkUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -package cn.axzo.workflow.server.common.util; - -import com.alibaba.fastjson.JSON; -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 java.util.Objects; - -/** - * 钉钉告警处理 - * - * @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" + - "> 创建参数: " + JSON.toJSONString(req) + "\n\n" + - "> ###### 异常信息: " + JSON.toJSONString(Objects.isNull(throwable.getCause()) ? throwable.getMessage() : - throwable.getCause().getMessage()) + " \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" + - "> 请求参数: " + JSON.toJSONString(requestParam) + "\n\n" + - "> ###### 结果: " + JSON.toJSONString(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/RedisUtils.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RedisUtils.java index 7ecef815f..fcb207f7a 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RedisUtils.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RedisUtils.java @@ -7,6 +7,7 @@ import org.redisson.api.RAtomicLong; import org.redisson.api.RBatch; import org.redisson.api.RBucket; import org.redisson.api.RBucketAsync; +import org.redisson.api.RFuture; import org.redisson.api.RKeys; import org.redisson.api.RList; import org.redisson.api.RMap; @@ -23,6 +24,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -140,6 +142,16 @@ public class RedisUtils { batch.execute(); } + + public static Boolean trySetObject(final String key, final T value, final Duration duration) { + RBatch batch = CLIENT.createBatch(); + RBucketAsync bucket = batch.getBucket(key); + RFuture future = bucket.trySetAsync(value, duration.toMillis(), TimeUnit.MILLISECONDS); + batch.execute(); + return future.join(); + } + + /** * 注册对象监听器 *

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..9a60107b2 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RpcExternalUtil.java @@ -0,0 +1,104 @@ +package cn.axzo.workflow.server.common.util; + +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 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/common/util/ShellUtil.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/ShellUtil.java new file mode 100644 index 000000000..07834b6c3 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/ShellUtil.java @@ -0,0 +1,123 @@ +package cn.axzo.workflow.server.common.util; + +import cn.hutool.core.date.DateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Date; +import java.util.Objects; + +/** + * 一个一次性 JVM 外部进程执行,并返回结果的 Shell 工具 + * + * @author wangli + * @since 2024-10-22 15:05 + */ +@Slf4j +public class ShellUtil { + + private ShellUtil() { + } + + public static String grepDateLog(String profile, String keyword, String dateStr) { + return grepLog(profile, keyword, DateUtil.parseDate(dateStr), null); + } + + public static String grepDateLog(String profile, String keyword, Date date) { + return grepLog(profile, keyword, date, null); + } + + public static String grepTodayLog(String profile, String keyword) { + return grepLog(profile, keyword, new Date(), null); + } + + /** + * 入参示例 zgrep '9812ea104cf643c4908e772a46abac17' workflowEngine.log | grep 'checkDeath()' + * @param profile 环境 + * @param keyword 具体的命令 + * @return + */ + public static String grepRealTimeLog(String profile, String keyword) { + return grepLog(profile, keyword, null, null); + } + + /** + * + * @param profile + * @param keyword + * @param date + * @param grepKeyword + * @return + */ + public static String grepLog(String profile, String keyword, Date date, String grepKeyword) { + return executeCmd("cd " + getLogPath(profile) + " && zgrep '" + keyword + "' workflowEngine.log" + + (Objects.nonNull(date) ? "." + DateUtil.formatDate(date) + ".*" : "") + + (StringUtils.hasText(grepKeyword) ? " | grep '" + grepKeyword + "'": "") + ); + } + + /** + * 入参示例 cd /mnt/app-logdata/java-dev/workflowEngine && zgrep '9812ea104cf643c4908e772a46abac17' workflowEngine.log | grep 'checkDeath()' + * @param fullCommand + * @return + */ + private static String executeCmd(String fullCommand) { + if (!StringUtils.hasText(fullCommand)) { + return "命令不能为空"; + } + try { + ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", fullCommand); + Process process = pb.start(); + InputStream inputStream = process.getInputStream(); + InputStream errorStream = process.getErrorStream(); + + StringBuilder outputBuilder = new StringBuilder(); + // 读取命令输出的线程 + Thread outputReader = new Thread(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + outputBuilder.append(line).append("\n"); + } + } catch (IOException e) { + log.warn("outputReader 发生异常: {}", e.getMessage(), e); + } + }); + outputReader.start(); + + // 读取命令错误输出的线程 + Thread errorReader = new Thread(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream))) { + String line; + while ((line = reader.readLine()) != null) { + outputBuilder.append("Error: ").append(line).append("\n"); + } + } catch (IOException e) { + log.warn("errorReader 发生异常: {}", e.getMessage(), e); + } + }); + errorReader.start(); + + // 等待命令执行完成 + outputReader.join(); + errorReader.join(); + + return outputBuilder.toString(); + } catch (IOException | InterruptedException e) { + log.error("Execute command 发生异常: {}", e.getMessage(), e); + return "Error executing command."; + } + } + + public static String getLogPath(String profile) { + String logSegment = profile; + if (Objects.equals("dev", profile)) { + logSegment = "java-dev"; + } + return "/mnt/app-logdata/" + logSegment + "/workflowEngine/"; + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/WpsUtil.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/WpsUtil.java new file mode 100644 index 000000000..6c892eb24 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/WpsUtil.java @@ -0,0 +1,211 @@ +package cn.axzo.workflow.server.common.util; + +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.maokai.api.client.OrganizationalNodeUserQueryApi; +import cn.axzo.maokai.api.vo.request.OrgNodeUserBriefInfoListReq; +import cn.axzo.maokai.api.vo.response.OrgNodeUserBriefInfoResp; +import cn.axzo.nanopart.doc.api.domain.FileReplaceContent; +import cn.axzo.nanopart.doc.api.filetemplate.FileTemplateApi; +import cn.axzo.nanopart.doc.api.filetemplate.request.FileTemplateReplaceRequest; +import cn.axzo.nanopart.doc.api.filetemplate.request.SimpleFileTemplateReplaceRequest; +import cn.axzo.nanopart.doc.api.index.request.DeleteNodeRequest; +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.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.model.dto.SignatureDTO; +import cn.axzo.workflow.common.model.dto.UploadFieldDTO; +import cn.axzo.workflow.common.model.dto.VariableObjectDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.engine.cmd.CustomGetProcessInstanceVariablesToObjectCmd; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONException; +import com.google.common.collect.Lists; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_NAME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_NAME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_PHONE; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC; +import static cn.axzo.workflow.common.model.dto.VariableObjectDTO.Type.signatureAndAdvice; + +/** + * WPS 工具类 + * + * @author wangli + * @since 2025-04-09 20:41 + */ +@Component +public class WpsUtil { + public static final List WPS_SUPPORTED_FILE_TYPES = Lists.newArrayList(FileTypeEnum.WORD, FileTypeEnum.EXCEL); + @Resource + private FileTemplateApi fileTemplateApi; + @Resource + private ServerFileServiceApi serverFileServiceApi; + @Resource + private OrganizationalNodeUserQueryApi organizationalNodeUserQueryApi; + + /** + * 调用 wps 文件变量替换接口 + * + * @param fileCode 基于该模板进行变量替换 + * @param fileKey 如果有值,则变量替换完成后,直接将文档覆盖该 fileKey 对应的文件; + * @return 返回替换变量后的文件oss 的 fileKey + */ + public String wpsFileVariableReplace(List wpsVariables, + String fileCode, String fileKey, String fileName) { + + List fileReplaceContents = BeanMapper.copyList(wpsVariables.stream() + .filter(i -> Objects.nonNull(i.getValue())) + .filter(i -> !(Objects.equals(i.getType().name(), "img") && !StringUtils.hasText(i.getValue().toString()))) + .filter(i -> Objects.equals(i.getType().name(), "img") || Objects.equals(i.getType().name(), "text")) + .collect(Collectors.toList()), FileReplaceContent.class, (s, t) -> { + t.setKey(s.getDesc()); + if (Objects.equals(s.getType().name(), "img")) { + if (isJson(s.getValue().toString())) { + t.setContent(JSON.parseArray(s.getValue().toString(), UploadFieldDTO.class).get(0).getFileUrl()); + } else { + t.setContent(JSON.parseArray(JSON.toJSONString(s.getValue()), UploadFieldDTO.class).get(0).getFileUrl()); + } + } else { + t.setContent(Objects.nonNull(s.getValue()) ? s.getValue().toString() : ""); + } + t.setType(s.getType().name()); + t.setPrefix("["); + t.setSuffix("]"); + }); + if (StringUtils.hasText(fileCode)) { + FileTemplateReplaceRequest request = new FileTemplateReplaceRequest(); + request.setFileCode(fileCode); + request.setFileKey(fileKey); + request.setReplaceContentList(fileReplaceContents); + return RpcExternalUtil.rpcProcessor(() -> fileTemplateApi.replaceWordText(request), "替换 WPS 文档变量", request); + } else { + SimpleFileTemplateReplaceRequest request = new SimpleFileTemplateReplaceRequest(); + request.setFileName(fileName); + request.setFileKey(fileKey); + request.setReplaceContentList(fileReplaceContents); + return RpcExternalUtil.rpcProcessor(() -> fileTemplateApi.replaceWordTextForFileKey(request), "替换 WPS 文档变量(simple)", request); + } + } + + public List getWpsReplaceVariables(ProcessEngineConfigurationImpl processEngineConfiguration, String processInstanceId) { + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + List wpsVariables = + commandExecutor.execute(new CustomGetProcessInstanceVariablesToObjectCmd(processInstanceId)); + + List signatureAndAdvices = Lists.newArrayList(); + wpsVariables.stream().filter(i -> Objects.equals(signatureAndAdvice, i.getType())).forEach(variableObjectDTO -> { + List signDetails = (List) variableObjectDTO.getValue(); + if (!CollectionUtils.isEmpty(signDetails)) { + SignatureDTO.SignDetail signDetail = signDetails.get(0); + ApiSignUrlDownloadRequest request = ApiSignUrlDownloadRequest.builder() + .fileKeys(Lists.newArrayList(signDetail.getSignature())).build(); + List signUrl = RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(request), "获取手写签图片地址", request); + signatureAndAdvices.add(VariableObjectDTO.builder() + .key(variableObjectDTO.getKey() + "_signature") + .desc(variableObjectDTO.getDesc() + "电子签名") + .value(Lists.newArrayList(UploadFieldDTO.builder().fileKey(signDetail.getSignature()).fileUrl(signUrl.get(0).getSignUrl()).build())) + .type(VariableObjectDTO.Type.img) + .build()); + signatureAndAdvices.add(VariableObjectDTO.builder() + .key(variableObjectDTO.getKey() + "_advice") + .desc(variableObjectDTO.getDesc() + "审批意见") + .value(signDetail.getAdvice()) + .type(VariableObjectDTO.Type.text) + .build()); + } + }); + wpsVariables.addAll(signatureAndAdvices); + + wpsVariables.stream().filter(i -> Objects.equals(i.getKey(), PRINT_VAR_PROCESS_INITIATOR)).findAny().ifPresent(i -> { + BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(i.getValue()); + Optional user = getUserInfo(initiator); + wpsVariables.add(VariableObjectDTO.builder() + .key(PRINT_VAR_PROCESS_INITIATOR_NAME) + .desc(PRINT_VAR_PROCESS_INITIATOR_NAME_DESC) + .value(user.isPresent() ? user.get().getRealName() : "") + .build()); + wpsVariables.add(VariableObjectDTO.builder() + .key(PRINT_VAR_PROCESS_INITIATOR_POSITION) + .desc(PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC) + .value(user.isPresent() && Objects.nonNull(user.get().getJob()) ? user.get().getJob().getName() : "") + .build()); + wpsVariables.add(VariableObjectDTO.builder() + .key(PRINT_VAR_PROCESS_INITIATOR_PHONE) + .desc(PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC) + .value(user.isPresent() ? user.get().getProfile().getPhone() : "") + .build()); + + }); + return wpsVariables; + } + + public void deleteWpsFile(String wpsCode) { + if (!StringUtils.hasText(wpsCode)) { + return; + } + DeleteNodeRequest request = new DeleteNodeRequest(); + request.setCode(wpsCode); + RpcExternalUtil.rpcProcessor(() -> fileTemplateApi.delete(request), "删除 WPS 文档", request); + } + + public static boolean isJson(String str) { + try { + JSON.parse(str); + return true; + } catch (JSONException e) { + return false; + } + } + + public static boolean isWpsFile(FileTypeEnum fileTypeEnum) { + return WPS_SUPPORTED_FILE_TYPES.contains(fileTypeEnum); + } + + private Optional getUserInfo(BpmnTaskDelegateAssigner assigner) { + if (Objects.isNull(assigner)) { + return Optional.empty(); + } + OrgNodeUserBriefInfoListReq req = new OrgNodeUserBriefInfoListReq(); + if (StringUtils.hasText(assigner.getTenantId())) { + req.setWorkspaceId(Long.valueOf(assigner.getTenantId())); + } + if (StringUtils.hasText(assigner.getOuId())) { + req.setOuId(Long.valueOf(assigner.getOuId())); + } + if (StringUtils.hasText(assigner.getNodeId())) { + req.setOrgNodeIds(Lists.newArrayList(Long.valueOf(assigner.getNodeId()))); + } + if (StringUtils.hasText(assigner.getPersonId())) { + req.setPersonIds(Lists.newArrayList(Long.valueOf(assigner.getPersonId()))); + } + req.setNeedJob(true); + req.setNeedUnit(true); + req.setNeedProfile(true); + req.setContainsExited(true); + List users = RpcExternalUtil.rpcApiResultProcessor(() -> organizationalNodeUserQueryApi.listOrgNodeUsers(req), + "查询审批人员组织信息", req); + + if (CollectionUtils.isEmpty(users)) { + return Optional.empty(); + } + + return users.stream().sorted(Comparator.comparing(OrgNodeUserBriefInfoResp::getExited).reversed()) + .collect(Collectors.toList()).stream().findFirst(); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/conf/ShutdownConfig.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/conf/ShutdownConfig.java new file mode 100644 index 000000000..ef8f41c03 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/conf/ShutdownConfig.java @@ -0,0 +1,29 @@ +package cn.axzo.workflow.server.conf; + +import cn.axzo.workflow.core.conf.CustomEventManager; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PreDestroy; + +/** + * 应用内事件处理 + * + * @author wangli + * @since 2025-04-07 17:09 + */ +@Configuration +public class ShutdownConfig { + + private final CustomEventManager eventManager; + + public ShutdownConfig(CustomEventManager eventManager) { + this.eventManager = eventManager; + } + + @PreDestroy + public void close() throws InterruptedException { + System.out.println("Closing application, waiting for events to be processed..."); + eventManager.waitForEventsToBeProcessed(); + System.out.println("All events have been processed."); + } +} \ No newline at end of file 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 bc21da41e..8833a7101 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,16 +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.exception.WorkflowEngineException; 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.server.common.util.DingTalkUtils; +import cn.axzo.workflow.core.engine.model.NoticeFlowElement; +import cn.axzo.workflow.core.util.DingTalkUtils; import cn.hutool.core.lang.Assert; import cn.hutool.http.HttpStatus; import cn.hutool.json.JSONUtil; @@ -28,16 +32,21 @@ 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; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.CALC_TASK_ASSIGNEE_ERROR; -import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_META_DATA_FORMAT_ERROR; -import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR; -import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_PARAM_ERROR; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.CALC_TASK_ASSIGNEE_ERROR; +import static cn.axzo.workflow.common.code.ConvertorRespCode.CONVERTOR_META_DATA_FORMAT_ERROR; +import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR; +import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_PARAM_ERROR; /** @@ -48,8 +57,16 @@ 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 boolean support(String param) { + return getType().equals(param); + } @Override public List select(FlowElement flowElement, DelegateExecution execution, @@ -67,6 +84,9 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign assigners.addAll(privateSelector(ApproverScopeEnum.valueOfProcessor(processor), flowElement, execution, throwException)); } + } else if (flowElement instanceof NoticeFlowElement) { + ApproverScopeEnum processor = ((NoticeFlowElement) flowElement).getProcessor(); + assigners.addAll(privateSelector(processor, flowElement, execution, throwException)); } return assigners.stream().filter(i -> StringUtils.hasText(i.getPersonId())).collect(Collectors.toList()); } @@ -77,16 +97,14 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign ApproverScopeProcessor.class); ApproverScopeDTO scopeDto = processor.build(flowElement, execution); if (CollectionUtils.isEmpty(scopeDto.getOrgScopes()) - && CollectionUtils.isEmpty(scopeDto.getWorkerTeamScopes())) { - if (throwException) { - throw new WorkflowEngineException(ENGINE_USER_TASK_PARAM_ERROR, flowElement.getId(), - processorType.getDesc()); - } + && CollectionUtils.isEmpty(scopeDto.getWorkerTeamScopes()) && Boolean.TRUE.equals(throwException)) { + throw new WorkflowEngineException(ENGINE_USER_TASK_PARAM_ERROR, flowElement.getId(), processorType.getDesc()); } try { - return invokeService(flowElement, execution, scopeDto); + return populateNameAndAvatar(invokeService(flowElement, execution, scopeDto), flowSupportApi, refreshProperties, applicationContext); } catch (Throwable t) { - if (throwException) { + if (Boolean.TRUE.equals(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()); @@ -103,19 +121,56 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign return Collections.emptyList(); } + protected final T parseFoundationApiResult(Supplier> supplier, String operatorDesc, + String extInfo, Object... param) { + StopWatch stopWatch = new StopWatch(operatorDesc); + log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param)); + stopWatch.start(); + cn.axzo.foundation.result.ApiResult result = supplier.get(); + stopWatch.stop(); + log.info("{}-Cost:{}, Result: {}", operatorDesc, + "API StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " 's", + JSONUtil.toJsonStr(result)); + try { + if (stopWatch.getTotalTimeSeconds() > refreshProperties.getApiTimeout() && Boolean.TRUE.equals(refreshProperties.getSendDingTalk())) { + DingTalkUtils.sendDingTalkForSlowUrl(applicationContext.getEnvironment() + .getProperty("spring.profiles.active"), + stopWatch.getTotalTimeSeconds(), + extInfo, + param, + result); + } + } catch (Exception e) { + // ignore + } + Assert.notNull(result, "服务调用异常"); + // 200自定义处理 + if (HttpStatus.HTTP_OK != result.getCode()) { + throw new WorkflowEngineException(CALC_TASK_ASSIGNEE_ERROR, "[API:" + extInfo + "]" + result.getMsg()); + } + return result.getData(); + } + + protected final T parseApiResult(Supplier> supplier, String operatorDesc, 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, - "StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " s", + "API StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " 's", JSONUtil.toJsonStr(result)); try { - if (stopWatch.getTotalTimeSeconds() > executionStartListener.getApiTimeout()) { - DingTalkUtils.sendDingTalkForSlowUrl(applicationContext.getEnvironment() + if (stopWatch.getTotalTimeSeconds() > refreshProperties.getApiTimeout() && refreshProperties.getSendDingTalk()) { + DingTalkUtils.sendDingTalkForSlowUrl(context.getEnvironment() .getProperty("spring.profiles.active"), stopWatch.getTotalTimeSeconds(), extInfo, @@ -152,6 +207,15 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign .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()); + } + return Collections.emptyList(); } else { return Collections.emptyList(); } @@ -161,9 +225,44 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign } } + 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) && !Objects.equals("system", 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()) || Objects.equals("system", assigner.getPersonId())) { + return; + } + long personId = Long.parseLong(Optional.ofNullable(assigner.getPersonId()).orElse("-1")); + if (personProfileMap.containsKey(personId)) { + log.info("查询到的人员信息:{}", 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..a1b2fcd66 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,31 @@ package cn.axzo.workflow.server.controller.delegate; +import cn.axzo.foundation.page.PageResp; +import cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi; +import cn.axzo.orggateway.api.nodeuser.dto.OrgNodeUserDTO; +import cn.axzo.orggateway.api.nodeuser.req.ListOrgNodeUserReq; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; import com.alibaba.fastjson.JSON; +import org.apache.commons.collections4.CollectionUtils; +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.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR; /** * 基于"固定人员"查询审批人 @@ -20,16 +35,14 @@ import java.util.List; */ @Component public class BasedFixedPersonTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { + @Resource + private OrgNodeUserApi orgNodeUserApi; + @Override public String getType() { return ApproverSpecifyEnum.fixedPerson.getType(); } - @Override - public boolean support(String param) { - return getType().equals(param); - } - @Override public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { @@ -39,7 +52,52 @@ public class BasedFixedPersonTaskAssigneeSelector extends AbstractBpmnTaskAssign } BpmnMetaParserHelper.getApproverSpecifyValue((UserTask) flowElement) .ifPresent(s -> assigners.addAll(JSON.parseArray(s, BpmnTaskDelegateAssigner.class))); - return assigners; + + 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()); + if (CollectionUtils.isEmpty(personIds)) { + return Collections.emptyList(); + } + List onlineUsers = new ArrayList<>(); + try { + ListOrgNodeUserReq searchReq = new ListOrgNodeUserReq(); + searchReq.setWorkspaceIds(workspaceIds); + searchReq.setPersonIds(personIds); + searchReq.setPage(1); + searchReq.setPageSize(personIds.size()); + PageResp pageResp = parseFoundationApiResult(() -> orgNodeUserApi.list(searchReq), "查询指定人员是否在职", + "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.list", searchReq); + if (pageResp != null) { + onlineUsers = pageResp.getData(); + } + } catch (Exception e) { + if (Boolean.TRUE.equals(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..a699ca334 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,19 +26,11 @@ import java.util.stream.Collectors; @Component public class BasedIdentityTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { - @Autowired - private FlowSupportApi flowSupportApi; - @Override public String getType() { return ApproverSpecifyEnum.identity.getType(); } - @Override - public boolean support(String param) { - return getType().equals(param); - } - @Override public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { @@ -59,11 +49,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/BasedInitiatorLeaderRecursionTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderRecursionTaskAssigneeSelector.java index d7f1992ce..54ad51f0c 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderRecursionTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorLeaderRecursionTaskAssigneeSelector.java @@ -1,24 +1,25 @@ package cn.axzo.workflow.server.controller.delegate; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; -import cn.axzo.workflow.core.common.exception.WorkflowEngineException; import org.flowable.bpmn.model.FlowElement; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import java.util.List; -import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_TYPE_NOT_SUPPORT; +import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_TYPE_NOT_SUPPORT; /** - * todo 本期需求不实现 + * 未不实现 * 基于"发起人多级主管"查询审批人 * * @author wangli * @since 2023/11/16 11:42 */ +@Deprecated // 方便识别未使用的审批人选择器 @Component public class BasedInitiatorLeaderRecursionTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { @Override @@ -26,11 +27,6 @@ public class BasedInitiatorLeaderRecursionTaskAssigneeSelector extends AbstractB return ApproverSpecifyEnum.initiatorLeaderRecursion.getType(); } - @Override - public boolean support(String param) { - return getType().equals(param); - } - @Override public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { 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 f2932da8a..3c7267fb9 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 @@ -1,9 +1,9 @@ package cn.axzo.workflow.server.controller.delegate; -import cn.axzo.maokai.api.client.OrganizationalNodeUserApi; -import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq; -import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq.IdentityPair; -import cn.axzo.maokai.api.vo.response.FlowTaskAssigner; +import cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi; +import cn.axzo.orggateway.api.nodeuser.req.FlowTaskAssignerReq; +import cn.axzo.orggateway.api.nodeuser.req.IdentityPair; +import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssigner; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDTO; @@ -13,9 +13,9 @@ 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 javax.annotation.Resource; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -32,19 +32,14 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; @Component public class BasedInitiatorLeaderTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { - @Autowired - private OrganizationalNodeUserApi organizationalNodeUserApi; + @Resource + private OrgNodeUserApi orgNodeUserApi; @Override public String getType() { return ApproverSpecifyEnum.initiatorLeader.getType(); } - @Override - public boolean support(String param) { - return getType().equals(param); - } - @Override public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { @@ -56,7 +51,7 @@ public class BasedInitiatorLeaderTaskAssigneeSelector extends AbstractBpmnTaskAs ApproverScopeDTO scopeDto) { // 获取发起人 BpmnTaskDelegateAssigner initiator = - (BpmnTaskDelegateAssigner) execution.getVariables().getOrDefault(INTERNAL_INITIATOR, null); + BpmnTaskDelegateAssigner.toObjectCompatible(execution.getVariables().get(INTERNAL_INITIATOR)); if (Objects.isNull(initiator)) { return super.invokeService(flowElement, execution, scopeDto); } @@ -68,11 +63,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), + parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssigner(req), "审批节点: " + flowElement.getId() + ", 通过发起人主管查询审批人", - "cn.axzo.maokai.api.client.OrganizationalNodeUserApi.listFlowTaskAssigner", req); + "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.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/BasedInitiatorSpecifiedTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorSpecifiedTaskAssigneeSelector.java new file mode 100644 index 000000000..a6dfb8885 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/BasedInitiatorSpecifiedTaskAssigneeSelector.java @@ -0,0 +1,84 @@ +package cn.axzo.workflow.server.controller.delegate; + +import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR; +import static cn.axzo.workflow.common.constant.BpmnConstants.INITIATOR_SPECIFY; + +/** + * 当节点配置为发起时指定,就使用当前类进行解析处理审批人逻辑 + * + * @author wangli + * @since 2025-01-10 14:32 + */ +@Slf4j +@Component +@AllArgsConstructor +public class BasedInitiatorSpecifiedTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { + + private final RuntimeService runtimeService; + + @Override + public String getType() { + return ApproverSpecifyEnum.initiatorSpecified.getType(); + } + + @Override + public boolean support(String param) { + return getType().equals(param) || ApproverSpecifyEnum.signerRelated.getType().equals(param); + } + + @Override + public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { + List assigners = new ArrayList<>(); + try { + populateAssigneeByInitiatorSpecify(assigners, flowElement, execution); + } catch (Throwable t) { + if (Boolean.TRUE.equals(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()); + } + throw t; + } else { + return Collections.emptyList(); + } + } + return assigners.stream().filter(i -> StringUtils.hasText(i.getPersonId())).collect(Collectors.toList()); + } + + /** + * 创建审批时,传入各个节点应该设置的审批人的数据信息,此处直接设置人 + * + * @param assigners + * @param flowElement + * @param execution + */ + private void populateAssigneeByInitiatorSpecify(List assigners, FlowElement flowElement, DelegateExecution execution) { + String processInstanceId = execution.getProcessInstanceId(); + @SuppressWarnings("unchecked") + Map> initiatorSpecifyMap = runtimeService.getVariable(processInstanceId, INITIATOR_SPECIFY, Map.class); + if (CollectionUtils.isEmpty(initiatorSpecifyMap)) { + return; + } + + assigners.addAll(initiatorSpecifyMap.getOrDefault(flowElement.getId(), Collections.emptyList())); + } +} 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..29e894e2c 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 @@ -1,8 +1,8 @@ package cn.axzo.workflow.server.controller.delegate; -import cn.axzo.maokai.api.client.OrganizationalNodeUserApi; -import cn.axzo.maokai.api.vo.request.FlowTaskAssignerReq; -import cn.axzo.maokai.api.vo.response.FlowTaskAssigner; +import cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi; +import cn.axzo.orggateway.api.nodeuser.req.FlowTaskAssignerReq; +import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssigner; import cn.axzo.workflow.common.enums.ApproverSpecifyEnum; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDTO; @@ -29,18 +29,13 @@ import java.util.stream.Collectors; public class BasedPositionTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { @Autowired - private OrganizationalNodeUserApi organizationalNodeUserApi; + private OrgNodeUserApi orgNodeUserApi; @Override public String getType() { return ApproverSpecifyEnum.position.getType(); } - @Override - public boolean support(String param) { - return getType().equals(param); - } - @Override public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { @@ -60,11 +55,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), + parseFoundationApiResult(() -> orgNodeUserApi.listFlowTaskAssigner(req), "审批节点: " + flowElement.getId() + ", 通过岗位查询审批人", - "cn.axzo.maokai.api.client.OrganizationalNodeUserApi.listFlowTaskAssigner", req); + "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.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..3149aad66 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,31 @@ 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.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 javax.annotation.Resource; +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; + /** * 基于"角色"查询审批人 * @@ -29,19 +36,17 @@ import java.util.stream.Collectors; @Component public class BasedRoleTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { - @Autowired - private TyrSaasRoleUserApi tyrSaasRoleUserApi; + @Resource + private CategoryService categoryService; + + @Resource + private BpmnProcessDefinitionService bpmnProcessDefinitionService; @Override public String getType() { return ApproverSpecifyEnum.role.getType(); } - @Override - public boolean support(String param) { - return getType().equals(param); - } - @Override public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { @@ -51,33 +56,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/PreNodeSpecifiedTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/PreNodeSpecifiedTaskAssigneeSelector.java index b6ee4403b..d7d76d3b9 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/PreNodeSpecifiedTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/PreNodeSpecifiedTaskAssigneeSelector.java @@ -32,11 +32,6 @@ public class PreNodeSpecifiedTaskAssigneeSelector extends AbstractBpmnTaskAssign return ApproverSpecifyEnum.preNodeSpecified.getType(); } - @Override - public boolean support(String param) { - return getType().equals(param); - } - @Override public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { 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 2db856823..274d7ec2e 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,24 +1,40 @@ 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.stereotype.Component; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import javax.annotation.Resource; +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; + /** * 基于"转交给管理员"查询审批人 * @@ -29,19 +45,14 @@ import java.util.stream.Collectors; @Component public class TransferToAdminTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { - @Autowired - private FlowSupportApi flowSupportApi; + @Resource + private SupportRefreshProperties supportRefreshProperties; @Override public String getType() { return ApproverEmptyHandleTypeEnum.transferToAdmin.getType(); } - @Override - public boolean support(String param) { - return getType().equals(param); - } - @Override public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { @@ -51,6 +62,16 @@ public class TransferToAdminTaskAssigneeSelector extends AbstractBpmnTaskAssigne @Override protected List invokeService(FlowElement flowElement, DelegateExecution execution, ApproverScopeDTO scopeDto) { + + if (Boolean.TRUE.equals(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() @@ -62,15 +83,95 @@ public class TransferToAdminTaskAssigneeSelector extends AbstractBpmnTaskAssigne .map(w -> BeanUtil.copyProperties(w, ListFlowTaskAssignerReq.OrgScope.class)) .collect(Collectors.toList())); } - ListFlowTaskAssignerReq req = builder.build(); + ListFlowTaskAssignerReq req = builder + .procInstId(execution.getProcessInstanceId()) + .workspaceAdmin(false) + .build(); + BpmnMetaParserHelper.getApproverScope((UserTask) flowElement) + .ifPresent(i -> { + if (Objects.equals(i, ApproverScopeEnum.projectWorkspace)) { + req.setWorkspaceAdmin(true); + } + }); List flowTaskAssigners = parseApiResult(() -> flowSupportApi.listTaskAssignerAdmin(req), "审批节点: " + flowElement.getId() + ", 通过管理员查询审批人", "cn.axzo.karma.client.feign.FlowSupportApi.listTaskAssignerAdmin", req); + if (CollUtil.isEmpty(flowTaskAssigners)) { return super.invokeService(flowElement, execution, scopeDto); } 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 (approverScope.isPresent() && Objects.equals(ApproverScopeEnum.entWorkspace, approverScope.get()) && + optSpecify.isPresent() && 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 (optSpecify.isPresent() && 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/delegate/TransferToSpecifyTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/TransferToSpecifyTaskAssigneeSelector.java new file mode 100644 index 000000000..db97aa496 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/TransferToSpecifyTaskAssigneeSelector.java @@ -0,0 +1,100 @@ +package cn.axzo.workflow.server.controller.delegate; + +import cn.axzo.foundation.page.PageResp; +import cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi; +import cn.axzo.orggateway.api.nodeuser.dto.OrgNodeUserDTO; +import cn.axzo.orggateway.api.nodeuser.req.ListOrgNodeUserReq; +import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR; + +/** + * 转交给指定人 + */ +@Slf4j +@Component +public class TransferToSpecifyTaskAssigneeSelector extends AbstractBpmnTaskAssigneeSelector { + + @Resource + private OrgNodeUserApi orgNodeUserApi; + + @Override + public String getType() { + return ApproverEmptyHandleTypeEnum.specifyAssignee.getType(); + } + + @Override + public List select(FlowElement flowElement, DelegateExecution execution, Boolean throwException) { + if (!(flowElement instanceof UserTask)) { + return Collections.emptyList(); + } + + List emptyAssignees = + BpmnMetaParserHelper.getEmptyApproverSpecify((UserTask) flowElement) + .map(listStr -> JSON.parseArray(listStr, BpmnTaskDelegateAssigner.class)) + .orElse(Collections.emptyList()); + if (CollectionUtils.isEmpty(emptyAssignees)) { + return Collections.emptyList(); + } + try { + List onlineUsers = new ArrayList<>(); + Set workspaceIds = emptyAssignees.stream() + .map(BpmnTaskDelegateAssigner::getTenantId) + .filter(NumberUtils::isDigits) + .map(Long::parseLong) + .collect(Collectors.toSet()); + + List personIds = emptyAssignees.stream() + .map(BpmnTaskDelegateAssigner::getPersonId) + .filter(NumberUtils::isDigits) + .map(Long::parseLong) + .collect(Collectors.toList()); + ListOrgNodeUserReq searchReq = new ListOrgNodeUserReq(); + searchReq.setWorkspaceIds(workspaceIds); + searchReq.setPersonIds(personIds); + searchReq.setPage(1); + searchReq.setPageSize(personIds.size()); + PageResp pageResp = parseFoundationApiResult(() -> orgNodeUserApi.list(searchReq), "SpecifyAssigneeTaskAssigneeSelector#查询指定人员是否在职", + "cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi.list", searchReq); + if (pageResp != null) { + onlineUsers = pageResp.getData(); + } + // 只要有在职的人,不会走审批人为空 + if (ListUtils.emptyIfNull(onlineUsers).stream() + .filter(i -> Objects.equals(i.getIsDelete(), 0L)) + .anyMatch(u -> emptyAssignees.stream() + .anyMatch(i -> Objects.equals(i.getPersonId(), String.valueOf(u.getPersonId())) + && Objects.equals(i.getOuId(), String.valueOf(u.getOrganizationalUnitId()))))) { + return emptyAssignees; + } + return Collections.emptyList(); + } catch (Exception e) { + if (Boolean.TRUE.equals(throwException)) { + throw new WorkflowEngineException(ENGINE_USER_TASK_CALC_ERROR, flowElement.getId(), + this.getType(), e.getMessage()); + } else { + return Collections.emptyList(); + } + } + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/FirstCopyTemplateFileActivityEvent_101_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/FirstCopyTemplateFileActivityEvent_101_Listener.java new file mode 100644 index 000000000..fef8d0e23 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/FirstCopyTemplateFileActivityEvent_101_Listener.java @@ -0,0 +1,165 @@ +//package cn.axzo.workflow.server.controller.listener.activity; +// +//import cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi; +//import cn.axzo.nanopart.doc.api.index.request.CopyNodeRequest; +//import cn.axzo.workflow.common.enums.FileTypeEnum; +//import cn.axzo.workflow.common.model.dto.SignFileDTO; +//import cn.axzo.workflow.common.model.dto.VariableObjectDTO; +//import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; +//import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCloneDTO; +//import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO; +//import cn.axzo.workflow.core.common.context.ActivityOperationContext; +//import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +//import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd; +//import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +//import cn.axzo.workflow.core.listener.BpmnActivityEventListener; +//import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign; +//import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper; +//import cn.axzo.workflow.core.service.ExtAxModelDocService; +//import cn.axzo.workflow.core.service.ExtAxProcessSignService; +//import cn.axzo.workflow.core.service.ExtAxReModelService; +//import cn.axzo.workflow.server.common.util.RpcExternalUtil; +//import cn.axzo.workflow.server.common.util.WpsUtil; +//import lombok.extern.slf4j.Slf4j; +//import org.flowable.bpmn.model.Process; +//import org.flowable.common.engine.impl.interceptor.CommandExecutor; +//import org.flowable.engine.delegate.DelegateExecution; +//import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +//import org.flowable.engine.impl.util.CommandContextUtil; +//import org.flowable.engine.impl.util.ProcessDefinitionUtil; +//import org.springframework.context.annotation.Scope; +//import org.springframework.core.Ordered; +//import org.springframework.stereotype.Component; +//import org.springframework.util.CollectionUtils; +// +//import javax.annotation.Resource; +//import java.util.ArrayList; +//import java.util.Collections; +//import java.util.List; +//import java.util.Objects; +//import java.util.Optional; +// +//import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; +// +/// ** +// * 签署业务才会执行 +// *

+// * 流程实例启用成功后: +// * 1、将模型关联的且实例勾选使用过的文档进行原始模板复制 +// * 2、进行初始数据的模板变量替换 +// *

+// * 并存至 ext_ax_process_sign 中的 doc_template 字段 +// * +// * @author wangli +// * @since 2025-04-23 14:32 +// */ +//@Slf4j +////@Component +//@Scope("prototype") +//public class FirstCopyTemplateFileActivityEvent_101_Listener extends AbstractBpmnEventListener implements BpmnActivityEventListener, Ordered { +// +// @Resource +// private ExtAxProcessSignService extAxProcessSignService; +// @Resource +// private ExtAxModelDocMapper extAxModelDocMapper; +// @Resource +// private ExtAxReModelService extAxReModelService; +// @Resource +// private ExtAxModelDocService extAxModelDocService; +// @Resource +// private DocAnonymousDatabaseApi docAnonymousApi; +// @Resource +// private WpsUtil wpsUtil; +// +// @Override +// public int getOrder() { +// return Integer.MIN_VALUE + 101; +// } +// +// @Override +// public void onStart(DelegateExecution execution) { +// if (!Objects.equals(NODE_STARTER.getType(), execution.getCurrentActivityId())) { +// return; +// } +// String processInstanceId = execution.getProcessInstanceId(); +// +// Process mainProcess = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId()).getMainProcess(); +// Optional signConfig = BpmnMetaParserHelper.getSignConfig(mainProcess); +// if (!signConfig.isPresent() || Objects.isNull(signConfig.get().getSignType())) { +// return; +// } +// +// ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); +// CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); +// List docs = commandExecutor.execute(new CustomGetModelDocsCmd(processInstanceId, true, extAxModelDocMapper, extAxReModelService)); +// ExtAxProcessSign processSign = new ExtAxProcessSign(); +// processSign.setProcessInstanceId(processInstanceId); +// processSign.setSignType(signConfig.get().getSignType().getType()); +// processSign.setPendingMessageId(signConfig.get().getSignPendingProperty().getPendingMessageId()); +// if (CollectionUtils.isEmpty(docs)) { +// processSign.setDocTemplate(Collections.emptyList()); +// processSign.setFileArchive(Collections.emptyList()); +// } else { +// // 复制基础模板 +// List docTemplates = copyTempTemplate(docs); +// processSign.setDocTemplate(docTemplates); +// +// List archives = replaceTemplateVariable(docTemplates, processEngineConfiguration, processInstanceId); +// processSign.setFileArchive(archives); +// } +// // 没有可用的文档,但仍然记录库表 +// extAxProcessSignService.save(processSign); +// } +// +// private List replaceTemplateVariable(List docTemplates, ProcessEngineConfigurationImpl processEngineConfiguration, String processInstanceId) { +// List archives = new ArrayList<>(); +// List wpsReplaceVariables = wpsUtil.getWpsReplaceVariables(processEngineConfiguration, processInstanceId); +// docTemplates.forEach(template -> { +// SignFileDTO signFileDTO = new SignFileDTO(); +// signFileDTO.setId(template.getId()); +// signFileDTO.setFileName(template.getFileName()); +// signFileDTO.setTemplateName(template.getTemplateName()); +// signFileDTO.setFileTag(template.getFileTag()); +// signFileDTO.setFileType(template.getFileType()); +// signFileDTO.setFileCode(template.getFileCode()); +// if (Objects.equals(template.getFileType(), FileTypeEnum.WORD) || Objects.equals(template.getFileType(), FileTypeEnum.EXCEL)) { +// String fileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, template.getFileCode(), null, template.getTemplateName() + template.getFileType().getSuffix()); +// signFileDTO.setFileKey(fileKey); +// } +// archives.add(signFileDTO); +// }); +// return archives; +// } +// +// private List copyTempTemplate(List docs) { +// List files = new ArrayList<>(); +// docs.forEach(doc -> { +// SignFileDTO signFileDTO = new SignFileDTO(); +// // 原始文档 ID +// signFileDTO.setId(doc.getId()); +// signFileDTO.setFileName(doc.getFileName()); +// signFileDTO.setTemplateName(doc.getTemplateName()); +// signFileDTO.setFileTag(doc.getTag()); +// signFileDTO.setFileType(doc.getFileType()); +// switch (doc.getFileType()) { +// case WORD: +// case EXCEL: +// CopyNodeRequest copy = new CopyNodeRequest(); +// copy.setCode(doc.getFileRelationId()); +// String fileCode = RpcExternalUtil.rpcProcessor(() -> docAnonymousApi.copy(copy), "复制文档", copy); +// signFileDTO.setFileCode(fileCode); +// break; +// case HIPRINT: +// Long newDocId = extAxModelDocService.cloneDoc(DocCloneDTO.builder().docId(doc.getId()).build(), true); +// signFileDTO.setFileCode(String.valueOf(newDocId)); +// break; +// case PDF: +// break; +// default: +// break; +// } +// files.add(signFileDTO); +// }); +// return files; +// } +//} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/OperationFileArchiveActivityEvent_101_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/OperationFileArchiveActivityEvent_101_Listener.java new file mode 100644 index 000000000..2afc96ca8 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/OperationFileArchiveActivityEvent_101_Listener.java @@ -0,0 +1,72 @@ +package cn.axzo.workflow.server.controller.listener.activity; + +import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.VariableObjectDTO; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; +import cn.axzo.workflow.core.common.context.ActivityOperationContext; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +import cn.axzo.workflow.core.listener.BpmnActivityEventListener; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign; +import cn.axzo.workflow.core.service.ExtAxProcessSignService; +import cn.axzo.workflow.server.common.util.WpsUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.Process; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_SIGN_DATA_NOT_EXISTS; + +/** + * 每个审批节点运行结束后,临时替换一次变量,并更新至 ext_ax_process_sign 中 file_archive 字段 + * + * @author wangli + * @since 2025-04-03 11:05 + */ +@Slf4j +@Component +@Scope("prototype") +public class OperationFileArchiveActivityEvent_101_Listener extends AbstractBpmnEventListener implements BpmnActivityEventListener, Ordered { + @Resource + private ExtAxProcessSignService extAxProcessSignService; + @Resource + private WpsUtil wpsUtil; + + @Override + public int getOrder() { + return Integer.MIN_VALUE + 101; + } + + @Override + public void onEnd(DelegateExecution execution) { + Process process = ProcessDefinitionUtil.getProcess(execution.getProcessDefinitionId()); + Optional signConfig = BpmnMetaParserHelper.getSignConfig(process); + if (!signConfig.isPresent() || Objects.isNull(signConfig.get().getSignType())) { + return; + } + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + ExtAxProcessSign processSign = extAxProcessSignService.findByProcessInstanceId(execution.getProcessInstanceId()); + if (Objects.isNull(processSign)) { + throw new WorkflowEngineException(PROCESS_SIGN_DATA_NOT_EXISTS); + } + List wpsReplaceVariables = wpsUtil.getWpsReplaceVariables(processEngineConfiguration, execution.getProcessInstanceId()); + processSign.getFileArchive().stream().filter(i -> Objects.equals(i.getFileType(), FileTypeEnum.WORD) + || Objects.equals(i.getFileType(), FileTypeEnum.EXCEL)) + .forEach(docBaseVO -> { + String newFileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, null, docBaseVO.getFileKey(), docBaseVO.getTemplateName() + docBaseVO.getFileType().getSuffix()); + docBaseVO.setFileKey(newFileKey); + }); + extAxProcessSignService.updateById(processSign); + } +} 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/RocketMqBpmActivityEventListener.java deleted file mode 100644 index 10691689a..000000000 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEventListener.java +++ /dev/null @@ -1,205 +0,0 @@ -package cn.axzo.workflow.server.controller.listener.activity; - -import cn.axzo.framework.rocketmq.Event; -import cn.axzo.framework.rocketmq.EventProducer; -import cn.axzo.workflow.common.enums.ProcessActivityEventEnum; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; -import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; -import cn.axzo.workflow.common.model.response.mq.ProcessActivityDTO; -import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; -import cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEvent; -import cn.axzo.workflow.core.listener.BpmnActivityEventListener; -import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; -import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; -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.delegate.DelegateExecution; -import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; -import org.flowable.engine.runtime.ProcessInstance; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.Ordered; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; - -import javax.annotation.Resource; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; -import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION; -import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; -import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_END; -import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_START; -import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_TAKE; -import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_WAIT_ASSIGNEE; - -/** - * 自定义实现的活动节点监听器, 实现了 Ordered 接口 - *

- * 注意:Order 值越小,优先级越高 - * - * @author wangli - * @since 2023/7/24 17:33 - */ -@Slf4j -@Component -public class RocketMqBpmActivityEventListener implements BpmnActivityEventListener, Ordered { - @Resource - private RuntimeService runtimeService; - @Resource - private RepositoryService repositoryService; - @Resource - private ExtAxHiTaskInstService hiTaskInstService; - @Resource - private EventProducer eventProducer; - @Value("${sendMq:true}") - private Boolean sendMQ; - - @Override - public void onStart(DelegateExecution execution) { - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmActivityEventListener#onStart...activityId: {}", execution.getCurrentActivityId()); - } - ProcessActivityDTO dto = buildActivityDTO(PROCESS_ACTIVITY_START, execution); - sendMessageQueue(dto, PROCESS_ACTIVITY_START); - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmActivityEventListener#onStart...end, activityId: {}", - execution.getCurrentActivityId()); - } - } - - - @Override - public void onWaitAssignee(BizSpecifyAssigneeEvent event) { - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmActivityEventListener#onWaitAssignee...activityId: {}", event.getActivityId()); - } - ProcessActivityDTO dto = new ProcessActivityDTO(); - dto.setType(PROCESS_ACTIVITY_WAIT_ASSIGNEE); - dto.setActivityId(event.getActivityId()); - dto.setActivityName(event.getActivityName()); - dto.setProcessInstanceId(event.getProcessInstanceId()); - dto.setProcessDefinitionId(event.getProcessDefinitionId()); - ProcessInstance processInstance = - runtimeService.createProcessInstanceQuery().processInstanceId(event.getProcessInstanceId()).singleResult(); - if (Objects.nonNull(processInstance)) { - dto.setProcessDefinitionKey(processInstance.getProcessDefinitionKey()); - dto.setBusinessKey(processInstance.getBusinessKey()); - } - dto.setTriggerId(event.getExecutionId()); - dto.setVariables(event.getVariables()); - String version = (String) runtimeService.getVariable(event.getProcessInstanceId(), WORKFLOW_ENGINE_VERSION); - if (Objects.isNull(version)) { - version = FLOW_SERVER_VERSION_121; - } - dto.setWorkflowEngineVersion(version); - sendMessageQueue(dto, PROCESS_ACTIVITY_WAIT_ASSIGNEE); - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmActivityEventListener#onWaitAssignee...end, activityId: {}", event.getActivityId()); - } - } - - @Override - public void onTake(DelegateExecution execution) { - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmActivityEventListener#onTake...activityId: {}", - execution.getCurrentActivityId()); - } - ProcessActivityDTO dto = buildActivityDTO(PROCESS_ACTIVITY_TAKE, execution); - - handlePassedAssignee(execution, dto); - - sendMessageQueue(dto, PROCESS_ACTIVITY_TAKE); - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmActivityEventListener#onTake...end, activityId: {}", - execution.getCurrentActivityId()); - } - } - - @Override - public void onEnd(DelegateExecution execution) { - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onEnd...activityId: {}", - execution.getCurrentActivityId()); - } - ProcessActivityDTO dto = buildActivityDTO(PROCESS_ACTIVITY_END, execution); - - handlePassedAssignee(execution, dto); - - sendMessageQueue(dto, PROCESS_ACTIVITY_END); - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmActivityEventListener#onEnd...end, activityId: {}", - execution.getCurrentActivityId()); - } - } - - private ProcessActivityDTO buildActivityDTO(ProcessActivityEventEnum type, DelegateExecution execution) { - ProcessActivityDTO dto = new ProcessActivityDTO(); - dto.setType(type); - dto.setProcessInstanceId(execution.getProcessInstanceId()); - dto.setBusinessKey(execution.getProcessInstanceBusinessKey()); - dto.setProcessDefinitionId(execution.getProcessDefinitionId()); - // 这个 ID 等于 ExecutionId - dto.setTriggerId(execution.getId()); - dto.setActivityId(execution.getCurrentActivityId()); - dto.setActivityName(((ExecutionEntityImpl) execution).getCurrentActivityName()); - ProcessInstance processInstance = - runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).includeProcessVariables().singleResult(); - if (Objects.nonNull(processInstance)) { - dto.setProcessDefinitionKey(processInstance.getProcessDefinitionKey()); - dto.setBusinessKey(processInstance.getBusinessKey()); - dto.setVariables(processInstance.getProcessVariables()); - } - String version = (String) runtimeService.getVariable(processInstance.getId(), WORKFLOW_ENGINE_VERSION); - if (Objects.isNull(version)) { - version = FLOW_SERVER_VERSION_121; - } - dto.setWorkflowEngineVersion(version); - Process mainProcess = repositoryService.getBpmnModel(execution.getProcessDefinitionId()).getMainProcess(); - BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); - return dto; - } - - private void handlePassedAssignee(DelegateExecution execution, ProcessActivityDTO dto) { - dto.setPassedAssigners(Collections.emptyList()); - String activityId = execution.getCurrentActivityId(); - ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); - searchDTO.setProcessInstanceId(execution.getProcessInstanceId()); - searchDTO.setTaskDefinitionKey(activityId); - searchDTO.setStatus(APPROVED); - List hiTasks = hiTaskInstService.queryList(searchDTO); - if (!CollectionUtils.isEmpty(hiTasks)) { - List passedAssigneeList = - hiTasks.stream().map(ExtAxHiTaskInst::getAssignee).distinct().collect(Collectors.toList()); - List originAssignerList = - runtimeService.getVariable(execution.getProcessInstanceId(), - INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + activityId, List.class); - if (!CollectionUtils.isEmpty(originAssignerList)) { - dto.setPassedAssigners(originAssignerList.stream().filter(i -> passedAssigneeList.contains(i.buildAssigneeId())).collect(Collectors.toList())); - } - } - } - - private void sendMessageQueue(ProcessActivityDTO dto, ProcessActivityEventEnum eventEnum) { - if (!sendMQ) { - return; - } - eventProducer.send(Event.builder() - .shardingKey(dto.getProcessInstanceId()) - .eventCode(eventEnum.getEventCode()) - .targetId(dto.getProcessInstanceId()) - .targetType(eventEnum.getTag()) - .data(dto) - .build()); - } - - @Override - public int getOrder() { - return Integer.MIN_VALUE; - } -} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEvent_100_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEvent_100_Listener.java new file mode 100644 index 000000000..f4d2a6e37 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEvent_100_Listener.java @@ -0,0 +1,244 @@ +package cn.axzo.workflow.server.controller.listener.activity; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventProducer; +import cn.axzo.workflow.common.enums.ProcessActivityEventEnum; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; +import cn.axzo.workflow.common.model.response.mq.ProcessActivityDTO; +import cn.axzo.workflow.core.common.context.ActivityOperationContext; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.event.BizCallbackEvent; +import cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEvent; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +import cn.axzo.workflow.core.listener.BpmnActivityEventListener; +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +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.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121; +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_APPLICATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_PROCESS_DEFINITION_KEY; +import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_OWNERSHIP_APPLICATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; +import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_CALLBACK; +import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_END; +import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_START; +import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_TAKE; +import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_WAIT_ASSIGNEE; + +/** + * 自定义实现的活动节点监听器, 实现了 Ordered 接口 + *

+ * 注意:Order 值越小,优先级越高 + * + * @author wangli + * @since 2023/7/24 17:33 + */ +@Slf4j +@Component +@Scope("prototype") +public class RocketMqBpmActivityEvent_100_Listener extends AbstractBpmnEventListener implements BpmnActivityEventListener, Ordered { + @Resource + private RuntimeService runtimeService; + @Resource + private RepositoryService repositoryService; + @Resource + private ExtAxHiTaskInstService hiTaskInstService; + @Resource + private EventProducer eventProducer; + @Value("${sendMq:true}") + private Boolean sendMQ; + + @Override + public void onStart(DelegateExecution execution) { + log.info("RocketMqBpmActivityEventListener#onStart...activityId: {}, processInstanceId: {}", execution.getCurrentActivityId(), execution.getProcessInstanceId()); + ProcessActivityDTO dto = build(PROCESS_ACTIVITY_START, execution); + sendMessageQueue(dto, PROCESS_ACTIVITY_START); + log.info("RocketMqBpmActivityEventListener#onStart...end, activityId: {}, processInstanceId: {}", + execution.getCurrentActivityId(), execution.getProcessInstanceId()); + } + + + @Override + public void onWaitAssignee(BizSpecifyAssigneeEvent event) { + log.info("RocketMqBpmActivityEventListener#onWaitAssignee...activityId: {}, processInstanceId: {}", event.getActivityId(), event.getProcessInstanceId()); + // 特殊的自定义业务事件, 不能使用公共的 build 方法 + ProcessActivityDTO dto = new ProcessActivityDTO(); + dto.setType(PROCESS_ACTIVITY_WAIT_ASSIGNEE); + dto.setActivityId(event.getActivityId()); + dto.setActivityName(event.getActivityName()); + dto.setProcessInstanceId(event.getProcessInstanceId()); + dto.setProcessDefinitionId(event.getProcessDefinitionId()); + ProcessInstance processInstance = getContext().getProcessInstance(() -> + runtimeService.createProcessInstanceQuery() + .processInstanceId(event.getProcessInstanceId()) + .includeProcessVariables() + .singleResult()); + if (Objects.nonNull(processInstance)) { + dto.setProcessDefinitionKey(processInstance.getProcessDefinitionKey()); + dto.setBusinessKey(processInstance.getBusinessKey()); + } + dto.setTriggerId(event.getExecutionId()); + dto.setVariables(event.getVariables()); + dto.setWorkflowEngineVersion(String.valueOf(event.getVariables().getOrDefault(WORKFLOW_ENGINE_VERSION, FLOW_SERVER_VERSION_121))); + sendMessageQueue(dto, PROCESS_ACTIVITY_WAIT_ASSIGNEE); + log.info("RocketMqBpmActivityEventListener#onWaitAssignee...end, activityId: {}, processInstanceId: {}", event.getActivityId(), event.getProcessInstanceId()); + } + + @Override + public void onTake(DelegateExecution execution) { + log.info("RocketMqBpmActivityEventListener#onTake...activityId: {}, processInstanceId: {}", + execution.getCurrentActivityId(), execution.getProcessInstanceId()); + ProcessActivityDTO dto = build(PROCESS_ACTIVITY_TAKE, execution); + + handlePassedAssignee(execution, dto); + + sendMessageQueue(dto, PROCESS_ACTIVITY_TAKE); + log.info("RocketMqBpmActivityEventListener#onTake...end, activityId: {}, processInstanceId: {}", + execution.getCurrentActivityId(), execution.getProcessInstanceId()); + } + + @Override + public void onCallback(BizCallbackEvent event) { + log.info("RocketMqBpmActivityEventListener#onCallback...activityId: {}, processInstanceId: {}", + event.getActivityId(), event.getProcessInstanceId()); + + // 特殊的自定义业务事件, 不能使用公共的 build 方法 + ProcessActivityDTO dto = new ProcessActivityDTO(); + dto.setType(PROCESS_ACTIVITY_CALLBACK); + dto.setActivityId(event.getActivityId()); + dto.setActivityName(event.getActivityName()); + dto.setProcessInstanceId(event.getProcessInstanceId()); + dto.setProcessDefinitionId(event.getProcessDefinitionId()); + ProcessInstance processInstance = getContext().getProcessInstance(() -> + runtimeService.createProcessInstanceQuery() + .processInstanceId(event.getProcessInstanceId()) + .singleResult()); + if (Objects.nonNull(processInstance)) { + dto.setProcessDefinitionKey(processInstance.getProcessDefinitionKey()); + dto.setBusinessKey(processInstance.getBusinessKey()); + } + dto.setTriggerId(event.getExecutionId()); + dto.setVariables(event.getVariables()); + dto.setWorkflowEngineVersion(String.valueOf(event.getVariables().getOrDefault(WORKFLOW_ENGINE_VERSION, FLOW_SERVER_VERSION_121))); + sendMessageQueue(dto, PROCESS_ACTIVITY_CALLBACK); + + log.info("RocketMqBpmActivityEventListener#onCallback...end, activityId: {}, processInstanceId: {}", + event.getActivityId(), event.getProcessInstanceId()); + } + + @Override + public void onEnd(DelegateExecution execution) { + log.info("RocketMqMessagePushEventListener#onEnd...activityId: {}, processInstanceId: {}", + execution.getCurrentActivityId(), execution.getProcessInstanceId()); + ProcessActivityDTO dto = build(PROCESS_ACTIVITY_END, execution); + + handlePassedAssignee(execution, dto); + + sendMessageQueue(dto, PROCESS_ACTIVITY_END); + log.info("RocketMqBpmActivityEventListener#onEnd...end, activityId: {}, processInstanceId: {}", + execution.getCurrentActivityId(), execution.getProcessInstanceId()); + } + + private ProcessActivityDTO build(ProcessActivityEventEnum type, DelegateExecution execution) { + ProcessActivityDTO dto = new ProcessActivityDTO(); + dto.setType(type); + dto.setProcessInstanceId(execution.getProcessInstanceId()); + dto.setBusinessKey(execution.getProcessInstanceBusinessKey()); + dto.setProcessDefinitionId(execution.getProcessDefinitionId()); + // 这个 ID 等于 ExecutionId + dto.setTriggerId(execution.getId()); + dto.setActivityId(execution.getCurrentActivityId()); + dto.setActivityName(execution.getCurrentFlowElement().getName()); + ProcessInstance processInstance = getContext().getProcessInstance(() -> + runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()) + .includeProcessVariables().singleResult()); + if (Objects.nonNull(processInstance)) { + dto.setProcessDefinitionKey(processInstance.getProcessDefinitionKey()); + dto.setBusinessKey(processInstance.getBusinessKey()); + dto.setVariables(processInstance.getProcessVariables()); + dto.setWorkflowEngineVersion(String.valueOf(processInstance.getProcessVariables() + .getOrDefault(WORKFLOW_ENGINE_VERSION, FLOW_SERVER_VERSION_121))); + } else { + dto.setWorkflowEngineVersion(FLOW_SERVER_VERSION_121); + } + Process mainProcess = repositoryService.getBpmnModel(execution.getProcessDefinitionId()).getMainProcess(); + BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); + return dto; + } + + private void handlePassedAssignee(DelegateExecution execution, ProcessActivityDTO dto) { + dto.setPassedAssigners(Collections.emptyList()); + String activityId = execution.getCurrentActivityId(); + ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); + searchDTO.setProcessInstanceId(execution.getProcessInstanceId()); + searchDTO.setTaskDefinitionKey(activityId); + searchDTO.setStatus(APPROVED); + List hiTasks = hiTaskInstService.queryList(searchDTO); + if (!CollectionUtils.isEmpty(hiTasks)) { + List passedAssigneeList = hiTasks.stream() + .map(ExtAxHiTaskInst::getAssignee).distinct().collect(Collectors.toList()); + List originAssignerList = + runtimeService.getVariable(execution.getProcessInstanceId(), + INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + activityId, List.class); + if (!CollectionUtils.isEmpty(originAssignerList)) { + dto.setPassedAssigners(originAssignerList.stream() + .filter(i -> passedAssigneeList.contains(i.buildAssigneeId())) + .collect(Collectors.toList())); + } + } + } + + private void sendMessageQueue(ProcessActivityDTO dto, ProcessActivityEventEnum eventEnum) { + if (!sendMQ) { + return; + } + Map header = new HashMap<>(); + if (StringUtils.hasText(dto.getProcessDefinitionKey())) { + log.info("record process definition key: {}", dto.getProcessDefinitionKey()); + header.put(MQ_OWNERSHIP_PROCESS_DEFINITION_KEY, dto.getProcessDefinitionKey()); + } + if (dto.getVariables().containsKey(PROCESS_OWNERSHIP_APPLICATION)) { + Object orDefault = dto.getVariables().getOrDefault(PROCESS_OWNERSHIP_APPLICATION, ""); + if (Objects.nonNull(orDefault)) { + log.info("record process ownership app name: {}", orDefault); + header.put(MQ_OWNERSHIP_APPLICATION, orDefault.toString()); + } + } + eventProducer.send(Event.builder() + .shardingKey(dto.getProcessInstanceId()) + .eventCode(eventEnum.getEventCode()) + .targetId(dto.getProcessInstanceId()) + .targetType(dto.getProcessDefinitionKey()) + .data(dto) + .build(), header); + } + + @Override + public int getOrder() { + return Integer.MIN_VALUE + 100; + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/doc/DocChangeListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/doc/DocChangeListener.java new file mode 100644 index 000000000..375fe0009 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/doc/DocChangeListener.java @@ -0,0 +1,54 @@ +package cn.axzo.workflow.server.controller.listener.doc; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventProducer; +import cn.axzo.workflow.common.enums.DocChangeEventEnum; +import cn.axzo.workflow.core.conf.CustomEventManager; +import cn.axzo.workflow.core.engine.event.DocChangeEvent; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import javax.annotation.Resource; + +/** + * Application 内的文档变更事件处理器,通过该接口广播 MQ + * + * @author wangli + * @since 2025-04-07 17:10 + */ +@Component +public class DocChangeListener { + @Resource + private CustomEventManager eventManager; + @Resource + private EventProducer eventProducer; + @Value("${sendMq:true}") + private Boolean sendMQ; + + @TransactionalEventListener(value = DocChangeEvent.class, phase = TransactionPhase.AFTER_COMMIT) + public void handleEvent(DocChangeEvent event) { + try { + sendMessageQueue(event); + } finally { + eventManager.eventProcessed(); + } + } + + + public void sendMessageQueue(DocChangeEvent event) { + if (!sendMQ) { + return; + } + eventProducer.send(Event.builder() + .shardingKey(event.getKey()) + .eventCode(DocChangeEventEnum.DOC_CHANGE.getEventCode()) + .targetId(event.getKey()) + .targetType(event.getKey()) + .data(JSON.toJSONString(event, SerializerFeature.WriteMapNullValue)) + .build()); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/error/ApproveErrorReporterEventListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/error/ApproveErrorReporterEventListener.java new file mode 100644 index 000000000..3d86383b0 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/error/ApproveErrorReporterEventListener.java @@ -0,0 +1,95 @@ +package cn.axzo.workflow.server.controller.listener.error; + +import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.event.MessagePushEvent; +import cn.axzo.workflow.core.engine.event.MessagePushEventBuilder; +import cn.axzo.workflow.core.engine.job.AsyncApproveTaskJobHandler; +import cn.axzo.workflow.core.engine.job.AsyncRejectTaskJobHandler; +import cn.axzo.workflow.core.listener.BpmnAsyncJobEventListener; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.flowable.bpmn.model.Process; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEvent; +import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.impl.event.FlowableEntityEventImpl; +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.api.JobInfo; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS; +import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE; +import static cn.axzo.workflow.core.listener.AbstractBpmnEventListener.parseProcessDefinitionKey; +import static org.flowable.common.engine.api.delegate.event.FlowableEngineEventType.JOB_MOVED_TO_DEADLETTER; + +/** + * 处理异步审批相关异常 + */ +@Slf4j +@Component +@Scope("prototype") +public class ApproveErrorReporterEventListener implements BpmnAsyncJobEventListener { + + private final List IGNORE_ERROR_CODES = Arrays.asList( + TASK_HAS_BEEN_COMPLETE.getRespCode(), + TASK_COMPLETE_FAIL_NOT_EXISTS.getRespCode(), + TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF.getRespCode()); + + private final List RESOLVE_HANDLER_TYPES = Arrays.asList( + AsyncApproveTaskJobHandler.TYPE, + AsyncRejectTaskJobHandler.TYPE); + + @Override + public boolean support(FlowableEngineEventType eventType) { + return Objects.equals(JOB_MOVED_TO_DEADLETTER, eventType); + } + + @Override + public void notify(FlowableEvent flowableEvent) { + log.info("ApproveErrorReporterEventListener-flowableEvent: {}", JSONUtil.toJsonStr(flowableEvent)); + if (!(flowableEvent instanceof FlowableEntityEventImpl)) { + return; + } + FlowableEntityEventImpl jobEvent = (FlowableEntityEventImpl) flowableEvent; + JobInfo jobInfo = (JobInfo) jobEvent.getEntity(); + String handlerType = jobInfo.getJobHandlerType(); + //只处理审批类型任务 + if (!RESOLVE_HANDLER_TYPES.contains(handlerType)) { + log.warn("非审批类型任务,handlerType:{},event:{}", handlerType, JSONUtil.toJsonStr(flowableEvent)); + return; + } + if (StringUtils.isBlank(jobInfo.getCustomValues())) { + log.warn("审批类型错误,CustomValues内容为空,event:{}", jobEvent); + return; + } + + BpmnTaskAuditDTO dto = JSONUtil.toBean(jobInfo.getCustomValues(), BpmnTaskAuditDTO.class); + Process process = ProcessDefinitionUtil.getProcess(jobEvent.getProcessDefinitionId()); + //获取消息配置 + Optional noticeConfig = BpmnMetaParserHelper.getNoticeConfig(process); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); + MessagePushEvent event = MessagePushEventBuilder.createPendingRollbackEvent(jobEvent.getProcessInstanceId(), + parseProcessDefinitionKey(jobEvent.getProcessDefinitionId()), + jobInfo.getTenantId(), dto.getTaskId(), noticeConfig.orElse(new BpmnNoticeConf())); + log.info("发送恢复待办的流程事件消息: taskDto:{},event:{}", JSONUtil.toJsonStr(dto), JSONUtil.toJsonStr(event)); + eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey()); + } + + @Override + public int getOrder() { + return Integer.MIN_VALUE; + } +} 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 new file mode 100644 index 000000000..f77d9bc8e --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/error/ErrorReporterEventListener.java @@ -0,0 +1,63 @@ +package cn.axzo.workflow.server.controller.listener.error; + +import cn.axzo.workflow.core.listener.BpmnAsyncJobEventListener; +import cn.axzo.workflow.server.common.annotation.ReporterType; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEvent; +import org.flowable.common.engine.impl.event.FlowableEntityExceptionEventImpl; +import org.flowable.job.api.JobInfo; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +import static org.flowable.common.engine.api.delegate.event.FlowableEngineEventType.JOB_EXECUTION_FAILURE; + +/** + * 异步任务执行异常的扩展监听器 + * + * @author wangli + * @since 2024/4/17 16:59 + */ +@Slf4j +@Component +@Scope("prototype") +public class ErrorReporterEventListener implements BpmnAsyncJobEventListener { + @Value("${spring.profiles.active}") + private String profile; + @Value("${workflow.sendDingTalk:true}") + private Boolean sendDingTalk; + + @Override + public boolean support(FlowableEngineEventType eventType) { + return Objects.equals(JOB_EXECUTION_FAILURE, eventType); + } + + @Override + public void notify(FlowableEvent flowableEvent) { + if (!(flowableEvent instanceof FlowableEntityExceptionEventImpl)) { + return; + } + FlowableEntityExceptionEventImpl event = (FlowableEntityExceptionEventImpl) flowableEvent; + JobInfo job = (JobInfo) event.getEntity(); + Throwable throwable = event.getCause(); + ReporterType reporterType = ReporterType.ONLY_LOG; + if (Lists.newArrayList("dev", "test", "pre").contains(profile)) { + reporterType = ReporterType.BOTH; + } else if (Lists.newArrayList("live", "master").contains(profile)) { + reporterType = ReporterType.ONLY_LOG; + } + if (job.getRetries() <= 1) { + reporterType.executeAction(profile, "异步任务执行异常, 重试 3 次后仍未成功", sendDingTalk, new Object[]{job}, "act_ru_job", "", throwable, false); + } + } + + @Override + public int getOrder() { + return Integer.MIN_VALUE; + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/notice/RocketMqMessagePushEventListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/notice/RocketMqMessagePushEventListener.java index 13f49dec1..abc275bbb 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/notice/RocketMqMessagePushEventListener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/notice/RocketMqMessagePushEventListener.java @@ -1,22 +1,36 @@ package cn.axzo.workflow.server.controller.listener.notice; +import cn.axzo.core.utils.converter.BeanConverter; import cn.axzo.framework.rocketmq.Event; import cn.axzo.framework.rocketmq.EventProducer; import cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; 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.mq.MessagePushDTO; +import cn.axzo.workflow.core.common.context.NoticeOperationContext; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper; import cn.axzo.workflow.core.engine.event.MessagePushEvent; +import cn.axzo.workflow.core.engine.event.MessagePushEventImpl; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; import cn.axzo.workflow.core.listener.BpmnMessagePushEventListener; import cn.axzo.workflow.core.service.BpmnProcessInstanceService; -import cn.axzo.workflow.core.service.CategoryService; -import com.alibaba.fastjson.JSON; +import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter; +import cn.hutool.json.JSONUtil; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.Process; import org.flowable.engine.HistoryService; +import org.flowable.engine.TaskService; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.service.impl.persistence.entity.TaskEntity; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -24,6 +38,7 @@ import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -33,7 +48,6 @@ import java.util.Objects; import java.util.stream.Collectors; import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; -import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_DELETE_PROCESS_FLAG; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_TENANT_ID; @@ -45,8 +59,13 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TY import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO; +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_ASSIGNER_BATCH_SIZE; +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_APPLICATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_PROCESS_DEFINITION_KEY; import static cn.axzo.workflow.common.constant.BpmnConstants.MULTI_INSTANCE_LOOP_COUNTER; import static cn.axzo.workflow.common.constant.BpmnConstants.NUMBER_OF_INSTANCES; +import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_OWNERSHIP_APPLICATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.SUPPORT_UPGRADE_VARIABLE; import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE; import static cn.axzo.workflow.common.constant.VariableConstants.VAR_ACTIVITY_ID; import static cn.axzo.workflow.common.constant.VariableConstants.VAR_ACTIVITY_NAME; @@ -63,11 +82,14 @@ import static cn.axzo.workflow.common.constant.VariableConstants.VAR_PROCESS_TEN import static cn.axzo.workflow.common.constant.VariableConstants.VAR_TASK_ID; import static cn.axzo.workflow.common.constant.VariableConstants.VAR_TASK_START_TIME; import static cn.axzo.workflow.common.constant.VariableConstants.VAR_TASK_USER_NAME; +import static cn.axzo.workflow.common.enums.BpmnButtonEnum.BPMN_UPGRADE; import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_CARBON_COPY; import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_CARBON_COPY_COMPLETE; +import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_IM; import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_NOTICE; import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_PENDING; import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_PENDING_COMPLETE; +import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_PENDING_ROLLBACK; import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_SMS; /** @@ -78,7 +100,8 @@ import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_ */ @Slf4j @Component -public class RocketMqMessagePushEventListener implements BpmnMessagePushEventListener, Ordered { +@Scope("prototype") +public class RocketMqMessagePushEventListener extends AbstractBpmnEventListener implements BpmnMessagePushEventListener, Ordered { @Resource private EventProducer eventProducer; @Resource @@ -86,9 +109,15 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis @Resource private HistoryService historyService; @Resource - private CategoryService categoryService; + private TaskService taskService; @Value("${sendMq:true}") private Boolean sendMQ; + @Resource + private BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter; + @Resource + private String serviceVersion; + @Value("${workflow.noticeTemplateCode}") + private String noticeTemplateCode; private static final List REMOVE_KEYS = Lists.newArrayList( INTERNAL_INITIATOR, @@ -112,23 +141,17 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis public void onNotice(MessagePushEvent event) { if (Objects.isNull(event.getNoticeConfig()) || Objects.isNull(event.getNoticeConfig().getNotice()) - || !StringUtils.hasText(event.getNoticeConfig().getNotice().getNoticeMessageId()) - || Objects.isNull(event.getAssigners())) { + || !event.getNoticeConfig().getNotice().getSendMessage() + || CollectionUtils.isEmpty(event.getAssigners())) { return; } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onNotice...msgTemplateId: {}, receivePerson: {}", - event.getNoticeConfig().getNotice().getNoticeMessageId(), JSON.toJSONString(event.getAssigners())); - } + log.info("RocketMqMessagePushEventListener#onNotice...msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getNotice().getNoticeMessageId(), JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); if (Objects.nonNull(event.getNoticeConfig().getNotice())) { - MessagePushDTO dto = build(event.getNoticeConfig().getNotice().getNoticeMessageId(), - PROCESS_PUSH_NOTICE, event, collectionVariable(event)); - sendMessageQueue(dto, PROCESS_PUSH_NOTICE); - } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onNotice...end, msgTemplateId: {}, receivePerson: {}", - event.getNoticeConfig().getNotice().getNoticeMessageId(), JSON.toJSONString(event.getAssigners())); + getMessagePushDtoSlice(event, noticeTemplateCode, PROCESS_PUSH_NOTICE).forEach(dto -> sendMessageQueue(dto, PROCESS_PUSH_NOTICE)); } + log.info("RocketMqMessagePushEventListener#onNotice...end, msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getNotice().getNoticeMessageId(), JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); } @@ -140,22 +163,64 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis || Objects.isNull(event.getAssigners())) { return; } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onPendingPush...msgTemplateId: {}, receivePerson: {}", - event.getNoticeConfig().getPending().getPendingMessageId(), - JSON.toJSONString(event.getAssigners())); - } + log.info("RocketMqMessagePushEventListener#onPendingPush...msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getPending().getPendingMessageId(), + JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); if (Objects.nonNull(event.getNoticeConfig().getPending())) { MessagePushDTO dto = build(event.getNoticeConfig().getPending().getPendingMessageId(), - PROCESS_PUSH_PENDING, event, collectionVariable(event)); - sendMessageQueue(dto, PROCESS_PUSH_PENDING); + PROCESS_PUSH_PENDING, event, collectionVariable(event), event.getActivitySignature()); + List buttons = new ArrayList<>(); + // 发送待办时, 计算当前人能操作的按钮有哪些? + if (StringUtils.hasText(event.getProcessDefinitionId())) { + BpmnTaskDelegateAssigner bpmnTaskDelegateAssigner = event.getAssigners().get(0); + Process process = getContext().getProcess(() -> ProcessDefinitionUtil.getBpmnModel(event.getProcessDefinitionId()).getMainProcess()); + BpmnMetaParserHelper.getButtonConfig(process, event.getCurrentTaskDefinitionKey()) + .ifPresent(buttonConf -> { + BpmnProcessInstanceVO instance = getContext().getInstanceVO(() -> getBpmnProcessInstanceVO(event)); + if (bpmnTaskDelegateAssigner.comparePersonIdToOther(instance.getInitiator())) { + // 发起人 + buttons.addAll(buttonConf.getInitiator()); + } + // 当前审批人 + buttons.addAll(buttonConf.getCurrent()); + if (isHistoryOperationUser(instance, bpmnTaskDelegateAssigner)) { + buttons.addAll(buttonConf.getHistory()); + } + if (isCarbonUsers(instance, bpmnTaskDelegateAssigner)) { + buttons.addAll(buttonConf.getCarbonCopy()); + } + }); + } + + // 如果提级审批的变量未 false + TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(event.getTaskId()).singleResult(); + Boolean supportUpgrade = (Boolean) task.getTransientVariablesLocal().getOrDefault(SUPPORT_UPGRADE_VARIABLE, false); + if (!supportUpgrade) { + buttons.removeIf(button-> Objects.equals(button.getBtnKey(), BPMN_UPGRADE.getBtnKey())); + } + dto.setButtons(buttons); + sendMessageQueue(dto, PROCESS_PUSH_PENDING); } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onPendingPush...end, msgTemplateId: {}, receivePerson: {}", - event.getNoticeConfig().getPending().getPendingMessageId(), - JSON.toJSONString(event.getAssigners())); + log.info("RocketMqMessagePushEventListener#onPendingPush...end, msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getPending().getPendingMessageId(), + JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); + } + + private boolean isHistoryOperationUser(BpmnProcessInstanceVO instance, BpmnTaskDelegateAssigner bpmnTaskDelegateAssigner) { + if (Objects.isNull(bpmnTaskDelegateAssigner) || Objects.isNull(instance)) { + return false; } + List users = CustomTaskHelper.getHistoryOperationUsers(CommandContextUtil.getCommandContext(), instance.getId(), historicTaskInstanceConverter, serviceVersion); + return users.stream().anyMatch(bpmnTaskDelegateAssigner::comparePersonIdToOther); + } + + private boolean isCarbonUsers(BpmnProcessInstanceVO instance, BpmnTaskDelegateAssigner bpmnTaskDelegateAssigner) { + if (Objects.isNull(bpmnTaskDelegateAssigner) || Objects.isNull(instance)) { + return false; + } + List users = CustomTaskHelper.getAllCarbonUsers(CommandContextUtil.getCommandContext(), instance.getId(), instance.getProcessDefinitionId()); + return users.stream().anyMatch(bpmnTaskDelegateAssigner::comparePersonIdToOther); } @Override @@ -165,21 +230,30 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis || !StringUtils.hasText(event.getNoticeConfig().getPending().getPendingMessageId())) { return; } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onPendingComplete...msgTemplateId: {}, receivePerson: {}", - event.getNoticeConfig().getPending().getPendingMessageId(), - JSON.toJSONString(event.getAssigners())); - } + log.info("RocketMqMessagePushEventListener#onPendingComplete...msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getPending().getPendingMessageId(), + JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); if (Objects.nonNull(event.getNoticeConfig().getPending())) { MessagePushDTO dto = build(event.getNoticeConfig().getPending().getPendingMessageId(), - PROCESS_PUSH_PENDING_COMPLETE, event, collectionVariable(event)); + PROCESS_PUSH_PENDING_COMPLETE, event, collectionVariable(event), event.getActivitySignature()); sendMessageQueue(dto, PROCESS_PUSH_PENDING_COMPLETE); } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onPendingComplete...end, msgTemplateId: {}, receivePerson: " + - "{}", event.getNoticeConfig().getPending().getPendingMessageId(), - JSON.toJSONString(event.getAssigners())); + log.info("RocketMqMessagePushEventListener#onPendingComplete...end, msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", event.getNoticeConfig().getPending().getPendingMessageId(), + JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); + } + + @Override + public void onPendingRollback(MessagePushEvent event) { + if (Objects.isNull(event.getNoticeConfig()) + || Objects.isNull(event.getNoticeConfig().getPending()) + || !StringUtils.hasText(event.getNoticeConfig().getPending().getPendingMessageId())) { + log.info("RocketMqMessagePushEventListener#onPendingRollback...ignore,pending message is null' event: {}", JSONUtil.toJsonStr(event)); + return; } + log.info("RocketMqMessagePushEventListener#onPendingRollback...start' event: {}", JSONUtil.toJsonStr(event)); + MessagePushDTO dto = build(event.getNoticeConfig().getPending().getPendingMessageId(), PROCESS_PUSH_PENDING_ROLLBACK, event, collectionVariable(event), event.getActivitySignature()); + sendMessageQueue(dto, PROCESS_PUSH_PENDING_ROLLBACK); + log.info("RocketMqMessagePushEventListener#onPendingRollback...end' event: {}", JSONUtil.toJsonStr(event)); } @Override @@ -189,21 +263,46 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis || !StringUtils.hasText(event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId())) { return; } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onCarbonCopy... cc' templateId: {}, receivePerson: {}", - event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), - JSON.toJSONString(event.getAssigners())); - } + log.info("RocketMqMessagePushEventListener#onCarbonCopy... cc' templateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), + JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); if (Objects.nonNull(event.getNoticeConfig().getCarbonCopy())) { - MessagePushDTO dto = build(event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), - PROCESS_CARBON_COPY, event, collectionVariable(event)); - sendMessageQueue(dto, PROCESS_CARBON_COPY); + //按人员拆分为多个批次发送消息 + getMessagePushDtoSlice(event, event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), PROCESS_CARBON_COPY) + .forEach(dto -> sendMessageQueue(dto, PROCESS_CARBON_COPY)); } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onCarbonCopy...end, cc' templateId: {}, receivePerson: {}", - event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), - JSON.toJSONString(event.getAssigners())); + log.info("RocketMqMessagePushEventListener#onCarbonCopy...end, cc' templateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), + JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); + } + + /** + * 根据人员数量,拆分消息发送 + * + * @param event 事件消息 + * @return 发送mq消息列表 + */ + private List getMessagePushDtoSlice(MessagePushEvent event, String templateId, ProcessMessagePushEventEnum type) { + if (event == null) { + throw new NullPointerException("event不能为空"); } + List assigners = event.getAssigners(); + if (CollectionUtils.isEmpty(assigners) || assigners.size() <= MQ_ASSIGNER_BATCH_SIZE) { + return Collections.singletonList(build(templateId, type, event, collectionVariable(event), event.getActivitySignature())); + } + List slice = new ArrayList<>(); + Map objectMap = collectionVariable(event); + int startIndex = 0; + do { + List batchAssigners = assigners.subList(startIndex, Integer.min(startIndex + MQ_ASSIGNER_BATCH_SIZE, assigners.size())); + MessagePushEventImpl messagePushEvent = new MessagePushEventImpl(event.getType()); + BeanConverter.convert(event, messagePushEvent); + messagePushEvent.setAssigner(batchAssigners); + MessagePushDTO dto = build(templateId, type, messagePushEvent, objectMap, event.getActivitySignature()); + slice.add(dto); + startIndex = startIndex + MQ_ASSIGNER_BATCH_SIZE; + } while (startIndex < assigners.size()); + return slice; } /** @@ -218,19 +317,15 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis || !StringUtils.hasText(event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId())) { return; } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onCarbonCopyComplete... cc' templateId: {}", - event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId()); - } + log.info("RocketMqMessagePushEventListener#onCarbonCopyComplete... cc' templateId: {}, processInstanceId: {}", + event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), event.getProcessInstanceId()); if (Objects.nonNull(event.getNoticeConfig().getCarbonCopy())) { MessagePushDTO dto = build(event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), - PROCESS_CARBON_COPY_COMPLETE, event, collectionVariable(event)); + PROCESS_CARBON_COPY_COMPLETE, event, collectionVariable(event), event.getActivitySignature()); sendMessageQueue(dto, PROCESS_CARBON_COPY_COMPLETE); } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onCarbonCopyComplete...end, cc' templateId: {}", - event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId()); - } + log.info("RocketMqMessagePushEventListener#onCarbonCopyComplete...end, cc' templateId: {}, processInstanceId: {}", + event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(), event.getProcessInstanceId()); } @Override @@ -241,31 +336,44 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis || Objects.isNull(event.getAssigners())) { return; } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onSms...msgTemplateId: {}, receivePerson: {}", - event.getNoticeConfig().getSms().getSmsId(), JSON.toJSONString(event.getAssigners())); - } + log.info("RocketMqMessagePushEventListener#onSms...msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getSms().getSmsId(), JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); if (Objects.nonNull(event.getNoticeConfig().getCarbonCopy())) { MessagePushDTO dto = build(event.getNoticeConfig().getSms().getSmsId(), - PROCESS_PUSH_SMS, event, collectionVariable(event)); + PROCESS_PUSH_SMS, event, collectionVariable(event), event.getActivitySignature()); sendMessageQueue(dto, PROCESS_PUSH_SMS); } - if (log.isDebugEnabled()) { - log.debug("RocketMqMessagePushEventListener#onSms...end, msgTemplateId: {}, receivePerson: {}", - event.getNoticeConfig().getSms().getSmsId(), JSON.toJSONString(event.getAssigners())); + log.info("RocketMqMessagePushEventListener#onSms...end, msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getNoticeConfig().getSms().getSmsId(), JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); + } + + @Override + public void onIm(MessagePushEvent event) { + if (!StringUtils.hasText(event.getImTemplateCode()) && !StringUtils.hasText(event.getTerminalType())) { + log.warn("RocketMqMessagePushEventListener#onIm...ignore, imTemplateCode or terminalType is empty, event: {}", JSONUtil.toJsonStr(event)); + return; } + log.info("RocketMqMessagePushEventListener#onIm...msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getImTemplateCode(), JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); + MessagePushDTO dto = build(event.getImTemplateCode(), + PROCESS_PUSH_IM, event, collectionVariable(event), event.getActivitySignature(), event.getTerminalType()); + sendMessageQueue(dto, PROCESS_PUSH_IM); + log.info("RocketMqMessagePushEventListener#onIm...end, msgTemplateId: {}, receivePerson: {}, processInstanceId: {}", + event.getImTemplateCode(), JSONUtil.toJsonStr(event.getAssigners()), event.getProcessInstanceId()); } private Map collectionVariable(MessagePushEvent event) { Map variables = new HashMap<>(); - BpmnProcessInstanceVO processInstance = getBpmnProcessInstanceVO(event); + BpmnProcessInstanceVO processInstance = getContext().getInstanceVO(() -> getBpmnProcessInstanceVO(event)); Map originVariables = processInstance.getVariables().entrySet().stream() .filter(e -> Objects.nonNull(e.getKey()) && Objects.nonNull(e.getValue())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - BpmnTaskDelegateAssigner initiator = (BpmnTaskDelegateAssigner) originVariables.getOrDefault(INTERNAL_INITIATOR, - new BpmnTaskDelegateAssigner()); + BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(originVariables.get(INTERNAL_INITIATOR)); + if (Objects.isNull(initiator)) { + initiator = new BpmnTaskDelegateAssigner(); + } SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); variables.put(INTERNAL_INITIATOR, initiator); variables.put(VAR_PROCESS_TENANT_ID, event.getTenantId()); @@ -273,6 +381,7 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis // 产品需求参数 variables.put(VAR_INITIATOR_USER_NAME, initiator.getAssignerName()); + variables.put(VAR_BUSINESS_NAME, processInstance.getCategoryDesc()); variables.put(VAR_PROCESS_INSTANCE_NAME, processInstance.getName()); variables.put(VAR_PROCESS_INSTANCE_ID, processInstance.getId()); variables.put(VAR_PROCESS_START_TIME, sdf.format(processInstance.getCreateAt())); @@ -286,12 +395,11 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis historyService.createHistoricTaskInstanceQuery().taskId(event.getTaskId()).list(); if (!CollectionUtils.isEmpty(tasks)) { // 应该只有 1 个, 但是为了防止有多个, 这里只取第一个 + @SuppressWarnings("unchecked") List assigners = (List) originVariables.getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + tasks.get(0).getTaskDefinitionKey(), Collections.emptyList()); assigners.stream().filter(i -> Objects.equals(i.buildAssigneeId(), tasks.get(0).getAssignee())).findAny() - .ifPresent(i -> { - variables.put(VAR_TASK_USER_NAME, i.getAssignerName()); - }); + .ifPresent(i -> variables.put(VAR_TASK_USER_NAME, i.getAssignerName())); variables.put(VAR_ACTIVITY_ID, tasks.get(0).getTaskDefinitionKey()); variables.put(VAR_ACTIVITY_NAME, tasks.get(0).getName()); variables.put(VAR_TASK_START_TIME, sdf.format(tasks.get(0).getCreateTime())); @@ -300,15 +408,9 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis } } - categoryService.get(BPM_MODEL_CATEGORY, processInstance.getCategory()).ifPresent(category -> { - variables.put(VAR_BUSINESS_NAME, category.getLabel()); - }); - // 传递业务的参数 REMOVE_KEYS.forEach(originVariables::remove); - if (log.isDebugEnabled()) { - log.debug("流程实例的参数: {}", JSON.toJSONString(originVariables)); - } + log.info("流程实例的参数: {}", JSONUtil.toJsonStr(originVariables)); if (CollectionUtils.isEmpty(originVariables)) { return variables; } @@ -335,14 +437,24 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis } public MessagePushDTO build(String templateId, ProcessMessagePushEventEnum type, MessagePushEvent event, Map variables) { + Object> variables, Boolean activitySignature) { + return build(templateId, type, event, variables, activitySignature, null); + } + + public MessagePushDTO build(String templateId, ProcessMessagePushEventEnum type, MessagePushEvent event, Map variables, Boolean activitySignature, String terminalType) { return new MessagePushDTO() .setProcessInstanceId(event.getProcessInstanceId()) + .setAdscriptionTenantId(event.getTenantId()) + .setProcessDefinitionKey(event.getProcessDefinitionKey()) .setType(type) .setTemplateId(templateId) .setTaskId(event.getTaskId()) .setReceivePersons(event.getAssigners()) - .setVariables(variables); + .setVariables(variables) + .setProcessApproveConf(event.getProcessApproveConfig()) + .setActivitySignature(activitySignature) + .setTerminalType(terminalType); } @@ -350,13 +462,25 @@ public class RocketMqMessagePushEventListener implements BpmnMessagePushEventLis if (!sendMQ) { return; } + Map header = new HashMap<>(); + if (StringUtils.hasText(dto.getProcessDefinitionKey())) { + log.info("record process definition key: {}", dto.getProcessDefinitionKey()); + header.put(MQ_OWNERSHIP_PROCESS_DEFINITION_KEY, dto.getProcessDefinitionKey()); + } + if (dto.getVariables().containsKey(PROCESS_OWNERSHIP_APPLICATION)) { + Object orDefault = dto.getVariables().getOrDefault(PROCESS_OWNERSHIP_APPLICATION, ""); + if (Objects.nonNull(orDefault)) { + log.info("record process ownership app name: {}", orDefault); + header.put(MQ_OWNERSHIP_APPLICATION, orDefault.toString()); + } + } eventProducer.send(Event.builder() .shardingKey(dto.getProcessInstanceId()) .eventCode(eventEnum.getEventCode()) .targetId(dto.getProcessInstanceId()) - .targetType(eventEnum.getTag()) + .targetType(dto.getProcessDefinitionKey()) .data(dto) - .build()); + .build(), header); } @Override diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/FileArchiveProcessEventListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/FileArchiveProcessEventListener.java new file mode 100644 index 000000000..985e7a6f1 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/FileArchiveProcessEventListener.java @@ -0,0 +1,120 @@ +package cn.axzo.workflow.server.controller.listener.process; + +import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.model.dto.SignFileDTO; +import cn.axzo.workflow.common.model.dto.VariableObjectDTO; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; +import cn.axzo.workflow.core.common.context.ProcessOperationContext; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +import cn.axzo.workflow.core.listener.BpmnProcessEventListener; +import cn.axzo.workflow.core.repository.entity.ExtAxDocContent; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign; +import cn.axzo.workflow.core.service.ExtAxDocContentService; +import cn.axzo.workflow.core.service.ExtAxModelDocService; +import cn.axzo.workflow.core.service.ExtAxProcessSignService; +import cn.axzo.workflow.server.common.util.WpsUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.Process; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; +import org.flowable.engine.HistoryService; +import org.flowable.engine.ProcessEngineConfiguration; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 签署业务,审批完成后,进行文件归档,并对所有工人发送业务待办 + * + * @author wangli + * @since 2025-04-01 17:58 + */ +@Slf4j +@Component +@Scope("prototype") +@RefreshScope +public class FileArchiveProcessEventListener extends AbstractBpmnEventListener implements BpmnProcessEventListener, Ordered { + @Resource + private HistoryService historyService; + @Resource + private ExtAxProcessSignService extAxProcessSignService; + @Resource + private ExtAxModelDocService extAxModelDocService; + @Resource + private ExtAxDocContentService extAxDocContentService; + @Resource + private WpsUtil wpsUtil; + + + @Override + public void onCompleted(FlowableEngineEntityEvent event) { + Process mainProcess = ProcessDefinitionUtil.getBpmnModel(event.getProcessDefinitionId()).getMainProcess(); + Optional signConfig = BpmnMetaParserHelper.getSignConfig(mainProcess); + if (!signConfig.isPresent() || Objects.isNull(signConfig.get().getSignType())) { + // 未配置签批设置 + return; + } + + String processInstanceId = event.getProcessInstanceId(); + HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId).includeProcessVariables().singleResult(); + + // 文件归档,将审批过程中产生的数据全部替换文档模板变量 + archiveFinalDocs(instance); + } + + private void archiveFinalDocs(HistoricProcessInstance instance) { + ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) getEngineConfiguration(); + ExtAxProcessSign processSign = extAxProcessSignService.findByProcessInstanceId(instance.getId()); + List wpsReplaceVariables = wpsUtil.getWpsReplaceVariables(processEngineConfiguration, instance.getId()); + processSign.getFileArchive().stream().filter(i -> Objects.equals(i.getFileType(), FileTypeEnum.WORD) + || Objects.equals(i.getFileType(), FileTypeEnum.EXCEL)) + .forEach(docBaseVO -> { + String newFileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, null, docBaseVO.getFileKey(), docBaseVO.getTemplateName() + docBaseVO.getFileType().getSuffix()); + docBaseVO.setFileKey(newFileKey); + }); + // 删除非 WPS 的临时文档 + removeTempFile(processSign); + // 暂时不删除 WPS 的临时文档 +// removeTempWpsFile(processSign); + extAxProcessSignService.updateById(processSign); + } + + private void removeTempWpsFile(ExtAxProcessSign processSign) { + processSign.getDocTemplate().stream() + .filter(i -> Objects.equals(i.getFileType(), FileTypeEnum.WORD) || i.getFileType().equals(FileTypeEnum.EXCEL)) + .forEach(docBaseVO -> { + wpsUtil.deleteWpsFile(docBaseVO.getFileCode()); + }); + } + + private void removeTempFile(ExtAxProcessSign processSign) { + List deleteTempFileIds = processSign.getDocTemplate().stream().filter(i -> Objects.equals(i.getFileType(), FileTypeEnum.HIPRINT)).map(SignFileDTO::getFileCode).map(Long::parseLong).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(deleteTempFileIds)) { + return; + } + List contents = extAxDocContentService.getByIds(deleteTempFileIds); + extAxModelDocService.batchDeleteDoc(contents.stream().map(ExtAxDocContent::getFileId).collect(Collectors.toList())); + } + + private ProcessEngineConfiguration getEngineConfiguration() { + return getContext().getProcessEngineConfiguration(CommandContextUtil::getProcessEngineConfiguration); + } + + @Override + public int getOrder() { + return Integer.MIN_VALUE + 3; + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/MessagePushProcessEventListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/MessagePushProcessEventListener.java index 316e3c7d0..75538d500 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/MessagePushProcessEventListener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/MessagePushProcessEventListener.java @@ -1,12 +1,14 @@ package cn.axzo.workflow.server.controller.listener.process; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; +import cn.axzo.workflow.core.common.context.ProcessOperationContext; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; 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.listener.AbstractBpmnEventListener; import cn.axzo.workflow.core.listener.BpmnProcessEventListener; -import com.alibaba.fastjson.JSON; +import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEvent; @@ -17,6 +19,7 @@ import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Scope; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -32,52 +35,37 @@ import java.util.Optional; */ @Slf4j @Component +@Scope("prototype") @RefreshScope -public class MessagePushProcessEventListener implements BpmnProcessEventListener, Ordered { +public class MessagePushProcessEventListener extends AbstractBpmnEventListener implements BpmnProcessEventListener, Ordered { @Value("${workflow.carbonCopyTemplateCode}") private String carbonCopyTemplateCode; @Override public void onCancelled(FlowableCancelledEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCancelled...{}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onCancelled...processInstanceId:{}", event.getProcessInstanceId()); pendingComplete(event); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCancelled...end: {}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onCancelled...end: processInstanceId: {}", event.getProcessInstanceId()); } @Override public void onRejected(FlowableCancelledEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onRejected...{}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onRejected...processInstanceId:{}", event.getProcessInstanceId()); pendingComplete(event); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onRejected...end: {}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onRejected...end: processInstanceId: {}", event.getProcessInstanceId()); } @Override public void onAborted(FlowableCancelledEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onAborted...{}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onAborted...processInstanceId:{}", event.getProcessInstanceId()); pendingComplete(event); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onAborted...end: {}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onAborted...end: processInstanceId: {}", event.getProcessInstanceId()); } @Override public void onCompleted(FlowableEngineEntityEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCompleted...{}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onCompleted...processInstanceId:{}", event.getProcessInstanceId()); pendingComplete(event); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCompleted...end: {}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onCompleted...end: processInstanceId: {}", event.getProcessInstanceId()); } private void pendingComplete(FlowableEngineEvent event) { @@ -88,11 +76,9 @@ public class MessagePushProcessEventListener implements BpmnProcessEventListener optNoticeConfig.ifPresent(noticeConfig -> { MessagePushEventImpl messagePushEvent = MessagePushEventBuilder.createEvent(MessagePushEventType.PENDING_COMPLETE, null, noticeConfig, - event.getProcessInstanceId(), null, null); + event.getProcessInstanceId(), parseProcessDefinitionKey(event.getProcessDefinitionId()), null, null, false); - if (log.isDebugEnabled()) { - log.debug("发送完成待办的消息: {}", JSON.toJSONString(messagePushEvent)); - } + log.info("发送完成实例下所有待办的消息: {}", JSONUtil.toJsonStr(messagePushEvent)); eventDispatcher.dispatchEvent(messagePushEvent, processEngineConfiguration.getEngineCfgKey()); if (Objects.nonNull(noticeConfig.getCarbonCopy())) { @@ -101,11 +87,9 @@ public class MessagePushProcessEventListener implements BpmnProcessEventListener } MessagePushEventImpl carbonCopyCompleteEvent = MessagePushEventBuilder.createEvent(MessagePushEventType.CARBON_COPY_COMPLETE, null, noticeConfig, - event.getProcessInstanceId(), null, null); + event.getProcessInstanceId(), parseProcessDefinitionKey(event.getProcessDefinitionId()), null, null, false); eventDispatcher.dispatchEvent(carbonCopyCompleteEvent, processEngineConfiguration.getEngineCfgKey()); - if (log.isDebugEnabled()) { - log.debug("发送完成抄送的消息: {}", JSON.toJSONString(carbonCopyCompleteEvent)); - } + log.info("发送完成抄送的消息: {}", JSONUtil.toJsonStr(carbonCopyCompleteEvent)); } }); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/RocketMqBpmnProcessEventListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/RocketMqBpmnProcessEventListener.java index d9ea6d5e2..0af876e95 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/RocketMqBpmnProcessEventListener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/RocketMqBpmnProcessEventListener.java @@ -5,7 +5,9 @@ import cn.axzo.framework.rocketmq.EventProducer; import cn.axzo.workflow.common.enums.ProcessInstanceEventEnum; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO; +import cn.axzo.workflow.core.common.context.ProcessOperationContext; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; import cn.axzo.workflow.core.listener.BpmnProcessEventListener; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.Process; @@ -18,15 +20,23 @@ import org.flowable.engine.delegate.event.impl.FlowableProcessCancelledEventImpl import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; import org.flowable.engine.repository.Deployment; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import static cn.axzo.workflow.common.constant.BpmnConstants.CLOSE_PROCESS_ASSIGNER; 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_PROCESS_DELETE_REASON; +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_APPLICATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_PROCESS_DEFINITION_KEY; +import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_OWNERSHIP_APPLICATION; import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION; import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INSTANCE_ABORTED; import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INSTANCE_CANCELLED; @@ -44,7 +54,8 @@ import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INS */ @Slf4j @Component -public class RocketMqBpmnProcessEventListener implements BpmnProcessEventListener, Ordered { +@Scope("prototype") +public class RocketMqBpmnProcessEventListener extends AbstractBpmnEventListener implements BpmnProcessEventListener, Ordered { @Resource private RuntimeService runtimeService; @Resource @@ -56,108 +67,101 @@ public class RocketMqBpmnProcessEventListener implements BpmnProcessEventListene @Override public void onCreated(FlowableEngineEntityEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCreated...{}", event.getProcessInstanceId()); - } - Deployment deployment = - repositoryService.createDeploymentQuery().deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId()).singleResult(); - Process mainProcess = repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess(); + log.info("MessagePushProcessEventListener#onCreated..., processInstanceId: {}", event.getProcessInstanceId()); + Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery() + .deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId()).singleResult()); + Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess()); + BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((ExecutionEntityImpl) event.getEntity()).getVariable(INTERNAL_INITIATOR))); ProcessInstanceDTO dto = new ProcessInstanceDTO() .setType(PROCESS_INSTANCE_CREATED) - .setCategory(deployment.getCategory()) + .setCategory(deployment.getKey()) .setProcessInstanceId(event.getProcessInstanceId()) .setCurrentElementKey(((ExecutionEntityImpl) event.getEntity()).getStartActivityId()) .setProcessDefinitionId(event.getProcessDefinitionId()) .setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey()) .setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion()) - .setInitiator(((ExecutionEntityImpl) event.getEntity()).getVariable(INTERNAL_INITIATOR, - BpmnTaskDelegateAssigner.class)) + .setInitiator(initiator) .setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables()) .setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime()) .setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId()) .setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getBusinessKey()) .setBusinessProcessInstanceName(((ExecutionEntityImpl) event.getEntity()).getName()); BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); - String version = (String) runtimeService.getVariable(event.getProcessInstanceId(), WORKFLOW_ENGINE_VERSION); - if (Objects.isNull(version)) { - version = FLOW_SERVER_VERSION_121; - } - dto.setWorkflowEngineVersion(version); + BpmnMetaParserHelper.getSignConfig(mainProcess).ifPresent(dto::setSignConf); + + setProcessInstanceVersion(event.getProcessInstanceId(), dto); sendMessageQueue(dto, PROCESS_INSTANCE_CREATED); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCreated...end: {}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onCreated...end, processInstanceId: {}", event.getProcessInstanceId()); } + @Override public void onStarted(FlowableProcessStartedEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onStarted...{}", ((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId()); - } - Deployment deployment = - repositoryService.createDeploymentQuery().deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId()) - .singleResult(); - Process mainProcess = - repositoryService.getBpmnModel(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionId()).getMainProcess(); + log.info("MessagePushProcessEventListener#onStarted..., processInstanceId: {}", event.getNestedProcessInstanceId()); + Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery() + .deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId()) + .singleResult()); + + Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(((ExecutionEntityImpl) event.getEntity()) + .getProcessDefinitionId()).getMainProcess()); + BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((ExecutionEntityImpl) event.getEntity()) + .getVariable(INTERNAL_INITIATOR))); + ProcessInstanceDTO dto = new ProcessInstanceDTO() .setType(PROCESS_INSTANCE_STARTED) - .setCategory(deployment.getCategory()) + .setCategory(deployment.getKey()) .setProcessInstanceId(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId()) .setCurrentElementKey(((ExecutionEntityImpl) event.getEntity()).getCurrentActivityId()) .setProcessDefinitionId(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionId()) .setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey()) .setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion()) - .setInitiator(((ExecutionEntityImpl) event.getEntity()).getVariable(INTERNAL_INITIATOR, - BpmnTaskDelegateAssigner.class)) + .setInitiator(initiator) .setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables()) .setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime()) .setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId()) .setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getProcessInstance().getBusinessKey()) .setBusinessProcessInstanceName(((ExecutionEntityImpl) event.getEntity()).getProcessInstance().getName()); BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); - String version = - (String) runtimeService.getVariable(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId(), - WORKFLOW_ENGINE_VERSION); - if (Objects.isNull(version)) { - version = FLOW_SERVER_VERSION_121; - } - dto.setWorkflowEngineVersion(version); + BpmnMetaParserHelper.getSignConfig(mainProcess).ifPresent(dto::setSignConf); + + setProcessInstanceVersion(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId(), dto); sendMessageQueue(dto, PROCESS_INSTANCE_STARTED); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onStarted...end: {}", ((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onStarted...end: {}", ((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId()); } @Override public void onCancelled(FlowableCancelledEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCancelled...{}", event.getProcessInstanceId()); - } - Deployment deployment = - repositoryService.createDeploymentQuery().deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId()) - .singleResult(); - Process mainProcess = repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess(); + log.info("MessagePushProcessEventListener#onCancelled..., processInstanceId: {}", event.getProcessInstanceId()); + Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery() + .deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId()) + .singleResult()); + Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess()); + BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((FlowableProcessCancelledEventImpl) event) + .getExecution().getVariable(INTERNAL_INITIATOR))); + ProcessInstanceDTO dto = new ProcessInstanceDTO() .setType(PROCESS_INSTANCE_CANCELLED) - .setCategory(deployment.getCategory()) + .setCategory(deployment.getKey()) .setProcessInstanceId(event.getProcessInstanceId()) .setCurrentElementKey("撤回流程实例无节点信息") .setProcessDefinitionId(event.getProcessDefinitionId()) .setProcessDefinitionKey(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionKey()) .setProcessDefinitionVersion(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionVersion()) - .setInitiator(((FlowableProcessCancelledEventImpl) event).getExecution().getVariable(INTERNAL_INITIATOR, BpmnTaskDelegateAssigner.class)) + .setInitiator(initiator) + .setLastOperationAssigner(getContext().getLastOperationAssigner(() -> BpmnTaskDelegateAssigner.toObjectCompatible( + runtimeService.getVariable(event.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, BpmnTaskDelegateAssigner.class)))) .setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables()) .setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime()) .setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId()) .setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey()) .setBusinessProcessInstanceName(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getName()); BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); + BpmnMetaParserHelper.getSignConfig(mainProcess).ifPresent(dto::setSignConf); + setProcessDeleteReason(event, dto); - setWorkflowEngineVersion(event, dto); + setProcessInstanceVersion(event.getProcessInstanceId(), dto); sendMessageQueue(dto, PROCESS_INSTANCE_CANCELLED); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCancelled...end: {}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onCancelled...end, processInstanceId: {}", event.getProcessInstanceId()); } /** @@ -167,66 +171,110 @@ public class RocketMqBpmnProcessEventListener implements BpmnProcessEventListene */ @Override public void onRejected(FlowableCancelledEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onRejected...{}", event.getProcessInstanceId()); - } - Deployment deployment = - repositoryService.createDeploymentQuery().deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId()) - .singleResult(); - Process mainProcess = repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess(); + log.info("MessagePushProcessEventListener#onRejected..., processInstanceId: {}", event.getProcessInstanceId()); + Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery() + .deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId()) + .singleResult()); + Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess()); + BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((FlowableProcessCancelledEventImpl) event) + .getExecution().getVariable(INTERNAL_INITIATOR))); + ProcessInstanceDTO dto = new ProcessInstanceDTO() .setType(PROCESS_INSTANCE_REJECTED) - .setCategory(deployment.getCategory()) + .setCategory(deployment.getKey()) .setProcessInstanceId(event.getProcessInstanceId()) .setCurrentElementKey("驳回流程实例无节点信息") .setProcessDefinitionId(event.getProcessDefinitionId()) .setProcessDefinitionKey(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionKey()) .setProcessDefinitionVersion(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionVersion()) - .setInitiator(((FlowableProcessCancelledEventImpl) event).getExecution().getVariable(INTERNAL_INITIATOR, BpmnTaskDelegateAssigner.class)) + .setInitiator(initiator) + .setLastOperationAssigner(getContext().getLastOperationAssigner(() -> BpmnTaskDelegateAssigner.toObjectCompatible( + runtimeService.getVariable(event.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, BpmnTaskDelegateAssigner.class)))) .setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables()) .setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime()) .setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId()) .setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey()) .setBusinessProcessInstanceName(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getName()); BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); + BpmnMetaParserHelper.getSignConfig(mainProcess).ifPresent(dto::setSignConf); + setProcessDeleteReason(event, dto); - setWorkflowEngineVersion(event, dto); + setProcessInstanceVersion(event.getProcessInstanceId(), dto); sendMessageQueue(dto, PROCESS_INSTANCE_REJECTED); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onRejected...end: {}", event.getProcessInstanceId()); - } + log.info("MessagePushProcessEventListener#onRejected...end, processInstanceId: {}", event.getProcessInstanceId()); } @Override public void onAborted(FlowableCancelledEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onAborted...{}", event.getProcessInstanceId()); - } - Deployment deployment = - repositoryService.createDeploymentQuery().deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId()) - .singleResult(); - Process mainProcess = repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess(); + log.info("MessagePushProcessEventListener#onAborted..., processInstanceId: {}", event.getProcessInstanceId()); + Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery() + .deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId()) + .singleResult()); + Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess()); + BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((FlowableProcessCancelledEventImpl) event) + .getExecution().getVariable(INTERNAL_INITIATOR))); + ProcessInstanceDTO dto = new ProcessInstanceDTO() .setType(PROCESS_INSTANCE_ABORTED) - .setCategory(deployment.getCategory()) + .setCategory(deployment.getKey()) .setProcessInstanceId(event.getProcessInstanceId()) .setCurrentElementKey("中止流程实例无节点信息") .setProcessDefinitionId(event.getProcessDefinitionId()) .setProcessDefinitionKey(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionKey()) .setProcessDefinitionVersion(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionVersion()) - .setInitiator(((FlowableProcessCancelledEventImpl) event).getExecution().getVariable(INTERNAL_INITIATOR, BpmnTaskDelegateAssigner.class)) + .setInitiator(initiator) + .setLastOperationAssigner(getContext().getLastOperationAssigner(() -> BpmnTaskDelegateAssigner.toObjectCompatible( + runtimeService.getVariable(event.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, BpmnTaskDelegateAssigner.class)))) .setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables()) .setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime()) .setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId()) .setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey()) .setBusinessProcessInstanceName(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getName()); BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); + BpmnMetaParserHelper.getSignConfig(mainProcess).ifPresent(dto::setSignConf); + setProcessDeleteReason(event, dto); - setWorkflowEngineVersion(event, dto); + setProcessInstanceVersion(event.getProcessInstanceId(), dto); sendMessageQueue(dto, PROCESS_INSTANCE_ABORTED); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onAborted...end: {}", event.getProcessInstanceId()); + log.info("MessagePushProcessEventListener#onAborted...end, processInstanceId: {}.", event.getProcessInstanceId()); + } + + @Override + public void onCompleted(FlowableEngineEntityEvent event) { + log.info("MessagePushProcessEventListener#onCompleted...,processInstanceId: {}", event.getProcessInstanceId()); + Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery() + .deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId()) + .singleResult()); + Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess()); + BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((ExecutionEntityImpl) event.getEntity()) + .getVariable(INTERNAL_INITIATOR))); + + ProcessInstanceDTO dto = new ProcessInstanceDTO() + .setType(PROCESS_INSTANCE_COMPLETED) + .setCategory(deployment.getKey()) + .setProcessInstanceId(event.getProcessInstanceId()) + .setCurrentElementKey("撤回流程实例无节点信息") + .setProcessDefinitionId(event.getProcessDefinitionId()) + .setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey()) + .setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion()) + .setInitiator(initiator) + .setLastOperationAssigner(getContext().getLastOperationAssigner(() -> BpmnTaskDelegateAssigner.toObjectCompatible( + runtimeService.getVariable(event.getProcessInstanceId(), CLOSE_PROCESS_ASSIGNER, BpmnTaskDelegateAssigner.class)))) + .setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables()) + .setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime()) + .setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId()) + .setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceBusinessKey()) + .setBusinessProcessInstanceName(((ExecutionEntityImpl) event.getEntity()).getName()); + BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); + BpmnMetaParserHelper.getSignConfig(mainProcess).ifPresent(dto::setSignConf); + + String version = (String) runtimeService.getVariable(event.getProcessInstanceId(), WORKFLOW_ENGINE_VERSION); + if (Objects.isNull(version)) { + version = FLOW_SERVER_VERSION_121; } + dto.setWorkflowEngineVersion(version); + sendMessageQueue(dto, PROCESS_INSTANCE_COMPLETED); + log.info("MessagePushProcessEventListener#onCompleted...end,processInstanceId: {}", event.getProcessInstanceId()); } private void setProcessDeleteReason(FlowableCancelledEvent event, ProcessInstanceDTO dto) { @@ -234,61 +282,39 @@ public class RocketMqBpmnProcessEventListener implements BpmnProcessEventListene dto.setReason(processDeleteReason); } - private void setWorkflowEngineVersion(FlowableCancelledEvent event, ProcessInstanceDTO dto) { - String version = (String) runtimeService.getVariable(event.getProcessInstanceId(), WORKFLOW_ENGINE_VERSION); - if (Objects.isNull(version)) { - version = FLOW_SERVER_VERSION_121; - } - dto.setWorkflowEngineVersion(version); - } - @Override - public void onCompleted(FlowableEngineEntityEvent event) { - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCompleted...{}", event.getProcessInstanceId()); - } - Deployment deployment = - repositoryService.createDeploymentQuery().deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId()) - .singleResult(); - Process mainProcess = repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess(); - ProcessInstanceDTO dto = new ProcessInstanceDTO() - .setType(PROCESS_INSTANCE_COMPLETED) - .setCategory(deployment.getCategory()) - .setProcessInstanceId(event.getProcessInstanceId()) - .setCurrentElementKey("撤回流程实例无节点信息") - .setProcessDefinitionId(event.getProcessDefinitionId()) - .setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey()) - .setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion()) - .setInitiator(((ExecutionEntityImpl) event.getEntity()).getVariable(INTERNAL_INITIATOR, - BpmnTaskDelegateAssigner.class)) - .setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables()) - .setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime()) - .setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId()) - .setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceBusinessKey()) - .setBusinessProcessInstanceName(((ExecutionEntityImpl) event.getEntity()).getName()); - BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); - String version = (String) runtimeService.getVariable(event.getProcessInstanceId(), WORKFLOW_ENGINE_VERSION); + private void setProcessInstanceVersion(String processInstanceId, ProcessInstanceDTO dto) { + String version = getContext().getProcessInstanceVersion(() -> runtimeService.getVariable(processInstanceId, WORKFLOW_ENGINE_VERSION, String.class)); + if (Objects.isNull(version)) { version = FLOW_SERVER_VERSION_121; } dto.setWorkflowEngineVersion(version); - sendMessageQueue(dto, PROCESS_INSTANCE_COMPLETED); - if (log.isDebugEnabled()) { - log.debug("MessagePushProcessEventListener#onCompleted...end: {}", event.getProcessInstanceId()); - } } private void sendMessageQueue(ProcessInstanceDTO dto, ProcessInstanceEventEnum eventEnum) { if (!sendMQ) { return; } + Map header = new HashMap<>(); + if (StringUtils.hasText(dto.getProcessDefinitionKey())) { + log.info("record process definition key: {}", dto.getProcessDefinitionKey()); + header.put(MQ_OWNERSHIP_PROCESS_DEFINITION_KEY, dto.getProcessDefinitionKey()); + } + if (dto.getVariables().containsKey(PROCESS_OWNERSHIP_APPLICATION)) { + Object orDefault = dto.getVariables().getOrDefault(PROCESS_OWNERSHIP_APPLICATION, ""); + if (Objects.nonNull(orDefault)) { + log.info("record process ownership app name: {}", orDefault); + header.put(MQ_OWNERSHIP_APPLICATION, orDefault.toString()); + } + } eventProducer.send(Event.builder() .shardingKey(dto.getProcessInstanceId()) .eventCode(eventEnum.getEventCode()) .targetId(dto.getProcessInstanceId()) - .targetType(eventEnum.getTag()) + .targetType(dto.getProcessDefinitionKey()) .data(dto) - .build()); + .build(), header); } @Override diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/SyncToEsProcessEventListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/SyncToEsProcessEventListener.java new file mode 100644 index 000000000..49ac875af --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/process/SyncToEsProcessEventListener.java @@ -0,0 +1,98 @@ +package cn.axzo.workflow.server.controller.listener.process; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventProducer; +import cn.axzo.workflow.common.enums.ElasticSearchEventEnum; +import cn.axzo.workflow.core.common.context.ProcessOperationContext; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +import cn.axzo.workflow.core.listener.BpmnProcessEventListener; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; +import org.flowable.engine.delegate.event.FlowableCancelledEvent; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 流程实例结束后,同步最新数据至 ES + * + * @author wangli + * @since 2024-10-18 11:09 + */ +@Slf4j +@Component +@Scope("prototype") +public class SyncToEsProcessEventListener extends AbstractBpmnEventListener implements BpmnProcessEventListener, Ordered { + @Resource + private EventProducer eventProducer; + @Value("${sendMq:true}") + private Boolean sendMQ; + + @Override + public int getOrder() { + return Integer.MIN_VALUE + 2; + } + + /** + * 流程实例被撤回后回调 + * + * @param event + */ + @Override + public void onCancelled(FlowableCancelledEvent event) { + syncToEs(event.getProcessInstanceId()); + } + + /** + * 流程实例被驳回后回调 + * + * @param event + */ + @Override + public void onRejected(FlowableCancelledEvent event) { + syncToEs(event.getProcessInstanceId()); + } + + /** + * 流程实例被中止后回调 + * + * @param event + */ + @Override + public void onAborted(FlowableCancelledEvent event) { + syncToEs(event.getProcessInstanceId()); + } + + /** + * 流程实例运行完成后回调 + *

+ * 注意: 完成只是说明流程实例已停止运行 + * + * @param event + */ + @Override + public void onCompleted(FlowableEngineEntityEvent event) { + syncToEs(event.getProcessInstanceId()); + } + + public void syncToEs(String processInstanceId) { + sendMessageQueue(processInstanceId, ElasticSearchEventEnum.ELASTIC_SEARCH_SYNC); + } + + private void sendMessageQueue(String processInstanceId, ElasticSearchEventEnum eventEnum) { + if (!sendMQ) { + return; + } + eventProducer.send(Event.builder() + .shardingKey(processInstanceId) + .eventCode(eventEnum.getEventCode()) + .targetId(processInstanceId) + .targetType(processInstanceId) + .data(processInstanceId) + .build()); + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/AutoOperatorEvent_101_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/AutoOperatorEvent_101_Listener.java new file mode 100644 index 000000000..a165a4771 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/AutoOperatorEvent_101_Listener.java @@ -0,0 +1,218 @@ +package cn.axzo.workflow.server.controller.listener.task; + +import cn.axzo.workflow.common.enums.AutoApprovalTypeEnum; +import cn.axzo.workflow.common.model.request.BpmnApproveConf; +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.common.context.TaskOperationContext; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.job.AsyncApproveTaskJobHandler; +import cn.axzo.workflow.core.engine.tx.listener.AutoPassTransactionListener; +import cn.axzo.workflow.core.engine.tx.listener.AutoRejectTransactionListener; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +import cn.axzo.workflow.core.listener.BpmnTaskEventListener; +import cn.axzo.workflow.core.version.MultiVersionBeanUtils; +import cn.axzo.workflow.server.controller.listener.task.service.CheckApproverService; +import cn.hutool.json.JSONUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.UserTask; +import org.flowable.common.engine.impl.cfg.TransactionState; +import org.flowable.common.engine.impl.context.Context; +import org.flowable.engine.RepositoryService; +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.service.delegate.DelegateTask; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.annotation.Nullable; +import java.util.Objects; +import java.util.Optional; + +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.WORKFLOW_ENGINE_VERSION; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; + +/** + * UserTask 节点自动通过和驳回处理 + * + * @author wangli + * @since 2023/11/21 11:30 + */ +@Slf4j +@Component +@Scope("prototype") +@AllArgsConstructor +public class AutoOperatorEvent_101_Listener extends AbstractBpmnEventListener implements BpmnTaskEventListener, Ordered { + @Override + public int getOrder() { + return Integer.MIN_VALUE + 101; + } + + private final RepositoryService repositoryService; + + @Override + public void onCreated(DelegateTask delegateTask) { + log.info("AutoOperatorEventListener#onCreated...{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + + if (Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) { + createAsyncApproveJob(delegateTask); + return; + } + + Process mainProcess = repositoryService.getBpmnModel(delegateTask.getProcessDefinitionId()).getMainProcess(); + UserTask userTask = (UserTask) mainProcess.getFlowElement(delegateTask.getTaskDefinitionKey()); + + Optional processApproveConf = BpmnMetaParserHelper.getProcessApproveConf(mainProcess); + // 开启了电子签名不能自动过审 + Boolean activitySignature = BpmnMetaParserHelper.getActivitySignature(mainProcess.getFlowElement(delegateTask.getTaskDefinitionKey())); + //自动过审配置连续节点自动过审才处理,历史数据默认不自动过审 + if (processApproveConf.isPresent() + && AutoApprovalTypeEnum.CONTINUOUS_NODES_AUTO_APPROVAL == processApproveConf.get().getAutoApprovalType() + && !activitySignature) { + Object versionVar = delegateTask.getVariable(WORKFLOW_ENGINE_VERSION); + String version = versionVar == null ? null : String.valueOf(versionVar); + CheckApproverService checkApproverService = MultiVersionBeanUtils.getSpecifiedVersionBean(CheckApproverService.class, version); + boolean exists = checkApproverService.checkApproverExists(delegateTask, userTask, mainProcess, getContext()); + log.info("是否需要自动过程判断 exists:{},processInstId:{},taskDefinitionKey:{}", exists, delegateTask.getProcessInstanceId(), delegateTask.getTaskDefinitionKey()); + if (exists) { + autoPass(delegateTask, null,"已同意(同一审批人,自动过审)"); + } + } + // 检测节点自身配置是否有自动操作 + checkApprovalMethod(delegateTask, userTask); + + log.info("AutoOperatorEventListener#onCreated...end:{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + } + + private void createAsyncApproveJob(DelegateTask delegateTask) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService(); + + JobEntity job = jobService.createJob(); + // 这里的 executionId 可为 null + job.setExecutionId(delegateTask.getExecutionId()); + job.setProcessInstanceId(delegateTask.getProcessInstanceId()); + job.setProcessDefinitionId(delegateTask.getProcessDefinitionId()); + job.setElementId(delegateTask.getTaskDefinitionKey()); + job.setElementName(delegateTask.getName()); + job.setJobHandlerType(AsyncApproveTaskJobHandler.TYPE); + job.setTenantId(delegateTask.getTenantId()); + + BpmnTaskAuditDTO dto = new BpmnTaskAuditDTO(); + dto.setTaskId(delegateTask.getId()); + + BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(delegateTask.getVariable(INTERNAL_INITIATOR))); + if (Objects.nonNull(initiator)) { + delegateTask.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId(), + initiator.toJson()); + } + dto.setApprover(initiator); + + // 携带自定义的数据 + job.setCustomValues(JSONUtil.toJsonStr(dto)); + + // 创建异步任务并调度 + jobService.createAsyncJob(job, false); + jobService.scheduleAsyncJob(job); + } + + /** + * 如果审批人为空时, 读取 approverEmptyHandleType = 自动通过或自动驳回 + * + * @param delegateTask + * @param userTask + */ + private void checkApproverEmptyHandle(DelegateTask delegateTask, UserTask userTask) { + if (!StringUtils.hasText(delegateTask.getAssignee())) { + BpmnMetaParserHelper.getApproverEmptyHandleType(userTask) + .ifPresent(approverEmptyHandleTypeEnum -> { + switch (approverEmptyHandleTypeEnum) { + case autoPassed: + autoPass(delegateTask, "", "未找到审批人,自动同意"); + break; + case autoRejection: + autoReject(delegateTask, "未找到审批人,自动驳回"); + break; + case autoSkipped: + // autoReject(delegateTask); + // 非产品需求, 暂时不实现, 这里的功能似乎可以用 taskService.deleteTask 来实现 + break; + default: + break; + } + }); + } + } + + /** + * 如果 approverMethod = 自动通过或自动驳回时 + * + * @param delegateTask + * @param userTask + */ + private void checkApprovalMethod(DelegateTask delegateTask, UserTask userTask) { + BpmnMetaParserHelper.getApprovalMethod(userTask) + .ifPresent(approvalMethodEnum -> { + switch (approvalMethodEnum) { + case autoPassed: + autoPass(delegateTask); + break; + case autoRejection: + autoReject(delegateTask); + break; + case bizSpecify: + break; + default: + checkApproverEmptyHandle(delegateTask, userTask); + break; + } + }); + } + + /** + * 注意:该方法的调用需要考虑到会签节点,动态增减审批人时,对判断是否离开当前节点条件表达式会有一定影响 + *

+ * 建议:优先考虑使用 {@link AutoRejectTransactionListener} 事务监听 + * + * @param delegateTask 自动操作的任务 + */ + private void autoReject(DelegateTask delegateTask, @Nullable String operationDesc) { + Context.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, + new AutoRejectTransactionListener(delegateTask, operationDesc)); + } + + private void autoReject(DelegateTask delegateTask) { + autoReject(delegateTask,"无需审批人,自动驳回" ); + } + + /** + * 注意:该方法的调用需要考虑到会签节点,动态增减审批人时,对判断是否离开当前节点条件表达式会有一定影响 + *

+ * 建议:优先考虑使用 {@link AutoPassTransactionListener} 事务监听 + * + * @param delegateTask 自动操作的任务 + * @param advice 添加自动操作任务时的额外意见 + */ + private void autoPass(DelegateTask delegateTask, @Nullable String advice, @Nullable String operationDesc) { + Context.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, + new AutoPassTransactionListener(delegateTask, advice, operationDesc)); + } + + /** + * 自动通过 + * + * @param delegateTask 自动操作的任务 + */ + private void autoPass(DelegateTask delegateTask) { + autoPass(delegateTask, null, "无需审批人,自动同意"); + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/AutoOperatorEvent_103_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/AutoOperatorEvent_103_Listener.java deleted file mode 100644 index 75b387343..000000000 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/AutoOperatorEvent_103_Listener.java +++ /dev/null @@ -1,288 +0,0 @@ -package cn.axzo.workflow.server.controller.listener.task; - -import cn.axzo.workflow.common.enums.BpmnFlowNodeType; -import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; -import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; -import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; -import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd; -import cn.axzo.workflow.core.listener.BpmnTaskEventListener; -import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; -import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.flowable.bpmn.model.FlowElement; -import org.flowable.bpmn.model.Process; -import org.flowable.bpmn.model.UserTask; -import org.flowable.common.engine.impl.interceptor.CommandExecutor; -import org.flowable.engine.HistoryService; -import org.flowable.engine.RepositoryService; -import org.flowable.engine.RuntimeService; -import org.flowable.engine.TaskService; -import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; -import org.flowable.engine.impl.persistence.entity.ActivityInstanceEntity; -import org.flowable.engine.impl.util.CommandContextUtil; -import org.flowable.task.api.history.HistoricTaskInstance; -import org.flowable.task.service.delegate.DelegateTask; -import org.springframework.core.Ordered; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; - -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_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; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_NAME; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; -import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_REJECT; -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_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.REJECTED; -import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.REJECTION_AUTO_COMPLETED; - -/** - * UserTask 节点自动通过和驳回处理 - * - * @author wangli - * @since 2023/11/21 11:30 - */ -@Slf4j -@Component -@AllArgsConstructor -public class AutoOperatorEvent_103_Listener implements BpmnTaskEventListener, Ordered { - @Override - public int getOrder() { - return Integer.MIN_VALUE + 103; - } - - private final TaskService taskService; - private final RuntimeService runtimeService; - private final RepositoryService repositoryService; - private final HistoryService historyService; - private final ExtAxHiTaskInstService extAxHiTaskInstService; - - @Override - public void onCreated(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("AutoOperatorEventListener#onCreated...{}", delegateTask.getTaskDefinitionKey()); - } - - if (Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) { - BpmnTaskDelegateAssigner initiator = delegateTask.getVariable(INTERNAL_INITIATOR, - BpmnTaskDelegateAssigner.class); - delegateTask.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId(), - initiator); - delegateTask.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + delegateTask.getId(), - APPROVED.getStatus()); - // 直接完成 - taskService.complete(delegateTask.getId(), runtimeService.getVariables(delegateTask.getExecutionId())); - - return; - } - - Process mainProcess = repositoryService.getBpmnModel(delegateTask.getProcessDefinitionId()).getMainProcess(); - UserTask userTask = (UserTask) mainProcess.getFlowElement(delegateTask.getTaskDefinitionKey()); - - boolean exists = checkApproverExists(delegateTask, userTask, mainProcess); - if (exists) { - taskService.addComment(delegateTask.getId(), delegateTask.getProcessInstanceId(), COMMENT_TYPE_ADVICE, - "同一审批人,自动过审"); - autoPass(delegateTask); - } - - // 检测节点自身配置是否有自动操作 - checkApprovalMethod(delegateTask, userTask); - - if (log.isDebugEnabled()) { - log.debug("AutoOperatorEventListener#onCreated...end: {}", delegateTask.getTaskDefinitionKey()); - } - } - - /** - * 校验当前的审批人是否存在过前一个节点 - * - * @param delegateTask - * @param userTask - * @param mainProcess - */ - private boolean checkApproverExists(DelegateTask delegateTask, UserTask userTask, Process mainProcess) { - AtomicBoolean exists = new AtomicBoolean(false); - FlowElement currentFlowElement = mainProcess.getFlowElement(delegateTask.getTaskDefinitionKey()); - BpmnFlowNodeType currentNodeType = BpmnMetaParserHelper.getNodeType(currentFlowElement).orElse(NODE_EMPTY); - if (!Objects.equals(currentNodeType, NODE_TASK)) { - 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 = delegateTask.getVariable(INTERNAL_INITIATOR, - BpmnTaskDelegateAssigner.class); - if (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)) { - ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); - searchDTO.setProcessInstanceId(delegateTask.getProcessInstanceId()); - searchDTO.setTaskDefinitionKey(i.getActivityId()); - 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)); - } - }); - } - }); - return exists.get(); - } - - /** - * 特殊审批人字段的比对, 兼容旧迭代导致的数据格式 - * - * @return - */ - private boolean specialApproverComparison(String compareAssignee, String currentAssignee) { - if (StringUtils.hasText(compareAssignee) && StringUtils.hasText(currentAssignee)) { - String[] compareSplit = compareAssignee.split("\\|"); - String[] currentSplit = currentAssignee.split("\\|"); - if (compareSplit.length == 2 || currentSplit.length == 2) { - return Objects.equals(compareSplit[1], currentSplit[1]); - } - } - return false; - } - - /** - * 如果审批人为空时, 读取 approverEmptyHandleType = 自动通过或自动驳回 - * - * @param delegateTask - * @param userTask - */ - private void checkApproverEmptyHandle(DelegateTask delegateTask, UserTask userTask) { - if (!StringUtils.hasText(delegateTask.getAssignee())) { - BpmnMetaParserHelper.getApproverEmptyHandleType(userTask) - .ifPresent(approverEmptyHandleTypeEnum -> { - switch (approverEmptyHandleTypeEnum) { - case autoPassed: - autoPass(delegateTask); - break; - case autoRejection: - autoReject(delegateTask); - break; - case autoSkipped: - // autoReject(delegateTask); - // 非产品需求, 暂时不实现, 这里的功能似乎可以用 taskService.deleteTask 来实现 - break; - default: - break; - } - }); - } - } - - /** - * 如果 approverMethod = 自动通过或自动驳回时 - * - * @param delegateTask - * @param userTask - */ - private void checkApprovalMethod(DelegateTask delegateTask, UserTask userTask) { - BpmnMetaParserHelper.getApprovalMethod(userTask) - .ifPresent(approvalMethodEnum -> { - switch (approvalMethodEnum) { - case autoPassed: - autoPass(delegateTask); - break; - case autoRejection: - autoReject(delegateTask); - break; - case bizSpecify: - break; - default: - checkApproverEmptyHandle(delegateTask, userTask); - break; - } - }); - } - - private void autoReject(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("AutoOperatorEventListener#autoReject...{}", delegateTask.getTaskDefinitionKey()); - } - taskService.addComment(delegateTask.getId(), delegateTask.getProcessInstanceId(), COMMENT_TYPE_OPERATION_DESC - , "自动驳回"); - delegateTask.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + delegateTask.getId(), REJECTED.getStatus()); - Map variables = new HashMap<>(); - variables.put(INTERNAL_END_TENANT_ID, delegateTask.getTenantId()); - variables.put(INTERNAL_END_USER_NAME, "系统"); - variables.put(INTERNAL_END_USER_ID, "system"); - variables.put(INTERNAL_DELETE_PROCESS_FLAG, INTERNAL_PROCESS_TYPE_REJECT); - runtimeService.setVariables(delegateTask.getProcessInstanceId(), variables); - - // FIXME 这里的删除流程实例, 应该利用 DeleteProcessInstanceOperation 实现 - List historyTaskList = - historyService.createHistoricTaskInstanceQuery().unfinished().list(); - if (!CollectionUtils.isEmpty(historyTaskList)) { - historyTaskList.stream().filter(i -> Objects.isNull(i.getExecutionId())) - .forEach(i -> taskService.complete(i.getId())); - } - // 删除流程实例,以实现驳回任务时,中止整个审批流程 - runtimeService.deleteProcessInstance(delegateTask.getProcessInstanceId(), REJECTION_AUTO_COMPLETED.getDesc()); - - if (log.isDebugEnabled()) { - log.debug("AutoOperatorEventListener#autoReject...end: {}", delegateTask.getTaskDefinitionKey()); - } - } - - - private void autoPass(DelegateTask delegateTask) { - ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); - CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); - BpmnTaskDelegateAssigner assigner = - delegateTask.getVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId(), - BpmnTaskDelegateAssigner.class); - commandExecutor.execute(new CustomApproveTaskCmd(delegateTask.getId(), null, "自动通过", - Collections.emptyList(), assigner, null)); - - - // if (log.isDebugEnabled()) { - // log.debug("AutoOperatorEventListener#autoPass...{}", delegateTask.getTaskDefinitionKey()); - // } - // taskService.addComment(delegateTask.getId(), delegateTask.getProcessInstanceId(), - // COMMENT_TYPE_OPERATION_DESC, - // "自动通过"); - // delegateTask.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + delegateTask.getId(), APPROVED - // .getStatus()); - // taskService.complete(delegateTask.getId(), runtimeService.getVariables(delegateTask.getExecutionId - // ())); - // if (log.isDebugEnabled()) { - // log.debug("AutoOperatorEventListener#autoPass...end: {}", delegateTask.getTaskDefinitionKey()); - // } - } -} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/FirstCopyTemplateFileTaskEvent_105_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/FirstCopyTemplateFileTaskEvent_105_Listener.java new file mode 100644 index 000000000..a5d8b6e0e --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/FirstCopyTemplateFileTaskEvent_105_Listener.java @@ -0,0 +1,164 @@ +package cn.axzo.workflow.server.controller.listener.task; + +import cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi; +import cn.axzo.nanopart.doc.api.index.request.CopyNodeRequest; +import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.model.dto.SignFileDTO; +import cn.axzo.workflow.common.model.dto.VariableObjectDTO; +import cn.axzo.workflow.common.model.request.bpmn.BpmnSignConf; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCloneDTO; +import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO; +import cn.axzo.workflow.core.common.context.TaskOperationContext; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +import cn.axzo.workflow.core.listener.BpmnTaskEventListener; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessSign; +import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper; +import cn.axzo.workflow.core.service.ExtAxModelDocService; +import cn.axzo.workflow.core.service.ExtAxProcessSignService; +import cn.axzo.workflow.core.service.ExtAxReModelService; +import cn.axzo.workflow.server.common.util.RpcExternalUtil; +import cn.axzo.workflow.server.common.util.WpsUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.Process; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.engine.impl.util.ProcessDefinitionUtil; +import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; + +/** + * 签署业务才会执行 + *

+ * 流程实例启用成功后: + * 1、将模型关联的且实例勾选使用过的文档进行原始模板复制 + * 2、进行初始数据的模板变量替换 + *

+ * 并存至 ext_ax_process_sign 中的 doc_template 字段 + * + * @author wangli + * @since 2025-05-16 15:12 + */ +@Slf4j +@Component +@Scope("prototype") +public class FirstCopyTemplateFileTaskEvent_105_Listener extends AbstractBpmnEventListener implements BpmnTaskEventListener, Ordered { + @Resource + private ExtAxProcessSignService extAxProcessSignService; + @Resource + private ExtAxModelDocMapper extAxModelDocMapper; + @Resource + private ExtAxReModelService extAxReModelService; + @Resource + private ExtAxModelDocService extAxModelDocService; + @Resource + private DocAnonymousDatabaseApi docAnonymousApi; + @Resource + private WpsUtil wpsUtil; + + @Override + public void onCompleted(DelegateTask delegateTask) { + if (!Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) { + return; + } + String processInstanceId = delegateTask.getProcessInstanceId(); + + Process mainProcess = ProcessDefinitionUtil.getBpmnModel(delegateTask.getProcessDefinitionId()).getMainProcess(); + Optional signConfig = BpmnMetaParserHelper.getSignConfig(mainProcess); + if (!signConfig.isPresent() || Objects.isNull(signConfig.get().getSignType())) { + return; + } + + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + List docs = commandExecutor.execute(new CustomGetModelDocsCmd(processInstanceId, true, extAxModelDocMapper, extAxReModelService)); + ExtAxProcessSign processSign = new ExtAxProcessSign(); + processSign.setProcessInstanceId(processInstanceId); + processSign.setSignType(signConfig.get().getSignType().getType()); + processSign.setPendingMessageId(signConfig.get().getSignPendingProperty().getPendingMessageId()); + if (CollectionUtils.isEmpty(docs)) { + processSign.setDocTemplate(Collections.emptyList()); + processSign.setFileArchive(Collections.emptyList()); + } else { + // 复制基础模板 + List docTemplates = copyTempTemplate(docs); + processSign.setDocTemplate(docTemplates); + + List archives = replaceTemplateVariable(docTemplates, processEngineConfiguration, processInstanceId); + processSign.setFileArchive(archives); + } + // 没有可用的文档,但仍然记录库表 + extAxProcessSignService.save(processSign); + } + + private List replaceTemplateVariable(List docTemplates, ProcessEngineConfigurationImpl processEngineConfiguration, String processInstanceId) { + List archives = new ArrayList<>(); + List wpsReplaceVariables = wpsUtil.getWpsReplaceVariables(processEngineConfiguration, processInstanceId); + docTemplates.forEach(template -> { + SignFileDTO signFileDTO = new SignFileDTO(); + signFileDTO.setId(template.getId()); + signFileDTO.setFileName(template.getFileName()); + signFileDTO.setTemplateName(template.getTemplateName()); + signFileDTO.setFileTag(template.getFileTag()); + signFileDTO.setFileType(template.getFileType()); + signFileDTO.setFileCode(template.getFileCode()); + if (Objects.equals(template.getFileType(), FileTypeEnum.WORD) || Objects.equals(template.getFileType(), FileTypeEnum.EXCEL)) { + String fileKey = wpsUtil.wpsFileVariableReplace(wpsReplaceVariables, template.getFileCode(), null, template.getTemplateName() + template.getFileType().getSuffix()); + signFileDTO.setFileKey(fileKey); + } + archives.add(signFileDTO); + }); + return archives; + } + + private List copyTempTemplate(List docs) { + List files = new ArrayList<>(); + docs.forEach(doc -> { + SignFileDTO signFileDTO = new SignFileDTO(); + // 原始文档 ID + signFileDTO.setId(doc.getId()); + signFileDTO.setFileName(doc.getFileName()); + signFileDTO.setTemplateName(doc.getTemplateName()); + signFileDTO.setFileTag(doc.getTag()); + signFileDTO.setFileType(doc.getFileType()); + switch (doc.getFileType()) { + case WORD: + case EXCEL: + CopyNodeRequest copy = new CopyNodeRequest(); + copy.setCode(doc.getFileRelationId()); + String fileCode = RpcExternalUtil.rpcProcessor(() -> docAnonymousApi.copy(copy), "复制文档", copy); + signFileDTO.setFileCode(fileCode); + break; + case HIPRINT: + Long newDocId = extAxModelDocService.cloneDoc(DocCloneDTO.builder().docId(doc.getId()).build(), true); + signFileDTO.setFileCode(String.valueOf(newDocId)); + break; + case PDF: + break; + default: + break; + } + files.add(signFileDTO); + }); + return files; + } + + @Override + public int getOrder() { + return Integer.MIN_VALUE + 105; + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_102_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_103_Listener.java similarity index 62% rename from workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_102_Listener.java rename to workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_103_Listener.java index 46231ca0e..d6243acec 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_102_Listener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_103_Listener.java @@ -1,14 +1,17 @@ package cn.axzo.workflow.server.controller.listener.task; +import cn.axzo.workflow.common.model.request.BpmnApproveConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.context.TaskOperationContext; import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; import cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEventImpl; 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.listener.AbstractBpmnEventListener; import cn.axzo.workflow.core.listener.BpmnTaskEventListener; -import com.alibaba.fastjson.JSON; +import cn.hutool.json.JSONUtil; import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -21,6 +24,7 @@ import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.context.annotation.Scope; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -34,7 +38,7 @@ import java.util.Optional; 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_ASSIGNEE_SKIP_FLAT; -import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature; import static cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEventType.ADD_ASSIGNEE; /** @@ -47,68 +51,60 @@ import static cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEventType.ADD */ @Slf4j @Component +@Scope("prototype") @AllArgsConstructor -public class MessagePushTaskEvent_102_Listener implements BpmnTaskEventListener, Ordered { +public class MessagePushTaskEvent_103_Listener extends AbstractBpmnEventListener implements BpmnTaskEventListener, Ordered { @Override public int getOrder() { - return Integer.MIN_VALUE + 102; + return Integer.MIN_VALUE + 103; } private final RuntimeService runtimeService; @Override public void onAssigned(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("MessagePushTaskEventListener#onAssigned...{}, assignee: {}, taskId: {}", - delegateTask.getTaskDefinitionKey(), delegateTask.getAssignee(), delegateTask.getId()); - } - if (Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) { - return; - } + log.info("MessagePushTaskEventListener#onAssigned...{}, assignee: {}, taskId: {}, processInstanceId:{}", + delegateTask.getTaskDefinitionKey(), delegateTask.getAssignee(), delegateTask.getId(), delegateTask.getProcessInstanceId()); +// if (Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) { +// return; +// } if (StringUtils.hasLength(delegateTask.getAssignee()) && delegateTask.getAssignee().contains(TASK_ASSIGNEE_SKIP_FLAT)) { // 转交功能原审批人完成待办, 由于在流程引擎侧, 任务是不会在转交时立即结束, 但待办消息需要立即完成, // 下面的 onDelete 事件根据测试情况,看是否需要过滤掉这种任务的"完成待办"事件的推送 - pendingComplete(delegateTask); + pendingCompleteEvent(delegateTask); return; } if (Objects.equals(NO_ASSIGNEE, delegateTask.getAssignee())) { // 业务指定审批人, 不用发待办 // 但需要触发 PROCESS_ACTIVITY_WAIT_ASSIGNEE 事件 - ProcessInstance processInstance = - runtimeService.createProcessInstanceQuery().includeProcessVariables().processInstanceId(delegateTask.getProcessInstanceId()) - .singleResult(); - ProcessEngineConfigurationImpl processEngineConfiguration = - CommandContextUtil.getProcessEngineConfiguration(); + ProcessInstance processInstance = getContext().getProcessInstance(() -> runtimeService.createProcessInstanceQuery() + .includeProcessVariables() + .processInstanceId(delegateTask.getProcessInstanceId()) + .singleResult()); + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); eventDispatcher.dispatchEvent(new BizSpecifyAssigneeEventImpl(ADD_ASSIGNEE, delegateTask, processInstance.getBusinessKey(), processInstance.getProcessVariables()), processEngineConfiguration.getEngineCfgKey()); return; } - pendingPush(delegateTask); - if (log.isDebugEnabled()) { - log.debug("MessagePushTaskEventListener#onAssigned...end: {}", delegateTask.getTaskDefinitionKey()); - } + pendingPushEvent(delegateTask); + log.info("MessagePushTaskEventListener#onAssigned...end:{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); } @Override public void onDeleted(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("MessagePushTaskEventListener#onDeleted...{}", delegateTask.getTaskDefinitionKey()); - } - if (Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) { - return; - } - pendingComplete(delegateTask); - if (log.isDebugEnabled()) { - log.debug("MessagePushTaskEventListener#onDeleted...end: {}", delegateTask.getTaskDefinitionKey()); - } + log.info("MessagePushTaskEventListener#onDeleted...{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); +// if (Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) { +// return; +// } + pendingCompleteEvent(delegateTask); + log.info("MessagePushTaskEventListener#onDeleted...end: {}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); } - private void pendingComplete(DelegateTask delegateTask) { + private void pendingCompleteEvent(DelegateTask delegateTask) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher(); - Process process = ProcessDefinitionUtil.getProcess(delegateTask.getProcessDefinitionId()); Optional optNoticeConfig = BpmnMetaParserHelper.getNoticeConfig(process); @@ -118,17 +114,20 @@ public class MessagePushTaskEvent_102_Listener implements BpmnTaskEventListener, BpmnMetaParserHelper.getNodePendingConfig(userTask).ifPresent(noticeConfig::setPending); MessagePushEventImpl event = MessagePushEventBuilder.createEvent(MessagePushEventType.PENDING_COMPLETE, - null, noticeConfig, delegateTask.getProcessInstanceId(), null, delegateTask.getId()); - if (log.isDebugEnabled()) { - log.debug("发送完成待办的消息: {}", JSON.toJSONString(event)); - } + null, noticeConfig, delegateTask.getProcessInstanceId(), + parseProcessDefinitionKey(delegateTask.getProcessDefinitionId()), + null, delegateTask.getId(), getActivitySignature(userTask)); + log.info("发送完成待办的消息: {}, processInstanceId:{}", JSONUtil.toJsonStr(event), delegateTask.getProcessInstanceId()); eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey()); }); } - private void pendingPush(DelegateTask delegateTask) { - ProcessInstance processInstance = - runtimeService.createProcessInstanceQuery().processInstanceId(delegateTask.getProcessInstanceId()).includeProcessVariables().singleResult(); + private void pendingPushEvent(DelegateTask delegateTask) { + ProcessInstance processInstance = getContext().getProcessInstance(() -> runtimeService.createProcessInstanceQuery() + .processInstanceId(delegateTask.getProcessInstanceId()) + .includeProcessVariables() + .singleResult()); + @SuppressWarnings("unchecked") List assigners = (List) processInstance.getProcessVariables() .getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + delegateTask.getTaskDefinitionKey(), Collections.emptyList()); @@ -141,20 +140,23 @@ public class MessagePushTaskEvent_102_Listener implements BpmnTaskEventListener, Process process = ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId()); Optional optNoticeConfig = BpmnMetaParserHelper.getNoticeConfig(process); + Optional processApproveConf = BpmnMetaParserHelper.getProcessApproveConf(process); optNoticeConfig.ifPresent(noticeConf -> { // 优先读取节点自身的待办模板配置信息 UserTask userTask = (UserTask) process.getFlowElement(delegateTask.getTaskDefinitionKey()); BpmnMetaParserHelper.getNodePendingConfig(userTask).ifPresent(noticeConf::setPending); - assigners.stream().filter(i -> Objects.equals(delegateTask.getAssignee(), i.buildAssigneeId())).findAny() .ifPresent(i -> { MessagePushEventImpl event = - MessagePushEventBuilder.createEvent(MessagePushEventType.PENDING_PUSH, - Lists.newArrayList(i), noticeConf, processInstance.getProcessInstanceId(), - processInstance.getTenantId(), delegateTask.getId()); - if (log.isDebugEnabled()) { - log.debug("发送推送待办的消息: {}", JSON.toJSONString(event)); - } + MessagePushEventBuilder.createPendingPushEvent(Lists.newArrayList(i), + noticeConf, + processApproveConf.orElse(new BpmnApproveConf()), + processInstance.getProcessInstanceId(), + processInstance.getProcessDefinitionId(), + processInstance.getProcessDefinitionKey(), userTask.getId(), + processInstance.getTenantId(), delegateTask.getId(), + getActivitySignature(userTask)); + log.info("发送推送待办的消息: {}, processInstanceId:{}", JSONUtil.toJsonStr(event), event.getProcessInstanceId()); eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey()); }); }); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_101_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_101_Listener.java deleted file mode 100644 index 9fa297793..000000000 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_101_Listener.java +++ /dev/null @@ -1,155 +0,0 @@ -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.BpmnTaskDelegateAssigner; -import cn.axzo.workflow.common.model.response.mq.ProcessTaskDTO; -import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; -import cn.axzo.workflow.core.listener.BpmnTaskEventListener; -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.repository.Deployment; -import org.flowable.engine.runtime.ProcessInstance; -import org.flowable.task.service.delegate.DelegateTask; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.Ordered; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -import javax.annotation.Resource; -import java.util.Objects; - -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.TASK_ASSIGNEE_SKIP_FLAT; -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; -import static cn.axzo.workflow.common.enums.ProcessTaskEventEnum.PROCESS_TASK_CREATED; -import static cn.axzo.workflow.common.enums.ProcessTaskEventEnum.PROCESS_TASK_DELETED; - - -/** - * 流程任务的 MQ 发送监听 - * - * @author wangli - * @since 2023/7/10 18:22 - */ -@Slf4j -@Component -public class RocketMqBpmnTaskEvent_101_Listener implements BpmnTaskEventListener, Ordered { - @Override - public int getOrder() { - return Integer.MIN_VALUE + 101; - } - @Resource - private EventProducer eventProducer; - @Resource - private RuntimeService runtimeService; - @Resource - private RepositoryService repositoryService; - @Value("${sendMq:true}") - private Boolean sendMQ; - - @Override - public void onAssigned(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmnTaskEventListener#onAssigned...{}", delegateTask.getTaskDefinitionKey()); - } - if (StringUtils.hasLength(delegateTask.getAssignee()) && delegateTask.getAssignee().contains(TASK_ASSIGNEE_SKIP_FLAT)) { - return; - } - ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_ASSIGNED); - sendMessageQueue(dto, PROCESS_TASK_ASSIGNED); - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmnTaskEventListener#onAssigned...end: {}", delegateTask.getTaskDefinitionKey()); - } - } - - @Override - public void onCreated(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmnTaskEventListener#onCreated...{}", delegateTask.getTaskDefinitionKey()); - } - ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_CREATED); - sendMessageQueue(dto, PROCESS_TASK_CREATED); - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmnTaskEventListener#onCreated...end: {}", delegateTask.getTaskDefinitionKey()); - } - } - - @Override - public void onCompleted(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmnTaskEventListener#onCompleted...{}", delegateTask.getTaskDefinitionKey()); - } - ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_COMPLETED); - sendMessageQueue(dto, PROCESS_TASK_COMPLETED); - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmnTaskEventListener#onCompleted...end: {}", delegateTask.getTaskDefinitionKey()); - } - } - - @Override - public void onDeleted(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmnTaskEventListener#onDeleted...{}", delegateTask.getTaskDefinitionKey()); - } - ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_DELETED); - sendMessageQueue(dto, PROCESS_TASK_DELETED); - if (log.isDebugEnabled()) { - log.debug("RocketMqBpmnTaskEventListener#onDeleted...end: {}", delegateTask.getTaskDefinitionKey()); - } - } - - private Deployment getDeployment(String processInstanceId) { - ProcessInstance processInstance = - runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); - return repositoryService.createDeploymentQuery().deploymentId(processInstance.getDeploymentId()).singleResult(); - } - - public ProcessTaskDTO build(DelegateTask delegateTask, ProcessTaskEventEnum type) { - Process mainProcess = repositoryService.getBpmnModel(delegateTask.getProcessDefinitionId()).getMainProcess(); - ProcessTaskDTO dto = new ProcessTaskDTO() - .setType(type) - .setCategory(getDeployment(delegateTask.getProcessInstanceId()).getCategory()) - .setProcessTaskId(delegateTask.getId()) - .setProcessInstanceId(delegateTask.getProcessInstanceId()) - .setCurrentElementKey(delegateTask.getTaskDefinitionKey()) - .setCurrentElementName(delegateTask.getName()) - .setProcessDefinitionId(delegateTask.getProcessDefinitionId()) - .setInitiator(delegateTask.getVariable(INTERNAL_INITIATOR, BpmnTaskDelegateAssigner.class)) - .setApprover(delegateTask.getVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId(), - BpmnTaskDelegateAssigner.class)) - .setVariables(delegateTask.getVariables()) - .setStartTime(delegateTask.getCreateTime()) - .setTenantId(delegateTask.getTenantId()); - BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf); - String version = (String) runtimeService.getVariable(delegateTask.getProcessInstanceId(), - WORKFLOW_ENGINE_VERSION); - if (Objects.isNull(version)) { - version = FLOW_SERVER_VERSION_121; - } - dto.setWorkflowEngineVersion(version); - return dto; - } - - public void sendMessageQueue(ProcessTaskDTO dto, ProcessTaskEventEnum eventEnum) { - if (!sendMQ) { - return; - } - eventProducer.send(Event.builder() - .shardingKey(dto.getProcessInstanceId()) - .eventCode(eventEnum.getEventCode()) - .targetId(dto.getProcessInstanceId()) - .targetType(eventEnum.getTag()) - .data(dto) - .build()); - } - - -} 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 new file mode 100644 index 000000000..ff1e2b666 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_102_Listener.java @@ -0,0 +1,204 @@ +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; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +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_APPLICATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_PROCESS_DEFINITION_KEY; +import static cn.axzo.workflow.common.constant.BpmnConstants.PROCESS_OWNERSHIP_APPLICATION; +import static cn.axzo.workflow.common.constant.BpmnConstants.SKIP_MQ; +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.TRANSFER_TO; +import static cn.axzo.workflow.common.constant.BpmnConstants.TRANSFER_TO_ADVICE; +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; +import static cn.axzo.workflow.common.enums.ProcessTaskEventEnum.PROCESS_TASK_CREATED; +import static cn.axzo.workflow.common.enums.ProcessTaskEventEnum.PROCESS_TASK_DELETED; +import static cn.axzo.workflow.common.enums.ProcessTaskEventEnum.PROCESS_TASK_TRANSFER; + + +/** + * 流程任务的 MQ 发送监听 + * + * @author wangli + * @since 2023/7/10 18:22 + */ +@Slf4j +@Component +@Scope("prototype") +public class RocketMqBpmnTaskEvent_102_Listener extends AbstractBpmnEventListener implements BpmnTaskEventListener, Ordered { + @Override + public int getOrder() { + return Integer.MIN_VALUE + 102; + } + + @Resource + private EventProducer eventProducer; + @Resource + private RuntimeService runtimeService; + @Resource + private TaskService taskService; + @Resource + private BpmnHistoricAttachmentConverter attachmentConverter; + @Resource + private RepositoryService repositoryService; + @Value("${sendMq:true}") + private Boolean sendMQ; + + @Override + public void onAssigned(DelegateTask delegateTask) { + log.info("RocketMqBpmnTaskEventListener#onAssigned...{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + if (StringUtils.hasLength(delegateTask.getAssignee()) && delegateTask.getAssignee().contains(TASK_ASSIGNEE_SKIP_FLAT)) { + return; + } + ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_ASSIGNED); + sendMessageQueue(dto, PROCESS_TASK_ASSIGNED); + log.info("RocketMqBpmnTaskEventListener#onAssigned...end: {}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + } + + @Override + public void onCreated(DelegateTask delegateTask) { + log.info("RocketMqBpmnTaskEventListener#onCreated...{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_CREATED); + sendMessageQueue(dto, PROCESS_TASK_CREATED); + log.info("RocketMqBpmnTaskEventListener#onCreated...end: {}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + } + + @Override + public void onCompleted(DelegateTask delegateTask) { + log.info("RocketMqBpmnTaskEventListener#onCompleted...{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + Object skipMq = delegateTask.getTransientVariable(SKIP_MQ); + if (Objects.nonNull(skipMq) && Boolean.TRUE.equals(skipMq)) { + // reject 使用了虚拟任务,会触发完成 + return; + } + ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_COMPLETED); + sendMessageQueue(dto, PROCESS_TASK_COMPLETED); + log.info("RocketMqBpmnTaskEventListener#onCompleted...end: {}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + } + + @Override + public void onDeleted(DelegateTask delegateTask) { + log.info("RocketMqBpmnTaskEventListener#onDeleted...{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_DELETED); + sendMessageQueue(dto, PROCESS_TASK_DELETED); + log.info("RocketMqBpmnTaskEventListener#onDeleted...end: {}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + } + + @Override + public void onTransfer(DelegateTask delegateTask) { + log.info("RocketMqBpmnTaskEventListener#onTransfer...{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + BpmnTaskDelegateAssigner assigner = (BpmnTaskDelegateAssigner) delegateTask.getTransientVariable(TRANSFER_TO); + String transferAdvice = (String) delegateTask.getTransientVariable(TRANSFER_TO_ADVICE); + ProcessTaskDTO dto = build(delegateTask, PROCESS_TASK_TRANSFER); + dto.setTransferTargetApprover(assigner); + dto.setAdvice(transferAdvice); + sendMessageQueue(dto, PROCESS_TASK_TRANSFER); + log.info("RocketMqBpmnTaskEventListener#onTransfer...end: {}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + } + + private Deployment getDeployment(String processInstanceId) { + ProcessInstance processInstance = + 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()).getKey(); + 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()) + .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); + 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; + } + + public void sendMessageQueue(ProcessTaskDTO dto, ProcessTaskEventEnum eventEnum) { + if (!sendMQ) { + return; + } + Map header = new HashMap<>(); + if (StringUtils.hasText(dto.getProcessDefinitionKey())) { + if (log.isDebugEnabled()) { + log.info("record process definition key: {}", dto.getProcessDefinitionKey()); + } + header.put(MQ_OWNERSHIP_PROCESS_DEFINITION_KEY, dto.getProcessDefinitionKey()); + } + if (dto.getVariables().containsKey(PROCESS_OWNERSHIP_APPLICATION)) { + Object orDefault = dto.getVariables().getOrDefault(PROCESS_OWNERSHIP_APPLICATION, ""); + if (Objects.nonNull(orDefault)) { + log.info("record process ownership app name: {}", orDefault); + header.put(MQ_OWNERSHIP_APPLICATION, orDefault.toString()); + } + } + eventProducer.send(Event.builder() + .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/SnapshotBpmnTaskTaskEvent_100_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/SnapshotBpmnTaskEvent_100_Listener.java similarity index 71% rename from workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/SnapshotBpmnTaskTaskEvent_100_Listener.java rename to workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/SnapshotBpmnTaskEvent_100_Listener.java index 272bae1eb..43d072d33 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/SnapshotBpmnTaskTaskEvent_100_Listener.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/SnapshotBpmnTaskEvent_100_Listener.java @@ -1,6 +1,8 @@ package cn.axzo.workflow.server.controller.listener.task; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.common.context.TaskOperationContext; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; import cn.axzo.workflow.core.listener.BpmnTaskEventListener; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -8,6 +10,7 @@ import org.apache.commons.collections4.ListUtils; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.context.annotation.Scope; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; @@ -29,8 +32,9 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELAT */ @Slf4j @Component +@Scope("prototype") @AllArgsConstructor -public class SnapshotBpmnTaskTaskEvent_100_Listener implements BpmnTaskEventListener, Ordered { +public class SnapshotBpmnTaskEvent_100_Listener extends AbstractBpmnEventListener implements BpmnTaskEventListener, Ordered { @Override public int getOrder() { return Integer.MIN_VALUE + 100; @@ -41,19 +45,18 @@ public class SnapshotBpmnTaskTaskEvent_100_Listener implements BpmnTaskEventList @Override public void onAssigned(DelegateTask delegateTask) { - if (log.isDebugEnabled()) { - log.debug("SnapshotBpmnTaskTaskEventListener#onAssigned...{}", delegateTask.getTaskDefinitionKey()); - } + log.info("SnapshotBpmnTaskTaskEventListener#onAssigned...{}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); + @SuppressWarnings("unchecked") List assignerList = runtimeService.getVariable(delegateTask.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + delegateTask.getTaskDefinitionKey(), List.class); // identityId_identityType - ListUtils.emptyIfNull(assignerList).stream().filter(i -> Objects.equals(delegateTask.getAssignee(), - i.buildAssigneeId())) + ListUtils.emptyIfNull(assignerList).stream() + .filter(i -> Objects.equals(delegateTask.getAssignee(), i.buildAssigneeId())) .findFirst() .ifPresent(i -> { // 保存每个 taskId 的审批人 - delegateTask.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId(), i); + delegateTask.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + delegateTask.getId(), i.toJson()); // 业务指定审批人时, 添加 operationDesc if (Objects.equals(DUMMY_ASSIGNEE_ID, i.buildAssigneeId_1_2_1())) { taskService.addComment(delegateTask.getId(), delegateTask.getProcessInstanceId(), @@ -61,8 +64,6 @@ public class SnapshotBpmnTaskTaskEvent_100_Listener implements BpmnTaskEventList } }); - if (log.isDebugEnabled()) { - log.debug("SnapshotBpmnTaskTaskEventListener#onAssigned...end: {}", delegateTask.getTaskDefinitionKey()); - } + log.info("SnapshotBpmnTaskTaskEventListener#onAssigned...end: {}, processInstanceId:{}", delegateTask.getTaskDefinitionKey(), delegateTask.getProcessInstanceId()); } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/SyncToEsTaskEvent_104_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/SyncToEsTaskEvent_104_Listener.java new file mode 100644 index 000000000..377a5fdf3 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/SyncToEsTaskEvent_104_Listener.java @@ -0,0 +1,61 @@ +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.ElasticSearchEventEnum; +import cn.axzo.workflow.core.common.context.TaskOperationContext; +import cn.axzo.workflow.core.listener.AbstractBpmnEventListener; +import cn.axzo.workflow.core.listener.BpmnTaskEventListener; +import lombok.extern.slf4j.Slf4j; +import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 同步任务相关数据至 ES + * + * @author wangli + * @since 2024-10-16 16:50 + */ +@Slf4j +@Component +@Scope("prototype") +public class SyncToEsTaskEvent_104_Listener extends AbstractBpmnEventListener implements BpmnTaskEventListener, Ordered { + @Resource + private EventProducer eventProducer; + @Value("${sendMq:true}") + private Boolean sendMQ; + + @Override + public int getOrder() { + return Integer.MIN_VALUE + 104; + } + + /** + * 用户任务已指派审核人 + * + * @param delegateTask + */ + @Override + public void onAssigned(DelegateTask delegateTask) { + sendMessageQueue(delegateTask.getProcessInstanceId(), ElasticSearchEventEnum.ELASTIC_SEARCH_SYNC); + } + + private void sendMessageQueue(String processInstanceId, ElasticSearchEventEnum eventEnum) { + if (!sendMQ) { + return; + } + eventProducer.send(Event.builder() + .shardingKey(processInstanceId) + .eventCode(eventEnum.getEventCode()) + .targetId(processInstanceId) + .targetType(processInstanceId) + .data(processInstanceId) + .build()); + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/service/CheckApproverService.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/service/CheckApproverService.java new file mode 100644 index 000000000..1039057a7 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/service/CheckApproverService.java @@ -0,0 +1,13 @@ +package cn.axzo.workflow.server.controller.listener.task.service; + +import cn.axzo.workflow.core.common.context.TaskOperationContext; +import cn.axzo.workflow.core.version.Versioned; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.UserTask; +import org.flowable.task.service.delegate.DelegateTask; + +public interface CheckApproverService extends Versioned { + + boolean checkApproverExists(DelegateTask delegateTask, UserTask userTask, Process mainProcess, TaskOperationContext taskOperationContext); + +} 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 new file mode 100644 index 000000000..983907017 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/service/impl/CheckApproverServiceImpl.java @@ -0,0 +1,153 @@ +package cn.axzo.workflow.server.controller.listener.task.service.impl; + +import cn.axzo.workflow.common.enums.BpmnButtonEnum; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +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.request.bpmn.task.ExtHiTaskSearchDTO; +import cn.axzo.workflow.core.common.context.TaskOperationContext; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import cn.axzo.workflow.server.controller.listener.task.service.CheckApproverService; +import lombok.AllArgsConstructor; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.persistence.entity.ActivityInstanceEntity; +import org.flowable.engine.impl.util.CommandContextUtil; +import org.flowable.task.service.delegate.DelegateTask; +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; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_BUSINESS; +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; + +/** + * REQ-2596. 自动过审规则: + * a. 审批人=发起人/历史审批人 + * b. 连续节点 + * c. 当前节点有同意按钮 + */ +@Component +@AllArgsConstructor +public class CheckApproverServiceImpl implements CheckApproverService { + + private final ExtAxHiTaskInstService extAxHiTaskInstService; + + public boolean checkApproverExists(DelegateTask delegateTask, UserTask userTask, Process mainProcess, TaskOperationContext taskOperationContext) { + AtomicBoolean exists = new AtomicBoolean(false); + FlowElement currentFlowElement = mainProcess.getFlowElement(delegateTask.getTaskDefinitionKey()); + BpmnFlowNodeType currentNodeType = BpmnMetaParserHelper.getNodeType(currentFlowElement).orElse(NODE_EMPTY); + //业务节点,指定业务审批人,或者业务设置了审批人,也要参与自动过审,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)) { + return exists.get(); + } + Optional optConfig = BpmnMetaParserHelper.getButtonConfig(mainProcess, delegateTask.getTaskDefinitionKey()); + if (!optConfig.isPresent()) { + return exists.get(); + } + BpmnButtonConf bpmnButtonConf = optConfig.get(); + List currentButtons = bpmnButtonConf.getCurrent(); + if (CollectionUtils.isEmpty(currentButtons)) { + 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(); + //不存在同意按钮 + 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()); + 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(); + } + + /** + * 特殊审批人字段的比对, 兼容旧迭代导致的数据格式 + * + * @return + */ + private boolean specialApproverComparison(String compareAssignee, String currentAssignee) { + if (StringUtils.hasText(compareAssignee) && StringUtils.hasText(currentAssignee)) { + String[] compareSplit = compareAssignee.split("\\|"); + String[] currentSplit = currentAssignee.split("\\|"); + if (compareSplit.length == 2 || currentSplit.length == 2) { + return Objects.equals(compareSplit[1], currentSplit[1]); + } + } + return false; + } + + @Override + public DefaultArtifactVersion getVersion() { +// return new DefaultArtifactVersion(FLOW_SERVER_VERSION_130); + return null; + } +} 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..34d6a0a03 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/BasicPopulateAvatarController.java @@ -0,0 +1,51 @@ +package cn.axzo.workflow.server.controller.web; + +import cn.axzo.karma.client.feign.FlowSupportApi; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import com.google.common.collect.Lists; +import org.springframework.context.ApplicationContext; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; + +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 7628389ee..8f4a09a4f 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,22 @@ package cn.axzo.workflow.server.controller.web; +import cn.axzo.framework.domain.ServiceException; 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.BpmnProcessInstanceQueryDTO; +import cn.axzo.workflow.common.model.request.form.definition.FormContentSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormInstanceSearchDTO; 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.repository.entity.ExtAxBpmnFormRelation; import cn.axzo.workflow.core.service.BpmnProcessInstanceService; +import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService; import cn.axzo.workflow.core.service.support.FlowNodeForecastService; +import cn.axzo.workflow.form.service.FormDefinitionService; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; +import cn.axzo.workflow.server.common.util.ShellUtil; +import cn.axzo.workflow.server.xxljob.EsIndexOperationJobHandler; +import cn.axzo.workflow.server.xxljob.SpecifyProcessInstanceSyncEsJobHandler; import cn.azxo.framework.common.model.CommonResponse; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.FlowElement; @@ -17,17 +26,32 @@ import org.flowable.engine.RepositoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.Deployment; +import org.flowable.form.api.FormDefinition; +import org.flowable.form.api.FormInfo; +import org.flowable.form.api.FormInstanceInfo; +import org.flowable.form.api.FormRepositoryService; +import org.flowable.form.api.FormService; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.PropertyPlaceholderHelper; +import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import javax.annotation.Resource; import java.io.InputStream; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -35,6 +59,7 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCES /** * 测试接口 + * * @author wangli * @since 2023/10/10 13:59 */ @@ -43,7 +68,8 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCES @RestController @Validated public class TestController { - + @Value("${spring.profiles.active}") + private String profile; @Autowired private FlowNodeForecastService forecastService; @Autowired @@ -58,11 +84,29 @@ public class TestController { private ProcessInstanceApi processInstanceApi; @Autowired private BpmnProcessInstanceService bpmnProcessInstanceService; + @Resource + private FormService formService; + @Resource + private FormRepositoryService formRepositoryService; + @Resource + private FormDefinitionService formDefinitionService; + @Resource + private ExtAxBpmnFormRelationService bpmnFormRelationService; + // @Autowired +// private WorkflowCoreService workflowCoreService; +// @Autowired +// private WorkflowManageService workflowManageService; + @Resource + private SpringProcessEngineConfiguration processEngineConfiguration; + @Resource + private EsIndexOperationJobHandler esIndexOperationJobHandler; + @Resource + private SpecifyProcessInstanceSyncEsJobHandler specifyProcessInstanceSyncEsJobHandler; @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); } @@ -124,4 +168,205 @@ public class TestController { return CommonResponse.success(true); } + /*@GetMapping("/create") + public CommonResponse test5(@RequestParam(required = false) Boolean sync) { + BpmnProcessInstanceCreateDTO dto = new BpmnProcessInstanceCreateDTO(); + dto.setProcessDefinitionKey("1"); + dto.setCooperationOrg(new CooperationOrgDTO()); + dto.setBusinessKey("businessKey"); + dto.setInitiator(new BpmnTaskDelegateAssigner()); + if (Objects.nonNull(sync)) { + if (sync) { + workflowCoreService.sync(); + } else { + workflowCoreService.async(); + } + } + return CommonResponse.success(workflowCoreService.createProcessInstance(dto)); + } + + @GetMapping("/approve") + public CommonResponse test6(@RequestParam(required = false) Boolean sync, @RequestParam String taskId) { + if (Objects.nonNull(sync)) { + if (sync) { + workflowCoreService.sync(); + } else { + workflowCoreService.async(); + } + } + BpmnTaskAuditDTO dto = new BpmnTaskAuditDTO(); + dto.setTaskId(taskId); + BpmnTaskDelegateAssigner approver = new BpmnTaskDelegateAssigner("2002456", "3", "彭建伦", "9000586", "374", "6064", "https://axzo-app.oss-cn-chengdu.aliyuncs.com/face/face_dev/f5f5634ff6e84bfd9291b8e1a8ad215d.jpg"); + dto.setApprover(approver); + return CommonResponse.success(workflowCoreService.approveTask(dto)); + } + + @GetMapping("/get") + public CommonResponse test7(@RequestParam(required = false) Boolean sync, @RequestParam String processInstanceId) { + if (Objects.isNull(sync)) { + throw new RuntimeException("sync 参数不能为空"); + } + if (Objects.nonNull(sync)) { + if (sync) { + workflowCoreService.sync(); + } else { + workflowCoreService.async(); + } + } + BpmnProcessInstanceQueryDTO dto = new BpmnProcessInstanceQueryDTO(); + dto.setProcessInstanceId(processInstanceId); + dto.setHasVariable(true); +// return CommonResponse.success(workflowCoreService.getProcessInstanceVO(dto)); + return CommonResponse.success(workflowCoreService.getProcessInstanceVO(dto)); + } + + @GetMapping("/get/vars") + public CommonResponse> test8(@RequestParam(required = false) Boolean sync, @RequestParam String processInstanceId) { + if (Objects.isNull(sync)) { + throw new RuntimeException("sync 参数不能为空"); + } + if (Objects.nonNull(sync)) { + if (sync) { + workflowCoreService.sync(); + } else { + workflowCoreService.async(); + } + } + return CommonResponse.success(workflowCoreService.getProcessVariables(processInstanceId, null)); + } + + @GetMapping("/create/var") + public CommonResponse test9(@RequestParam(required = false) Boolean sync, @RequestParam String processInstanceId) { + if (Objects.isNull(sync)) { + throw new RuntimeException("sync 参数不能为 null"); + } + if (Objects.nonNull(sync)) { + if (sync) { + workflowCoreService.sync(); + } else { + workflowCoreService.async(); + } + } + RestBpmnProcessVariable variable = new RestBpmnProcessVariable(); + variable.setName("testVar"); + variable.setValue("testValue"); +// workflowCoreService.createVariable(processInstanceId, variable); + return CommonResponse.success(null); + } + + @GetMapping("/batch/abort") + public CommonResponse test10(@RequestParam(required = false) Boolean sync) { + if (Objects.isNull(sync)) { + throw new RuntimeException("sync 参数不能为 null"); + } + if (Objects.nonNull(sync)) { + if (sync) { + workflowCoreService.sync(); + } else { + workflowCoreService.async(); + } + } + List dtos = new ArrayList<>(); + BpmnProcessInstanceAbortDTO abort = new BpmnProcessInstanceAbortDTO(); + abort.setProcessInstanceId("1"); + abort.setTenantId("2"); + abort.setReason("reason"); + dtos.add(abort); + workflowCoreService.batchAbortProcessInstance(dtos); + return CommonResponse.success(dtos); + } + + @GetMapping("/tenant/get/ids") + public CommonResponse> test11() { + List tenantIds = workflowManageService.sync().getTenantIds(); + return CommonResponse.success(tenantIds); + }*/ + + @GetMapping("log") + public String log(@RequestParam String keyword) throws Exception { + if (!StringUtils.hasText(keyword)) { + return "命令不能为空"; + } + return ShellUtil.grepRealTimeLog(profile, keyword); + } + + @GetMapping("form") + public CommonResponse getForm(@RequestParam String definitionId, @RequestParam(required = false) String processInstanceId, @RequestParam(required = false) String taskId) { +// FormInfo startFormModel = bpmnProcessInstanceService.getStartFormModel(definitionId, processInstanceId); +// formService.getFormInstanceModelByKey("we", null, null, null); + FormContentSearchDTO contentSearch = new FormContentSearchDTO(); + contentSearch.setProcessInstanceId(processInstanceId); + contentSearch.setTaskId(taskId); + contentSearch.setTenantId("400"); + FormInfo formInfo = formDefinitionService.getFormInfo(contentSearch); + FormInfo we = formService.getFormModelWithVariablesByKey("we", null, null); + FormInstanceInfo we1 = formService.getFormInstanceModelByKey("we", StringUtils.hasText(taskId) ? taskId : null, null, null, "", false); + FormInfo formModelByKey = formRepositoryService.getFormModelByKey("we"); + FormInfo startFormModel = runtimeService.getStartFormModel(definitionId, processInstanceId); + return CommonResponse.success(null); + } + + /** + * 获取表单实例 + * + * @param dto + * @return + */ + @PostMapping("/form/get") + public CommonResponse getFormInstance(@RequestBody FormInstanceSearchDTO dto) { + if (!StringUtils.hasText(dto.getProcessInstanceId()) && !StringUtils.hasText(dto.getTaskId())) { + throw new ServiceException("不允许同时为空"); + } + HistoricProcessInstance instance; + if (StringUtils.hasText(dto.getProcessInstanceId())) { + instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult(); + dto.setTenantId(instance.getTenantId()); + } else { + HistoricTaskInstance task = historyService.createHistoricTaskInstanceQuery().taskId(dto.getTaskId()).singleResult(); + instance = historyService.createHistoricProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); + dto.setTenantId(instance.getTenantId()); + } + ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(instance.getProcessDefinitionId()); + FormDefinition formDefinition = formRepositoryService.createFormDefinitionQuery().deploymentId(relation.getFormDeploymentId()).singleResult(); + FormInstanceInfo formInstance = formService.getFormInstanceModelById(formDefinition.getId(), dto.getTaskId(), dto.getProcessInstanceId(), dto.getVariables(), relation.getTenantId(), false); + return CommonResponse.success(formInstance); + } + + /** + * 表达式测试 + * + * @param expression + * @param variables + * @return + */ + @PostMapping("/expression/testing") + public CommonResponse parseExpression(@RequestParam String expression, @RequestBody Map variables) { +// ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager(); +// Expression exp = expressionManager.createExpression(expression); +// VariableContainerWrapper variableContainer = new VariableContainerWrapper(variables); +// String stringFromField = ExpressionUtils.getStringFromField(exp, variableContainer); +// Object value = exp.getValue(variableContainer); + + PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}"); + Properties properties = new Properties(); + variables.forEach(properties::setProperty); + + // 自定义PlaceholderResolver实现类,用于处理占位符找不到对应值时返回空字符串 + PropertyPlaceholderHelper.PlaceholderResolver resolver = placeholder -> properties.getProperty(placeholder, ""); + String value = propertyPlaceholderHelper.replacePlaceholders(expression, resolver); + return CommonResponse.success(value); + } + + @GetMapping("/es/index") + public CommonResponse esIndex(@RequestParam String str) { + esIndexOperationJobHandler.execute(str); + return CommonResponse.success(); + } + + @GetMapping("/es/sync") + public CommonResponse syncProcessInstanceToEs(@RequestParam String processInstanceId) { + specifyProcessInstanceSyncEsJobHandler.execute(processInstanceId); + return CommonResponse.success(); + } } + 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 e010dbfb4..f95ed8e24 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 @@ -1,13 +1,18 @@ package cn.axzo.workflow.server.controller.web.bpmn; 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.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; @@ -23,8 +28,9 @@ import javax.validation.constraints.NotBlank; import java.util.ArrayList; import java.util.List; -import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ACTIVITY_BIZ_SET_ASSIGNEE_HAS_REPEAT; +import static cn.axzo.workflow.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; /** * 流程活动相关控制器 @@ -37,28 +43,48 @@ 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; /** * 业务节点唤醒 + * 旧版本使用的接口, 建议使用 {@link BpmnProcessActivityController#trigger(BpmnActivityTriggerDTO)} * - * @param triggerId 触发 ID,数据来源于事件 + * @param triggerId * @return */ @Operation(summary = "业务节点唤醒") @GetMapping("/trigger") @Override @RepeatSubmit - public CommonResponse trigger(@RequestParam @NotBlank(message = "触发 ID 不能为空") String triggerId) { - bpmnProcessActivityService.trigger(triggerId); - return CommonResponse.success(true); + @Deprecated + public CommonResponse trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId) { + log.info("业务节点唤醒 trigger2 ===>>>参数:{}", triggerId); + return trigger(new BpmnActivityTriggerDTO(triggerId, true, null)); + } + + /** + * 业务节点唤醒 + * + * @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); } /** * 业务节点设置审批人 + *

+ * 不支持重复调用设置审批人,需一次性传入所有审批人 * * @param dto * @return @@ -68,6 +94,7 @@ public class BpmnProcessActivityController implements ProcessActivityApi { @Override @RepeatSubmit public CommonResponse setAssignee(@Validated @RequestBody BpmnActivitySetAssigneeDTO dto) { + log.info("业务节点设置审批人 setAssignee ===>>>参数:{}", JSON.toJSONString(dto)); if (!dto.getServerSideDeduplication()) { List personIds = new ArrayList<>(); for (BpmnTaskDelegateAssigner assigner : dto.getAssigners()) { @@ -79,7 +106,37 @@ public class BpmnProcessActivityController implements ProcessActivityApi { } else { dto.setAssigners(removeDuplicateByPersonId(dto.getAssigners())); } + + //填充头像 + populateUsersAvatar(dto.getAssigners()); + bpmnProcessActivityService.setAssignee(dto); - return CommonResponse.success(true); + return success(true); + } + + /** + * 该功能应该利用引擎的 TimerBoundaryEvent 来实现,但为了简便,先利用引擎的任务调度来实现 + * + * @param dto + * @return + */ + @PostMapping("/timeout/trigger") + @Override + public CommonResponse setTimeoutTrigger(@Validated @RequestBody BpmnActivityTimeoutTriggerDTO dto) { + log.info("倒计时触发 setTimeoutTrigger ===>>>参数:{}", JSON.toJSONString(dto)); + return CommonResponse.success(bpmnProcessActivityService.setTimeoutTrigger(dto)); + } + + /** + * 为指定业务节点设置定时回调 + * + * @param dto + * @return + */ + @PostMapping("/timeout/callback") + @Override + public CommonResponse setTimeoutCallback(@Validated @RequestBody BpmnActivityTimeoutCallbackDTO dto) { + log.info("倒计时回调 setTimeoutCallback ===>>>参数:{}", JSON.toJSONString(dto)); + return CommonResponse.success(bpmnProcessActivityService.setTimeOutCallback(dto)); } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessDefinitionController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessDefinitionController.java index 6d4fd0489..7b15bcfd5 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessDefinitionController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessDefinitionController.java @@ -4,14 +4,21 @@ import cn.axzo.workflow.client.feign.bpmn.ProcessDefinitionApi; import cn.axzo.workflow.common.model.request.bpmn.definition.BpmnProcessDefinitionUpdateDTO; 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.request.form.FormJsonModel; +import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionSearchDTO; 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.process.BpmnProcessDefinitionVO; +import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation; import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; import cn.axzo.workflow.core.service.BpmnProcessModelService; +import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService; import cn.axzo.workflow.core.service.converter.BpmnProcessDefinitionConverter; +import cn.axzo.workflow.form.service.FormDefinitionService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.azxo.framework.common.model.CommonResponse; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; @@ -30,7 +37,9 @@ import javax.annotation.Resource; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.Map; +import java.util.Objects; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_DESCRIPTION; import static cn.azxo.framework.common.model.CommonResponse.success; @@ -49,6 +58,10 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { private BpmnProcessDefinitionConverter processDefinitionConverter; @Resource private BpmnProcessModelService bpmnProcessModelService; + @Resource + private FormDefinitionService formDefinitionService; + @Resource + private ExtAxBpmnFormRelationService bpmnFormRelationService; /** @@ -58,7 +71,7 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { @GetMapping("/page") @Override public CommonResponse> getProcessDefinitionPage(@Validated @RequestBody BpmnProcessDefinitionPageDTO dto) { - log.info("获取活跃的流程定义分页getProcessDefinitionPage===>>>参数:{}", JSON.toJSONString(dto)); + log.info("获取活跃的流程定义分页getProcessDefinitionPage===>>>参数:{}", JSONUtil.toJsonStr(dto)); return success(bpmnProcessDefinitionService.getProcessDefinitionPage(dto)); } @@ -86,7 +99,7 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { @GetMapping("/get") @Override public CommonResponse getProcessDefinition(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId) { - log.info("获取活跃的流程定义分页getProcessDefinition===>>>参数:{}", JSON.toJSONString(processDefinitionId)); + log.info("获取活跃的流程定义分页getProcessDefinition===>>>参数:{}", JSONUtil.toJsonStr(processDefinitionId)); return success(bpmnProcessDefinitionService.getProcessDefinition(processDefinitionId)); } @@ -102,7 +115,7 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { @Override public CommonResponse getProcessDefinitionByDeploymentId( @NotBlank(message = "流程部署 ID 不能为空") @RequestParam String deploymentId) { - log.info(" 获得 deploymentId 对应的 getProcessDefinitionByDeploymentId===>>>参数:{}", JSON.toJSONString(deploymentId)); + log.info(" 获得 deploymentId 对应的 getProcessDefinitionByDeploymentId===>>>参数:{}", JSONUtil.toJsonStr(deploymentId)); ProcessDefinition result = bpmnProcessDefinitionService.getProcessDefinitionByDeploymentId(deploymentId); return success(processDefinitionConverter.toVo(result)); } @@ -117,11 +130,10 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { @Operation(summary = "获得流程定义标识对应的激活的流程定义") @GetMapping("/active/getByKey") @Override - public CommonResponse getActiveProcessDefinitionByKey(@NotBlank(message = - "模型定义KEY" + - "不能为空") @RequestParam String key) { - log.info("获得流程定义标识对应的激活的流程定义 getActiveProcessDefinitionByKey===>>>参数:{}", JSON.toJSONString(key)); - return success(bpmnProcessDefinitionService.getActiveProcessDefinitionByKey(key, key)); + public CommonResponse getActiveProcessDefinitionByKey(@NotBlank(message = "模型定义KEY不能为空") @RequestParam String key, + @RequestParam(required = false, defaultValue = NO_TENANT_ID) String tenantId) { + log.info("获得流程定义标识对应的激活的流程定义 getActiveProcessDefinitionByKey===>>>参数:{}", JSONUtil.toJsonStr(key)); + return success(bpmnProcessDefinitionService.getActiveProcessDefinitionByKey(key, tenantId)); } /** @@ -132,7 +144,7 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { */ @Operation(summary = "获取指定流程定义是否有激活状态的数据") public CommonResponse hasActiveProcessDefinition(String deploymentId) { - log.info("获得流程定义标识对应的激活的流程定义 getActiveProcessDefinitionByKey===>>>参数:{}", JSON.toJSONString(deploymentId)); + log.info("获得流程定义标识对应的激活的流程定义 getActiveProcessDefinitionByKey===>>>参数:{}", JSONUtil.toJsonStr(deploymentId)); return success(bpmnProcessDefinitionService.hasActiveProcessDefinition(deploymentId)); } @@ -143,7 +155,7 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { * {@link SuspensionState} * * @param processDefinitionId 流程定义 ID - * @param state 状态 + * @param state 状态 */ @Operation(summary = "挂起/激活流程") @PutMapping("/state/update") @@ -177,21 +189,21 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { /** * 获取指定模型激活的流程定义 JSON 模型 * - * @param modelId 模型 ID + * @param modelId 模型 ID * @param tenantId 租户 ID - * @param category 业务分类 + * @param key 业务分类 * @return */ @Operation(summary = "获取指定模型激活的流程定义 JSON 模型") @GetMapping("/active/json/model") @Override public CommonResponse getActiveProcessDefinitionJsonModel(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId, - @NotBlank(message = "分类不能为空") @RequestParam(required = false) String category, + @NotBlank(message = "分类不能为空") @RequestParam(required = false) String key, @RequestParam(required = false) String tenantId) { - String finalTenantId = StringUtils.hasText(tenantId) ? tenantId : ""; + String finalTenantId = StringUtils.hasText(tenantId) ? tenantId : NO_TENANT_ID; BpmnModelDetailVO modelDetail = bpmnProcessModelService.getById(modelId, finalTenantId); String activeProcessDefinitionId = bpmnProcessDefinitionService.getActiveProcessDefinitionId(finalTenantId, - category); + key); BpmnProcessDefinitionVO processDefinition = bpmnProcessDefinitionService.getProcessDefinition(activeProcessDefinitionId); @@ -200,12 +212,25 @@ public class BpmnProcessDefinitionController implements ProcessDefinitionApi { updateDTO.setProcessModelId(modelDetail.getId()); updateDTO.setKey(modelDetail.getKey()); updateDTO.setName(modelDetail.getName()); - updateDTO.setCategory(modelDetail.getCategory()); updateDTO.setTenantId(modelDetail.getTenantId()); updateDTO.setJsonModel(processDefinition.getJsonModel()); + @SuppressWarnings("unchecked") Map metaInfoMap = JSON.parseObject(modelDetail.getMetaInfo(), Map.class); updateDTO.setDescription(metaInfoMap.get(MODEL_DESCRIPTION)); + + ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(processDefinition.getId()); + if (Objects.isNull(relation)) { + return success(updateDTO); + } + FormDefinitionSearchDTO search = new FormDefinitionSearchDTO(); + search.setKey(modelDetail.getKey()); + search.setParentDeploymentId(processDefinition.getDeploymentId()); + search.setTenantId(finalTenantId); + FormDefinitionVO formDefinitionVO = formDefinitionService.get(search); + if (Objects.nonNull(formDefinitionVO)) { + updateDTO.setFormJsonModel(new FormJsonModel(modelDetail.getName(), formDefinitionVO.getFields())); + } return success(updateDTO); } 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 01b9b2456..caa8b01d7 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,31 +1,74 @@ package cn.axzo.workflow.server.controller.web.bpmn; +import cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi; +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.dto.CooperationOrgDTO; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.SignFileDTO; +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +import cn.axzo.workflow.common.model.request.bpmn.log.LogApproveSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BeforeProcessInstanceCreateDTO; 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.request.bpmn.process.HistoricProcessInstanceSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.SuperBpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; +import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO; 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.ExtProcessLogVO; import cn.axzo.workflow.common.model.response.bpmn.process.HistoricProcessInstanceVO; +import cn.axzo.workflow.common.model.response.bpmn.process.NodesByModelVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.process.doc.DocPendingVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceLogVO; +import cn.axzo.workflow.common.valid.group.ValidGroup; +import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd; +import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog; +import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper; import cn.axzo.workflow.core.service.BpmnProcessInstanceService; +import cn.axzo.workflow.core.service.BpmnProcessTaskService; +import cn.axzo.workflow.core.service.ExtAxProcessLogService; +import cn.axzo.workflow.core.service.ExtAxProcessSignService; +import cn.axzo.workflow.core.service.ExtAxReModelService; +import cn.axzo.workflow.core.service.ExtAxReadRecordService; 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 cn.hutool.core.util.NumberUtil; +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.common.engine.impl.interceptor.CommandExecutor; import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.beans.BeanUtils; +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; @@ -41,8 +84,15 @@ import javax.annotation.Resource; 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.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_DOC_ID_NOT_IN_MODEL; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_DOC_READ_PARAM_ERROR; +import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_EXT_LOG_PARAM_ERROR; import static cn.azxo.framework.common.model.CommonResponse.success; /** @@ -53,10 +103,28 @@ 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 BpmnProcessTaskService bpmnProcessTaskService; + @Resource + private ServerFileServiceApi serverFileServiceApi; + @Resource + private DocAnonymousDatabaseApi docAnonymousApi; + @Resource + private ExtAxReadRecordService readRecordService; + @Resource + private ExtAxModelDocMapper extAxModelDocMapper; + @Resource + private SpringProcessEngineConfiguration processEngineConfiguration; + @Resource + private ExtAxProcessSignService extAxProcessSignService; + @Resource + private ExtAxReModelService extAxReModelService; + @Resource + private ExtAxProcessLogService extAxProcessLogService; /** * 超管查询所有流程实例 @@ -68,7 +136,7 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @PostMapping("/page/all") @Override public CommonResponse> getAllProcessInstancePage(@Validated @RequestBody BpmnProcessInstanceAdminPageReqVO dto) { - log.info("超管查询审批列表 getAllProcessInstancePage===>>>参数:{}", JSON.toJSONString(dto)); + log.info("超管查询审批列表 getAllProcessInstancePage===>>>参数:{}", JSONUtil.toJsonStr(dto)); return success(bpmnProcessInstanceService.getAdminProcessInstancePage(dto)); } @@ -79,10 +147,25 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @PostMapping("/page/my") @Override public CommonResponse> getMyProcessInstancePage(@Validated @RequestBody BpmnProcessInstanceMyPageReqVO dto) { - log.info("我发起的审批列表 getOwnTaskPage===>>>参数:{}", JSON.toJSONString(dto)); + log.info("我发起的审批列表 getOwnTaskPage===>>>参数:{}", JSONUtil.toJsonStr(dto)); return success(bpmnProcessInstanceService.getMyProcessInstancePage(dto)); } + /** + * 创建审批实例 + */ + @Operation(summary = "创建审批流程前,返回模型节点列表以及节点能否设置审批人") + @PostMapping("/create/before") + @Override + @RepeatSubmit + public CommonResponse> nodesBeforeCreateProcessInstance(@Validated @RequestBody BeforeProcessInstanceCreateDTO dto) { + log.info("发起审核前,节点发起人自选nodesBeforeCreateProcessInstance===>>>参数:{}", JSONUtil.toJsonStr(dto)); + List nodes = bpmnProcessInstanceService.nodesBeforeCreateProcessInstance(dto); + // 填充头像 + populateUsersAvatar(nodes.stream().flatMap(e -> e.getAssigners().stream().filter(Objects::nonNull)).collect(Collectors.toList())); + return success(nodes); + } + /** * 创建审批实例 */ @@ -91,25 +174,19 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @Override @RepeatSubmit public CommonResponse createProcessInstance(@Validated @RequestBody BpmnProcessInstanceCreateDTO dto) { - log.info("发起审核createProcessInstance===>>>参数:{}", JSON.toJSONString(dto)); + log.info("发起审核createProcessInstance===>>>参数:{}", JSONUtil.toJsonStr(dto)); + // 填充名称头像 + populateUsersAvatar(dto.getInitiator()); + // 填充指定审批人名称头像 + if (!CollectionUtils.isEmpty(dto.getSpecifyAssignerMap())) { + populateUsersAvatar(dto.getSpecifyAssignerMap().entrySet().stream().flatMap(e -> e.getValue().stream().filter(Objects::nonNull)).collect(Collectors.toList())); + } + // 填充签署人名称头像 + populateUsersAvatar(dto.getSignatories()); + dto.setAsync(false); return success(bpmnProcessInstanceService.createProcessInstance(dto)); } - /** - * 创建审批流程并带上表单 - * - * @param dto - * @return - */ - @Operation(summary = "创建审批流程并带上表单") - @PostMapping("/form/create") - @Override - @RepeatSubmit - public CommonResponse createProcessInstanceWith(@Validated @RequestBody BpmnProcessInstanceCreateWithFormDTO dto) { - log.info("发起审核createProcessInstanceWith===>>>参数:{}", JSON.toJSONString(dto)); - return success(bpmnProcessInstanceService.createProcessInstanceWithForm(dto)); - } - /** * 撤回流程实例 * @@ -120,8 +197,21 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @DeleteMapping("/cancel") @Override @RepeatSubmit - public CommonResponse cancelProcessInstance(@Validated @RequestBody BpmnProcessInstanceCancelDTO dto) { - log.info("撤回审核cancelProcessInstant===>>>参数:{}", JSON.toJSONString(dto)); + public CommonResponse cancelProcessInstance(@Validated(ValidGroup.Update.class) @RequestBody BpmnProcessInstanceCancelDTO dto) { + log.info("撤回审核cancelProcessInstant===>>>参数:{}", JSONUtil.toJsonStr(dto)); + populateUsersAvatar(dto.getInitiator()); + SuperBpmnProcessInstanceCancelDTO target = new SuperBpmnProcessInstanceCancelDTO(); + BeanUtils.copyProperties(dto, target); + return success(bpmnProcessInstanceService.cancelProcessInstance(target)); + } + + @Operation(summary = "撤回流程实例(Super)") + @DeleteMapping("/super/cancel") + @RepeatSubmit + @Override + public CommonResponse superCancelProcessInstance(@Validated(ValidGroup.Insert.class) @RequestBody SuperBpmnProcessInstanceCancelDTO dto) { + log.info("超级撤回审核 superCancelProcessInstant===>>>参数:{}", JSONUtil.toJsonStr(dto)); + populateUsersAvatar(dto.getInitiator()); return success(bpmnProcessInstanceService.cancelProcessInstance(dto)); } @@ -136,10 +226,27 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @DeleteMapping("/abort") @RepeatSubmit public CommonResponse abortProcessInstance(@Validated @RequestBody BpmnProcessInstanceAbortDTO dto) { - log.info("中止流程实例abortProcessInstant===>>>参数:{}", JSON.toJSONString(dto)); + log.info("中止流程实例abortProcessInstant===>>>参数:{}", JSONUtil.toJsonStr(dto)); + populateUsersAvatar(dto.getAssigner()); return success(bpmnProcessInstanceService.abortProcessInstance(dto)); } + /** + * 批量中止流程实例 + * + * @param dtos 请求参数 + * @return + */ + @Operation(summary = "批量中止流程实例") + @Override + @DeleteMapping("/batch/abort") + @RepeatSubmit + public CommonResponse batchAbortProcessInstance(@Validated @RequestBody List dtos) { + log.info("批量中止流程实例abortProcessInstants===>>>参数:{}", JSONUtil.toJsonStr(dtos)); + return success(bpmnProcessInstanceService.batchAbortProcessInstance(dtos)); + } + + /** * 抄送流程 * @@ -151,7 +258,8 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @PostMapping("/carbon-copy") @Override public CommonResponse carbonCopyProcessInstance(@Validated @RequestBody BpmnProcessInstanceCarbonCopyDTO dto) { - log.info("抄送流程实例carbonCopyProcessInstance===>>>参数:{}", JSON.toJSONString(dto)); + log.info("抄送流程实例carbonCopyProcessInstance===>>>参数:{}", JSONUtil.toJsonStr(dto)); + populateUsersAvatar(dto.getCopyAssigners()); return success(bpmnProcessInstanceService.carbonCopyProcessInstance(dto)); } @@ -165,7 +273,7 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { @GetMapping("/get") @Override public CommonResponse getProcessInstanceVO(@Validated @RequestBody BpmnProcessInstanceQueryDTO dto) { - log.info("获得历史的流程实例 getProcessInstanceVO===>>>参数:{}", JSON.toJSONString(dto)); + log.info("获得历史的流程实例 getProcessInstanceVO===>>>参数:{}", JSONUtil.toJsonStr(dto)); BpmnProcessInstanceVO result = bpmnProcessInstanceService.getProcessInstanceVO(dto); return success(result); } @@ -191,7 +299,7 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { * 获取审批流程实例的运行图 * * @param processInstanceId 流程实例 ID - * @param tenantId 租户 ID + * @param tenantId 租户 ID * @return */ @Operation(summary = "获取审批流程实例的运行图") @@ -218,20 +326,44 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { } /** - * 获取指定流程实例的协作单位 + * 推断指定流程实例的过滤掉部分节点执行顺序 + * + * @param processInstanceId + * @param tenantId + * @param allNode 如果为真时,相当于调用 {@link ProcessInstanceApi#processInstanceNodeForecast(String, String)} 方法,切会直接丢弃 nodeDefinitionKeys 参数 + * 如果为假时,才结合 nodeDefinitionKeys 过滤掉传入的节点 + * @param nodeDefinitionKeys + * @return + */ + @Operation(summary = "推断指定流程实例的过滤掉部分节点执行顺序") + @GetMapping("/node/filter/forecasting") + @Override + public CommonResponse> processInstanceFilterNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId, + @Nullable @RequestParam(required = false) String tenantId, + @RequestParam(required = false, defaultValue = "false") Boolean allNode, + @Nullable @RequestParam(required = false) List nodeDefinitionKeys) { + if (allNode) { + return success(bpmnProcessInstanceService.getProcessInstanceNodeForecast(processInstanceId, tenantId)); + } else { + return success(bpmnProcessInstanceService.getProcessInstanceNodeFilterForecast(processInstanceId, tenantId, nodeDefinitionKeys)); + } + } + + /** + * 获取指定流程实例的流程变量 * * @param processInstanceId * @param tenantId * @return */ - @Operation(summary = "获取指定流程实例的协作单位") + @Operation(summary = "获取指定流程实例的流程变量") @GetMapping("/cooperation-org") @Override - public CommonResponse getCooperationOrg(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, - @Nullable String tenantId) { + public CommonResponse> getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, + @Nullable String tenantId) { HistoricProcessInstance processInstance = bpmnProcessInstanceService.getProcessInstance(processInstanceId, tenantId, true); - return success((CooperationOrgDTO) processInstance.getProcessVariables().get(BIZ_ORG_RELATION)); + return success(processInstance.getProcessVariables()); } /** @@ -256,4 +388,200 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { public CommonResponse> getTenantIds() { return success(bpmnProcessInstanceService.getTenantIds()); } + + /** + * 校验指定流程实例下,是否存在指定的审批人 + * + * @return true 是在当前流程实例中,存在指定的审批人 + */ + @Operation(summary = "校验指定流程实例下,是否存在指定的审批人") + @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); + resetPersonInfo(log); + return success(log); + } + + + /** + * 根据任务id查询任务状态,按钮详情 + * + * @param taskButtonsSearchDTO 请求参数 + * @return + */ + @Override + @Operation(summary = "根据流程实例id,任务id查询任务状态,按钮详情") + @PostMapping("/task/buttons/find") + public CommonResponse findProcessSingleTaskButtons(@Validated @RequestBody BpmnTaskButtonSearchDTO taskButtonsSearchDTO) { + return success(bpmnProcessInstanceService.findTaskButtons(taskButtonsSearchDTO)); + } + + private void resetPersonInfo(BpmnProcessInstanceLogVO log) { + List users = log.getTaskDetails().stream().flatMap(taskLog -> { + Stream assigner = Objects.nonNull(taskLog.getAssigneeSnapshot()) ? Stream.of(taskLog.getAssigneeSnapshot()) : Stream.empty(); + Stream forecastAssignees = CollectionUtils.isEmpty(taskLog.getForecastAssignees()) ? Stream.empty() : taskLog.getForecastAssignees().stream(); + return Stream.concat(assigner, forecastAssignees); + }).collect(Collectors.toList()); +// List users = log.getTaskDetails().stream().map(BpmnTaskInstanceLogVO::getAssigneeSnapshot).filter(Objects::nonNull).filter(e -> NumberUtils.isNumber(e.getPersonId())).collect(Collectors.toList()); + users.add(log.getInitiator()); + populateUsersAvatar(users); +// users.stream().collect(Collectors.toMap(BpmnTaskDelegateAssigner::getPersonId, Function.identity(), (s,t)->s)) +// .forEach(); + + } + + 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); + } + + @Operation(summary = "更新指定流程表单最后一次编辑内容中指定 KEY 的全部数据") + @PostMapping("/form/variable/update") + @Override + public CommonResponse updateInstanceFormVariables(@Validated @RequestBody FormVariablesUpdateDTO dto) { + bpmnProcessInstanceService.overrideFormVariables(dto); + return CommonResponse.success(true); + } + + @Override + @Operation(summary = "签署业务流程实例在审批待办中查询使用的文档列表") + @PostMapping("/select/doc/list") + public CommonResponse> processInstanceSelectDocs(@Validated @RequestBody ProcessDocQueryDTO dto) { + List docs = bpmnProcessInstanceService.processInstanceSelectDocs(dto); + if (CollectionUtils.isEmpty(docs)) { + return CommonResponse.success(docs); + } + List fileKeys = docs.stream().map(DocPendingVO::getFileKey).filter(StringUtils::hasText).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(fileKeys)) { + return CommonResponse.success(docs); + } + ApiSignUrlDownloadRequest ossRequest = new ApiSignUrlDownloadRequest(); + ossRequest.setFileKeys(fileKeys); + Map ossUrlMap = RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(ossRequest), "批量获取文件 OSS 地址", ossRequest) + .stream().collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, Function.identity(), (s, t) -> s)); + docs.forEach(i -> { + if (StringUtils.hasText(i.getFileKey())) { + ApiSignUrlDownloadResponse ossFileInfo = ossUrlMap.getOrDefault(i.getFileKey(), null); + if (Objects.nonNull(ossFileInfo)) { + i.setOssUrl(ossFileInfo.getSignUrl()); + i.setStorageSize(ossFileInfo.getStorageSize()); + } + } + }); + return success(docs); + } + + @Override + @Operation(summary = "获取审批人阅读状态") + @PostMapping("/approver/read/status") + public CommonResponse> approverReadStatus(@Validated @RequestBody ApproverReadStatusDTO dto) { + if (!StringUtils.hasText(dto.getAssigner().getPersonId())) { + throw new WorkflowEngineException(PROCESS_DOC_READ_PARAM_ERROR); + } + return success(readRecordService.queryReadStatus(dto)); + } + + @Override + @Operation(summary = "修改审批人关联文档阅读状态") + @PostMapping("/approver/read/status/change") + public CommonResponse approveReadStatusChange(@Validated @RequestBody ChangeApproverReadStatusDTO dto) { + if (!StringUtils.hasText(dto.getAssigner().getPersonId())) { + throw new WorkflowEngineException(PROCESS_DOC_READ_PARAM_ERROR); + } + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + List relationDocs = commandExecutor.execute(new CustomGetModelDocsCmd(dto.getProcessInstanceId(), true, extAxModelDocMapper, extAxReModelService)); + relationDocs.stream().filter(i -> Objects.equals(i.getId(), dto.getDocId())) + .findAny().orElseThrow(() -> new WorkflowEngineException(PROCESS_DOC_ID_NOT_IN_MODEL)); + + return success(readRecordService.changeReadStatus(dto)); + } + + @Override + @Operation(summary = "获取签署业务流程最后替换的文档 fileKey 集合") + @GetMapping("/final/docs") + public CommonResponse> getProcessInstanceFinalDocs(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId) { + return success(extAxProcessSignService.findByProcessInstanceId(processInstanceId).getFileArchive()); + } + + /** + * 查询 ExtAxProcessLog 表中审批人的冗余信息 + * + * @param dto + * @return + */ + @Operation(summary = "查询 ExtAxProcessLog 表中审批人的冗余信息") + @GetMapping("/log/approve/ext") + @Override + public CommonResponse> getProcessLogByInstanceIdAndPersonId(@Validated @RequestBody LogApproveSearchDTO dto) { + + ExtAxProcessLog query = new ExtAxProcessLog(); + query.setProcessInstanceId(dto.getProcessInstanceId()); + if (StringUtils.hasText(dto.getTaskId())) { + query.setTaskId(dto.getTaskId()); + } else { + if (!NumberUtil.isLong(dto.getPersonId())) { + throw new WorkflowEngineException(PROCESS_EXT_LOG_PARAM_ERROR); + } + query.setAssigneeId(Long.valueOf(dto.getPersonId())); + query.setAssigneeTenantId(dto.getTenantId()); + query.setAssigneeOuId(Objects.isNull(dto.getOuId()) ? "" : dto.getOuId()); + } + + if (Objects.isNull(dto.getStatus())) { + query.setStatus(BpmnProcessInstanceResultEnum.PROCESSING.getStatus()); + } else { + query.setStatus(dto.getStatus().getStatus()); + } + List extAxProcessLogs = extAxProcessLogService.genericQuery(query); + return success(ListUtils.emptyIfNull(extAxProcessLogs).stream().map(i -> { + ExtProcessLogVO logVO = new ExtProcessLogVO(); + logVO.setId(i.getId()); + logVO.setProcessInstanceId(i.getProcessInstanceId()); + logVO.setProcessTenantId(i.getTenantId()); + logVO.setActivityId(i.getActivityId()); + logVO.setActivityName(i.getActivityName()); + logVO.setTaskId(i.getTaskId()); + logVO.setAssigneePersonId(String.valueOf(i.getAssigneeId())); + logVO.setAssigneeTenantId(i.getAssigneeTenantId()); + logVO.setAssigneeName(i.getAssigneeName()); + logVO.setAssigneeOuId(i.getAssigneeOuId()); + logVO.setStatus(BpmnProcessInstanceResultEnum.valueOfStatus(i.getStatus())); + logVO.setOrgStructureSnapshotInfo(i.getExtra()); + return logVO; + }).collect(Collectors.toList())); + } } 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 new file mode 100644 index 000000000..571ccd432 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessJobController.java @@ -0,0 +1,66 @@ +package cn.axzo.workflow.server.controller.web.bpmn; + +import cn.axzo.workflow.client.feign.bpmn.ProcessJobApi; +import cn.axzo.workflow.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 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; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +import static cn.axzo.workflow.common.code.AsyncJobRespCode.RESUME_JOB_REQUEST_PARAM_ERROR; +import static cn.azxo.framework.common.model.CommonResponse.success; + +/** + * 流程job控制器 + */ +@Slf4j +@RequestMapping({"/web/v1/api/process/job", "/api/process/job"}) +@RestController +@ErrorReporter +@Validated +public class BpmnProcessJobController implements ProcessJobApi { + + @Resource + private BpmnProcessJobService bpmnProcessJobService; + + @Override + @GetMapping("/dead-letter/resume") + public CommonResponse executeDeadLetterJobAction(@RequestParam String jobId, @RequestParam String procInstId) { + log.info("将死信任务重新激活 executeDeadLetterJobAction ===>>>参数:{}, {}", jobId, procInstId); + if (StringUtils.isBlank(jobId) && StringUtils.isBlank(procInstId)) { + throw new WorkflowEngineException(RESUME_JOB_REQUEST_PARAM_ERROR); + } + if (StringUtils.isNotBlank(jobId)) { + bpmnProcessJobService.executeDeadLetterJobActionByJobId(jobId); + } else { + bpmnProcessJobService.executeDeadLetterJobActionByProcInstId(procInstId); + } + 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/BpmnProcessModelController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessModelController.java index 9c0c94951..d1d6b13d6 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessModelController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessModelController.java @@ -1,22 +1,72 @@ package cn.axzo.workflow.server.controller.web.bpmn; +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi; +import cn.axzo.nanopart.doc.api.index.request.CopyNodeRequest; +import cn.axzo.oss.http.api.ServerFileServiceApi; +import cn.axzo.oss.http.model.copyobject.ServerFileBatchCopyObjectRequest; +import cn.axzo.oss.http.model.copyobject.ServerFileBatchCopyObjectResponse; import cn.axzo.workflow.client.feign.bpmn.ProcessModelApi; +import cn.axzo.workflow.common.enums.FileTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; 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.model.doc.DocByIdDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCloneDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocResetDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocTenantQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.RestPrintTemplateConfigDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; 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.model.doc.DocBaseVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; +import cn.axzo.workflow.common.model.response.print.PrintModelDTO; +import cn.axzo.workflow.core.conf.CustomEventManager; +import cn.axzo.workflow.core.engine.cmd.CustomGetModelByDefinitionIdCmd; +import cn.axzo.workflow.core.engine.cmd.CustomGetModelDocsCmd; +import cn.axzo.workflow.core.engine.event.DocChangeEventImpl; +import cn.axzo.workflow.core.repository.entity.ExtAxModelDoc; +import cn.axzo.workflow.core.repository.mapper.ExtAxModelDocMapper; +import cn.axzo.workflow.core.service.AggregateModelService; +import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; +import cn.axzo.workflow.core.service.BpmnProcessInstanceService; import cn.axzo.workflow.core.service.BpmnProcessModelService; +import cn.axzo.workflow.core.service.ExtAxDocContentService; +import cn.axzo.workflow.core.service.ExtAxModelDocService; import cn.axzo.workflow.core.service.ExtAxReModelService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; +import cn.axzo.workflow.server.common.util.WpsUtil; import cn.azxo.framework.common.model.CommonResponse; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.repository.Model; +import org.flowable.spring.SpringProcessEngineConfiguration; +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; @@ -30,8 +80,19 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_CLONE_ERROR; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_HIPRINT_ID_INVAILD; +import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_QUERY_ERROR; +import static cn.axzo.workflow.common.code.OtherRespCode.ILLEGAL_PARAM_ERROR; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; +import static cn.axzo.workflow.common.enums.FileTypeEnum.EXCEL; +import static cn.axzo.workflow.common.enums.FileTypeEnum.WORD; import static cn.azxo.framework.common.model.CommonResponse.success; /** @@ -44,11 +105,32 @@ import static cn.azxo.framework.common.model.CommonResponse.success; @ErrorReporter @Validated public class BpmnProcessModelController implements ProcessModelApi { - @Resource private BpmnProcessModelService bpmnProcessModelService; @Resource + private BpmnProcessDefinitionService bpmnProcessDefinitionService; + @Resource + private AggregateModelService aggregateModelService; + @Resource private ExtAxReModelService reModelService; + @Resource + private BpmnProcessInstanceService bpmnProcessInstanceService; + @Resource + private ExtAxModelDocService modelDocService; + @Resource + private ExtAxDocContentService docContentService; + @Resource + private DocAnonymousDatabaseApi docAnonymousApi; + @Resource + private ServerFileServiceApi fileServiceApi; + @Resource + private SpringProcessEngineConfiguration springProcessEngineConfiguration; + @Resource + private ExtAxModelDocMapper extAxModelDocMapper; + @Resource + private WpsUtil wpsUtil; + @Resource + private CustomEventManager eventPublisher; /** * 获取流程模型的查询结果 @@ -57,26 +139,47 @@ public class BpmnProcessModelController implements ProcessModelApi { @GetMapping("/page") @Override public CommonResponse> page(@Validated @RequestBody BpmnModelSearchDTO dto) { - log.info("获取流程模型getModelPage===>>>参数:{}", JSON.toJSONString(dto)); + log.info("获取流程模型getModelPage===>>>参数:{}", JSONUtil.toJsonStr(dto)); BpmPageResult result = bpmnProcessModelService.getModelPage(dto); return success(result); } /** * 创建流程模型 - * return modelId的主键 + * return bpmnModelId的主键 */ @Operation(summary = "创建流程模型") @PostMapping("/create") @Override @RepeatSubmit public CommonResponse create(@Validated @RequestBody BpmnModelCreateDTO dto) { - log.info("创建流程createBpmModel===>>>参数:{}", JSON.toJSONString(dto)); - String result = bpmnProcessModelService.createBpmModel(dto); - return success(result); + log.info("创建流程createBpmModel===>>>参数:{}", JSONUtil.toJsonStr(dto)); + String bpmnModelId; + if (Objects.isNull(dto.getFormJsonModel())) { + // 无表单的模型 + bpmnModelId = bpmnProcessModelService.createBpmModel(dto); + } else { + bpmnModelId = aggregateModelService.createBpmnAndFormModel(dto); + } + + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + List commonDocBaseVos = commandExecutor.execute(new CustomGetModelDocsCmd(null, dto.getKey(), null, false, false, extAxModelDocMapper, reModelService)); + //通用模板的文档 + if (!CollectionUtils.isEmpty(commonDocBaseVos)) { + //进行克隆 + commonDocBaseVos.forEach(cb -> this.singleCloneDoc(cb.getId(), DocModelInfo.builder() + .targetModelKey(dto.getKey()) + .targetModelId(bpmnModelId) + .tenantId(dto.getTenantId()) + .tag(cb.getTag()) + .build())); + eventPublisher.publishEvent(new DocChangeEventImpl(dto.getKey(), dto.getTenantId(), Collections.emptyList(), modelDocService.querySetting(dto.getKey(), NO_TENANT_ID))); + } + return success(bpmnModelId); } /** + * 引擎内部测试接口,勿随意调用 * 通过 Xml 创建模型 * * @return @@ -84,7 +187,6 @@ public class BpmnProcessModelController implements ProcessModelApi { @Operation(summary = "通过 Xml 创建模型", hidden = true) @PostMapping("/xml/create") public CommonResponse createWithXml() { - // TODO 不完善的接口 return success(bpmnProcessModelService.createBpmModelWithXml("")); } @@ -95,7 +197,7 @@ public class BpmnProcessModelController implements ProcessModelApi { @GetMapping("/get") @Override public CommonResponse getById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam String processModelId, @RequestParam(required = false) String tenantId) { - log.info("获取模型getModelDetailById===>>>参数:{}", JSON.toJSONString(processModelId)); + log.info("获取模型getModelDetailById===>>>参数:{}", JSONUtil.toJsonStr(processModelId)); BpmnModelDetailVO result = bpmnProcessModelService.getById(processModelId, tenantId); return success(result); } @@ -108,7 +210,7 @@ public class BpmnProcessModelController implements ProcessModelApi { @GetMapping("/getByKey") @Override public CommonResponse getByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam String processModelKey, @NotBlank(message = "租户不能为空") @RequestParam String tenantId) { - log.info("获取模型getModelDetailByKey===>>>参数:{}", JSON.toJSONString(processModelKey)); + log.info("获取模型getModelDetailByKey===>>>参数:{}", JSONUtil.toJsonStr(processModelKey)); BpmnModelDetailVO result = bpmnProcessModelService.getByKey(processModelKey, tenantId); return success(result); } @@ -124,7 +226,7 @@ public class BpmnProcessModelController implements ProcessModelApi { @Override public CommonResponse getModelExt(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId) { - log.info("获取指定模型的扩展属性getModelExt===>>>参数:{}", JSON.toJSONString(modelId)); + log.info("获取指定模型的扩展属性getModelExt===>>>参数:{}", JSONUtil.toJsonStr(modelId)); return success(reModelService.getStatusByModelId(modelId)); } @@ -136,7 +238,11 @@ public class BpmnProcessModelController implements ProcessModelApi { @Override public CommonResponse update(@Validated @RequestBody BpmnModelUpdateDTO dto) { log.info("修改流程信息updateBpmModel===>>> 模型 ID: {}", dto.getProcessModelId()); - bpmnProcessModelService.updateBpmModel(dto); + if (Objects.isNull(dto.getFormJsonModel())) { + bpmnProcessModelService.updateBpmModel(dto); + } else { + aggregateModelService.updateBpmnAndFormModel(dto); + } return success(dto.getProcessModelId()); } @@ -153,10 +259,10 @@ public class BpmnProcessModelController implements ProcessModelApi { @PostMapping("/deploy") @Override public CommonResponse deployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId, @RequestParam(required = false, defaultValue = "") String modelTenantId, @RequestParam(required = false) String operator) { - log.info("部署模型deployBpmModelById===>>>参数:{}, 租户 ID: {}, operator: {}", JSON.toJSONString(processModelId), + log.info("部署模型deployBpmModelById===>>>参数:{}, 租户 ID: {}, operator: {}", JSONUtil.toJsonStr(processModelId), modelTenantId, operator); BpmnTaskDelegateAssigner assignee = JSON.parseObject(operator, BpmnTaskDelegateAssigner.class); - String result = bpmnProcessModelService.deployBpmModelById(processModelId, modelTenantId, assignee); + String result = aggregateModelService.deployBpmnAndFormModel(processModelId, modelTenantId, assignee); return success(result); } @@ -173,7 +279,7 @@ public class BpmnProcessModelController implements ProcessModelApi { @PostMapping("/deployByKey") @Override public CommonResponse deployByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam(required = false) String processModelKey, @NotBlank(message = "租户不能为空") @RequestParam(required = false) String modelTenantId, @RequestParam(required = false) String operator) { - log.info("部署模型deployBpmModelByKey===>>>参数:{}", JSON.toJSONString(processModelKey)); + log.info("部署模型deployBpmModelByKey===>>>参数:{}", JSONUtil.toJsonStr(processModelKey)); BpmnTaskDelegateAssigner assignee = JSON.parseObject(operator, BpmnTaskDelegateAssigner.class); String result = bpmnProcessModelService.deployBpmModelByKey(processModelKey, modelTenantId, assignee); return success(result); @@ -193,7 +299,7 @@ public class BpmnProcessModelController implements ProcessModelApi { public CommonResponse unDeployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId, @RequestParam(required = false, defaultValue = "") String tenantId, @RequestParam(required = false) String operator) { - log.info("部署模型unDeployBpmModelById===>>>参数:{}", JSON.toJSONString(processModelId)); + log.info("部署模型unDeployBpmModelById===>>>参数:{}", JSONUtil.toJsonStr(processModelId)); BpmnTaskDelegateAssigner assignee = JSON.parseObject(operator, BpmnTaskDelegateAssigner.class); bpmnProcessModelService.unDeployBpmModelById(processModelId, tenantId, assignee); return success(); @@ -210,7 +316,7 @@ public class BpmnProcessModelController implements ProcessModelApi { @Override public CommonResponse deleteById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam String processModelId, @RequestParam(required = false, defaultValue = "") String tenantId) { - log.info("删除模型deleteBpmModel===>>>参数:{}", JSON.toJSONString(processModelId)); + log.info("删除模型deleteBpmModel===>>>参数:{}", JSONUtil.toJsonStr(processModelId)); bpmnProcessModelService.deleteBpmModelById(processModelId, tenantId); return success(); } @@ -227,7 +333,7 @@ public class BpmnProcessModelController implements ProcessModelApi { @Override public CommonResponse deleteByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam String processModelKey, @RequestParam(required = false, defaultValue = "") String tenantId) { - log.info("删除模型deleteBpmModel===>>>参数:{}", JSON.toJSONString(processModelKey)); + log.info("删除模型deleteBpmModel===>>>参数:{}", JSONUtil.toJsonStr(processModelKey)); bpmnProcessModelService.deleteBpmModelByKey(processModelKey, tenantId); return success(); } @@ -243,13 +349,39 @@ public class BpmnProcessModelController implements ProcessModelApi { @Operation(summary = "修改模型状态") @PostMapping("/changeStatus") @Override - public CommonResponse changeStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId, - @NotNull(message = "状态不能为空") @RequestParam Integer status, - @RequestParam(required = false) String operator) { - log.info("修改模型状态changeStatus===>>>参数:{}", JSON.toJSONString(modelId)); - BpmnTaskDelegateAssigner assignee = JSON.parseObject(operator, BpmnTaskDelegateAssigner.class); + public CommonResponse changeStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId, + @NotNull(message = "状态不能为空") @RequestParam Integer status, + @RequestParam(required = false) String operator) { + log.info("修改模型状态changeStatus===>>>参数:{}", JSONUtil.toJsonStr(modelId)); + BpmnTaskDelegateAssigner assignee = null; + if (StringUtils.hasText(operator)) { + assignee = JSON.parseObject(operator, BpmnTaskDelegateAssigner.class); + } bpmnProcessModelService.changeStatus(modelId, status, assignee); - return success(); + return success(true); + } + + /** + * 修改模型打印开关状态 + * + * @param modelId + * @param status + * @param operator + * @return + */ + @Operation(summary = "修改模型打印开关状态") + @PostMapping("/print/changeStatus") + @Override + public CommonResponse changePrintStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId, + @NotNull(message = "状态不能为空") @RequestParam Integer status, + @RequestParam(required = false) String operator) { + log.info("修改模型打印开关状态changeStatus===>>>参数:{}", JSONUtil.toJsonStr(modelId)); + BpmnTaskDelegateAssigner assignee = null; + if (StringUtils.hasText(operator)) { + assignee = JSON.parseObject(operator, BpmnTaskDelegateAssigner.class); + } + bpmnProcessModelService.changePrintStatus(modelId, status, assignee); + return success(true); } /** @@ -273,8 +405,353 @@ public class BpmnProcessModelController implements ProcessModelApi { @Operation(summary = "查询模型的租户集合") @GetMapping("/tenant/ids") @Override - public CommonResponse> getTenantIds() { + public CommonResponse> getModelTenantIds() { log.info("查询模型的租户集合getTenantIds"); return success(bpmnProcessModelService.getTenantIds()); } + + /** + * 打印模板配置内容 + * + * @param dto + * @return + */ + @Operation(summary = "打印模板配置内容") + @PostMapping("/print/template/upsert") + @Override + public CommonResponse printTemplateConfig(@Validated @RequestBody PrintTemplateConfigUpsertDTO dto) { + log.info("操作打印模板配置内容: {}", JSON.toJSONString(dto)); + bpmnProcessModelService.printTemplateConfig(dto); + return success(); + } + + /** + * 获取打印模板配置内容 + * + * @param dto + * @return + */ + @Operation(summary = "获取打印模板配置内容") + @PostMapping("/print/template/config/query") + @Override + public CommonResponse getPrintTemplateConfig(@Validated @RequestBody PrintTemplateConfigQueryDTO dto) { + log.info("获取打印模板配置内容"); + if (!StringUtils.hasText(dto.getModelId()) && !StringUtils.hasText(dto.getProcessInstanceId()) && !StringUtils.hasText(dto.getProcessDefinitionKey())) { + if (!StringUtils.hasText(dto.getProcessDefinitionKey()) && !StringUtils.hasText(dto.getTenantId())) { + throw new WorkflowEngineException(ILLEGAL_PARAM_ERROR, "processDefinitionKey 与 tenantId 不能同时为空"); + } + throw new WorkflowEngineException(ILLEGAL_PARAM_ERROR, "模型 ID 、实例 ID 和业务 ID 不能同时为空"); + } + if (!StringUtils.hasText(dto.getModelId()) && StringUtils.hasText(dto.getProcessInstanceId())) { + dto.setModelId(bpmnProcessInstanceService.getModelIdByProcessInstanceId(dto.getProcessInstanceId())); + } + if (StringUtils.hasText(dto.getProcessDefinitionKey())) { + BpmnProcessDefinitionVO definition = bpmnProcessDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(), dto.getTenantId()); + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + Model model = commandExecutor.execute(new CustomGetModelByDefinitionIdCmd(definition.getId())); + dto.setModelId(model.getId()); + } + return success(bpmnProcessModelService.getPrintTemplateConfig(dto.getModelId())); + } + + /** + * 代运营重置打印模板 + * + * @param dto + * @return + */ + @Operation(summary = "代运营重置打印模板") + @PostMapping(value = "/print/template/config/reset") + public CommonResponse resetPrintTemplateConfig(@Validated @RequestBody RestPrintTemplateConfigDTO dto) { + String activeProcessDefinitionId = bpmnProcessDefinitionService.getActiveProcessDefinitionId(NO_TENANT_ID, dto.getProcessDefinitionKey()); + String modelId = bpmnProcessInstanceService.getModelIdByProcessDefinitionId(null, activeProcessDefinitionId); + PrintModelDTO config = bpmnProcessModelService.getPrintTemplateConfig(modelId); + bpmnProcessModelService.printTemplateConfig(PrintTemplateConfigUpsertDTO.builder() + .modelId(dto.getModelId()) + .printFileName(config.getPrintFileName()) + .printTemplateName(config.getPrintTemplateName()) + .printTemplateConfig(StringUtils.hasText(config.getPrintTemplateConfig()) ? config.getPrintTemplateConfig() : "") + .build()); + return success(true); + } + + /** + * 搜索文档列表 + * + * @param dto + * @return + */ + @Override + @Operation(summary = "搜索文档列表") + @PostMapping("/doc/page") + public CommonResponse> docPage(@Validated @RequestBody DocSearchDTO dto) { + return success(modelDocService.docPage(dto)); + } + + /** + * 获取指定 docIds 文档列表 + * + * @param dto + * @return + */ + @Override + @Operation(summary = "获取指定 docIds 文档列表") + @PostMapping("/doc/ids") + public CommonResponse> docByIds(@Validated @RequestBody DocByIdDTO dto) { + return success(modelDocService.getIds(dto.getIds())); + } + + /** + * 根据业务 ID 获取模型文档列表,自动适配公共模板和代运营 + * + * @param dto + * @return + */ + @Override + @Operation(summary = "根据业务 ID 获取模型文档列表,自动适配公共模板和代运营") + @PostMapping(value = "/doc/list") + public CommonResponse> docList(@Validated @RequestBody DocQueryDTO dto) { + if (!StringUtils.hasText(dto.getProcessInstanceId()) && !StringUtils.hasText(dto.getProcessDefinitionKey())) { + throw new WorkflowEngineException(MODEL_FILE_QUERY_ERROR); + } + return success(modelDocService.docList(dto)); + } + + /** + * 获取关联 HiPrint 类型文档模板内容 + * + * @param fileRelationId + * @return + */ + @Override + @Operation(summary = "获取关联 HiPrint 类型文档模板内容") + @PostMapping(value = "/hi-print/content/get") + public CommonResponse getHiPrintContent(@RequestParam String fileRelationId) { + if (!NumberUtil.isNumber(fileRelationId)) { + throw new WorkflowEngineException(MODEL_FILE_HIPRINT_ID_INVAILD); + } + return success(docContentService.getContent(Long.valueOf(fileRelationId))); + } + + /** + * 添加关联文档 + * + * @param dto + * @return + */ + @Override + @Operation(summary = "添加关联文档") + @PutMapping(value = "/doc/create") + public CommonResponse createDoc(@Validated @RequestBody DocCreateDTO dto) { + return success(modelDocService.createDoc(dto, true) > 0); + } + + /** + * 修改关联文档 + * + * @param dto + * @return + */ + @Override + @Operation(summary = "修改关联文档") + @PostMapping(value = "/doc/update") + public CommonResponse updateDoc(@Validated @RequestBody DocUpdateDTO dto) { + return success(modelDocService.updateDoc(dto)); + } + + /** + * 克隆关联文档 + * + * @param docId + * @return + */ + @Override + @Operation(summary = "克隆关联文档") + @PostMapping(value = "/doc/clone") + public CommonResponse cloneDoc(@RequestParam("id") Long docId) { + return success(this.singleCloneDoc(docId, null)); + } + + /** + * 单个复制文档模版 + * + * @param docId 复制的模版id + * @param docModelInfo 模版关联的审批业务模版信息 + * @return 是否成功 + */ + private Boolean singleCloneDoc(Long docId, DocModelInfo docModelInfo) { + DocBaseVO originDoc = modelDocService.get(docId); + ArrayList wpsSupported = Lists.newArrayList(WORD, EXCEL); + if (wpsSupported.contains(originDoc.getFileType())) { + // WPS 文档 clone + CopyNodeRequest copy = new CopyNodeRequest(); + copy.setCode(originDoc.getFileRelationId()); + CommonResponse response = docAnonymousApi.copy(copy); + if (Objects.isNull(response) || !Objects.equals(response.getCode(), 200)) { + throw new WorkflowEngineException(MODEL_FILE_CLONE_ERROR, Objects.isNull(response) ? "网络异常" : response.getMsg()); + } + DocCreateDTO newDoc = BeanMapper.copyBean(originDoc, DocCreateDTO.class); + newDoc.setFileRelationId(response.getData()); + newDoc.setTag(""); + if (docModelInfo != null) { + newDoc.setModelId(docModelInfo.getTargetModelId()); + newDoc.setModelKey(docModelInfo.getTargetModelKey()); + newDoc.setTenantId(docModelInfo.getTenantId()); + newDoc.setTag(docModelInfo.getTag()); + } + return modelDocService.createDoc(newDoc, false) > 0; + } + if (Objects.equals(originDoc.getFileType(), FileTypeEnum.PDF)) { + // PDF 文档 clone + CommonResponse response = fileServiceApi.batchCopyObject(ServerFileBatchCopyObjectRequest.builder() + .copyObjects(Sets.newHashSet(ServerFileBatchCopyObjectRequest.ServerFileCopyObjectRequest.builder() + .srcFileKey(originDoc.getFileRelationId()) + .build())) + .build()); + if (Objects.isNull(response) || Objects.equals(response.getCode(), 200)) { + throw new WorkflowEngineException(MODEL_FILE_CLONE_ERROR, Objects.isNull(response) ? "网络异常" : response.getMsg()); + } + DocCreateDTO newDoc = BeanMapper.copyBean(originDoc, DocCreateDTO.class); + newDoc.setFileRelationId(response.getData().getResponses().get(0).getSrcFileKey()); + newDoc.setTag(""); + if (docModelInfo != null) { + newDoc.setModelId(docModelInfo.getTargetModelId()); + newDoc.setModelKey(docModelInfo.getTargetModelKey()); + newDoc.setTenantId(docModelInfo.getTenantId()); + newDoc.setTag(docModelInfo.getTag()); + } + return modelDocService.createDoc(newDoc, false) > 0; + } + return modelDocService.cloneDoc(DocCloneDTO.builder() + .docId(docId) + .targetTenantId(docModelInfo.getTenantId()) + .targetModelId(docModelInfo.getTargetModelId()) + .targetFileTag(docModelInfo.getTag()) + .build()) > 0; + } + + /** + * 删除指定文档 + * + * @param docId + * @return + */ + @Override + @Operation(summary = "删除指定文档") + @DeleteMapping(value = "/doc/delete") + public CommonResponse deleteDoc(@RequestParam("id") Long docId) { + Boolean result = modelDocService.deleteDoc(docId); + if (Objects.equals(Boolean.TRUE, result)) { + DocBaseVO doc = modelDocService.get(docId, true); + if (Objects.equals(WORD, doc.getFileType()) || Objects.equals(EXCEL, doc.getFileType())) { + wpsUtil.deleteWpsFile(doc.getFileRelationId()); + } + } + return success(result); + } + + /** + * 关联文档配置排序 + * + * @param dto + * @return + */ + @Operation(summary = "关联文档配置排序") + @PostMapping(value = "/doc/order") + @Override + public CommonResponse orderDoc(@Validated @RequestBody DocOrderDTO dto) { + return success(modelDocService.orderDoc(dto)); + } + + /** + * 设置关联文档状态 + * + * @param dto + * @return + */ + @Operation(summary = "设置关联文档状态") + @PostMapping(value = "/doc/status") + public CommonResponse statusDoc(@Validated @RequestBody DocStatusDTO dto) { + return success(modelDocService.statusDoc(dto)); + } + + /** + * 设置关联文档的必选状态 + * + * @param dto + * @return + */ + @Override + @Operation(summary = "设置关联文档的必选状态") + @PostMapping(value = "/doc/require") + public CommonResponse requireDoc(@Validated @RequestBody DocStatusDTO dto) { + return success(modelDocService.requireDoc(dto)); + } + + /** + * 特殊的查询设置过关联过文档的工作台 ID 集合 + * + * @param dto + * @return + */ + @Override + @Operation(summary = "特殊的查询设置过关联过文档的工作台 ID 集合") + @PostMapping(value = "/has/docs/tenantId") + public CommonResponse> hasFilesTenantIds(@Validated @RequestBody DocTenantQueryDTO dto) { + return success(modelDocService.querySetting(dto.getProcessDefinitionKey(), null) + .stream().map(ExtAxModelDoc::getTenantId) + .filter(StringUtils::hasText) + .map(Long::parseLong) + .distinct() + .collect(Collectors.toList())); + } + + /** + * 重置模版关联文档 + * + * @param dto + * @return + */ + @Override + @Operation(summary = "重置模版关联文档") + @PostMapping(value = "/doc/reset") + public CommonResponse resetDoc(@Validated @RequestBody DocResetDTO dto) { + DocSearchDTO docSearchDTO = new DocSearchDTO(); + docSearchDTO.setModelId(dto.getModelId()); + docSearchDTO.setTenantId(dto.getWorkspaceId()); + docSearchDTO.setPageNo(1); + docSearchDTO.setPageSize(999); + BpmPageResult docBaseVOBpmPageResult = modelDocService.docPage(docSearchDTO); + List docBaseVOS = ListUtils.emptyIfNull(docBaseVOBpmPageResult.getList()); + //先删除 + if (!CollectionUtils.isEmpty(docBaseVOS)) { + modelDocService.batchDeleteDoc(docBaseVOS.stream().map(DocBaseVO::getId).collect(Collectors.toList())); + } + + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + List commonDocBaseVos = commandExecutor.execute(new CustomGetModelDocsCmd(null, + dto.getCategory(), null, false, false, extAxModelDocMapper, reModelService)); + //通用模板的文档 + if (!CollectionUtils.isEmpty(commonDocBaseVos)) { + //进行克隆 + commonDocBaseVos.forEach(cb -> this.singleCloneDoc(cb.getId(), DocModelInfo.builder() + .targetModelKey(dto.getCategory()) + .targetModelId(dto.getModelId()) + .tenantId(dto.getWorkspaceId()) + .tag(cb.getTag()) + .build())); + } + return CommonResponse.success(Boolean.TRUE); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class DocModelInfo { + private String targetModelId; + private String targetModelKey; + private String tenantId; + private String tag; + } } 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 4507cff07..4bf89d658 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 @@ -3,16 +3,23 @@ package cn.axzo.workflow.server.controller.web.bpmn; 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.BpmnNodeBackSystemOperateDTO; +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.BpmnTaskAuditWithFormDTO; +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.BpmnTaskResetApproversDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; 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.task.BpmnHistoricTaskInstanceGroupVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO; @@ -21,11 +28,14 @@ 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.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.apache.commons.collections4.ListUtils; import org.flowable.form.api.FormInfo; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -38,8 +48,11 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Nullable; import javax.annotation.Resource; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import static cn.azxo.framework.common.model.CommonResponse.success; @@ -51,12 +64,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; - /** * 待审核列表 */ @@ -64,7 +76,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @GetMapping("/page/todo") @Override public CommonResponse> getTodoTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto) { - log.info("待审核列表 getTodoTaskPage===>>>参数:{}", dto); + log.info("待审核列表 getTodoTaskPage===>>>参数:{}", JSON.toJSONString(dto)); return success(bpmnProcessTaskService.getTodoTaskPage(dto)); } @@ -75,7 +87,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @GetMapping("/page/done") @Override public CommonResponse> getDoneTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto) { - log.info("已完成的审批列表 getDoneTaskPage===>>>参数:{}", dto); + log.info("已完成的审批列表 getDoneTaskPage===>>>参数:{}", JSON.toJSONString(dto)); return success(bpmnProcessTaskService.getDoneTaskPage(dto)); } @@ -88,7 +100,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @Override @RepeatSubmit public CommonResponse approveTask(@Validated @RequestBody BpmnTaskAuditDTO dto) { - log.info("同意 approveTask===>>>参数:{}", dto); + log.info("同意 approveTask===>>>参数:{}", JSON.toJSONString(dto)); List tempAttachments = ListUtils.defaultIfNull(dto.getAttachmentList(), new ArrayList<>()); if (StringUtils.hasText(dto.getSignatureUrl())) { AttachmentDTO signature = new AttachmentDTO(); @@ -98,10 +110,90 @@ public class BpmnProcessTaskController implements ProcessTaskApi { tempAttachments.add(signature); dto.setAttachmentList(tempAttachments); } + populateUsersAvatar(dto.getApprover()); bpmnProcessTaskService.approveTask(dto); return success(true); } + @Operation(summary = "通过任务") + @Override + @PostMapping("/form/approve") + @RepeatSubmit + public CommonResponse approveTaskWithForm(@Validated @RequestBody BpmnTaskAuditWithFormDTO dto) { + log.info("同意 approveTaskWithForm===>>>参数:{}", JSON.toJSONString(dto)); + List tempAttachments = ListUtils.defaultIfNull(dto.getAttachmentList(), new ArrayList<>()); + if (StringUtils.hasText(dto.getSignatureUrl())) { + AttachmentDTO signature = new AttachmentDTO(); + signature.setType(AttachmentTypeEnum.signature); + signature.setName(dto.getApprover().getAssignerName() + "的签名"); + signature.setUrl(dto.getSignatureUrl()); + tempAttachments.add(signature); + dto.setAttachmentList(tempAttachments); + } + populateUsersAvatar(dto.getApprover()); + if (CollectionUtils.isEmpty(dto.getFormVariables())) { + bpmnProcessTaskService.approveTask(dto); + } else { + bpmnProcessTaskService.approveTaskWithForm(dto); + } + return success(true); + } + + /** + * 批量同意 + */ + @Operation(summary = "批量同意") + @PostMapping("/batch/approve") + @Override + @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.getBackOptionalNodesByTaskId(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); + } + + /** + * 回退到指定节点 + */ + @Operation(summary = "系统操作回退任务到指定节点") + @PostMapping("/system/back") + @RepeatSubmit + @Override + public CommonResponse systemBackTask(@Validated @RequestBody BpmnNodeBackSystemOperateDTO dto) { + log.info("系统回退 systemBackTask===>>>参数:{}", JSON.toJSONString(dto)); + bpmnProcessTaskService.systemBackTask(dto); + return success(true); + } + /** * 驳回 */ @@ -110,11 +202,26 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @Override @RepeatSubmit public CommonResponse rejectTask(@Validated @RequestBody BpmnTaskAuditDTO dto) { - log.info("驳回 rejectTask===>>>参数:{}", dto); + log.info("驳回 rejectTask===>>>参数:{}", JSON.toJSONString(dto)); + populateUsersAvatar(dto.getApprover()); bpmnProcessTaskService.rejectTask(dto); return success(true); } + /** + * 批量驳回 + */ + @Operation(summary = "批量驳回任务") + @PostMapping("/batch/reject") + @Override + @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)); + } + /** * 转交 */ @@ -123,10 +230,24 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @Override @RepeatSubmit public CommonResponse transferTask(@Validated @RequestBody BpmnTaskTransferDTO dto) { + log.info("转交任务 transferTask===>>>参数:{}", JSON.toJSONString(dto)); + // 填充头像 + populateUsersAvatar(dto.getTargetAssigner()); bpmnProcessTaskService.transferTask(dto); return success(true); } + @Operation(summary = "批量转交任务") + @PostMapping("/batch/transfer") + @Override + @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)); + } + /** * 评论 */ @@ -134,8 +255,10 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @PostMapping("/comment") @Override @RepeatSubmit - public CommonResponse commentTask(@Validated @RequestBody BpmnTaskCommentDTO 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); } @@ -147,8 +270,28 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @Override @PostMapping("/countersign") @RepeatSubmit - public CommonResponse countersignTask(@Validated @RequestBody BpmnTaskCountersignDTO 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); + } + + /** + * 提级审批 + * @param dto + * @return + */ + @Operation(summary = "重置审批人") + @Override + @PostMapping("/approvers/reset") + @RepeatSubmit + public CommonResponse resetTaskApprovers(@Validated @RequestBody BpmnTaskResetApproversDTO dto) { + populateUsersAvatar(dto.getOriginAssigner()); + populateUsersAvatar(dto.getTargetAssignerList()); + bpmnProcessTaskService.resetTaskApprovers(dto); return success(true); } @@ -163,6 +306,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @Override @RepeatSubmit public CommonResponse remindTask(@Validated @RequestBody BpmnTaskRemindDTO dto) { + log.info("催办任务 remindTask===>>>参数:{}", JSON.toJSONString(dto)); bpmnProcessTaskService.remindTask(dto); return success(true); } @@ -178,6 +322,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @Override @RepeatSubmit public CommonResponse createRobotTask(@Validated @RequestBody BpmnRobotTaskCreateDTO dto) { + log.info("创建机器人节点, 暂停流程任务 createRobotTask===>>>参数:{}", JSON.toJSONString(dto)); return success(bpmnProcessTaskService.createRobotTask(dto)); } @@ -192,6 +337,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @Override @RepeatSubmit public CommonResponse completeRobotTask(@Validated @RequestBody BpmnRobotTaskCompleteDTO dto) { + log.info("完成机器人节点, 继续流程任务 completeRobotTask===>>>参数:{}", JSON.toJSONString(dto)); bpmnProcessTaskService.completeRobotTask(dto); return success(true); } @@ -280,4 +426,20 @@ public class BpmnProcessTaskController implements ProcessTaskApi { @RequestParam(required = false) @NotBlank(message = "自然人 ID 不能为空") String personId) { return success(bpmnProcessTaskService.findTaskIdByInstanceIdAndPersonId(processInstanceId, personId)); } + + /** + * 根据实例 ID 和自然人 ID 查询对应待处理的任务 ID + * + * @param processInstanceIds 流程实例列表 + * @param personId 人员列表 + * @return key为流程实例id,value为对应任务id + */ + @Operation(summary = "根据实例 ID 和自然人 ID 查询对应待处理的任务 ID") + @GetMapping("/batch/find") + @Override + public CommonResponse> findTaskIdByInstanceIdsAndPersonId(@RequestParam @NotEmpty(message = "流程实例 ID列表 不能为空") List processInstanceIds, + @RequestParam @NotBlank(message = "自然人 ID 不能为空") String personId) { + return success(bpmnProcessTaskService.findTaskIdByInstanceIdsAndPersonId(processInstanceIds, personId)); + } + } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessVariableController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessVariableController.java new file mode 100644 index 000000000..85bedadb0 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessVariableController.java @@ -0,0 +1,67 @@ +package cn.axzo.workflow.server.controller.web.bpmn; + +import cn.axzo.workflow.client.feign.bpmn.ProcessVariableApi; +import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable; +import cn.axzo.workflow.core.service.BpmnProcessVariableService; +import cn.axzo.workflow.server.common.annotation.ErrorReporter; +import cn.azxo.framework.common.model.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.slf4j.Slf4j; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.validation.constraints.NotBlank; +import java.util.Arrays; + +import static cn.azxo.framework.common.model.CommonResponse.success; + +/** + * 流程变量相关控制器 + */ +@Slf4j +@RequestMapping({"/web/v1/api/process/variable", "/api/process/variable"}) +@RestController +@ErrorReporter +@Validated +public class BpmnProcessVariableController implements ProcessVariableApi { + + @Resource + private BpmnProcessVariableService variableService; + + + @Operation(summary = "新增流程变量") + @GetMapping("/create/{executionId}") + @Override + public CommonResponse createVariable(@PathVariable @NotBlank(message = "流程实例 ID 不能为空") String executionId, + @RequestBody @Validated RestBpmnProcessVariable restVariable) { + variableService.createVariable(executionId, restVariable); + return success(); + } + + @Operation(summary = "修改流程变量") + @GetMapping("/update/{executionId}") + @Override + public CommonResponse updateVariable(@PathVariable @NotBlank(message = "流程实例 ID 不能为空") String executionId, + @RequestBody @Validated RestBpmnProcessVariable restVariable) { + variableService.updateVariable(executionId, restVariable); + return success(); + } + + @Operation(summary = "批量删除流程变量") + @DeleteMapping("/delete/{executionId}") + @Override + public CommonResponse deleteVariables(@PathVariable("executionId") String executionId, + @RequestParam String variableNames, + @RequestParam(value = "scope", required = false) String scope) { + String[] varsArr = variableNames.split(","); + variableService.deleteVariables(executionId, Arrays.asList(varsArr), scope); + return success(); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/es/ElasticSearchController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/es/ElasticSearchController.java new file mode 100644 index 000000000..1d121a4b5 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/es/ElasticSearchController.java @@ -0,0 +1,64 @@ +package cn.axzo.workflow.server.controller.web.es; + +import cn.axzo.workflow.client.feign.es.EsProcessInstanceApi; +import cn.axzo.workflow.common.model.request.es.InstanceSearchReqDTO; +import cn.axzo.workflow.common.model.request.es.TaskSearchReqDTO; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.common.model.response.es.ProcessInstanceDocumentVO; +import cn.axzo.workflow.common.model.response.es.ProcessTaskDocumentVO; +import cn.axzo.workflow.es.service.aggregation.AggregateProcessInstanceService; +import cn.axzo.workflow.es.service.aggregation.AggregateProcessTaskService; +import cn.azxo.framework.common.model.CommonResponse; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +/** + * ES 搜索相关操作 + * + * @author wangli + * @since 2024-09-26 14:47 + */ +@Slf4j +@RequestMapping({"/web/v1/api/es", "/api/es"}) +@RestController +@Validated +public class ElasticSearchController implements EsProcessInstanceApi { + @Resource + private AggregateProcessInstanceService aggregateProcessInstanceService; + @Autowired + private AggregateProcessTaskService aggregateProcessTaskService; + + /** + * 审批数据搜索 + * + * @param dto + * @return + */ + @PostMapping("/instance/search") + public CommonResponse> searchInstanceInEs(@Validated @RequestBody InstanceSearchReqDTO dto) { + log.info("审批数据搜索 searchInstanceInEs===>>>参数:{}", JSONUtil.toJsonStr(dto)); + return CommonResponse.success(aggregateProcessInstanceService.search(dto)); + } + + /** + * 审批任务数据搜索 + * + * @param dto + * @return + */ + @PostMapping("/task/search") + public CommonResponse> searchTaskInEs(@Validated @RequestBody TaskSearchReqDTO dto) { + log.info("审批任务数据搜索 searchTaskInEs===>>>参数:{}", JSONUtil.toJsonStr(dto)); + return CommonResponse.success(aggregateProcessTaskService.search(dto)); + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormAdminController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormAdminController.java new file mode 100644 index 000000000..9460e19a4 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormAdminController.java @@ -0,0 +1,63 @@ +package cn.axzo.workflow.server.controller.web.form; + +import cn.axzo.workflow.client.feign.manage.FormAdminApi; +import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO; +import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormSearchDTO; +import cn.axzo.workflow.common.model.response.form.FormVO; +import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; +import cn.axzo.workflow.core.service.FormCoreService; +import cn.axzo.workflow.server.common.annotation.ErrorReporter; +import cn.azxo.framework.common.model.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.azxo.framework.common.model.CommonResponse.success; + +/** + * 表单实例相关控制器 + * + * @author wangli + * @since 2023/7/21 15:23 + */ +@Slf4j +@RequestMapping({"/web/v1/api/form/admin", "/api/form/admin"}) +@RestController +@ErrorReporter +@Validated +public class FormAdminController implements FormAdminApi { + + @Resource + private FormCoreService formCoreService; + + @Operation(summary = "表单列表") + @PostMapping("/form/page") + public CommonResponse> formPage(@Validated @RequestBody FormSearchDTO dto) { + BpmnFormRelationSearchDTO searchDTO = new BpmnFormRelationSearchDTO(); + searchDTO.setKey(dto.getKey()); + searchDTO.setTenantId(dto.getTenantId()); + return success(formCoreService.pageForm(searchDTO)); + } + + @Operation(summary = "获取指定业务含有表单的定义内容和权限") + @PostMapping("/start/form") + public CommonResponse getFormDefinition(@Validated @RequestBody StartFormSearchDTO dto) { + return success(formCoreService.getStartForm(dto)); + } + + @Operation(summary = "获取指定表单审批的实例信息") + @PostMapping("/instance/render") + public CommonResponse getFormInstance(@Validated @RequestBody FormDetailDTO dto) { + return success(formCoreService.getFormInstance(dto)); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormDefinitionController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormDefinitionController.java index b5cbc3eaa..57b0f880f 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormDefinitionController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormDefinitionController.java @@ -1,9 +1,8 @@ package cn.axzo.workflow.server.controller.web.form; -import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionUpdateDTO; +import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionSearchDTO; import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; -import cn.axzo.workflow.core.service.FormDefinitionService; -import cn.axzo.workflow.core.service.FormModelService; +import cn.axzo.workflow.form.service.FormDefinitionService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.azxo.framework.common.model.CommonResponse; import io.swagger.v3.oas.annotations.Operation; @@ -11,19 +10,18 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; -import javax.validation.constraints.NotBlank; import static cn.azxo.framework.common.model.CommonResponse.success; /** * 表单定义相关控制器 + *

+ * 对应的是发布后的 definition 表 * * @author wangli * @since 2023/7/19 16:45 @@ -39,23 +37,11 @@ public class FormDefinitionController { @Resource private FormDefinitionService formDefinitionService; - @Resource - private FormModelService formModelService; - @Operation(summary = "更新表单定义内容") - @PutMapping("/update") - public CommonResponse updateFormDefinition(@Validated @RequestBody FormDefinitionUpdateDTO dto) { - formDefinitionService.updateFormDefinition(dto); - return success(true); - } - - - @Operation(summary = "通过表单模型 ID 获取表单定义内容") + @Operation(summary = "通过表单模型 ID 获取表单定义发布内容") @GetMapping("/get") - public CommonResponse get(@NotBlank(message = "模型 ID 不能为空") @RequestParam String formModelId, - @NotBlank(message = "租户不能为空") @RequestParam String tenantId) { - return success(formDefinitionService.get(formModelId, tenantId)); + public CommonResponse get(@Validated @RequestBody FormDefinitionSearchDTO dto) { + return success(formDefinitionService.get(dto)); } - } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormInstanceController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormInstanceController.java deleted file mode 100644 index 070528406..000000000 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormInstanceController.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.axzo.workflow.server.controller.web.form; - -import cn.axzo.workflow.common.model.request.form.instance.FormContentUpdateDTO; -import cn.axzo.workflow.core.service.FormInstanceService; -import cn.axzo.workflow.server.common.annotation.ErrorReporter; -import cn.azxo.framework.common.model.CommonResponse; -import io.swagger.v3.oas.annotations.Operation; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static cn.azxo.framework.common.model.CommonResponse.success; - -/** - * 表单模型相关控制器 - * - * @author wangli - * @since 2023/7/21 15:23 - */ -@Slf4j -@RequestMapping("/web/v1/api/form/instance") -@RestController -@ErrorReporter -@Validated -public class FormInstanceController { - - @Autowired - private FormInstanceService formInstanceService; - - @Operation(summary = "更新表单内容") - @PostMapping("/content/update") - public CommonResponse updateFormContent(@Validated @RequestBody FormContentUpdateDTO dto) { - formInstanceService.updateFormContent(dto); - return success(true); - } - -} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormModelController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormModelController.java index 9f4af7732..c7073ef4b 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormModelController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/form/FormModelController.java @@ -5,7 +5,7 @@ import cn.axzo.workflow.common.model.request.form.model.FormModelCreateDTO; import cn.axzo.workflow.common.model.request.form.model.FormModelUpdateDTO; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.form.model.FormModelBaseVO; -import cn.axzo.workflow.core.service.FormModelService; +import cn.axzo.workflow.form.service.FormModelService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.azxo.framework.common.model.CommonResponse; import io.swagger.annotations.ApiParam; @@ -84,7 +84,7 @@ public class FormModelController { @Operation(summary = "获取指定表单模型 ID 的内容") @GetMapping("/get") public CommonResponse getById(@NotBlank(message = "表单模型 ID 不能为空") @RequestParam String formModelId, - @NotBlank(message = "租户不能为空") @RequestParam String tenantId) { + @RequestParam(required = false, defaultValue = "") String tenantId) { return success(formModelService.getById(formModelId, tenantId)); } @@ -99,7 +99,7 @@ public class FormModelController { @PostMapping("/deploy") public CommonResponse deployById(@NotBlank(message = "模型 ID 不能为空") @RequestParam String formModelId, @NotBlank(message = "租户不能为空") @RequestParam String tenantId) { - return success(formModelService.deployFormModelById(formModelId, tenantId)); + return success(formModelService.deployFormModelById(formModelId, tenantId, null)); } @Operation(summary = "通过表单模型 KEY 发布") diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/PrintAdminController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/PrintAdminController.java new file mode 100644 index 000000000..9ff3009be --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/PrintAdminController.java @@ -0,0 +1,411 @@ +package cn.axzo.workflow.server.controller.web.manage; + +import cn.axzo.maokai.api.client.OrganizationalNodeUserQueryApi; +import cn.axzo.maokai.api.vo.request.OrgNodeUserBriefInfoListReq; +import cn.axzo.maokai.api.vo.response.OrgNodeUserBriefInfoResp; +import cn.axzo.workflow.client.feign.manage.PrintAdminApi; +import cn.axzo.workflow.common.enums.VarTypeEnum; +import cn.axzo.workflow.common.exception.WorkflowEngineException; +import cn.axzo.workflow.common.model.dto.print.FieldAttributeDTO; +import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintFieldQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.common.model.request.category.CategoryGroupVarSearchDto; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; +import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper; +import cn.axzo.workflow.core.engine.cmd.CustomGetFormInstanceLatestValuesCmd; +import cn.axzo.workflow.core.engine.cmd.CustomGetProcessInstanceVariablesCmd; +import cn.axzo.workflow.core.service.BpmnProcessDefinitionService; +import cn.axzo.workflow.core.service.BpmnProcessInstanceService; +import cn.axzo.workflow.core.service.CategoryGroupService; +import cn.axzo.workflow.server.common.annotation.ErrorReporter; +import cn.axzo.workflow.server.common.util.RpcExternalUtil; +import cn.axzo.workflow.server.controller.web.bpmn.BpmnProcessInstanceController; +import cn.azxo.framework.common.model.CommonResponse; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.Operation; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.form.api.FormInfo; +import org.flowable.form.api.FormRepositoryService; +import org.flowable.form.model.FormContainer; +import org.flowable.form.model.FormField; +import org.flowable.form.model.FormFieldTypes; +import org.flowable.form.model.SimpleFormModel; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.validation.constraints.NotBlank; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +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.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_IMAGE; +import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_INPUT; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_NAME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_NAME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_PHONE; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOGS; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOGS_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ADVICE; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_ADVICE_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_APPROVER_NAME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_OPERATION_TIME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_OPERATION_TIME_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_POSITION; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_POSITION_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_SIGNATURE; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_UNIT; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_LOG_UNIT_DESC; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME; +import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME_DESC; +import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.form; +import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.sign; +import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.signature; +import static cn.axzo.workflow.common.enums.PrintFieldCategoryEnum.system; +import static cn.azxo.framework.common.model.CommonResponse.success; + +/** + * 打印相关的控制器 + * + * @author wangli + * @since 2025-01-16 17:48 + */ +@Slf4j +@RequestMapping({"/web/v1/api/print/admin", "/api/print/admin"}) +@RestController +@ErrorReporter +@Validated +public class PrintAdminController implements PrintAdminApi { + + @Resource + private FormRepositoryService formRepositoryService; + @Resource + private BpmnProcessDefinitionService processDefinitionService; + @Resource + private SpringProcessEngineConfiguration processEngineConfiguration; + @Resource + private OrganizationalNodeUserQueryApi organizationalNodeUserQueryApi; + @Resource + private BpmnProcessInstanceController bpmnProcessInstanceController; + @Resource + private BpmnProcessInstanceService bpmnProcessInstanceService; + @Resource + private CategoryGroupService categoryGroupService; + + /** + * 查询指定流程实例是否能使用打印 + * + * @param processInstanceId + * @return + */ + @Operation(summary = "查询指定流程实例是否能使用打印") + @Override + @GetMapping("/template/exists") + public CommonResponse hasPrintTemplate(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId) { + return CommonResponse.success(bpmnProcessInstanceService.hasPrintTemplate(processInstanceId, null)); + } + + /** + * 获取打印模板中可打印的字段, 或者是 WPS 模板中可配置的变量字段 + * + * @param dto + * @return + */ + @Operation(summary = "获取打印模板中可打印的字段, 或者是 WPS 模板中可配置的变量字段") + @PostMapping("/fields") + @Override + public CommonResponse> getPrintFields(@Validated @RequestBody PrintFieldQueryDTO dto) { + List printFields = new ArrayList<>(); + FormInfo formModel; + try { + formModel = formRepositoryService.getFormModelByKey(dto.getProcessDefinitionKey(), dto.getTenantId(), false); + // 表单字段 + List formFields = ((SimpleFormModel) formModel.getFormModel()).getFields(); + formFields.forEach(formField -> { + FormContainer formContainer = (FormContainer) formField; + printFields.addAll(formContainer.getFields().get(0).stream().map(field -> { + PrintFieldDTO printFieldDTO = new PrintFieldDTO() + .setName(field.getName()) + .setCode(field.getId()) + .setFieldCategoryType(form) + .setFieldFormType(field.getType()); + switchAmount(field, printFieldDTO); + switchFormContainer(field, printFieldDTO); + return printFieldDTO; + } + ).collect(Collectors.toList())); + }); + } catch (FlowableObjectNotFoundException e) { + log.warn("can't found form model"); + if (Objects.equals(Boolean.TRUE, dto.getThrowException())) { + throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS); + } + } + + // 生成固定的系统字段 + printFields.addAll(generateSystemFields(dto.getProcessDefinitionKey(), dto.getTenantId())); + + // 生成业务对应的变量相应字段 + // generateDictVariables(printFields, dto.getProcessDefinitionKey()); + + return success(printFields); + } + + // 自定义组件类型的额外处理 + private static void switchFormContainer(FormField field, PrintFieldDTO printFieldDTO) { + if (field instanceof FormContainer) { + FormContainer container = (FormContainer) field; + printFieldDTO.setAttributes(container.getFields().get(0) + .stream().map(subField -> new FieldAttributeDTO() + .setCode(subField.getId()) + .setFieldFormType(subField.getType()) + .setName(subField.getName())) + .collect(Collectors.toList())); + } + } + + // 金额组件类型的额外处理 + private static void switchAmount(FormField field, PrintFieldDTO printFieldDTO) { + if (Objects.equals(field.getType(), FormFieldTypes.AMOUNT)) { + List attributes = new ArrayList<>(); + attributes.add(new FieldAttributeDTO() + .setCode("standardNumerals") + .setName("小写") + .setFieldFormType(FORM_FIELD_TYPE_INPUT) + ); + if (Boolean.parseBoolean(field.getParam("toUpper").toString())) { + attributes.add(new FieldAttributeDTO() + .setCode("uppercaseNumerals") + .setName("大写") + .setFieldFormType(FORM_FIELD_TYPE_INPUT)); + } + printFieldDTO.setAttributes(attributes); + } + } + + private List generateDictVariables(List printFields, String processDefinitionKey) { + if (CollectionUtils.isEmpty(printFields)) { + printFields = new ArrayList<>(); + } + // 加载业务管理中自定义的业务变量 + List categoryGroupVarItemVos = categoryGroupService.searchGroupAndVarList(CategoryGroupVarSearchDto.builder() + .category(processDefinitionKey) + .build()); + if (CollectionUtils.isEmpty(categoryGroupVarItemVos)) { + return printFields; + } + + printFields.addAll(categoryGroupVarItemVos.stream().map(i -> + new PrintFieldDTO().setName(i.getGroupName()).setCode("group_" + i.getId()).setFieldCategoryType(sign) + .setAttributes(i.getVars().stream().map(j -> new FieldAttributeDTO() + .setName(j.getName()) + .setCode(j.getCode()) + .setFieldFormType(mappingFormFieldType(j.getType()))) + .collect(Collectors.toList()) + )).collect(Collectors.toList())); + return printFields; + } + + private String mappingFormFieldType(VarTypeEnum varTypeEnum) { + switch (varTypeEnum) { + case TEXT: + return FORM_FIELD_TYPE_INPUT; + case PICTURE: + return FORM_FIELD_TYPE_IMAGE; + default: + return ""; + } + } + + private List generateSystemFields(String processDefinitionKey, String tenantId) { + List printFields = new ArrayList<>(); + + printFields.add(new PrintFieldDTO().setName(PRINT_VAR_PROCESS_DEFINITION_KEY_DESC).setCode(PRINT_VAR_PROCESS_DEFINITION_KEY).setFieldCategoryType(system).setFieldFormType("input")); + printFields.add(new PrintFieldDTO().setName(PRINT_VAR_PROCESS_INSTANCE_ID_DESC).setCode(PRINT_VAR_PROCESS_INSTANCE_ID).setFieldCategoryType(system).setFieldFormType("input")); + printFields.add(new PrintFieldDTO().setName(PRINT_VAR_PROCESS_START_TIME_DESC).setCode(PRINT_VAR_PROCESS_START_TIME).setFieldCategoryType(system).setFieldFormType("input")); + printFields.add(new PrintFieldDTO().setName(PRINT_VAR_PROCESS_END_TIME_DESC).setCode(PRINT_VAR_PROCESS_END_TIME).setFieldCategoryType(system).setFieldFormType("input")); + printFields.add(new PrintFieldDTO().setName(PRINT_VAR_PROCESS_INITIATOR_NAME_DESC).setCode(PRINT_VAR_PROCESS_INITIATOR_NAME).setFieldCategoryType(system).setFieldFormType("input")); + printFields.add(new PrintFieldDTO().setName(PRINT_VAR_PROCESS_INITIATOR_POSITION_DESC).setCode(PRINT_VAR_PROCESS_INITIATOR_POSITION).setFieldCategoryType(system).setFieldFormType("input")); + printFields.add(new PrintFieldDTO().setName(PRINT_VAR_PROCESS_INITIATOR_PHONE_DESC).setCode(PRINT_VAR_PROCESS_INITIATOR_PHONE).setFieldCategoryType(system).setFieldFormType("input")); + printFields.add(new PrintFieldDTO().setName(PRINT_VAR_PROCESS_LOGS_DESC).setCode(PRINT_VAR_PROCESS_LOGS).setFieldCategoryType(system).setFieldFormType("table") + .setAttributes(Lists.newArrayList( + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME).setName(PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME_DESC), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_APPROVER_NAME).setName(PRINT_VAR_PROCESS_LOG_APPROVER_NAME_DESC), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_UNIT).setName(PRINT_VAR_PROCESS_LOG_UNIT_DESC), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_POSITION).setName(PRINT_VAR_PROCESS_LOG_POSITION_DESC), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ADVICE).setName(PRINT_VAR_PROCESS_LOG_ADVICE_DESC), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_OPERATION_TIME).setName(PRINT_VAR_PROCESS_LOG_OPERATION_TIME_DESC), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_SIGNATURE).setName(PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC) + ))); + + // 电子签名 + BpmnProcessDefinitionVO definition = processDefinitionService.getActiveProcessDefinitionByKey(processDefinitionKey, tenantId); + printFields.addAll(processDefinitionService.findFlowElements(definition.getId()) + .stream() + .filter(BpmnMetaParserHelper::getActivitySignature) + .map(activity -> new PrintFieldDTO() + .setName(activity.getName()) + .setCode(activity.getId()) + .setFieldCategoryType(signature) + .setFieldFormType("signature") + .setAttributes(Lists.newArrayList(new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_SIGNATURE).setName(PRINT_VAR_PROCESS_LOG_SIGNATURE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT), + new FieldAttributeDTO().setCode(PRINT_VAR_PROCESS_LOG_ADVICE).setName(PRINT_VAR_PROCESS_LOG_ADVICE_DESC).setFieldFormType(FORM_FIELD_TYPE_INPUT)))) + .collect(Collectors.toList()) + ); + + return printFields; + } + + /** + * 获取指定流程下用于替换打印的相关变量 + * + * @param processInstanceId + * @return + */ + @Operation(summary = "获取指定流程下用于替换打印的相关变量") + @GetMapping("/field/variables") + @Override + public CommonResponse> getPrintFieldVariables(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId) { + Map result = new HashMap<>(); + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + byte[] formInstanceValue = commandExecutor.execute(new CustomGetFormInstanceLatestValuesCmd(processInstanceId)); + if (!ObjectUtils.isEmpty(formInstanceValue)) { + JSONObject treeNode = JSON.parseObject(new String(formInstanceValue)); + result.putAll(treeNode.getJSONObject("values").getInnerMap()); + } + + // 生成系统字段的变量 + generateSystemFieldVariables(processInstanceId, result); + + // 将所有变量都转换成 JSON 对象返回 + convertValueToObject(result); + return success(result); + } + + private void convertValueToObject(Map result) { + if (CollectionUtils.isEmpty(result)) { + return; + } + + result.forEach((key, value) -> { + if (!(value instanceof String)) { + return; + } + if (((String) value).startsWith("[")) { + result.put(key, JSONArray.parseArray((String) value)); + } else if (((String) value).startsWith("{")) { + result.put(key, JSONObject.parseObject((String) value)); + } + }); + } + + private void generateSystemFieldVariables(String processInstanceId, Map result) { + CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); + Map variables = commandExecutor.execute(new CustomGetProcessInstanceVariablesCmd(processInstanceId)); + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss"); + // 解析发起人 + BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(variables.getOrDefault(PRINT_VAR_PROCESS_INITIATOR, null)); + if (Objects.nonNull(initiator)) { + Optional user = getUserInfo(initiator); + variables.put(PRINT_VAR_PROCESS_INITIATOR_NAME, user.isPresent() ? user.get().getRealName() : ""); + variables.put(PRINT_VAR_PROCESS_INITIATOR_POSITION, user.isPresent() && Objects.nonNull(user.get().getJob()) ? user.get().getJob().getName() : ""); + variables.put(PRINT_VAR_PROCESS_INITIATOR_PHONE, user.isPresent() ? user.get().getProfile().getPhone() : ""); + variables.remove(PRINT_VAR_PROCESS_INITIATOR); + } + // 填充审批日志 + CommonResponse processInstanceLogs = bpmnProcessInstanceController + .getProcessInstanceLogs(BpmnProcessInstanceLogQueryDTO.builder() + .processInstanceId(processInstanceId) + .hasButton(false) + .encrypt(false) + .build()); + List> taskLogs = new ArrayList<>(); + processInstanceLogs.getData().getTaskDetails().stream() + .filter(e -> !e.isVirtual() && Objects.nonNull(e.getEndTime())) + .forEach(taskLog -> { + Map taskLogMap = new HashMap<>(); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_ACTIVITY_NAME, taskLog.getName()); + Optional user = getUserInfo(taskLog.getAssigneeSnapshot()); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_APPROVER_NAME, user.isPresent() ? user.get().getRealName() : ""); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_UNIT, user.isPresent() ? user.get().getOrganizationalUnitName() : ""); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_POSITION, user.isPresent() && Objects.nonNull(user.get().getJob()) ? user.get().getJob().getName() : ""); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_ADVICE, taskLog.getAdvice()); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_OPERATION_TIME, sdf.format(taskLog.getCreateTime())); + taskLogMap.put(PRINT_VAR_PROCESS_LOG_SIGNATURE, taskLog.getSignatureUrl()); + taskLogs.add(taskLogMap); + variables.put(PRINT_VAR_PROCESS_LOGS, taskLogs); + }); + result.putAll(variables); + } + + private Optional getUserInfo(BpmnTaskDelegateAssigner assigner) { + if (Objects.isNull(assigner)) { + return Optional.empty(); + } + OrgNodeUserBriefInfoListReq req = new OrgNodeUserBriefInfoListReq(); + if (StringUtils.hasText(assigner.getTenantId())) { + req.setWorkspaceId(Long.valueOf(assigner.getTenantId())); + } + if (StringUtils.hasText(assigner.getOuId())) { + req.setOuId(Long.valueOf(assigner.getOuId())); + } + if (StringUtils.hasText(assigner.getNodeId())) { + req.setOrgNodeIds(Lists.newArrayList(Long.valueOf(assigner.getNodeId()))); + } + if (StringUtils.hasText(assigner.getPersonId())) { + req.setPersonIds(Lists.newArrayList(Long.valueOf(assigner.getPersonId()))); + } + req.setNeedJob(true); + req.setNeedUnit(true); + req.setNeedProfile(true); + req.setContainsExited(true); + List users = RpcExternalUtil.rpcApiResultProcessor(() -> organizationalNodeUserQueryApi.listOrgNodeUsers(req), + "查询审批人员组织信息", req); + + if (CollectionUtils.isEmpty(users)) { + return Optional.empty(); + } + + return users.stream().sorted(Comparator.comparing(OrgNodeUserBriefInfoResp::getExited).reversed()) + .collect(Collectors.toList()).stream().findFirst(); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessAdminController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessAdminController.java new file mode 100644 index 000000000..b1a06da42 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessAdminController.java @@ -0,0 +1,122 @@ +package cn.axzo.workflow.server.controller.web.manage; + +import cn.axzo.framework.domain.ServiceException; +import cn.axzo.workflow.admin.repository.entity.ExtAxProcessAdmin; +import cn.axzo.workflow.admin.service.ExtAxProcessAdminService; +import cn.axzo.workflow.client.feign.manage.ProcessAdminApi; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminCreateDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminDeleteDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminQueryDTO; +import cn.axzo.workflow.common.model.response.admin.ProcessAdminVo; +import cn.axzo.workflow.server.common.annotation.ErrorReporter; +import cn.azxo.framework.common.model.CommonResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.util.CollectionUtils; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.constant.BpmnConstants.MAX_ORG_WORKSPACE_ADMIN_COUNT; + +/** + * 流程管理员控制器 + */ +@Slf4j +@RequestMapping({"/web/v1/api/process/admin", "/api/process/admin"}) +@RestController +@ErrorReporter +@Validated +public class ProcessAdminController implements ProcessAdminApi { + + @Resource + private ExtAxProcessAdminService extAxProcessAdminService; + + @PostMapping("/query") + @Override + public CommonResponse> queryProcessAdmins(@RequestBody ProcessAdminQueryDTO dto) { + List extAxProcessAdmins = extAxProcessAdminService.query(dto); + if (CollectionUtils.isEmpty(extAxProcessAdmins)) { + return CommonResponse.success(); + } + List adminVos = extAxProcessAdmins.stream() + .map(ad -> { + ProcessAdminVo vo = new ProcessAdminVo(); + vo.setProcessAdminId(ad.getId()); + BeanUtils.copyProperties(ad, vo); + return vo; + }) + .collect(Collectors.toList()); + return CommonResponse.success(adminVos); + } + + @Override + @PostMapping("/query/count") + public CommonResponse queryProcessAdminsCount(ProcessAdminQueryDTO dto) { + return CommonResponse.success(extAxProcessAdminService.queryCount(dto)); + } + + @PostMapping("/create") + @Override + public CommonResponse createProcessAdmin(@RequestBody ProcessAdminCreateDTO dto) { + List extAxProcessAdmins = extAxProcessAdminService.query(ProcessAdminQueryDTO + .builder() + .organizationalUnitId(dto.getOrganizationalUnitId()) + .workspaceId(dto.getWorkspaceId()) + .build()); + if (!CollectionUtils.isEmpty(extAxProcessAdmins) && extAxProcessAdmins.size() > MAX_ORG_WORKSPACE_ADMIN_COUNT) { + throw new ServiceException(String.format("管理员最多不超过%d人", MAX_ORG_WORKSPACE_ADMIN_COUNT)); + } + ExtAxProcessAdmin processAdmin = new ExtAxProcessAdmin(); + BeanUtils.copyProperties(dto, processAdmin); + Long id = extAxProcessAdminService.insert(processAdmin); + return CommonResponse.success(id); + } + + @PostMapping("/batch/create") + @Override + public CommonResponse batchCreateProcessAdmin(@RequestBody List dtos) { + if (CollectionUtils.isEmpty(dtos)) { + return CommonResponse.success(); + } + List extAxProcessAdmins = dtos.stream() + .map(d -> { + ExtAxProcessAdmin processAdmin = new ExtAxProcessAdmin(); + BeanUtils.copyProperties(d, processAdmin); + return processAdmin; + }) + .collect(Collectors.toList()); + extAxProcessAdminService.batchInsert(extAxProcessAdmins); + return CommonResponse.success(); + } + + @DeleteMapping("/delete") + @Override + public CommonResponse deleteCommonProcessAdmin(@RequestParam Long id) { + return CommonResponse.success(extAxProcessAdminService.deleteCommonAdminsByIds(Collections.singletonList(id))); + } + + @DeleteMapping("/delete/criteria") + @Override + public CommonResponse deleteProcessAdminCriteria(@RequestBody ProcessAdminDeleteDTO dto) { + return CommonResponse.success(extAxProcessAdminService.delete(dto)); + } + + @DeleteMapping("/batch/delete") + @Override + public CommonResponse batchDeleteProcessAdmin(@RequestBody List ids) { + if (CollectionUtils.isEmpty(ids)) { + return CommonResponse.success(); + } + return CommonResponse.success(extAxProcessAdminService.deleteCommonAdminsByIds(ids)); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessCategoryController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessCategoryController.java index a14f1e4aa..6922a2542 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessCategoryController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessCategoryController.java @@ -1,36 +1,26 @@ package cn.axzo.workflow.server.controller.web.manage; import cn.axzo.workflow.client.feign.manage.ProcessCategoryApi; -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.request.category.*; import cn.axzo.workflow.common.model.response.BpmPageResult; import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; import cn.axzo.workflow.common.model.response.category.CategoryItemVO; import cn.axzo.workflow.core.service.CategoryConfigService; import cn.axzo.workflow.core.service.CategoryService; +import cn.axzo.workflow.core.service.CategoryGroupService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; import cn.azxo.framework.common.model.CommonResponse; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; 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.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.List; -import static cn.azxo.framework.common.model.CommonResponse.success; +import static cn.azxo.framework.common.model.CommonResponse.*; /** * 业务分类相关控制器 @@ -49,6 +39,8 @@ public class ProcessCategoryController implements ProcessCategoryApi { private CategoryService categoryService; @Resource private CategoryConfigService categoryConfigService; + @Resource + private CategoryGroupService categoryGroupService; /** * 获取指定业务分类 @@ -228,4 +220,28 @@ public class ProcessCategoryController implements ProcessCategoryApi { public CommonResponse checkCategoryStatus(@RequestParam Long tenantId, @RequestParam String categoryCode) { return success(categoryService.checkCategoryStatus(tenantId, categoryCode)); } + + /** + * 业务分类的分组和变量查询 + * + * @return 业务分类的分组以及变量 + */ + @Operation(summary = "业务分类的分组和变量查询") + @PostMapping("/group-with-vars/list") + @Override + public CommonResponse> searchCategoryGroupAndVars(@Validated @RequestBody CategoryGroupVarSearchDto dto) { + return success(categoryGroupService.searchGroupAndVarList(dto)); + } + + /** + * 新增/更新 业务分类的分组和变量 + * + * @return 是否成功 + */ + @Operation(summary = "新增/更新 业务分类的分组和变量") + @PostMapping("/group-with-vars/upsert") + @Override + public CommonResponse upsertCategoryGroupAndVars(@Validated @RequestBody CategoryGroupVarUpsertDto dto) { + return success(categoryGroupService.upsertGroupAndVars(dto)); + } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessConfigController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessConfigController.java index 2201cd9bf..ada0b84a9 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessConfigController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/ProcessConfigController.java @@ -46,6 +46,10 @@ public class ProcessConfigController implements ProcessConfigApi { .setBtnKey(buttonEnum.getBtnKey()) .setBtnName(buttonEnum.getBtnName()) .setChecked(false) - .setDisabled(false)).collect(Collectors.toList())); + .setDisabled(false) + .setHidden(false) + .setSupportBizType(buttonEnum.getSupportBizType()) + ) + .collect(Collectors.toList())); } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/engine/ext/listener/TaskEntityEventHandle.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/engine/ext/listener/TaskEntityEventHandle.java new file mode 100644 index 000000000..b4fdbab47 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/engine/ext/listener/TaskEntityEventHandle.java @@ -0,0 +1,389 @@ +package cn.axzo.workflow.server.engine.ext.listener; + +import cn.axzo.foundation.page.PageResp; +import cn.axzo.orggateway.api.cooperateship.CooperateShipHierarchicalQueryApi; +import cn.axzo.orggateway.api.cooperateship.req.ListCooperateShipAncestorReq; +import cn.axzo.orggateway.api.cooperateship.resp.ListCooperateShipResp; +import cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi; +import cn.axzo.orggateway.api.nodeuser.dto.OrgNodeUserDTO; +import cn.axzo.orggateway.api.nodeuser.req.ListOrgNodeUserReq; +import cn.axzo.orgmanax.dto.common.WorkspaceOuPair; +import cn.axzo.orgmanax.dto.nodeuser.req.ListNodeUserReq; +import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; +import cn.axzo.workflow.common.enums.BpmnFlowNodeType; +import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum; +import cn.axzo.workflow.common.model.dto.JobInfo; +import cn.axzo.workflow.common.model.dto.NodeInfo; +import cn.axzo.workflow.common.model.dto.OrgSnapshotInfo; +import cn.axzo.workflow.common.model.dto.OrgStructureSnapshotInfo; +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.alibaba.fastjson.JSON; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.UserTask; +import org.flowable.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.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +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.SUPPORT_UPGRADE_VARIABLE; +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.HANDLING; +import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature; +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; + private final OrgNodeUserApi orgNodeUserApi; + private final CooperateShipHierarchicalQueryApi cooperateShipHierarchicalQueryApi; + + @Override + public boolean support(Object entity) { + return entity instanceof TaskEntity; + } + + @Override + public TaskEntity convert(Object entity) { + return (TaskEntity) entity; + } + + @Override + public void onCreate(TaskEntity taskEntity) { + // 记录发起人 + boolean isNodeStarter = Objects.equals(taskEntity.getTaskDefinitionKey(), NODE_STARTER.getType()); + + BpmnModel bpmnModel = ProcessDefinitionUtil.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()); + String operationDesc = taskEntity.getVariable(COMMENT_TYPE_OPERATION_DESC, String.class); + log.setOperationDesc(StringUtils.hasText(operationDesc) ? operationDesc : HANDLING.getDesc()); + log.setStartTime(taskEntity.getCreateTime()); + log.setStatus(PROCESSING.getStatus()); + log.setSignature(getActivitySignature(flowElement)); + + processLogService.insert(log); + } + + @Override + public void onInitialized(TaskEntity taskEntity) { + Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId()); + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId()); + queryLog.setTaskId(taskEntity.getId()); + ExtAxProcessLog updateLog = new ExtAxProcessLog(); + BpmnMetaParserHelper.getButtonConfig(process, taskEntity.getTaskDefinitionKey()) + .ifPresent(updateLog::setButtonConf); + BpmnMetaParserHelper.getFormFieldPermissionConf(process.getFlowElement(taskEntity.getTaskDefinitionKey())) + .ifPresent(updateLog::setFormFieldPermissionConf); + processLogService.update(queryLog, updateLog); + } + + @Override + public void onUpdated(TaskEntity taskEntity) { + 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(); + @SuppressWarnings("unchecked") + 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 -> { + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId()); + queryLog.setTaskId(taskEntity.getId()); + + // 快照审批人的组织架构信息 + OrgStructureSnapshotInfo snapshotInfo = buildApproverOrgStructureInfo(assignee, taskEntity); + + processLogService.updateAssigneeAndSnapshot(queryLog, assignee, snapshotInfo); + }); + } + } + + private OrgStructureSnapshotInfo buildApproverOrgStructureInfo(BpmnTaskDelegateAssigner assignee, TaskEntity taskEntity) { + // 先将审批人的提级审批重置为 false + assignee.setSupportUpgradeApproval(false); + log.info("build approve org info -> approve : {}, activity: {}", JSON.toJSONString(assignee), taskEntity.getTaskDefinitionKey()); + List nodeUsers = fetchUserExtInfo(assignee); + OrgStructureSnapshotInfo.OrgStructureSnapshotInfoBuilder builder = OrgStructureSnapshotInfo.builder(); + if (CollectionUtils.isEmpty(nodeUsers)) { + log.info("fetch user ext info is empty"); + return builder.build(); + } + OrgNodeUserDTO firstNodeUser = nodeUsers.get(0); + assignee.setNodeId(String.valueOf(firstNodeUser.getTopNodeId())); + + OrgStructureSnapshotInfo snapshotInfo = builder.personName(firstNodeUser.getRealName()) + .avatarUrl(Objects.nonNull(firstNodeUser.getPersonProfile()) ? firstNodeUser.getPersonProfile().getAvatarUrl() : "") + .phone(firstNodeUser.getPhone()) + .topNodeId(firstNodeUser.getTopNodeId()) + .workspaceType(firstNodeUser.getWorkspace().getType()) + .snapshotInfo(Objects.nonNull(firstNodeUser.getCooperateShip()) ? OrgSnapshotInfo.builder() + .workspaceName(firstNodeUser.getCooperateShip().getWorkspaceName()) + .workspaceId(String.valueOf(firstNodeUser.getCooperateShip().getWorkspaceId())) + .workspaceType(firstNodeUser.getCooperateShip().getWorkspaceType()) + .cooperationType(firstNodeUser.getCooperateShip().getCooperateType()) + .ouName(firstNodeUser.getCooperateShip().getOrganizationalUnitName()) + .ouId(String.valueOf(firstNodeUser.getCooperateShip().getOrganizationalUnitId())) + .topNodeId(String.valueOf(firstNodeUser.getTopNodeId())) + .nodeInfos(ListUtils.emptyIfNull(nodeUsers).stream() + .filter(i -> Objects.nonNull(i.getNode())) + .map(i -> NodeInfo.builder() + .nodeId(i.getNode().getId()) + .nodeName(i.getNode().getNodeName()) + .build()) + .collect(Collectors.toList()) + ) + .jobInfos(ListUtils.emptyIfNull(nodeUsers).stream() + .filter(i -> Objects.nonNull(i.getJob())) + .map(i -> JobInfo.builder() + .jobCode(i.getJob().getCode()) + .jobName(i.getJob().getName()) + .build()) + .collect(Collectors.toList())) + .build() : OrgSnapshotInfo.builder().jobInfos(Collections.emptyList()).build()) + .build(); + + FlowElement flowElement = ProcessDefinitionUtil.getBpmnModel(taskEntity.getProcessDefinitionId()).getFlowElement(taskEntity.getTaskDefinitionKey()); + BpmnMetaParserHelper.getUpgradeApprovalConf(flowElement).ifPresent(conf -> { + Boolean supportUpgradeApproval; + if (Objects.equals(Boolean.TRUE, conf.getEnabled())) { + if (Objects.equals(SignApproverOrgLimitEnum.LV_ALL, conf.getOrgLimit())) { + supportUpgradeApproval = true; + } else { + ListCooperateShipAncestorReq build = ListCooperateShipAncestorReq.builder() + .upLevel(conf.getOrgLimit().getCode()) + .organizationNodeId(Long.parseLong(assignee.getNodeId())) + .includeCurrentNode(false) + .build(); + List result = listAncestors(build); + supportUpgradeApproval = !CollectionUtils.isEmpty(result); + } + assignee.setSupportUpgradeApproval(supportUpgradeApproval); + taskEntity.setTransientVariableLocal(SUPPORT_UPGRADE_VARIABLE, supportUpgradeApproval); + } + }); + return snapshotInfo; + } + + @Override + public void onDeleted(TaskEntity taskEntity) { + ExtAxProcessLog queryLog = new ExtAxProcessLog(); + queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId()); + queryLog.setTaskId(taskEntity.getId()); + queryLog.setOperationDesc(HANDLING.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(NumberUtils.isDigits(assignee.getPersonId()) ? Long.valueOf(assignee.getPersonId()) : null); + update.setAssigneeTenantId(assignee.getTenantId()); + update.setAssigneeName(assignee.getAssignerName()); + update.setAssigneeOuId(assignee.getOuId()); + update.setOperationDesc(assignee.getAssignerName()); + } + + 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())) { + update.setAdvice(advice.toString()); + } + Object operationDesc = taskEntity.getTransientVariableLocal(COMMENT_TYPE_OPERATION_DESC); + if (Objects.nonNull(operationDesc) && StringUtils.hasText(operationDesc.toString())) { + update.setOperationDesc(Objects.nonNull(assignee) ? + (StringUtils.hasText(assignee.getAssignerName()) ? assignee.getAssignerName() : "") + operationDesc + : operationDesc.toString()); + } else { + update.setOperationDesc(Objects.nonNull(assignee) ? + (StringUtils.hasText(assignee.getAssignerName()) ? assignee.getAssignerName() : "") + : ""); + } + + + String completionType = taskEntity.getVariable(TASK_COMPLETE_OPERATION_TYPE + taskEntity.getId(), String.class); + if (StringUtils.hasText(completionType) && !Objects.equals(DELETED.getStatus(), completionType)) { + update.setStatus(completionType); + } else { + // 多实例除操作人以外的任务,直接删除日志, 例如一个节点有两个人或签,A 人驳回了,那么 B 人不再需要操作,任务自动删除。而会签也同理 + update.setStatus(DELETED.getStatus());// delete标志着是多实例删除 + needDelete = true; + } + } + update.setEndTime(new Date()); + + // 判断是否抄送节点,如果是的话,需要将抄送人集合放入对应字段 + if (isCarbonCopyNode(queryLog)) { + // 抄送人集合 + @SuppressWarnings("unchecked") + 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()); + } + + 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; + } + + private List fetchUserExtInfo(BpmnTaskDelegateAssigner assigner) { + String personId = assigner.getPersonId(); + String tenantId = assigner.getTenantId(); + String ouId = assigner.getOuId(); + String nodeId = assigner.getNodeId(); + if (NumberUtils.isDigits(personId) && NumberUtils.isDigits(tenantId) && NumberUtils.isDigits(ouId)) { + ListOrgNodeUserReq build = ListOrgNodeUserReq.builder() + .personIds(Lists.newArrayList(Long.parseLong(personId))) + .workspaceOuPairs(Lists.newArrayList(WorkspaceOuPair.builder() + .workspaceId(Long.parseLong(tenantId)) + .ouId(Long.parseLong(ouId)) + .build())) + .needs(ListNodeUserReq.Needs.builder() + .job(true) + .unit(true) + .node(true) + .cooperateShip(true) + .personProfile(true) + .workspace(true) + .build()) + .pageSize(Integer.MAX_VALUE) + .build(); + + if (NumberUtils.isDigits(nodeId)) { + build.setAncestorNodeIds(Lists.newArrayList(Long.parseLong(nodeId))); + } + return orgNodeUserList(build).getData(); + } + return Collections.emptyList(); + + } + + /** + * 查询orgNodeUser + */ + public PageResp orgNodeUserList(ListOrgNodeUserReq listOrgNodeUserReq) { + try { + log.info("orgNodeUserList,params:{}", JSON.toJSONString(listOrgNodeUserReq)); + cn.axzo.foundation.result.ApiResult> apiResult = orgNodeUserApi.list(listOrgNodeUserReq); + log.info("orgNodeUserList,result:{}", JSON.toJSONString(apiResult)); + if (Objects.isNull(apiResult) || !apiResult.isSuccess() || Objects.isNull(apiResult.getData()) || org.apache.commons.collections.CollectionUtils.isEmpty(apiResult.getData().getData())) { + return new PageResp<>(0, listOrgNodeUserReq.getPageSize(), listOrgNodeUserReq.getPage(), org.apache.commons.compress.utils.Lists.newArrayList()); + } + return apiResult.getData(); + } catch (Exception e) { + log.warn("orgNodeUserList,exception", e); + throw e; + } + } + + public List listAncestors(ListCooperateShipAncestorReq req) { + try { + log.info("listAncestors,params:{}", JSON.toJSONString(req)); + cn.axzo.foundation.result.ApiResult> listApiResult = cooperateShipHierarchicalQueryApi.listAncestors(req); + log.info("listAncestors,result:{}", JSON.toJSONString(listApiResult)); + if (Objects.isNull(listApiResult) || !listApiResult.isSuccess() || Objects.isNull(listApiResult.getData()) || org.apache.commons.collections.CollectionUtils.isEmpty(listApiResult.getData())) { + return org.apache.commons.compress.utils.Lists.newArrayList(); + } + return listApiResult.getData(); + } catch (Exception e) { + log.warn("orgNodeUserList,exception", e); + throw e; + } + } +} + diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/ExtDatabaseInitializer.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/ExtDatabaseInitializer.java index aa41a9f4c..94e48ce0d 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/ExtDatabaseInitializer.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/initializer/ExtDatabaseInitializer.java @@ -1,9 +1,9 @@ package cn.axzo.workflow.server.initializer; -import cn.axzo.framework.jackson.utility.JSON; -import lombok.AllArgsConstructor; +import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.context.ApplicationContext; @@ -33,30 +33,44 @@ import static org.flowable.common.engine.impl.AbstractEngineConfiguration.DB_SCH */ @Component @Slf4j -@AllArgsConstructor public class ExtDatabaseInitializer implements ApplicationRunner { + @Value("${workflow.dbAutoUpdate:true}") + private Boolean dbAutoUpdate; private static final String FILE_PREFIX = "ext_ax_"; private final JdbcTemplate jdbcTemplate; private final ResourceLoader resourceLoader; private final ApplicationContext applicationContext; - private ResourcePatternResolver resourcePatternResolver; + private final ResourcePatternResolver resourcePatternResolver; + + public ExtDatabaseInitializer(JdbcTemplate jdbcTemplate, + ResourceLoader resourceLoader, + ApplicationContext applicationContext, + ResourcePatternResolver resourcePatternResolver) { + this.jdbcTemplate = jdbcTemplate; + this.resourceLoader = resourceLoader; + this.applicationContext = applicationContext; + this.resourcePatternResolver = resourcePatternResolver; + } + @Override public void run(ApplicationArguments args) throws Exception { SpringProcessEngineConfiguration springProcessEngineConfiguration = (SpringProcessEngineConfiguration) applicationContext.getBean("springProcessEngineConfiguration"); if (Objects.equals(DB_SCHEMA_UPDATE_FALSE, springProcessEngineConfiguration.getDatabaseSchemaUpdate())) { - return; + if (!dbAutoUpdate) { + return; + } } List tables = selectExtTables(); if (Objects.equals(DB_SCHEMA_UPDATE_DROP_CREATE, springProcessEngineConfiguration.getDatabaseSchemaUpdate())) { tables.forEach(this::executeSqlScript); } - log.debug("tables: {}", JSON.toJSONString(tables)); + log.info("tables: {}", JSONUtil.toJsonStr(tables)); localSqlFiles().forEach(i -> { if (!tables.contains(i.replace(".sql", "").toUpperCase())) { - log.debug("execute sql script: {}", i); + log.info("execute sql script: {}", i); executeSqlScript(i); } }); 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 9121b32b3..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 @@ -1,10 +1,10 @@ package cn.axzo.workflow.server.initializer; import cn.azxo.framework.common.utils.LogUtil; -import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.context.ApplicationContext; @@ -35,41 +35,57 @@ import static org.flowable.common.engine.impl.AbstractEngineConfiguration.DB_SCH */ @Component @Slf4j -@AllArgsConstructor public class VersionUpgradeInitializer implements ApplicationRunner { + @Value("${workflow.dbAutoUpdate:true}") + private Boolean dbAutoUpdate; private static final String FILE_PREFIX = "upgrade_to_"; private static final String COLUMN_NAME = "workflow.engine.version"; private final JdbcTemplate jdbcTemplate; private final ResourceLoader resourceLoader; private final ApplicationContext applicationContext; - private ResourcePatternResolver resourcePatternResolver; + private final ResourcePatternResolver resourcePatternResolver; + + public VersionUpgradeInitializer(JdbcTemplate jdbcTemplate, + ResourceLoader resourceLoader, + ApplicationContext applicationContext, + ResourcePatternResolver resourcePatternResolver) { + this.jdbcTemplate = jdbcTemplate; + this.resourceLoader = resourceLoader; + this.applicationContext = applicationContext; + this.resourcePatternResolver = resourcePatternResolver; + } @Override public void run(ApplicationArguments args) throws Exception { SpringProcessEngineConfiguration springProcessEngineConfiguration = (SpringProcessEngineConfiguration) applicationContext.getBean("springProcessEngineConfiguration"); if (Objects.equals(DB_SCHEMA_UPDATE_FALSE, springProcessEngineConfiguration.getDatabaseSchemaUpdate())) { - return; + if (!dbAutoUpdate) { + return; + } } DefaultArtifactVersion dbVersion = new DefaultArtifactVersion(selectWorkflowEngineVersion()); AtomicBoolean result = new AtomicBoolean(true); List newVersions = new ArrayList<>(); - localSqlFiles().forEach(i -> { - String upgradeVersionStr = i.replace(FILE_PREFIX, "").replace(".sql", ""); - DefaultArtifactVersion upgradeVersion = new DefaultArtifactVersion(upgradeVersionStr); - log.info("数据库升级版本比较: upgradeVersion: {}, inDbVersion:{} ", upgradeVersion, dbVersion); - if (upgradeVersion.compareTo(dbVersion) > 0) { - newVersions.add(upgradeVersion); - try { - executeSqlScript(i); - log.debug("execute sql script: {}", i); - } catch (Exception e) { - LogUtil.error(LogUtil.ErrorType.ERROR_SQL, "升级服务数据库版本发生异常,文件: {}, 异常信息: {}", i, e.getMessage()); - result.compareAndSet(true, false); - } - } - }); + localSqlFiles().stream().map(str -> str.replace(FILE_PREFIX, "").replace(".sql", "")) + .map(DefaultArtifactVersion::new) + .sorted() + .forEach(upgradeVersion -> { + if (upgradeVersion.compareTo(dbVersion) > 0) { + log.info("数据库升级版本比较: upgradeVersion: {}, inDbVersion:{} ", upgradeVersion, dbVersion); + newVersions.add(upgradeVersion); + String fileName = FILE_PREFIX + upgradeVersion + ".sql"; + try { + executeSqlScript(fileName); + log.info("execute sql script: {}", fileName); + } catch (Exception e) { + LogUtil.error(LogUtil.ErrorType.ERROR_SQL, "升级服务数据库版本发生异常,文件: {}, 异常信息: {}", fileName, e.getMessage()); + result.compareAndSet(true, false); + } + } + }); + updateVersion(result, newVersions); } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/SelfBoradcastRocketConfiguration.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/SelfBoradcastRocketConfiguration.java new file mode 100644 index 000000000..f8f0bac78 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/SelfBoradcastRocketConfiguration.java @@ -0,0 +1,38 @@ +package cn.axzo.workflow.server.mq.inside; + +import cn.axzo.framework.rocketmq.BaseListener; +import cn.axzo.framework.rocketmq.EventConsumer; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.spring.core.RocketMQListener; + +import javax.annotation.Resource; + +/** + * 监听引擎自己的广播事件 + * + * @author wangli + * @since 2024-11-06 15:05 + */ +//@Configuration(proxyBeanMethods = false) +public class SelfBoradcastRocketConfiguration { + public static final String DEFAULT_EVENT = "topic_workflow_engine_"; + +// @Component +// @ConditionalOnProperty(name = "rocketmq.name-server") +// @RocketMQMessageListener(topic = DEFAULT_EVENT + "${spring.profiles.active}", +// consumerGroup = "GID_${spring.application.name}_workflow_engine_${spring.profiles.active}_consumer", +// consumeMode = ConsumeMode.ORDERLY, +// maxReconsumeTimes = 3, +// nameServer = "${rocketmq.name-server}" +// ) + public static class WorkflowEngineBroadcastConsumer extends BaseListener implements RocketMQListener { + @Resource + private EventConsumer eventConsumer; + + @Override + public void onMessage(MessageExt message) { + super.onEvent(message, eventConsumer); + } + + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/consumer/ElasticSearchBatchSyncListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/consumer/ElasticSearchBatchSyncListener.java new file mode 100644 index 000000000..2f9e38f0e --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/consumer/ElasticSearchBatchSyncListener.java @@ -0,0 +1,138 @@ +package cn.axzo.workflow.server.mq.inside.consumer; + +import cn.axzo.framework.domain.data.IdHelper; +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.utils.TraceUtils; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.aggregation.AggregateProcessInstanceService; +import com.alibaba.fastjson.JSONObject; +import com.google.common.base.Strings; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties; +import org.flowable.engine.HistoryService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static cn.axzo.workflow.common.enums.ElasticSearchEventEnum.ELASTIC_SEARCH_SYNC; + +/** + * 批量同步 ES + * + * @author wangli + * @since 2024-11-20 18:06 + */ +@Component +@Slf4j +public class ElasticSearchBatchSyncListener { + @Resource + private SupportRefreshProperties refreshProperties; + @Resource + private RocketMQProperties rocketMQProperties; + @Value("GID_${spring.application.name}_workflow_engine_${spring.profiles.active}_consumer") + private String consumerGroup; + @Value("topic_workflow_engine_${spring.profiles.active}") + private String topic; + @Resource + private HistoryService historyService; + @Resource + private AggregateProcessInstanceService aggregateProcessInstanceService; + + @SneakyThrows + @PostConstruct + public void init() { + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup); + consumer.setNamesrvAddr(rocketMQProperties.getNameServer()); + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); + consumer.subscribe(topic, ELASTIC_SEARCH_SYNC.getTag()); + consumer.setConsumeMessageBatchMaxSize(refreshProperties.getEsSyncBatchSize()); +// consumer.setConsumeThreadMax(1); +// consumer.setConsumeThreadMin(1); + consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { + log.info("batch get msg size: {}", msgs.size()); + if(CollectionUtils.isEmpty(msgs)) { + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + try { + HashSet idSet = new HashSet<>(); + for (MessageExt msg : msgs) { + String msgBody = new String(msg.getBody(), StandardCharsets.UTF_8); + Event event = convertEvent(msg, msgBody); + if (support(event)) { + String processInstanceId = event.normalizedData(String.class); + idSet.add(processInstanceId); + } + } + log.info("deduplication ids size: {}", idSet.size()); + idSet.forEach(this::sync); + } catch (Exception e) { + return ConsumeConcurrentlyStatus.RECONSUME_LATER; + } + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + }); + consumer.start(); + } + + private Boolean support(Event event) { + if (Objects.isNull(event)) { + return false; + } + return Objects.equals(event.getEventModule(), ELASTIC_SEARCH_SYNC.getModule()) + && Objects.equals(event.getEventName(), ELASTIC_SEARCH_SYNC.getTag()); + } + + private Event convertEvent(MessageExt msg, String msgBody) { + try { + return JSONObject.parseObject(msgBody, Event.class); + } catch (Exception e) { + log.error("====MQ CONSUMER {} ===={}, msgId = {}, parse event error, event = {}", + getTraceId(msg), msg.getMsgId(), msgBody, e.getMessage(), e); + throw e; + } + } + + public String getTraceId(MessageExt msg) { + Map headers = msg.getProperties(); + MDC.put(TraceUtils.CTX_LOG_ID, headers.get(TraceUtils.TRACE_ID)); + MDC.put(TraceUtils.TRACE_ID, headers.get(TraceUtils.TRACE_ID)); + MDC.put(TraceUtils.TRACE_ID_IN_MDC, headers.get(TraceUtils.TRACE_ID)); + if (log.isDebugEnabled()) { + log.debug("received message, topic={}, headers={}", topic, headers); + } + String traceId = null; + if (!CollectionUtils.isEmpty(headers) && headers.containsKey(TraceUtils.TRACE_ID)) { + traceId = headers.get(TraceUtils.TRACE_ID); + } + if (Strings.isNullOrEmpty(traceId)) { + traceId = IdHelper.get32UUID(); + } + TraceUtils.putTraceId(traceId); + return traceId; + } + + private void sync(String processInstanceId) { + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + // 删除指定实例的父子文档 + log.info("delete document processInstanceId: {}", processInstanceId); + aggregateProcessInstanceService.deleteDocumentParentAndChild(processInstanceId); + log.info("reInsert document processInstanceId: {}", processInstanceId); + List processTaskDocuments = aggregateProcessInstanceService.syncProcessInstance(historicProcessInstance, null); + log.info("Insert Summary: ProcessInstance Count: {}, ProcessTask Count: {}", 1, processTaskDocuments.size()); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/consumer/ElasticSearchSyncListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/consumer/ElasticSearchSyncListener.java new file mode 100644 index 000000000..4290b7015 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/inside/consumer/ElasticSearchSyncListener.java @@ -0,0 +1,49 @@ +package cn.axzo.workflow.server.mq.inside.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.framework.rocketmq.EventHandler; +import cn.axzo.workflow.common.enums.ElasticSearchEventEnum; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.aggregation.AggregateProcessInstanceService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.HistoryService; +import org.flowable.engine.history.HistoricProcessInstance; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * ES 数据同步处理 + * + * @author wangli + * @since 2024-11-06 15:11 + */ +@Slf4j +@Component +@AllArgsConstructor +public class ElasticSearchSyncListener implements EventHandler, InitializingBean { + private final EventConsumer eventConsumer; + private final HistoryService historyService; + private final AggregateProcessInstanceService aggregateProcessInstanceService; + @Override + public void onEvent(Event event, EventConsumer.Context context) { + String processInstanceId = event.normalizedData(String.class); + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + // 删除指定实例的父子文档 + log.info("delete document processInstanceId: {}", processInstanceId); + aggregateProcessInstanceService.deleteDocumentParentAndChild(processInstanceId); + log.info("reInsert document processInstanceId: {}", processInstanceId); + List processTaskDocuments = aggregateProcessInstanceService.syncProcessInstance(historicProcessInstance, null); + log.info("Insert Summary: ProcessInstance Count: {}, ProcessTask Count: {}", 1, processTaskDocuments.size()); + } + + @Override + public void afterPropertiesSet() { + eventConsumer.registerHandler(ElasticSearchEventEnum.ELASTIC_SEARCH_SYNC.getEventCode(), this); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/DingtalkRocketConfiguration.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/DingtalkRocketConfiguration.java new file mode 100644 index 000000000..ae4b1383b --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/DingtalkRocketConfiguration.java @@ -0,0 +1,80 @@ +package cn.axzo.workflow.server.mq.outside; + +import cn.axzo.framework.rocketmq.BaseListener; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.framework.rocketmq.EventProducer; +import cn.axzo.framework.rocketmq.RocketMQEventProducer; +import cn.axzo.workflow.server.mq.outside.producer.DingtalkSendProducer; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.spring.annotation.ConsumeMode; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.annotation.SelectorType; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.HashMap; + +/** + * 钉钉消息 Rocket 配置 + * + * @author wangli + * @since 2024-10-25 13:39 + */ +@Slf4j +@Configuration(proxyBeanMethods = false) +public class DingtalkRocketConfiguration { + @Value("${spring.profiles.active:dev}") + private String activeProfile; + private static final String DEFAULT_MODULE = "workflowEngine"; + private static final String DEFAULT_EVENT = "topic_third_party_sync_event_"; + private static final String MODULE_NAME_SUFFIX = "_dingtalk_message"; + + @Bean + public DingtalkSendProducer dingtalkSendProducer(RocketMQTemplate rocketMQTemplate) { + return new DingtalkSendProducer(rocketMQTemplate, + DEFAULT_MODULE, + DEFAULT_MODULE + MODULE_NAME_SUFFIX, + EventProducer.Context.builder() + .meta(RocketMQEventProducer.RocketMQMessageMeta.builder() + .topic(DEFAULT_EVENT + activeProfile) + .build()) + .headers(new HashMap<>()) + .syncSending(Boolean.TRUE) + .exceptionHandler(context -> { + log.error("MQ, send event error: {}, event: {}", + context.getThrowable().getCause().getMessage(), + context.getEvent().toPrettyJsonString(), + context.getThrowable()); + }) + .build(), + null + ); + } + + @Component + @ConditionalOnProperty(name = "rocketmq.name-server") + @RocketMQMessageListener(topic = "topic_third_party_sync_event_${spring.profiles.active}", + consumerGroup = "GID_${spring.application.name}_riven_${spring.profiles.active}_consumer", + consumeMode = ConsumeMode.CONCURRENTLY, + selectorType = SelectorType.TAG, + selectorExpression = "riven-dingtalk-receive", + maxReconsumeTimes = 3, + nameServer = "${rocketmq.name-server}" + ) + public static class ReplyMessageRocketConsumer extends BaseListener implements RocketMQListener { + @Resource + private EventConsumer eventConsumer; + + @Override + public void onMessage(MessageExt message) { + super.onEvent(message, eventConsumer); + } + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/consumer/DingtalkReceiveListener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/consumer/DingtalkReceiveListener.java new file mode 100644 index 000000000..e27269337 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/consumer/DingtalkReceiveListener.java @@ -0,0 +1,70 @@ +package cn.axzo.workflow.server.mq.outside.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.framework.rocketmq.EventHandler; +//import cn.axzo.riven.client.common.enums.DingtalkEventEnum; +//import cn.axzo.riven.client.model.DingtalkReceiveMqModel; +//import cn.axzo.riven.client.model.DingtalkSendMqModel; +//import cn.axzo.riven.client.model.SampleText; +import cn.axzo.workflow.server.mq.outside.producer.DingtalkSendProducer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 监听钉钉群消息的事件 + * + * @author wangli + * @since 2024-10-25 11:16 + */ +@Slf4j +@Component +public class DingtalkReceiveListener implements EventHandler, InitializingBean { + @Value("${spring.application.name}") + private String applicationName; + @Resource + private EventConsumer eventConsumer; + @Resource + private DingtalkSendProducer dingtalkSendProducer; + + @Override + public void onEvent(Event event, EventConsumer.Context context) { + + } + + @Override + public void afterPropertiesSet() throws Exception { + + } + +// @Override +// public void onEvent(Event event, EventConsumer.Context context) { +// log.info("receive dingding message: {}", event.getTargetId()); +// +// if (!Objects.equals(applicationName, event.getTargetType())) { +// return; +// } +// DingtalkReceiveMqModel data = event.normalizedData(DingtalkReceiveMqModel.class); +// if (log.isDebugEnabled()) { +// log.debug("message data: {}", JSON.toJSONString(data)); +// } +// +// DingtalkSendMqModel sendModel = new DingtalkSendMqModel<>(); +// sendModel.setTraceId(data.getTraceId()); +// sendModel.setConversationId(data.getConversationId()); +// sendModel.setMsgId(data.getMsgId()); +// sendModel.setRobotCode(data.getRobotCode()); +// sendModel.setMessage(SampleText.from("由 WorkflowEngine 处理的消息: " + data.getContent())); +// +// dingtalkSendProducer.send(sendModel); +// } + +// @Override +// public void afterPropertiesSet() throws Exception { +// eventConsumer.registerHandler(DingtalkEventEnum.receive.getEventCode(), this); +// } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/producer/DingtalkSendProducer.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/producer/DingtalkSendProducer.java new file mode 100644 index 000000000..6af3d3799 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/mq/outside/producer/DingtalkSendProducer.java @@ -0,0 +1,39 @@ +package cn.axzo.workflow.server.mq.outside.producer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.RocketMQEventProducer; +//import cn.axzo.riven.client.common.enums.DingtalkEventEnum; +//import cn.axzo.riven.client.model.DingtalkSendMqModel; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.function.BiConsumer; + +/** + * 回复钉钉消息给 Riven 的事件生产者 + * + * @author wangli + * @since 2024-10-25 11:33 + */ +@Slf4j +@Component +public class DingtalkSendProducer extends RocketMQEventProducer { + @Value("${spring.application.name}") + private String applicationName; + public DingtalkSendProducer(RocketMQTemplate rocketMQTemplate, String defaultModule, String appName, Context defaultContext, BiConsumer> sendCallback) { + super(rocketMQTemplate, defaultModule, appName, defaultContext, sendCallback); + } + +// public void send(DingtalkSendMqModel model) { +// send(Event.builder() +// .shardingKey(applicationName) +// .eventCode(DingtalkEventEnum.send.getEventCode()) +// .targetId(model.getTraceId()) +// .targetType(DingtalkEventEnum.send.getTag()) +// .data(model) +// .build()); +// } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/CompletedProcessInstanceSyncEsJobHandler.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/CompletedProcessInstanceSyncEsJobHandler.java new file mode 100644 index 000000000..7c12649c1 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/CompletedProcessInstanceSyncEsJobHandler.java @@ -0,0 +1,185 @@ +package cn.axzo.workflow.server.xxljob; + +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.dto.es.DataSyncSummaryDTO; +import cn.axzo.workflow.common.model.dto.es.HistoricProcessInstanceSearchForEsDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.engine.cmd.CustomInsertPropertyCmd; +import cn.axzo.workflow.core.service.BpmnProcessInstanceForEsService; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.aggregation.AggregateProcessInstanceService; +import cn.axzo.workflow.server.controller.listener.task.SyncToEsTaskEvent_104_Listener; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.NumberUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static cn.axzo.workflow.common.constant.BpmnConstants.LATEST_SYNC_TO_ELASTICSEARCH_TIME; +import static cn.axzo.workflow.server.controller.delegate.AbstractBpmnTaskAssigneeSelector.parseApiResult; + +/** + * 审批结束的流程实例数据同步至 ES + *

+ * 在途的,或新发起的流程实例,均通过 {@link SyncToEsTaskEvent_104_Listener} 进行实时同步, + * 但由于用户的操作时间不可控,所以统一由 {@link OtherProcessInstanceSyncEsJobHandler} 单独进行一次同步 + * + * @author wangli + * @since 2024-09-27 11:03 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class CompletedProcessInstanceSyncEsJobHandler { + private final BpmnProcessInstanceForEsService bpmnProcessInstanceForEsService; + private final AggregateProcessInstanceService aggregateProcessInstanceService; + private final SpringProcessEngineConfiguration springProcessEngineConfiguration; + private final ApplicationContext context; + private final SupportRefreshProperties refreshProperties; + private final FlowSupportApi flowSupportApi; + + @XxlJob("completedProcessInstanceSyncToEs") + public ReturnT execute(String s) { + // 分片参数 + int shardIndex = XxlJobHelper.getShardIndex(); + int shardTotal = XxlJobHelper.getShardTotal(); + log.info("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal); + XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal); + + log.debug("start exec finished process instance data sync... "); + XxlJobHelper.log("start exec finished process instance data sync... "); + + HistoricProcessInstanceSearchForEsDTO search = new HistoricProcessInstanceSearchForEsDTO(); + search.setFinished(true); + Date endTime = new Date(); + search.setEndTime(endTime); + + try { + if (StringUtils.hasText(s)) { + search = JSON.parseObject(s, HistoricProcessInstanceSearchForEsDTO.class); + log.info("根据入参转换后的查询入参:{}", JSON.toJSONString(search)); + XxlJobHelper.log("根据入参转换后的查询入参:{}", JSON.toJSONString(search)); + } else { + log.info("入参为空, 将以默认条件执行"); + XxlJobHelper.log("入参为空, 将以默认条件执行"); + } + } catch (Exception e) { + log.warn("无法解析传参, 入参必须是 JSON 格式, 入参可参考 HistoricProcessInstanceSearchForEsDTO 模型"); + XxlJobHelper.log("无法解析传参, 入参必须是 JSON 格式, 入参可参考 HistoricProcessInstanceSearchForEsDTO 模型"); + return ReturnT.FAIL; + } + + // 开始同步完成的实例到 ES + DataSyncSummaryDTO summary = doSync(search, shardIndex, shardTotal); + + // 执行完同步后的一些额外操作 + afterSync(endTime); + + log.info("Insert Summary: ProcessInstance Count: {}, ProcessTask Count: {}", summary.getProcessInstanceCount(), summary.getProcessTaskCount()); + XxlJobHelper.log("Insert Summary: ProcessInstance Count: {}, ProcessTask Count: {}", summary.getProcessInstanceCount(), summary.getProcessTaskCount()); + return ReturnT.SUCCESS; + } + + /** + * 1. 记录本次操作的时间点 + */ + private void afterSync(Date endTime) { + //1. 记录本次操作的时间点, 多次执行也会自动更新操作时间点 + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + commandExecutor.execute(new CustomInsertPropertyCmd(LATEST_SYNC_TO_ELASTICSEARCH_TIME, DateUtil.formatDateTime(endTime))); + } + + /** + * 同步指定查询结果的数据至 ES + *

+ * 内部自动分页循环同步 + * + * @param search + * @return 应同步至 ES 的统计数据 + */ + private DataSyncSummaryDTO doSync(HistoricProcessInstanceSearchForEsDTO search, int shardIndex, int shardTotal) { + Long totalProcessInstanceCount = bpmnProcessInstanceForEsService.queryHistoricProcessInstanceTotalCount(search); + log.info("查询到待同步实例维度数据量: {} 条", totalProcessInstanceCount); + XxlJobHelper.log("查询到待同步实例维度数据量: {} 条", totalProcessInstanceCount); + AtomicLong totalProcessTaskCount = new AtomicLong(0); + IntStream.iterate(0, i -> i + search.getOverPageSize()).limit((totalProcessInstanceCount + search.getOverPageSize() - 1) / search.getOverPageSize()) + .forEach(skipRows -> { + log.info("处理进度: {} %, current startRow: {}, endRow: {}", String.format("%.2f", (double) skipRows / totalProcessInstanceCount * 100), + skipRows, skipRows + search.getOverPageSize()); + XxlJobHelper.log("处理进度: {} %, current startRow: {}, endRow: {}", String.format("%.2f", (double) skipRows / totalProcessInstanceCount * 100), + skipRows, skipRows + search.getOverPageSize()); + int pageNo = skipRows / search.getOverPageSize() + 1; + List instances = bpmnProcessInstanceForEsService.queryHistoricProcessInstance(search, new Page(pageNo, search.getOverPageSize())); + + instances.parallelStream().filter(hpi -> { + if (shardIndex >= 0 && shardTotal >= 0) { + if (hpi.getId().contains("-")) { + // 最开始的实例编号是 UUID,无法取模,所以统一交给第一个节点处理 + return shardIndex == 0; + } + return Long.parseLong(hpi.getId().substring(12)) % shardTotal == shardIndex; + } + // 如果配置的非分片广播的方式,那么 shardingVO 将是 null + return true; + }).forEach(hpi -> { + List processTaskDocuments = aggregateProcessInstanceService.syncProcessInstance(hpi, this::queryPersonByIds); + totalProcessTaskCount.getAndSet(totalProcessTaskCount.get() + processTaskDocuments.size()); + }); + }); + return new DataSyncSummaryDTO(totalProcessInstanceCount, totalProcessTaskCount.get()); + } + + private Map queryPersonByIds(List users) { + Map result = new HashMap<>(); + if (CollectionUtils.isEmpty(users)) { + return result; + } + Map map = users.stream() + .filter(i -> StringUtils.hasText(i.getPersonId()) && !Objects.equals("null", i.getPersonId()) && NumberUtil.isNumber(i.getPersonId())) + .collect(Collectors.toMap(i -> NumberUtil.parseLong(i.getPersonId()), Function.identity(), (s, t) -> s)); + + PersonProfileQueryReq query = new PersonProfileQueryReq(); + ArrayList personIds = Lists.newArrayList(map.keySet()); + query.setPersonIds(personIds); + 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)); + + personProfileMap.forEach((k, v) -> { + result.put(k, BpmnTaskDelegateAssigner.builder() + .personId(String.valueOf(v.getId())) + .assignerName(v.getRealName()) + .avatar(v.getAvatarUrl()) + .build()); + }); + return result; + } + + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/DeadLetterJobRetryHandler.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/DeadLetterJobRetryHandler.java new file mode 100644 index 000000000..72ac7ae57 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/DeadLetterJobRetryHandler.java @@ -0,0 +1,68 @@ +package cn.axzo.workflow.server.xxljob; + +import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable; +import cn.axzo.workflow.core.service.BpmnProcessJobService; +import cn.axzo.workflow.core.service.BpmnProcessVariableService; +import cn.hutool.json.JSONUtil; +import com.alibaba.nacos.common.utils.StringUtils; +import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +import static com.xxl.job.core.biz.model.ReturnT.FAIL_CODE; + +/** + * deadletter-job重试 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class DeadLetterJobRetryHandler { + + @Resource + private BpmnProcessVariableService variableService; + + @Resource + private BpmnProcessJobService jobService; + + @XxlJob("deadLetterJobRetry") + public ReturnT execute(String param) { + if (StringUtils.isBlank(param)) { + return new ReturnT<>(FAIL_CODE, "参数不能为空"); + } + try { + AsyncJobRetryDto bean = JSONUtil.toBean(param, AsyncJobRetryDto.class); + if (bean == null) { + throw new NullPointerException("反序列化实体为空"); + } + if (StringUtils.isBlank(bean.getProcessInstanceId())) { + throw new IllegalArgumentException("流程实例id不能为空"); + } + //设置变量 + if (bean.getVariables() != null && !bean.getVariables().isEmpty()) { + variableService.updateVariables(bean.getProcessInstanceId(), bean.getVariables()); + } + //进行重试 + jobService.executeDeadLetterJobActionByProcInstId(bean.getProcessInstanceId()); + return ReturnT.SUCCESS; + } catch (Exception e) { + return new ReturnT<>(FAIL_CODE, e.getMessage()); + } + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static final class AsyncJobRetryDto { + private String processInstanceId; + private List variables; + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/EsIndexOperationJobHandler.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/EsIndexOperationJobHandler.java new file mode 100644 index 000000000..b07e23343 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/EsIndexOperationJobHandler.java @@ -0,0 +1,56 @@ +package cn.axzo.workflow.server.xxljob; + +import cn.axzo.workflow.es.service.EsProcessInstanceService; +import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * 生产环境下手动创建索引 + * + * @author wangli + * @since 2024-10-09 11:34 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class EsIndexOperationJobHandler { + private final EsProcessInstanceService esProcessInstanceService; + + @XxlJob("esIndexOperation") + public ReturnT execute(String param) { + if (!StringUtils.hasText(param)) { + createIndex(); + } else { + deleteIndex(); + } + return ReturnT.SUCCESS; + } + + /** + * 删除索引 + */ + private void deleteIndex() { + log.info("开始删除父子文档索引..."); + XxlJobHelper.log("开始删除父子文档索引..."); + Boolean index = esProcessInstanceService.deleteIndex(); + log.info("删除完成. 响应结果: {}", index); + XxlJobHelper.log("删除完成. 响应结果: {}", index); + } + + /** + * 创建索引 + */ + private void createIndex() { + log.info("开始创建父子文档索引..."); + XxlJobHelper.log("开始创建父子文档索引..."); + Boolean index = esProcessInstanceService.createIndex(); + // 如果重复调用,避免报错,在创建的逻辑中,内置了判断是否存在指定索引,如果存在则不再创建,直接返回 false. + log.info("创建完成. 响应结果: {}", index); + XxlJobHelper.log("创建完成. 响应结果: {}", index); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/OperationDataJobHandler.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/OperationDataJobHandler.java new file mode 100644 index 000000000..14a127b58 --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/OperationDataJobHandler.java @@ -0,0 +1,64 @@ +package cn.axzo.workflow.server.xxljob; + +import cn.axzo.framework.jackson.utility.JSON; +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO; +import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst; +import cn.axzo.workflow.core.repository.mapper.CommonMapper; +import cn.axzo.workflow.core.service.ExtAxHiTaskInstService; +import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; + +/** + * 操作数据表 + * + * @author wangli + * @since 2024/4/28 14:06 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class OperationDataJobHandler { + @Resource + private CommonMapper commonMapper; + @Resource + private ExtAxHiTaskInstService extAxHiTaskInstService; + + @XxlJob("executeDynamicSql") + public ReturnT execute(String s) throws Exception { + if (StringUtils.hasText(s)) { + XxlJobHelper.log("执行动态 sql"); + List> maps = commonMapper.executeDynamicSQL(s); + XxlJobHelper.log("result: {}", JSON.toJSONString(maps)); + } else { + XxlJobHelper.log("仅修复 extAxTaskInst 表数据"); + repairData(); + } + return ReturnT.SUCCESS; + } + + private void repairData() { + ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO(); + searchDTO.setTaskDefinitionKey("NODE_STARTER"); + searchDTO.setStatus(BpmnProcessInstanceResultEnum.PROCESSING); + List extAxHiTaskInsts = extAxHiTaskInstService.queryList(searchDTO); + if (!CollectionUtils.isEmpty(extAxHiTaskInsts)) { + extAxHiTaskInsts.forEach(i -> { + i.setStatus(APPROVED.getStatus()); + extAxHiTaskInstService.update(i); + }); + } + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/OtherProcessInstanceSyncEsJobHandler.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/OtherProcessInstanceSyncEsJobHandler.java new file mode 100644 index 000000000..f33683bdb --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/OtherProcessInstanceSyncEsJobHandler.java @@ -0,0 +1,166 @@ +package cn.axzo.workflow.server.xxljob; + +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.dto.es.DataSyncSummaryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.engine.cmd.CustomGetPropertyCmd; +import cn.axzo.workflow.core.service.BpmnProcessInstanceForEsService; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.aggregation.AggregateProcessInstanceService; +import cn.hutool.core.util.NumberUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static cn.axzo.workflow.common.constant.BpmnConstants.LATEST_SYNC_TO_ELASTICSEARCH_TIME; +import static cn.axzo.workflow.server.controller.delegate.AbstractBpmnTaskAssigneeSelector.parseApiResult; + +/** + * 在途的和新发起的流程实例数据同步至 ES + * + * @author wangli + * @since 2024-09-27 11:03 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class OtherProcessInstanceSyncEsJobHandler { + private final BpmnProcessInstanceForEsService bpmnProcessInstanceForEsService; + private final AggregateProcessInstanceService aggregateProcessInstanceService; + private final SpringProcessEngineConfiguration springProcessEngineConfiguration; + private final ApplicationContext context; + private final SupportRefreshProperties refreshProperties; + private final FlowSupportApi flowSupportApi; + + @XxlJob("otherProcessInstanceSyncToEs") + public ReturnT execute(String param) { + // 分片参数 + int shardIndex = XxlJobHelper.getShardIndex(); + int shardTotal = XxlJobHelper.getShardTotal(); + log.info("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal); + XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal); + + + log.debug("start exec other process instance data sync... "); + XxlJobHelper.log("start exec other process instance data sync... "); + + // 同步前的一些额外动作 + Date endTime = beforeSync(); + if (Objects.isNull(endTime)) { + log.info("请先执行“流程实例数据同步 ES 第一步”, Bean Name:completedProcessInstanceSyncToEs。 待其执行完成后再执行该任务!"); + XxlJobHelper.log("请先执行“流程实例数据同步 ES 第一步”, Bean Name:completedProcessInstanceSyncToEs。 待其执行完成后再执行该任务!"); + return ReturnT.FAIL; + } + + // 开始同步流程数据到 ES + DataSyncSummaryDTO summary = doSync(endTime, param, shardIndex, shardTotal); + + log.info("Insert Summary: ProcessInstance Count: {}, ProcessTask Count: {}", summary.getProcessInstanceCount(), summary.getProcessTaskCount()); + XxlJobHelper.log("Insert Summary: ProcessInstance Count: {}, ProcessTask Count: {}", summary.getProcessInstanceCount(), summary.getProcessTaskCount()); + return ReturnT.SUCCESS; + } + + private Date beforeSync() { + CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor(); + return commandExecutor.execute(new CustomGetPropertyCmd(LATEST_SYNC_TO_ELASTICSEARCH_TIME)); + } + + /** + * 开始处理的入口 + * + * @param endTime + * @param pageSizeStr + */ + private DataSyncSummaryDTO doSync(Date endTime, String pageSizeStr, int shardIndex, int shardTotal) { + Long totalCount = bpmnProcessInstanceForEsService.queryHistoricProcessInstanceByUnfinishedAndAlterEndTimeTotalCount(endTime); + log.info("查询到待同步实例维度数据量: {} 条", totalCount); + XxlJobHelper.log("查询到待同步实例维度数据量: {} 条", totalCount); + AtomicInteger pageSize = new AtomicInteger(50); + if (StringUtils.hasText(pageSizeStr)) { + try { + pageSize.set(Integer.parseInt(pageSizeStr)); + } catch (Exception e) { + // ignore + } + } + AtomicLong totalProcessTaskCount = new AtomicLong(0); + IntStream.iterate(0, i -> i + pageSize.get()).limit((totalCount + pageSize.get() - 1) / pageSize.get()) + .forEach(skipRows -> { + log.info("处理进度: {} %, current startRow: {}, endRow: {}", String.format("%.2f", (double) skipRows / totalCount * 100), + skipRows, skipRows + pageSize.get()); + XxlJobHelper.log("处理进度: {} %, current startRow: {}, endRow: {}", String.format("%.2f", (double) skipRows / totalCount * 100), + skipRows, skipRows + pageSize.get()); + int pageNo = skipRows / pageSize.get() + 1; + List instances = bpmnProcessInstanceForEsService.queryHistoricProcessInstanceByUnfinishedAndAlterEndTime(endTime, new Page(pageNo, pageSize.get())); + + instances.parallelStream().filter(hpi -> { + if (shardIndex >= 0 && shardTotal >= 0) { + if (hpi.getId().contains("-")) { + // 最开始的实例编号是 UUID,无法取模,所以统一交给第一个节点处理 + return shardIndex == 0; + } + return Long.parseLong(hpi.getId().substring(12)) % shardTotal == shardIndex; + } + // 如果配置的非分片广播的方式,那么 shardingVO 将是 null + return true; + }).forEach(hpi -> { + List processTaskDocuments = aggregateProcessInstanceService.syncProcessInstance(hpi, this::queryPersonByIds); + totalProcessTaskCount.getAndSet(totalProcessTaskCount.get() + processTaskDocuments.size()); + }); + }); + return new DataSyncSummaryDTO(totalCount, totalProcessTaskCount.get()); + } + + private Map queryPersonByIds(List users) { + Map result = new HashMap<>(); + if (CollectionUtils.isEmpty(users)) { + return result; + } + Map map = users.stream() + .filter(i -> StringUtils.hasText(i.getPersonId()) && !Objects.equals("null", i.getPersonId()) && NumberUtil.isNumber(i.getPersonId())) + .collect(Collectors.toMap(i -> NumberUtil.parseLong(i.getPersonId()), Function.identity(), (s, t) -> s)); + + PersonProfileQueryReq query = new PersonProfileQueryReq(); + ArrayList personIds = Lists.newArrayList(map.keySet()); + query.setPersonIds(personIds); + 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)); + + personProfileMap.forEach((k, v) -> { + result.put(k, BpmnTaskDelegateAssigner.builder() + .personId(String.valueOf(v.getId())) + .assignerName(v.getRealName()) + .avatar(v.getAvatarUrl()) + .build()); + }); + return result; + } + +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/SpecifyProcessInstanceSyncEsJobHandler.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/SpecifyProcessInstanceSyncEsJobHandler.java new file mode 100644 index 000000000..81bfdd5cf --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/xxljob/SpecifyProcessInstanceSyncEsJobHandler.java @@ -0,0 +1,113 @@ +package cn.axzo.workflow.server.xxljob; + +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.dto.es.DataSyncSummaryDTO; +import cn.axzo.workflow.common.model.dto.es.HistoricProcessInstanceSearchForEsDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.service.BpmnProcessInstanceForEsService; +import cn.axzo.workflow.es.model.ProcessTaskDocument; +import cn.axzo.workflow.es.service.aggregation.AggregateProcessInstanceService; +import cn.hutool.core.util.NumberUtil; +import com.google.common.collect.Lists; +import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.server.controller.delegate.AbstractBpmnTaskAssigneeSelector.parseApiResult; + +/** + * 在途的和新发起的流程实例数据同步至 ES + * + * @author wangli + * @since 2024-09-27 11:03 + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class SpecifyProcessInstanceSyncEsJobHandler { + private final BpmnProcessInstanceForEsService bpmnProcessInstanceForEsService; + private final AggregateProcessInstanceService aggregateProcessInstanceService; + private final SpringProcessEngineConfiguration springProcessEngineConfiguration; + private final ApplicationContext context; + private final SupportRefreshProperties refreshProperties; + private final FlowSupportApi flowSupportApi; + + @XxlJob("specifyProcessInstanceSyncToEs") + public ReturnT execute(String processInstanceId) { + log.info("Sync specify processInstance id: {}", processInstanceId); + XxlJobHelper.log("Sync specify processInstance id: {}", processInstanceId); + + // 开始同步流程数据到 ES + DataSyncSummaryDTO summary = doSync(processInstanceId); + + log.info("Insert Summary: ProcessInstance Count: {}, ProcessTask Count: {}", summary.getProcessInstanceCount(), summary.getProcessTaskCount()); + XxlJobHelper.log("Insert Summary: ProcessInstance Count: {}, ProcessTask Count: {}", summary.getProcessInstanceCount(), summary.getProcessTaskCount()); + return ReturnT.SUCCESS; + } + + /** + * 开始处理的入口 + * + * @param processInstanceId + */ + private DataSyncSummaryDTO doSync(String processInstanceId) { + HistoricProcessInstanceSearchForEsDTO search = new HistoricProcessInstanceSearchForEsDTO(); + search.setProcessInstanceId(processInstanceId); + List instances = bpmnProcessInstanceForEsService.queryHistoricProcessInstance(search, null); + AtomicLong totalProcessTaskCount = new AtomicLong(0); + instances.forEach(hpi -> { + aggregateProcessInstanceService.deleteDocumentParentAndChild(hpi.getId()); + List processTaskDocuments = aggregateProcessInstanceService.syncProcessInstance(hpi, this::queryPersonByIds); + totalProcessTaskCount.getAndSet(totalProcessTaskCount.get() + processTaskDocuments.size()); + }); + return new DataSyncSummaryDTO(1L, totalProcessTaskCount.get()); + + } + + private Map queryPersonByIds(List users) { + Map result = new HashMap<>(); + if (CollectionUtils.isEmpty(users)) { + return result; + } + Map map = users.stream() + .filter(i -> StringUtils.hasText(i.getPersonId()) && !Objects.equals("null", i.getPersonId()) && NumberUtil.isNumber(i.getPersonId())) + .collect(Collectors.toMap(i -> NumberUtil.parseLong(i.getPersonId()), Function.identity(), (s, t) -> s)); + + PersonProfileQueryReq query = new PersonProfileQueryReq(); + ArrayList personIds = Lists.newArrayList(map.keySet()); + query.setPersonIds(personIds); + 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)); + + personProfileMap.forEach((k, v) -> { + result.put(k, BpmnTaskDelegateAssigner.builder() + .personId(String.valueOf(v.getId())) + .assignerName(v.getRealName()) + .avatar(v.getAvatarUrl()) + .build()); + }); + return result; + } + +} diff --git a/workflow-engine-server/src/main/java/com/alibaba/cloud/nacos/discovery/NacosWatch.java b/workflow-engine-server/src/main/java/com/alibaba/cloud/nacos/discovery/NacosWatch.java new file mode 100644 index 000000000..bfeeb41ed --- /dev/null +++ b/workflow-engine-server/src/main/java/com/alibaba/cloud/nacos/discovery/NacosWatch.java @@ -0,0 +1,191 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.cloud.nacos.discovery; + +import com.alibaba.cloud.nacos.NacosDiscoveryProperties; +import com.alibaba.cloud.nacos.NacosServiceManager; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.Event; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.discovery.event.HeartbeatEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.SmartLifecycle; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author xiaojing + * @author yuhuangbin + */ +public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycle { + + private static final Logger log = LoggerFactory.getLogger(NacosWatch.class); + + private Map listenerMap = new ConcurrentHashMap<>(16); + + private final AtomicBoolean running = new AtomicBoolean(false); + + private final AtomicLong nacosWatchIndex = new AtomicLong(0); + + private ApplicationEventPublisher publisher; + + private ScheduledFuture watchFuture; + + private NacosServiceManager nacosServiceManager; + + private final NacosDiscoveryProperties properties; + + private final ThreadPoolTaskScheduler taskScheduler; + + public NacosWatch(NacosServiceManager nacosServiceManager, + NacosDiscoveryProperties properties, + ObjectProvider taskScheduler) { + this.nacosServiceManager = nacosServiceManager; + this.properties = properties; + this.taskScheduler = taskScheduler.stream().findAny() + .orElseGet(NacosWatch::getTaskScheduler); + } + + private static ThreadPoolTaskScheduler getTaskScheduler() { + ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + taskScheduler.setBeanName("Nacos-Watch-Task-Scheduler"); + taskScheduler.initialize(); + return taskScheduler; + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + @Override + public boolean isAutoStartup() { + return true; + } + + @Override + public void stop(Runnable callback) { + this.stop(); + callback.run(); + } + + @Override + public void start() { + if (this.running.compareAndSet(false, true)) { + EventListener eventListener = listenerMap.computeIfAbsent(buildKey(), + event -> new EventListener() { + @Override + public void onEvent(Event event) { + if (event instanceof NamingEvent) { + List instances = ((NamingEvent) event) + .getInstances(); + Optional instanceOptional = selectCurrentInstance( + instances); + instanceOptional.ifPresent(currentInstance -> { + resetIfNeeded(currentInstance); + }); + } + } + }); + + NamingService namingService = nacosServiceManager + .getNamingService(properties.getNacosProperties()); + try { + namingService.subscribe(properties.getService(), properties.getGroup(), + Arrays.asList(properties.getClusterName()), eventListener); + } catch (Exception e) { + log.error("namingService subscribe failed, properties:{}", properties, e); + } + + this.watchFuture = this.taskScheduler.scheduleWithFixedDelay( + this::nacosServicesWatch, this.properties.getWatchDelay()); + } + } + + private String buildKey() { + return String.join(":", properties.getService(), properties.getGroup()); + } + + private void resetIfNeeded(Instance instance) { + if (!properties.getMetadata().equals(instance.getMetadata())) { + properties.setMetadata(instance.getMetadata()); + } + } + + private Optional selectCurrentInstance(List instances) { + return instances.stream() + .filter(instance -> properties.getIp().equals(instance.getIp()) + && properties.getPort() == instance.getPort()) + .findFirst(); + } + + @Override + public void stop() { + if (this.running.compareAndSet(true, false)) { + if (this.watchFuture != null) { + // shutdown current user-thread, + // then the other daemon-threads will terminate automatic. + this.taskScheduler.shutdown(); + this.watchFuture.cancel(true); + } + + EventListener eventListener = listenerMap.get(buildKey()); + try { + NamingService namingService = nacosServiceManager + .getNamingService(properties.getNacosProperties()); + namingService.unsubscribe(properties.getService(), properties.getGroup(), + Arrays.asList(properties.getClusterName()), eventListener); + } catch (Exception e) { + log.error("namingService unsubscribe failed, properties:{}", properties, + e); + } + } + } + + @Override + public boolean isRunning() { + return this.running.get(); + } + + @Override + public int getPhase() { + return Integer.MAX_VALUE - 1; + } + + public void nacosServicesWatch() { + + // nacos doesn't support watch now , publish an event every 30 seconds. + this.publisher.publishEvent( + new HeartbeatEvent(this, nacosWatchIndex.getAndIncrement())); + + } + +} diff --git a/workflow-engine-server/src/main/java/com/alibaba/nacos/client/naming/core/PushReceiver.java b/workflow-engine-server/src/main/java/com/alibaba/nacos/client/naming/core/PushReceiver.java new file mode 100644 index 000000000..dd9e4e795 --- /dev/null +++ b/workflow-engine-server/src/main/java/com/alibaba/nacos/client/naming/core/PushReceiver.java @@ -0,0 +1,140 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.client.naming.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.lifecycle.Closeable; +import com.alibaba.nacos.common.utils.IoUtils; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.common.utils.ThreadUtils; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.nio.charset.Charset; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; + +import static com.alibaba.nacos.client.utils.LogUtils.NAMING_LOGGER; + +/** + * Push receiver. + * + * @author xuanyin + */ +public class PushReceiver implements Runnable, Closeable { + + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private static final int UDP_MSS = 64 * 1024; + + private ScheduledExecutorService executorService; + + private DatagramSocket udpSocket; + + private HostReactor hostReactor; + + private volatile boolean closed = false; + + public PushReceiver(HostReactor hostReactor) { + try { + this.hostReactor = hostReactor; + this.udpSocket = new DatagramSocket(); + this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("com.alibaba.nacos.naming.push.receiver"); + return thread; + } + }); + + this.executorService.execute(this); + } catch (Exception e) { + NAMING_LOGGER.error("[NA] init udp socket failed", e); + } + } + + @Override + public void run() { + while (!closed) { + try { + + // byte[] is initialized with 0 full filled by default + byte[] buffer = new byte[UDP_MSS]; + DatagramPacket packet = new DatagramPacket(buffer, buffer.length); + + udpSocket.receive(packet); + + String json = new String(IoUtils.tryDecompress(packet.getData()), UTF_8).trim(); + NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString()); + + PushPacket pushPacket = JacksonUtils.toObj(json, PushPacket.class); + String ack; + if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) { + hostReactor.processServiceJson(pushPacket.data); + + // send ack to server + ack = "{\"type\": \"push-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + "\", \"data\":" + + "\"\"}"; + } else if ("dump".equals(pushPacket.type)) { + // dump data to server + ack = "{\"type\": \"dump-ack\"" + ", \"lastRefTime\": \"" + pushPacket.lastRefTime + "\", \"data\":" + + "\"" + StringUtils.escapeJavaScript(JacksonUtils.toJson(hostReactor.getServiceInfoMap())) + + "\"}"; + } else { + // do nothing send ack only + ack = "{\"type\": \"unknown-ack\"" + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + + "\", \"data\":" + "\"\"}"; + } + + udpSocket.send(new DatagramPacket(ack.getBytes(UTF_8), ack.getBytes(UTF_8).length, + packet.getSocketAddress())); + } catch (Exception e) { + if (closed) { + return; + } + NAMING_LOGGER.error("[NA] error while receiving push data", e); + } + } + } + + @Override + public void shutdown() throws NacosException { + String className = this.getClass().getName(); + NAMING_LOGGER.info("{} do shutdown begin", className); + ThreadUtils.shutdownThreadPool(executorService, NAMING_LOGGER); + closed = true; + udpSocket.close(); + NAMING_LOGGER.info("{} do shutdown stop", className); + } + + public static class PushPacket { + + public String type; + + public long lastRefTime; + + public String data; + } + + public int getUdpPort() { + return this.udpSocket.getLocalPort(); + } +} diff --git a/workflow-engine-server/src/main/resources/application.yaml b/workflow-engine-server/src/main/resources/application.yml similarity index 100% rename from workflow-engine-server/src/main/resources/application.yaml rename to workflow-engine-server/src/main/resources/application.yml diff --git a/workflow-engine-server/src/main/resources/bootstrap.yml b/workflow-engine-server/src/main/resources/bootstrap.yml index 451a44f92..48ac5499a 100644 --- a/workflow-engine-server/src/main/resources/bootstrap.yml +++ b/workflow-engine-server/src/main/resources/bootstrap.yml @@ -18,7 +18,8 @@ logging: level: com.alibaba.nacos.client.config.impl: WARN org.flowable: INFO - +server: + shutdown: graceful --- #开发环境 spring: @@ -29,7 +30,7 @@ spring: nacos: config: # server-addr: ${NACOS_HOST:nacos.mybiwin.top}:${NACOS_PORT:9090} - server-addr: ${NACOS_HOST:https://dev-nacos.axzo.cn}:${NACOS_PORT:443} + server-addr: ${NACOS_HOST:https://dev-nacos-hs.axzo.cn}:${NACOS_PORT:443} file-extension: yaml # namespace: ${NACOS_NAMESPACE_ID:1b5d2a22-b340-4503-8464-7d7fc2059d39} namespace: ${NACOS_NAMESPACE_ID:f82179f1-81a9-41a1-a489-4f9ab5660a6e} @@ -43,7 +44,7 @@ spring: cloud: nacos: config: - server-addr: ${NACOS_HOST:https://dev-nacos.axzo.cn}:${NACOS_PORT:443} + server-addr: ${NACOS_HOST:https://dev-nacos-hs.axzo.cn}:${NACOS_PORT:443} file-extension: yaml namespace: ${NACOS_NAMESPACE_ID:35eada10-9574-4db8-9fea-bc6a4960b6c7} @@ -56,7 +57,7 @@ spring: cloud: nacos: config: - server-addr: ${NACOS_HOST:https://test-nacos.axzo.cn}:${NACOS_PORT:443} + server-addr: ${NACOS_HOST:https://test-nacos-hs.axzo.cn}:${NACOS_PORT:443} file-extension: yaml namespace: ${NACOS_NAMESPACE_ID:f3c0f0d2-bac4-4498-bee7-9c3636b3afdf} @@ -72,3 +73,16 @@ spring: server-addr: ${NACOS_HOST:https://pre-nacos.axzo.cn}:${NACOS_PORT:443} file-extension: yaml namespace: ${NACOS_NAMESPACE_ID:8b4cf725-7595-4c92-b2a6-9260a51ce078} + +--- +#预发布环境 +spring: + config: + activate: + on-profile: live + cloud: + nacos: + config: + server-addr: ${NACOS_HOST:https://live-nacos.axzo.cn}:${NACOS_PORT:443} + file-extension: yaml + namespace: ${NACOS_NAMESPACE_ID:64faf030-7112-45c3-9f06-a40571f685c2} diff --git a/workflow-engine-server/src/main/resources/convertorXML.bpmn b/workflow-engine-server/src/main/resources/convertorXML.bpmn index 6da5d351d..f22d10439 100644 --- a/workflow-engine-server/src/main/resources/convertorXML.bpmn +++ b/workflow-engine-server/src/main/resources/convertorXML.bpmn @@ -1,2916 +1,2618 @@ - - + + remark + + + + + + + + + - - - + - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - + - + - + + + + + - + - + - + - + + - + + + + + - - - + + + - - + + - - - - + + + + - - + + - + - - - - - - - - - - + + + + + + + + - + - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - - - + + + + - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - + - - - - - - - + + + + + + - + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - - - + + + - + - - - - - - - - - + + + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - - + + - + ${nrOfInstances == nrOfCompletedInstances} - - - + + - - + + - + - - - - - - - + + + + + + - + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - - + + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - - - + + + - - + + - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - - + + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - - - - + + + + + + + + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - - + + + - - - - - - - + + + + + + - + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - + + - - - - - - - + + + + + + - + - - + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - - + - + ${nrOfInstances == nrOfCompletedInstances} - - - - - - - - - - - - + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - + + + + + - - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + - - - - - + + + + + - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - + \ No newline at end of file diff --git a/workflow-engine-server/src/main/resources/logback-spring.xml b/workflow-engine-server/src/main/resources/logback-spring.xml index 17a324393..eaaa97bd8 100644 --- a/workflow-engine-server/src/main/resources/logback-spring.xml +++ b/workflow-engine-server/src/main/resources/logback-spring.xml @@ -1,9 +1,61 @@ + + + + ${LOG_PATH}/ERROR/error.log + + + ${LOG_PATH}/ERROR/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz + + 50MB + + 30 + + + + %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n + + + ERROR + + + + + + ${LOG_PATH}/WARN/warn.log + + + ${LOG_PATH}/WARN/%d{yyyy-MM}/warn.%d{yyyy-MM-dd}.%i.log.gz + + 50MB + + 30 + + + + %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n + + + WARN + + - + + + - \ No newline at end of file + + + + + + + + + + + diff --git a/workflow-engine-server/src/main/resources/request.json b/workflow-engine-server/src/main/resources/request.json index c279b3074..dad1701d3 100644 --- a/workflow-engine-server/src/main/resources/request.json +++ b/workflow-engine-server/src/main/resources/request.json @@ -2427,7 +2427,24 @@ "multiMode": "AND", "approverEmptyHandleType": "autoPassed", "emptyApproverSpecify": null, - "fieldPermission": null, + "fieldPermission": { + "initiator": [ + { + "fieldKey": "field_1", + "readOnly": true, + "editRequired": false, + "editable": false, + "hidden": false + }, + { + "fieldKey": "field_1", + "readOnly": true, + "editRequired": false, + "editable": false, + "hidden": false + } + ] + }, "buttonPermission": { "initiator": [ { @@ -2759,8 +2776,8 @@ "children": { "id": "node_350811186561", "parentId": "node_347816395827", - "type": "NODE_TASK", - "name": "审批节点", + "type": "NODE_SIGN", + "name": "签署确认人节点", "children": { "id": null, "parentId": null, @@ -2772,10 +2789,11 @@ }, "branches": null, "property": { - "approvalMethod": "autoPassed", - "approverScope": "entWorkspace", - "approverSpecify": "position", - "specifyValue": "", + "approvalMethod": "human", + "signApproverLimit": { + "orgLimit": "LV_0", + "roleLimit": "LEADER", + }, "isMultiTask": true, "isSequential": false, "multiMode": "AND", @@ -10917,6 +10935,17 @@ "branches": null, "property": null }, + "approveConf": { + "supportBatchOperation": true, + "userAgreeSignature": true + }, + "signConf": { + "signType": "SINGLE", + "signPendingProperty": { + "pendingMessageId": "123", + "viewJson": "{\"templateName\":\"任务审批\",\"templateCode\":\"9684565285b840388f90b6890b3ca60b\",\"category\":\"APPROVAL_PENDING_MESSAGE\",\"title\":\"审批审批\",\"content\":\"您有新任务需要审批,点击查看详情,清及时处理\",\"groupNodeNamePaths\":[\"APP管理端待办/施工管理/任务审批\",\"CMS待办中心/施工管理/任务审批\"],\"status\":\"ENABLE\"}" + } + }, "noticeConf": { "notice": { "noticeMessageId": "", @@ -11361,4 +11390,4 @@ ] }, "id": "202403041453000000001" -} +} \ No newline at end of file diff --git a/workflow-engine-spring-boot-starter/README.md b/workflow-engine-spring-boot-starter/README.md new file mode 100644 index 000000000..341f4682c --- /dev/null +++ b/workflow-engine-spring-boot-starter/README.md @@ -0,0 +1,29 @@ +## Workflow Engine Spring Boot Starter + +### 说明 + +该 module 主要是为业务方提供快速接入工作流引擎的能力,starter +的架构设计点击[查看](https://alidocs.dingtalk.com/i/nodes/Obva6QBXJwDeXvymIv1mQmR48n4qY5Pr) + +### 功能点 + +- 包装监听 Workflow Engine 服务端所有的广播事件,业务方仅简单实现或继承指定类完成自己的业务逻辑即可,不再关注其他细节。 + - 业务可实现监听器: + 1. ProcessInstanceListener + 2. ProcessTaskListener + 3. ProcessActivityListener + 4. MessageNotificationListener +- 内部与工作流的数据交互,也将异步解耦,确保在调用对端服务时,不会因为对端服务中止而失败。 + - 接口需要有分类: + 1. 部分接口仅支持同步调 + 2. 大部分接口支持同步和异步调用, + - 异步调用则是利用 MQ 进行触发 + - 同步调用则是扫API包后,根据注解为某些接口生成 Feign 的代理类,而非让自动生成 Feign 代理类 + 3. 接口需要有调用限制的控制,并非搜有的使用方均可直接调用 + 4. 针对同步调用设计支持回调接口,供使用方自行处理 +- 内部将主动监听 MQ 队列,避免因为顺序消费从而导致的队列阻塞。 + - 降级实现,只要能确保顺序消费把所有的异常情况都考虑到,不阻塞消费便无需该功能。 +- 本地启动将不再主动消费容器环境中的 MQ 广播,避免本地异常消费。 +- 包装引擎抛出的所有异常,不再由业务方判断各类异常 code,而由 starter 统一确定是否抛出异常。 + +### 使用方法 diff --git a/workflow-engine-spring-boot-starter/pom.xml b/workflow-engine-spring-boot-starter/pom.xml new file mode 100644 index 000000000..4f5a03342 --- /dev/null +++ b/workflow-engine-spring-boot-starter/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + cn.axzo.workflow + workflow-engine + ${revision} + + workflow-engine-spring-boot-starter + jar + Workflow Engine Spring Boot Starter + + + org.springframework.boot + spring-boot-starter + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + cn.axzo.framework.rocketmq + axzo-common-rocketmq + provided + + + com.aliyun + alibaba-dingtalk-service-sdk + provided + + + org.apache.rocketmq + rocketmq-tools + 4.9.1 + + + cn.axzo.workflow + workflow-engine-api + + + jakarta.servlet + jakarta.servlet-api + provided + + + diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/BroadcastListenerProperties.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/BroadcastListenerProperties.java new file mode 100644 index 000000000..679d752bd --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/BroadcastListenerProperties.java @@ -0,0 +1,157 @@ +package cn.axzo.workflow.starter; + +import cn.axzo.workflow.starter.common.enums.FailHandleTypeEnum; + +import java.util.HashSet; +import java.util.Set; + +import static cn.axzo.workflow.starter.common.enums.FailHandleTypeEnum.FAIL_OVER; + +/** + * 监听流程引擎广播的事件时一些行为控制 + *

比如:
+ * 1. 在遇到异常时的处理策略
+ * 2. 过滤事件
+ * 
+ * + * @author wangli + * @since 2024/6/4 14:14 + */ +public class BroadcastListenerProperties { + /** + * 是否开启根据应用名过滤 MQ 事件 + *

+ * 如果为 true 时,在不对 {@link BroadcastListenerProperties#filterApplicationNames} 设任何值时,默认会将当前应用名加入进去 + */ + private Boolean enableFilterApplicationName = false; + + /** + * 仅过滤这些应用名称创建的流程实例 + */ + private Set filterApplicationNames = new HashSet<>(); + + /** + * 是否开启根据业务 ID 集合过滤 MQ 事件 + * + * 业务 ID:从 OMS 平台中的“审批业务”查看, + *

+     * 
+     * 
+ * 或者从“审批模板”中“业务 ID”进行查看 + *
+     *     
+     * 
+ */ + private Boolean enableFilterDefinitionKey = false; + + /** + * 过滤出 MQ 事件中包含这些业务 ID 的事件 + *

+ * 只有当 {@link BroadcastListenerProperties#enableFilterDefinitionKey} 才生效 + *

+ * 注意: 如果 enableFilterDefinitionKey = true,但该属性集合为空, 将不会过滤任何消息 + */ + private Set filterProcessDefinitionKeys = new HashSet<>(); + + /** + * 失败处理策略: + *

+     * 1、FAIL_OVER, 当前listener执行出错,在经历重试后,抛出异常,并将消息加入死信队列,然后继续往下执行(默认策略)
+     * 2、FAIL_FAST, 快速失败,不管内部是什么异常类型,都将吞掉异常,正确结束,不会增加死信队列计数
+     * 3、FAIL_BACK, 失败自动恢复,在后台记录失败的消息,并按照一定的策略后期再进行重试,目前暂不支持
+     * 
+ */ + private FailHandleTypeEnum failHandleType = FAIL_OVER; + + /** + * 广播的 DLQ 监控 + */ + private Boolean monitorStatus = false; + + /** + * 自动重试次数 + */ + private int numOfRetries = 3; + + /** + * 初始等待时间,单位:毫秒 + */ + private int waitTimeInMs = 1500; + + /** + * 重试累乘因子, 意味多次重试时,每次重试间隔为 waitTimeInMs * waitIncreaseFactor 毫秒 + */ + private int waitIncreaseFactor = 3; + + public Boolean getEnableFilterApplicationName() { + return enableFilterApplicationName; + } + + public void setEnableFilterApplicationName(Boolean enableFilterApplicationName) { + this.enableFilterApplicationName = enableFilterApplicationName; + } + + public Set getFilterApplicationNames() { + return filterApplicationNames; + } + + public void setFilterApplicationNames(Set filterApplicationNames) { + this.filterApplicationNames = filterApplicationNames; + } + + public Boolean getEnableFilterDefinitionKey() { + return enableFilterDefinitionKey; + } + + public void setEnableFilterDefinitionKey(Boolean enableFilterDefinitionKey) { + this.enableFilterDefinitionKey = enableFilterDefinitionKey; + } + + public Set getFilterProcessDefinitionKeys() { + return filterProcessDefinitionKeys; + } + + public void setFilterProcessDefinitionKeys(Set filterProcessDefinitionKeys) { + this.filterProcessDefinitionKeys = filterProcessDefinitionKeys; + } + + public FailHandleTypeEnum getFailHandleType() { + return failHandleType; + } + + public void setFailHandleType(FailHandleTypeEnum failHandleType) { + this.failHandleType = failHandleType; + } + + public Boolean getMonitorStatus() { + return monitorStatus; + } + + public void setMonitorStatus(Boolean monitorStatus) { + this.monitorStatus = monitorStatus; + } + + public int getNumOfRetries() { + return numOfRetries; + } + + public void setNumOfRetries(int numOfRetries) { + this.numOfRetries = numOfRetries; + } + + public int getWaitTimeInMs() { + return waitTimeInMs; + } + + public void setWaitTimeInMs(int waitTimeInMs) { + this.waitTimeInMs = waitTimeInMs; + } + + public int getWaitIncreaseFactor() { + return waitIncreaseFactor; + } + + public void setWaitIncreaseFactor(int waitIncreaseFactor) { + this.waitIncreaseFactor = waitIncreaseFactor; + } +} 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 new file mode 100644 index 000000000..0d3b029a8 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterBroadcastMQConfiguration.java @@ -0,0 +1,137 @@ +package cn.axzo.workflow.starter; + +import cn.axzo.framework.rocketmq.BaseListener; +import cn.axzo.framework.rocketmq.DefaultEventConsumer; +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.framework.rocketmq.EventHandlerRepository; +import cn.axzo.workflow.starter.common.condition.NonContainerEnvironmentCondition; +import cn.axzo.workflow.starter.common.exception.WorkflowEngineStarterException; +import cn.axzo.workflow.starter.handler.filter.global.BroadcastMessageQueueFilter; +import cn.axzo.workflow.starter.mq.broadcast.consumer.InnerWorkflowListener; +import cn.axzo.workflow.starter.mq.broadcast.consumer.WorkflowEngineBroadcastEventListener; +import cn.axzo.workflow.starter.mq.broadcast.filter.InnerFilterDefinitionKey; +import cn.axzo.workflow.starter.mq.broadcast.filter.InnerFilterExtension; +import cn.axzo.workflow.starter.mq.broadcast.filter.InnerFilterMQOwnerShip; +import cn.axzo.workflow.starter.mq.broadcast.filter.InnerMessageQueueHandleBeforeFilter; +import com.google.common.collect.ImmutableList; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.spring.annotation.ConsumeMode; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +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; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; +import java.util.function.Consumer; + +import static cn.axzo.workflow.starter.StarterRPCInvokeMQConfiguration.DEFAULT_EVENT; + +/** + * 配置监听流程引擎服务广播消息的 RocketMQ 相关配置 + * + * @author wangli + * @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"; + public static final String BROADCAST_EVENT_CONSUMER_BEAN_NAME = "broadcastEventConsumer"; + @Value("${spring.application.name}") + private String applicationName; + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + //================================= Workflow Engine Broadcast MQ =================================// + @Bean(BROADCAST_EVENT_HANDLER_REPOSITORY_BEAN_NAME) + @ConditionalOnMissingBean(name = BROADCAST_EVENT_HANDLER_REPOSITORY_BEAN_NAME) + public EventHandlerRepository broadcastEventHandlerRepository() { + return new EventHandlerRepository((ex, logText) -> { + // 不管 cn.axzo.workflow.starter.BroadcastListenerProperties.failHandleType 设置是哪种策略,这里最终都会打印 WARN 级别日志,框架底层会对所有异常的消息打印 ERROR 日志 + // 如果不想打印需要扩展内部逻辑,请主动注册自己扩展的 Bean,且名称为 StarterBroadcastMQConfiguration.BROADCAST_EVENT_HANDLER_REPOSITORY_BEAN_NAME + log.warn("Workflow Engine Starter MQ, an exception occurred during processing {}", logText, ex); + // 让其进入死信队列 + throw new WorkflowEngineStarterException(ex); + }); + } + + @Bean(BROADCAST_EVENT_CONSUMER_BEAN_NAME) + @ConditionalOnMissingBean(name = BROADCAST_EVENT_CONSUMER_BEAN_NAME) + public EventConsumer broadcastEventConsumer(@Qualifier(BROADCAST_EVENT_HANDLER_REPOSITORY_BEAN_NAME) EventHandlerRepository eventHandlerRepository) { + Consumer callback = eventWrapper -> { + if (eventWrapper.isHandled()) { + // 只收集被App真正消费的消息. + if (log.isDebugEnabled()) { + Event event = eventWrapper.getEvent(); + log.debug("WorkflowEngine Broadcast MQ, handled event: {}", event.toPrettyJsonString()); + } + } + }; + return new DefaultEventConsumer(applicationName, eventHandlerRepository, callback); + } + + @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}" + ) + public static class WorkflowEngineBroadcastConsumer extends BaseListener implements RocketMQListener, InitializingBean { + private final Logger log = LoggerFactory.getLogger(WorkflowEngineBroadcastConsumer.class); + + @Resource(name = BROADCAST_EVENT_CONSUMER_BEAN_NAME) + private EventConsumer broadcastEventConsumer; + @Value("${spring.application.name}") + private String applicationName; + @Resource + private WorkflowEngineStarterProperties starterProperties; + @Resource + private ObjectProvider> businessFilterProvider; + private List filters; + + @Override + public void onMessage(MessageExt message) { + // 处理 properties 配置进行事件过滤 + for (InnerMessageQueueHandleBeforeFilter filter : filters) { + if (filter.doFilter(message)) { + if (log.isDebugEnabled()) { + log.debug("【{}】message has been filtered, messageId: {}", this.getClass().getSimpleName(), message.getMsgId()); + } + return; + } + } + super.onEvent(message, broadcastEventConsumer); + } + + @Override + public void afterPropertiesSet() { + this.filters = ImmutableList.of( + new InnerFilterMQOwnerShip(starterProperties, applicationName), + new InnerFilterDefinitionKey(starterProperties), + new InnerFilterExtension(businessFilterProvider)); + } + } + + @Bean("workflowEngineBroadcastEventListener") + public WorkflowEngineBroadcastEventListener workflowEngineBroadcastEventListener(@Qualifier(BROADCAST_EVENT_CONSUMER_BEAN_NAME) EventConsumer broadcastEventConsumer, + WorkflowEngineStarterProperties workflowEngineStarterProperties, + List listenerProvider) { + return new WorkflowEngineBroadcastEventListener(broadcastEventConsumer, workflowEngineStarterProperties, listenerProvider); + } +} 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 new file mode 100644 index 000000000..3051b32c6 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterFeignClientConfiguration.java @@ -0,0 +1,33 @@ +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; + +/** + * 根据参数动态实例化可用 Bean + * + * @author wangli + * @since 2024/6/11 21:26 + */ +@Configuration(proxyBeanMethods = false) +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 new file mode 100644 index 000000000..8c74d24a1 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/StarterRPCInvokeMQConfiguration.java @@ -0,0 +1,220 @@ +package cn.axzo.workflow.starter; + +import cn.axzo.framework.domain.data.IdHelper; +import cn.axzo.framework.rocketmq.BaseListener; +import cn.axzo.framework.rocketmq.DefaultEventConsumer; +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.framework.rocketmq.EventHandlerRepository; +import cn.axzo.framework.rocketmq.EventProducer; +import cn.axzo.framework.rocketmq.RocketMQEventProducer; +import cn.axzo.workflow.starter.api.WorkflowCoreService; +import cn.axzo.workflow.starter.api.WorkflowManageService; +import cn.axzo.workflow.starter.common.condition.NonContainerEnvironmentCondition; +import cn.axzo.workflow.starter.common.exception.WorkflowEngineStarterException; +import cn.axzo.workflow.starter.mq.broadcast.filter.InnerFilterMQOwnerShip; +import cn.axzo.workflow.starter.mq.broadcast.filter.InnerMessageQueueHandleBeforeFilter; +import cn.axzo.workflow.starter.mq.retry.consumer.WorkflowEngineStarterRetryEventListener; +import cn.axzo.workflow.starter.mq.retry.producer.RpcInvokeEventProducer; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.spring.annotation.ConsumeMode; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +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; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static cn.axzo.framework.rocketmq.RocketMQEventProducer.MQ_MESSAGE_ID; + +/** + * 配置 RPC 动作的 RocketMQ 消息的发送方和消息方等配置信息 + * + * @author wangli + * @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"; + public static final String WORKFLOW_ENGINE_STARTER_EVENT_CONSUMER_BEAN_NAME = "workflowEngineStarterEventConsumer"; + public static final String WORKFLOW_ENGINE_STARTER_EVENT_HANDLER_REPOSITORY_BEAN_NAME = "workflowEngineStarterEventHandlerRepository"; + public static final String DEFAULT_MODULE = "workflowEngine"; + public static final String DEFAULT_EVENT = "topic_workflow_engine_"; + private static final String MODULE_NAME_SUFFIX = "_rpc_retry_starter"; + + @Value("${spring.application.name}") + private String applicationName; + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + //======================================= RPC Invoke MQ ========================================// + + /** + * 客户端 RPC Retry 事件生产者 + * + * @param rocketMQTemplate + * @return + */ + @Bean(WORKFLOW_ENGINE_STARTER_EVENT_PRODUCER_BEAN_NAME) + public RpcInvokeEventProducer workflowEngineStarterEventProducer(RocketMQTemplate rocketMQTemplate) { + return new RpcInvokeEventProducer(rocketMQTemplate, + DEFAULT_MODULE, + applicationName + MODULE_NAME_SUFFIX, + EventProducer.Context.builder() + .meta(RocketMQEventProducer.RocketMQMessageMeta.builder() + .topic(DEFAULT_EVENT + activeProfile) + .build()) + .headers(new HashMap<>()) + .syncSending(Boolean.TRUE) + .exceptionHandler(context -> { + log.error("MQ, send event error: {}, event: {}", + context.getThrowable().getCause().getMessage(), + context.getEvent().toPrettyJsonString(), + context.getThrowable()); + }) + .build(), + getSendBeforeCallback(), + getSendAfterCallback(), + getTransactionRollbackHandler() + ); + } + + /** + * 真实执行 MQ 发送前的回调, + *

+ * 将整个待发送的事件内容通过 spring 的事件分发器发送出去, 现目前主要是记录 MQ 的发送记录 + * + * @return + */ + private BiConsumer> getSendBeforeCallback() { + return (event, context) -> { + event.setEventId(IdHelper.get32UUID()); + if (log.isDebugEnabled()) { + log.debug("mq_send_Before: {}, uniqueId: {}", event.getShardingKey(), event.getEventId()); + } + }; + } + + /** + * 真实执行 MQ 发送后的回调 + *

+ * 将发送前的 MQ 发送记录更新 MQ 组件自己的 MessageId 字段。 + * + * @return + */ + private BiConsumer> getSendAfterCallback() { + return (event, context) -> { + String messageId = context.getHeaders().get(MQ_MESSAGE_ID); + if (log.isDebugEnabled()) { + log.debug("mq_send_after: {}, uniqueId: {}, messageId: {}", event.getShardingKey(), event.getEventId(), messageId); + } + }; + } + + /** + * 如果 MQ 注册的事务回滚后的回调 + *

+ * 将 MQ 发送记录更新为删除状态,意为这类数据可以不关注,可以物理删除,但该功能还是用逻辑删除。 + * + * @return + */ + private BiConsumer> getTransactionRollbackHandler() { + return (event, context) -> { + if (log.isDebugEnabled()) { + log.debug("mq_transaction_rollback: {}, uniqueId: {}", event.getShardingKey(), event.getEventId()); + } + }; + } + + @Bean(WORKFLOW_ENGINE_STARTER_EVENT_HANDLER_REPOSITORY_BEAN_NAME) + @ConditionalOnMissingBean(name = WORKFLOW_ENGINE_STARTER_EVENT_HANDLER_REPOSITORY_BEAN_NAME) + public EventHandlerRepository workflowEngineStarterEventHandlerRepository() { + return new EventHandlerRepository((ex, logText) -> { + log.warn("MQ event handle repository has exception record: {}", logText, ex); + if (Objects.nonNull(ex)) { + throw new WorkflowEngineStarterException(ex); + } + }); + } + + @Bean(WORKFLOW_ENGINE_STARTER_EVENT_CONSUMER_BEAN_NAME) + @ConditionalOnMissingBean(name = WORKFLOW_ENGINE_STARTER_EVENT_CONSUMER_BEAN_NAME) + public EventConsumer workflowEngineStarterEventConsumer(@Qualifier(WORKFLOW_ENGINE_STARTER_EVENT_HANDLER_REPOSITORY_BEAN_NAME) EventHandlerRepository workflowEngineStarterEventHandlerRepository) { + Consumer callback = eventWrapper -> { + if (eventWrapper.isHandled()) { + // 只收集被App真正消费的消息. + Event event = eventWrapper.getEvent(); + if (log.isDebugEnabled()) { + log.debug("WorkflowEngineStarter RPC MQ, handled event: {}", event.toPrettyJsonString()); + } + } + }; + return new DefaultEventConsumer(applicationName + MODULE_NAME_SUFFIX, workflowEngineStarterEventHandlerRepository, callback); + } + + @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", + consumeMode = ConsumeMode.CONCURRENTLY, + maxReconsumeTimes = 7, // 发布时需调整为 7, 总共耗时在 15min 内, 目前容器为滚动发布,一版不会出现停机这么久 + nameServer = "${rocketmq.name-server}" + ) + public static class WorkflowEngineStarterRetryConsumer extends BaseListener implements RocketMQListener, InitializingBean { + private final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterRetryConsumer.class); + + @Resource(name = WORKFLOW_ENGINE_STARTER_EVENT_CONSUMER_BEAN_NAME) + private EventConsumer workflowEngineStarterEventConsumer; + @Value("${spring.application.name}") + private String applicationName; + @Resource + private WorkflowEngineStarterProperties starterProperties; + private InnerMessageQueueHandleBeforeFilter filter; + + @Override + public void onMessage(MessageExt message) { + if (filter.doFilter(message)) { + if (log.isDebugEnabled()) { + log.debug("【{}】message has been filtered, messageId: {}", this.getClass().getSimpleName(), message.getMsgId()); + } + return; + } + super.onEvent(message, workflowEngineStarterEventConsumer); + } + + @Override + public void afterPropertiesSet() { + this.filter = new InnerFilterMQOwnerShip(starterProperties, applicationName); + } + } + + @Bean("workflowEngineClientRetryEventListener") + public WorkflowEngineStarterRetryEventListener workflowEngineClientRetryEventListener(@Qualifier(WORKFLOW_ENGINE_STARTER_EVENT_CONSUMER_BEAN_NAME) EventConsumer workflowEngineStarterEventConsumer, + Environment environment, + WorkflowEngineStarterProperties starterProperties, + ObjectProvider workflowCoreServiceObjectProvider, + ObjectProvider workflowManageServiceObjectProvider) { + return new WorkflowEngineStarterRetryEventListener(workflowEngineStarterEventConsumer, environment, starterProperties, + workflowCoreServiceObjectProvider, workflowManageServiceObjectProvider); + } + +} 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 new file mode 100644 index 000000000..40490f986 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java @@ -0,0 +1,158 @@ +package cn.axzo.workflow.starter; + +import cn.axzo.workflow.starter.common.enums.FailHandleTypeEnum; +import cn.axzo.workflow.starter.common.exception.WorkflowUnsupportedException; +import cn.axzo.workflow.starter.handler.MessageNotificationEventHandler; +import cn.axzo.workflow.starter.handler.ProcessActivityEventHandler; +import cn.axzo.workflow.starter.handler.ProcessInstanceEventHandler; +import cn.axzo.workflow.starter.handler.ProcessTaskEventHandler; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import cn.axzo.workflow.starter.handler.execute.interceptor.ExecuteInterceptor; +import cn.axzo.workflow.starter.handler.execute.interceptor.ExecutorInvoker; +import cn.axzo.workflow.starter.handler.execute.interceptor.FailFastInterceptor; +import cn.axzo.workflow.starter.handler.execute.interceptor.FailOverInterceptor; +import cn.axzo.workflow.starter.handler.execute.interceptor.LogInterceptor; +import cn.axzo.workflow.starter.handler.filter.MessageNotificationEventFilter; +import cn.axzo.workflow.starter.handler.filter.ProcessActivityEventFilter; +import cn.axzo.workflow.starter.handler.filter.ProcessInstanceEventFilter; +import cn.axzo.workflow.starter.handler.filter.ProcessTaskEventFilter; +import cn.axzo.workflow.starter.handler.monitor.BroadcastDLQReporter; +import cn.axzo.workflow.starter.handler.monitor.RpcDLQReporter; +import cn.axzo.workflow.starter.mq.broadcast.consumer.InnerActivityEventListener; +import cn.axzo.workflow.starter.mq.broadcast.consumer.InnerInstanceEventListener; +import cn.axzo.workflow.starter.mq.broadcast.consumer.InnerNotificationEventListener; +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; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; + +import java.util.ArrayList; +import java.util.List; + +/** + * Workflow Engine Auto Configuration + * + * @author wangli + * @since 2024/5/21 11:47 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(WorkflowEngineStarterProperties.class) +@Import({StarterFeignClientConfiguration.class, MetaFeignClientEnableSelector.class, StarterRPCInvokeMQConfiguration.class, StarterBroadcastMQConfiguration.class}) +public class WorkflowEngineStarterAutoConfiguration { + + private final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterAutoConfiguration.class); + + @Bean + public ListenerExecutor listenerExecutor(WorkflowEngineStarterProperties starterProperties, + List additionalInterceptors) { + List interceptors = new ArrayList<>(); + interceptors.add(new LogInterceptor()); + interceptors.add(getFailInterceptor(starterProperties)); + // 没有扩展场景,暂不设计实现 +// if (!CollectionUtils.isEmpty(additionalInterceptors)) { +// additionalInterceptors.forEach(interceptor -> interceptor.setNext(interceptor)); +// } + interceptors.add(new ExecutorInvoker()); + return new ListenerExecutor(interceptors); + } + + @Bean + public InnerWorkflowListener innerProcessEventListener(ListenerExecutor executor, + ObjectProvider> handlerProvider, + ObjectProvider> filterProvider) { + return new InnerInstanceEventListener(executor, handlerProvider, filterProvider); + } + + @Bean + public InnerWorkflowListener innerActivityEventListener(ListenerExecutor executor, + ObjectProvider> handlerProvider, + ObjectProvider> filterProvider) { + return new InnerActivityEventListener(executor, handlerProvider, filterProvider); + } + + @Bean + public InnerWorkflowListener innerTaskEventListener(ListenerExecutor executor, + ObjectProvider> handlerProvider, + ObjectProvider> filterProvider) { + return new InnerTaskEventListener(executor, handlerProvider, filterProvider); + } + + @Bean + public InnerWorkflowListener innerNotificationEventListener(ListenerExecutor executor, + ObjectProvider> handlerProvider, + ObjectProvider> filterProvider) { + return new InnerNotificationEventListener(executor, handlerProvider, filterProvider); + } + + private ExecuteInterceptor getFailInterceptor(WorkflowEngineStarterProperties starterProperties) { + BroadcastListenerProperties listenerRetry = starterProperties.getBroadcast(); + FailHandleTypeEnum failHandleType = listenerRetry.getFailHandleType(); + if (log.isDebugEnabled()) { + log.debug("workflow engine starter fail handle type : {}", failHandleType); + } + switch (failHandleType) { + case FAIL_BACK: +// return new FailBackInterceptor(); + throw new WorkflowUnsupportedException("暂不支持该模式, 请调整 MQ 处理失败策略, workflow.engine.starter.broadcast.fail-handle-type"); + case FAIL_FAST: + return new FailFastInterceptor(); + case FAIL_OVER: + default: + return new FailOverInterceptor(starterProperties.getBroadcast()); + } + } + + @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"); + if (StringUtils.isBlank(namesrvAddress)) { + log.error("Build DefaultMQAdminExt error, namesrv is null"); + throw new RuntimeException("Build DefaultMQAdminExt error, namesrv is null", null); + } + DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt((RPCHook) null, 5000L); + defaultMQAdminExt.setInstanceName("workflow-engine-starter-" + System.currentTimeMillis()); + defaultMQAdminExt.setNamesrvAddr(namesrvAddress); + try { + defaultMQAdminExt.start(); + } catch (MQClientException ex) { + log.error(String.format("init default admin error, namesrv=%s", System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY)), ex); + } + return defaultMQAdminExt; + } + + @Bean + public WorkflowEngineStarterMQMonitorController workflowEngineStarterMQMonitorController() { + return new WorkflowEngineStarterMQMonitorController(); + } + + @Bean + @ConditionalOnBean(RocketMQTemplate.class) + @ConditionalOnProperty(prefix = "workflow.engine.starter", value = "enable-dlq-monitor", havingValue = "true", matchIfMissing = true) + public WorkflowEngineStarterDefaultMQMonitor workflowEngineStarterDefaultMQMonitor(ObjectProvider mqAdminExtObjectProvider, + ObjectProvider broadcastDLQProcessorObjectProvider, + ObjectProvider rpcDLQProcessorObjectProvider, + WorkflowEngineStarterProperties workflowEngineStarterProperties, + 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 new file mode 100644 index 000000000..cbe1ff22e --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterProperties.java @@ -0,0 +1,174 @@ +package cn.axzo.workflow.starter; + +import cn.axzo.workflow.common.annotation.InvokeMode; +import cn.axzo.workflow.common.enums.RpcInvokeModeEnum; +import cn.axzo.workflow.starter.api.WorkflowCoreService; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.ASYNC; + +/** + * Workflow Engine Starter Properties + *

+ * 全量参数参考:META-INF/application.yml.demo + * + * @author wangli + * @since 2024/5/21 15:24 + */ +@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; + + /** + * 特殊用途,不建议接入方使用 + */ + private Boolean manageable = false; + + /** + *

该参数只针对容器环境生效

+ * 本地启动时,是否将本地的 MQ 消费者加入集群消费组 + *
+     * 默认 false, 本地启动应用时, 将创建消息组名称中含有"debugging"的消费组.
+     * 否则, 本地启动应用时, 消费者将加入容器环境, 进行集群消费.
+     * 
+ */ + private Boolean joinContainerGroup = false; + + /** + *

该参数只针对容器环境生效

+ * 配合 joinContainerGroup 使用,且只在 joinContainerGroup = false 时生效 + *
+     * 在本地有多台开发机同时启动时,又会组成新的集群消费,也会导致消息异常消费,
+     * 所以该参数就是为了创建完全唯一的消费者,避免本地开发机组成集群。
+     * 
+ */ + private String specialId; + + /** + * WorkflowCoreService 类中所有方法未标记{@link InvokeMode}注解的方法调用时, 默认采用的模式 + * + *
+     * 如果是同步调用,则直接通过普通 FeignClient 进行调用,
+     * 否则将通过 MQ 将 RPC 调用进行解耦
+     * 
+ *

+ * 如果方法上有{@link InvokeMode}注解, 则以注解上的模式优先, 如果还想覆盖注解中的模式, + * 则可以通过 {@link WorkflowCoreService#sync()}或{@link WorkflowCoreService#async()}方法进行覆盖 + */ + private RpcInvokeModeEnum invokeMode = ASYNC; + + /** + * 监听流程引擎广播的处理器,异常后的重试相关策略及配置 + */ + @NestedConfigurationProperty + private BroadcastListenerProperties broadcast = new BroadcastListenerProperties(); + + /** + * 是否开启死信队列的监控的特性,不代表真实开始监控,如果需要默认开始,请设置 monitorStatus = true + */ + private Boolean enableDlqMonitor = true; + + /** + * 监控状态,默认 true,真实开始监控 RPC 的私信队列监控 + */ + private Boolean rpcMonitorStatus = true; + + /** + * 监控死信队列,周期间隔,单位:毫秒 + */ + private long dlqMonitorIntervalInMs = 4 * 60 * 60 * 1000L; + + /** + * 开启后,会在主动给“工作流小分队”群中发送信息 + *

+ * 只针对容器环境中的应用生效 + */ + private Boolean alert = false; + + public Boolean getMetaFeign() { + return metaFeign; + } + + public void setMetaFeign(Boolean metaFeign) { + this.metaFeign = metaFeign; + } + + public Boolean getManageable() { + return manageable; + } + + public void setManageable(Boolean manageable) { + this.manageable = manageable; + } + + public Boolean getJoinContainerGroup() { + return joinContainerGroup; + } + + public void setJoinContainerGroup(Boolean joinContainerGroup) { + this.joinContainerGroup = joinContainerGroup; + } + + public String getSpecialId() { + return specialId; + } + + public void setSpecialId(String specialId) { + this.specialId = specialId; + } + + public RpcInvokeModeEnum getInvokeMode() { + return invokeMode; + } + + public void setInvokeMode(RpcInvokeModeEnum invokeMode) { + this.invokeMode = invokeMode; + } + + public BroadcastListenerProperties getBroadcast() { + return broadcast; + } + + public void setBroadcast(BroadcastListenerProperties broadcast) { + this.broadcast = broadcast; + } + + public Boolean getEnableDlqMonitor() { + return enableDlqMonitor; + } + + public void setEnableDlqMonitor(Boolean enableDlqMonitor) { + this.enableDlqMonitor = enableDlqMonitor; + } + + public Boolean getRpcMonitorStatus() { + return rpcMonitorStatus; + } + + public void setRpcMonitorStatus(Boolean rpcMonitorStatus) { + this.rpcMonitorStatus = rpcMonitorStatus; + } + + public long getDlqMonitorIntervalInMs() { + return dlqMonitorIntervalInMs; + } + + public void setDlqMonitorIntervalInMs(long dlqMonitorIntervalInMs) { + this.dlqMonitorIntervalInMs = dlqMonitorIntervalInMs; + } + + public Boolean getAlert() { + return alert; + } + + public void setAlert(Boolean alert) { + this.alert = alert; + } +} 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 new file mode 100644 index 000000000..0ee910c9a --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java @@ -0,0 +1,460 @@ +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.common.annotation.Manageable; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import cn.axzo.workflow.common.annotation.InvokeMode; +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 io.swagger.v3.oas.annotations.Operation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import javax.validation.constraints.NotBlank; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnNodeBackSystemOperateDTO; +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.BpmnTaskAuditWithFormDTO; +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.BpmnTaskResetApproversDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; +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.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.annotation.Nullable; +import javax.validation.constraints.NotEmpty; +import java.util.List; +import java.util.Map; +import cn.axzo.workflow.common.model.dto.SignFileDTO; +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BeforeProcessInstanceCreateDTO; +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.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.SuperBpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; +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.NodesByModelVO; +import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.process.doc.DocPendingVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PutMapping; +import javax.validation.constraints.NotNull; + +/** + * Workflow Engine Starter Core Service + *

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

+ * Auto generation by workflow engine, It cannot be manually modified + */ +@org.springframework.cloud.openfeign.FeignClient(name = "workflow-engine", 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") + @InvokeMode(SYNC) + Boolean trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId); + + /** + * 业务节点唤醒 + * + * @param dto + * @return + */ + @PostMapping("/api/process/activity/trigger") + Boolean trigger(@Validated @RequestBody BpmnActivityTriggerDTO dto); + + /** + * 业务节点设置审批人, 不支持重复设置 + *

+ * 当模型中使用了“业务节点”,且设置了“业务指定审批人”模式,则当业务监听到 PROCESS_ACTIVITY_WAIT_ASSIGNEE 事件时,可通过该接口设置动态设置审批人 + *

+ * 注意:如果调用接口时,传入的审批人集合为空,流程引擎将对该审批流程实例自动中止。 + * + * @param dto + * @return + */ + @PostMapping("/api/process/activity/assignee/set") + @Operation(summary = "业务节点设置审批人,不支持重复调用设置审批人,需一次性传入所有审批人") + Boolean setAssignee(@Validated @RequestBody BpmnActivitySetAssigneeDTO dto); + + /** + * 同意 + * + *

+     * MQ 触发规则:
+     *  1. 当前审批任务会依次触发 process-task-completed 和 process-task-deleted 事件(如果有下一级审批,则会触发第 2.1 点中的事件,
+     *  如果当前审核任务最后一级审批,则会触发第 2.2 点中的事件)
+     *  2.1. 下一级审批任务会依次触发 process-task-assigned 和 process-task-created 事件
+     *  2.2. 流程实例正常结束会触发 process-instance-completed 事件
+     * 
+ */ + @Operation(summary = "同意,MQ 触发规则:1. 当前审批任务会依次触发 process-task-completed 和 process-task-deleted 事件(如果有下一级审批,则会触发第 2.1 点中的事件,如果当前审核任务最后一级审批,则会触发第 2.2 点中的事件),2.1. 下一级审批任务会依次触发 process-task-assigned 和 process-task-created 事件,2.2. 流程实例正常结束会触发 process-instance-completed 事件") + @PostMapping("/api/process/task/approve") + Boolean approveTask(@Validated @RequestBody BpmnTaskAuditDTO dto); + + /** + * 同意时并提交表单数据 + * + * @param dto + * @return + */ + @Operation(summary = "同意时并提交表单") + @PostMapping("/api/process/task/form/approve") + Boolean approveTaskWithForm(@Validated @RequestBody BpmnTaskAuditWithFormDTO dto); + + /** + * 批量同意 + * + * @param dtos + * @return + */ + @Operation(summary = "批量同意") + @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); + + /** + * 用于系统内部操作,跳转到指定节点 + * @param dto 请求参数 + * @return 是否成功 + */ + @Operation(summary = "系统操作回退任务到指定节点") + @PostMapping("/api/process/task/system/back") + Boolean systemBackTask(@Validated @RequestBody BpmnNodeBackSystemOperateDTO dto); + + /** + * 驳回 + * + *
+     * MQ 触发规则:
+     *   1. 当前审批任务会触发 process-task-deleted 事件
+     *   2. 当前流程实例会触发 process-instance-rejected 事件
+     * 
+ */ + @Operation(summary = "驳回,MQ 触发规则:1. 当前审批任务会触发 process-task-deleted 事件, 2. 当前流程实例会触发 process-instance-rejected 事件") + @PostMapping("/api/process/task/reject") + Boolean rejectTask(@Validated @RequestBody BpmnTaskAuditDTO dto); + + /** + * 批量驳回 + * + * @param dtos 批量请求参数 + * @return + */ + @PostMapping("/api/process/task/batch/reject") + BatchOperationResultVO batchRejectTask(@Validated @RequestBody List dtos); + + /** + * 转交 + * + * @param dto + * @return + */ + @Operation(summary = "直接修改审批任务的审批人") + @PostMapping("/api/process/task/transfer") + Boolean transferTask(@Validated @RequestBody BpmnTaskTransferDTO dto); + + /** + * 批量转交 + * + * @param dtos + * @return + */ + @Operation(summary = "批量修改审批任务的审批人") + @PostMapping("/api/process/task/batch/transfer") + BatchOperationResultVO batchTransferTask(@Validated @RequestBody List dtos); + + /** + * 评论 + * + * @param dto 评论请求参数 + * @return + */ + @Operation(summary = "审批流程评论") + @PostMapping("/api/process/task/comment") + Boolean commentTask(@Validated @RequestBody BpmnTaskCommentDTO dto); + + /** + * 加签 + * + * @param dto 加签请求参数 + * @return + */ + @Operation(summary = "审批流程加签") + @PostMapping("/api/process/task/countersign") + Boolean countersignTask(@Validated @RequestBody BpmnTaskCountersignDTO dto); + + /** + * 重置节点审批人(提级审批) + * + * @param dto + * @return + */ + @Operation(summary = "重置节点审批人(提级审批)") + @PostMapping("/api/process/task/approvers/reset") + Boolean resetTaskApprovers(@Validated @RequestBody BpmnTaskResetApproversDTO dto); + + /** + * 暂停流程任务,并创建机器人节点,等待业务推动 + * + * @param dto + * @return 返回机器人节点任务 ID + */ + @Operation(summary = "创建机器人节点, 暂停流程任务") + @PostMapping("/api/process/task/robot/create") + String createRobotTask(@Validated @RequestBody BpmnRobotTaskCreateDTO dto); + + /** + * 完成机器人节点 + * + * @param dto + * @return + */ + @Operation(summary = "完成机器人节点, 继续流程任务") + @PostMapping("/api/process/task/robot/complete") + Boolean completeRobotTask(@Validated @RequestBody BpmnRobotTaskCompleteDTO dto); + + /** + * 创建流程前的节点列表 + * 用于发起人自选 + * + * @return + */ + @Operation(summary = "创建审批流程前,返回模型节点列表以及节点能否设置审批人") + @PostMapping("/api/process/instance/create/before") + @InvokeMode(SYNC) + List nodesBeforeCreateProcessInstance(@Validated @RequestBody BeforeProcessInstanceCreateDTO dto); + + /** + * 创建审批流程 + * + *
+     *   MQ 触发规则:
+     *     1. 当前流程实例会依次触发 process-instance-created 和 process-instance-started 事件
+     *     2. 第一个审批任务会依次触发 process-task-assigned 和 process-task-created 事件
+     * 
+ * + * @param dto {@link BpmnProcessInstanceCreateDTO} + */ + @Operation(summary = "创建审批流程, MQ 触发规则:1. 当前流程实例会依次触发 process-instance-created 和 process-instance-started 事件,2. 第一个审批任务会依次触发 process-task-assigned 和 process-task-created 事件") + @PostMapping("/api/process/instance/create") + @InvokeMode(SYNC) + String createProcessInstance(@Validated @RequestBody BpmnProcessInstanceCreateDTO dto); + + /** + * 发起人主动撤回审核 + * + *
+     *   MQ 触发规则:
+     *     1. 当前流程实例中现存的任务会依次触发 process-task-deleted 事件
+     *     2. 当前流程实例会触发 process-instance-cancelled 事件
+     * 
+ * + * @param dto {@link BpmnProcessInstanceCancelDTO} + * @return + */ + @Operation(summary = "发起人主动撤回审核,MQ 触发规则:1. 当前流程实例中现存的任务会依次触发 process-task-deleted 事件,2. 当前流程实例会触发 process-instance-cancelled 事件") + @DeleteMapping("/api/process/instance/cancel") + Boolean cancelProcessInstance(@Validated @RequestBody BpmnProcessInstanceCancelDTO dto); + + /** + * 中止流程实例 + * + * @param dto + * @return + */ + @Operation(summary = "中止流程实例") + @DeleteMapping("/api/process/instance/abort") + Boolean abortProcessInstance(@Validated @RequestBody BpmnProcessInstanceAbortDTO dto); + + /** + * 批量中止流程实例 + * + * @param dtos + * @return + */ + @Operation(summary = "批量中止流程实例") + @DeleteMapping("/api/process/instance/batch/abort") + BatchOperationResultVO batchAbortProcessInstance(@Validated @RequestBody List dtos); + + /** + * 抄送流程实例(未实现) + * + * @param dto + * @return + */ + @Operation(summary = "抄送流程实例") + @PostMapping("/api/process/instance/carbon-copy") + @Deprecated + Boolean carbonCopyProcessInstance(@Validated @RequestBody BpmnProcessInstanceCarbonCopyDTO dto); + + /** + * 获得流程实例 + * + * @param dto {@link BpmnProcessInstanceQueryDTO} 可根据 Id,BusinessKey进行查询 + * @return 流程实例, 租户Id不必传 + */ + @Operation(summary = "获得流程实例") + @GetMapping("/api/process/instance/get") + @InvokeMode(SYNC) + BpmnProcessInstanceVO getProcessInstanceVO(@Validated @RequestBody BpmnProcessInstanceQueryDTO dto); + + /** + * 获取指定流程实例的流程变量 + * + * @param processInstanceId + * @param tenantId + * @return + */ + @Operation(summary = "获取指定流程实例的流程变量") + @GetMapping("/api/process/instance/cooperation-org") + @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); + + /** + * 根据任务id查询任务状态,按钮详情 + * + * @param taskButtonsSearchDTO 请求参数 + * @return + */ + @Operation(summary = "根据任务id查询任务状态,按钮详情") + @PostMapping("/api/process/instance/task/buttons/find") + @InvokeMode(SYNC) + BpmnTaskButtonVo findProcessSingleTaskButtons(@Validated @RequestBody BpmnTaskButtonSearchDTO taskButtonsSearchDTO); + + /** + * 更新指定流程表单最后一次编辑的内容 + * + * @param dto + * @return + */ + @Operation(summary = "更新指定流程表单最后一次编辑的内容") + @PostMapping("/api/process/instance/form/variable/update") + @InvokeMode(SYNC) + Boolean updateInstanceFormVariables(@Validated @RequestBody FormVariablesUpdateDTO dto); + + /** + * 签署业务流程实例在审批待办中查询使用的文档列表 + * + * @return + */ + @Operation(summary = "签署业务流程实例在审批待办中查询使用的文档列表") + @PostMapping("/api/process/instance/select/doc/list") + @InvokeMode(SYNC) + List processInstanceSelectDocs(@Validated @RequestBody ProcessDocQueryDTO dto); + + /** + * 获取审批人阅读状态 + * + * @return + */ + @Operation(summary = "获取审批人阅读状态") + @PostMapping("/api/process/instance/approver/read/status") + @InvokeMode(SYNC) + List approverReadStatus(@Validated @RequestBody ApproverReadStatusDTO dto); + + /** + * 修改审批人关联文档阅读状态 + */ + @Operation(summary = "修改审批人关联文档阅读状态") + @PostMapping("/api/process/instance/approver/read/status/change") + @InvokeMode(SYNC) + Boolean approveReadStatusChange(@Validated @RequestBody ChangeApproverReadStatusDTO dto); + + /** + * 获取签署业务流程最后替换的文档 fileKey 集合 + * + * @return + */ + @Operation(summary = "获取签署业务流程最后替换的文档 fileKey 集合") + @GetMapping("/api/process/instance/final/docs") + @InvokeMode(SYNC) + List getProcessInstanceFinalDocs(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId); + + /** + * 强制使用‘异步’模式调用该方法,请在调用真实方法前调用该方法 + *
+     *   workflowCoreService.async().createProcessInstance();
+     * 
+ */ + default WorkflowCoreService sync() { + ThreadUtil.set(SYNC); + return this; + } + + default WorkflowCoreService async() { + ThreadUtil.set(ASYNC); + return this; + } +} 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 new file mode 100644 index 000000000..b435064bb --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java @@ -0,0 +1,1095 @@ +package cn.axzo.workflow.starter.api; + +import cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterFeignConfiguration; +import cn.axzo.workflow.common.util.ThreadUtil; +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.definition.BpmnProcessDefinitionUpdateDTO; +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.process.BpmnProcessDefinitionVO; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.validation.annotation.Validated; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; +import java.util.List; +import cn.axzo.workflow.common.model.request.es.InstanceSearchReqDTO; +import cn.axzo.workflow.common.model.response.es.ProcessInstanceDocumentVO; +import cn.axzo.workflow.common.model.request.category.*; +import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO; +import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo; +import cn.axzo.workflow.common.model.response.category.CategoryItemVO; +import org.springframework.web.bind.annotation.*; +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.*; +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 io.swagger.v3.oas.annotations.Operation; +import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable; +import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintFieldQueryDTO; +import java.util.Map; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnNodeBackSystemOperateDTO; +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.BpmnTaskAuditWithFormDTO; +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.BpmnTaskResetApproversDTO; +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.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.annotation.Nullable; +import javax.validation.constraints.NotEmpty; +import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormSearchDTO; +import cn.axzo.workflow.common.model.response.form.FormVO; +import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO; +import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO; +import cn.axzo.workflow.common.model.dto.SignFileDTO; +import cn.axzo.workflow.common.model.dto.SimpleDocDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BeforeProcessInstanceCreateDTO; +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.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.SuperBpmnProcessInstanceCancelDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ChangeApproverReadStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.doc.ProcessDocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskButtonSearchDTO; +import cn.axzo.workflow.common.model.request.form.instance.FormVariablesUpdateDTO; +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.NodesByModelVO; +import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.process.doc.DocPendingVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskButtonVo; +import com.fasterxml.jackson.databind.node.ObjectNode; +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.doc.DocByIdDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocResetDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocStatusDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocTenantQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigQueryDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigUpsertDTO; +import cn.axzo.workflow.common.model.request.bpmn.print.RestPrintTemplateConfigDTO; +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.model.doc.DocBaseVO; +import cn.axzo.workflow.common.model.response.print.PrintModelDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminCreateDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminDeleteDTO; +import cn.axzo.workflow.common.model.request.admin.ProcessAdminQueryDTO; +import cn.axzo.workflow.common.model.response.admin.ProcessAdminVo; + +/** + * Workflow Engine Starter Management Service + *

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

+ * Auto generation by workflow engine, It cannot be manually modified + */ +@org.springframework.cloud.openfeign.FeignClient(name = "workflow-engine", url = "${axzo.service.workflow-engine:http://workflow-engine:8080}", configuration = WorkflowEngineStarterFeignConfiguration.class) +public interface WorkflowManageService { + + /** + * 获取活跃的流程定义分页 + */ + @GetMapping("/api/process/definition/page") + @InvokeMode(SYNC) + BpmPageResult getProcessDefinitionPage(@Validated @RequestBody BpmnProcessDefinitionPageDTO dto); + + /** + * 通过模型 ID 更新流程定义 + * + * @param dto + * @return + */ + @PutMapping("/api/process/definition/update") + @InvokeMode(SYNC) + Boolean updateProcessDefinition(@Validated @RequestBody BpmnProcessDefinitionUpdateDTO dto); + + /** + * 获得指定定义编号对应的流程定义内容 + * + * @param processDefinitionId 编号 + * @return 流程定义 + */ + @GetMapping("/api/process/definition/get") + @InvokeMode(SYNC) + BpmnProcessDefinitionVO getProcessDefinition(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId); + + /** + * 获得 deploymentId 对应的 ProcessDefinition + * + * @param deploymentId 部署编号 + * @return 流程定义 + */ + @GetMapping("/api/process/definition/getByDeploymentId") + @InvokeMode(SYNC) + BpmnProcessDefinitionVO getProcessDefinitionByDeploymentId(@NotBlank(message = "流程部署 ID 不能为空") @RequestParam String deploymentId); + + /** + * 获得流程定义标识对应的激活的流程定义 + * + * @param key 流程定义的标识 + * @return 流程定义 + */ + @GetMapping("/api/process/definition/active/getByKey") + @InvokeMode(SYNC) + BpmnProcessDefinitionVO getActiveProcessDefinitionByKey(@NotBlank(message = "模型定义KEY不能为空") @RequestParam String key, @RequestParam(required = false, defaultValue = NO_TENANT_ID) String tenantId); + + /** + * 挂起/激活流程, + * 激活:SuspensionState.ACTIVE.getStateCode() + * 挂起:SuspensionState.SUSPENDED.getStateCode() + * {@see SuspensionState} + */ + @PutMapping("/api/process/definition/state/update") + @InvokeMode(SYNC) + Boolean getActiveProcessDefinitionByKey(@NotBlank(message = "流程定义ID不能为空") @RequestParam String processDefinitionId, @NotNull(message = "状态不能为空") @RequestParam Integer state); + + /** + * 获取指定模型的定义 ID + * + * @return 流程定义ID + */ + @GetMapping("/api/process/definition/active/getByCategory") + @InvokeMode(SYNC) + String getActiveProcessDefinitionId(@RequestParam(required = false) String tenantId, @NotBlank(message = "分类不能为空") @RequestParam(required = false) String category); + + /** + * 获取指定模型激活的流程定义 JSON 模型 + * + * @return 流程定义ID + */ + @GetMapping("/api/process/definition/active/json/model") + @InvokeMode(SYNC) + BpmnModelUpdateDTO getActiveProcessDefinitionJsonModel(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId, @NotBlank(message = "分类不能为空") @RequestParam(required = false) String key, @RequestParam(required = false) String tenantId); + + /** + * 删除流程部署及定义 + * + * @param deploymentId 流程定义部署 ID + * @param cascade 是否级联参数定义对应的流程实例及 job 等管理内容 + * @return + */ + @GetMapping("/api/process/definition/delete") + @InvokeMode(SYNC) + Void delete(@NotBlank(message = "流程定义部署 ID 不能为空") String deploymentId, @RequestParam(required = false) Boolean cascade); + + /** + * 将死信队列中的任务转移到正常 JOB 队列中 + *

+ * 入参是二选一:当只有 jobId 时,仅将指定的 JOB 转移到正常的队列中; + * 而传入的是具体的实例 ID,那么会将这个流程下的所有在死信队列中的任务都转移到正常的队列中 + * + * @param jobId 具体的 JOB ID + * @param procInstId 具体的实例 ID + * @return + */ + @GetMapping("/api/process/job/dead-letter/resume") + @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); + + /** + * 获取流程操作按钮列表 + * + * @return 流程操作按钮列表 + */ + @GetMapping("/api/process/config/button/list") + @InvokeMode(SYNC) + List getDefaultButtons(); + + /** + * 从 ES 中搜索符合条件的实例纬度数据 + * + * @param dto + * @return + */ + @PostMapping("/api/es/instance/search") + @InvokeMode(SYNC) + BpmPageResult searchInstanceInEs(@Validated @RequestBody InstanceSearchReqDTO dto); + + /** + * 获取指定业务分类 + * + * @return + */ + @GetMapping("/api/process/category/get") + @InvokeMode(SYNC) + CategoryItemVO get(@RequestParam Long id); + + /** + * 获取指定业务分类集合 + * + * @param ids + * @return + */ + @GetMapping("/api/process/category/getByIds") + @InvokeMode(SYNC) + List getByIds(@RequestParam List ids); + + /** + * 获取指定业务分类集合 + * + * @param values + * @return + */ + @GetMapping("/api/process/category/getByValues") + @InvokeMode(SYNC) + List getByValues(@RequestParam List values); + + /** + * 新增业务分类 + * + * @return + */ + @PostMapping("/api/process/category/create") + @InvokeMode(SYNC) + CategoryItemVO create(@Validated @RequestBody CategoryCreateDTO req); + + /** + * 更新业务分类 + * + * @return + */ + @PutMapping("/api/process/category/update") + @InvokeMode(SYNC) + CategoryItemVO update(@Validated @RequestBody CategoryUpdateDTO dto); + + /** + * 删除指定业务分类 + * + * @param id + * @return + */ + @DeleteMapping("/api/process/category/delete") + @InvokeMode(SYNC) + Boolean delete(@RequestParam Long id); + + /** + * 更新指定分类状态 + * + * @param id + * @param state + * @return + */ + @PutMapping("/api/process/category/update/state") + @InvokeMode(SYNC) + Boolean updateState(@RequestParam Long id, @RequestParam Boolean state); + + /** + * 获取指定业务分类集合 + * + * @return + */ + @PostMapping("/api/process/category/list") + @InvokeMode(SYNC) + List list(@RequestBody CategorySearchDTO dto); + + /** + * 搜索业务分类 + * + * @return + */ + @PostMapping("/api/process/category/page/search") + @InvokeMode(SYNC) + BpmPageResult search(@RequestBody CategorySearchDTO dto); + + /** + * 业务分类创建黑白名单配置项 + * + * @param dto + * @return + */ + @PostMapping("/api/process/category/config/create") + @InvokeMode(SYNC) + Boolean createConfig(@RequestBody CategoryConfigCreateDTO dto); + + /** + * 删除指定分类的配置项数据 + * + * @param id + * @return + */ + @DeleteMapping("/api/process/category/config/delete/{id}") + @InvokeMode(SYNC) + Boolean deleteConfig(@PathVariable Long id); + + /** + * 业务分类黑白名单查询 + * + * @return + */ + @PostMapping("/api/process/category/config/page/search") + @InvokeMode(SYNC) + BpmPageResult configSearch(@RequestBody CategoryConfigSearchDTO dto); + + /** + * 更新业务分类的配置类型 + * + * @param id + * @param configType + * @return + */ + @PostMapping("/api/process/category/config/type/update") + @InvokeMode(SYNC) + Boolean updateCategoryConfigType(@RequestParam Long id, @RequestParam String configType); + + /** + * 用于业务方判断指定的业务分类的黑白名单配置 + * + * @param tenantId 租户ID + * @param categoryCode 业务分类编码 + * @return true: 可以发起创建流程实例, false: 不可用 + */ + @GetMapping("/api/process/category/check/status") + @InvokeMode(SYNC) + Boolean checkCategoryStatus(@RequestParam Long tenantId, @RequestParam String categoryCode); + + /** + * 查询分类对应的分组以及分组下的变量 + * @param dto 请求参数 + * @return 分组以及分组下的变量 + */ + @PostMapping("/api/process/category/group-with-vars/list") + @InvokeMode(SYNC) + List searchCategoryGroupAndVars(@Validated @RequestBody CategoryGroupVarSearchDto dto); + + /** + * 新增或者更新分组或者变量 + * @param dto 请求参数 + * @return 是否成功 + */ + @PostMapping("/api/process/category/group-with-vars/upsert") + Boolean upsertCategoryGroupAndVars(@Validated @RequestBody CategoryGroupVarUpsertDto dto); + + /** + * 该功能应该利用引擎的 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); + + /** + * 为指定流程新增变量 + */ + @PostMapping("/api/process/variable/create/{executionId}") + @InvokeMode(SYNC) + Void createVariable(@PathVariable @NotBlank(message = "流程实例 ID 不能为空") String executionId, @RequestBody @Validated RestBpmnProcessVariable restVariable); + + /** + * 仅更新流程已存在的变量 + * + * @param executionId + * @param restVariable + * @return + */ + @PostMapping("/api/process/variable/update/{executionId}") + @InvokeMode(SYNC) + Void updateVariable(@PathVariable @NotBlank(message = "流程实例 ID 不能为空") String executionId, @RequestBody @Validated RestBpmnProcessVariable restVariable); + + /** + * 批量删除流程变量 + */ + @DeleteMapping("/api/process/variable/delete/{executionId}") + @InvokeMode(SYNC) + Void deleteVariables(@PathVariable("executionId") String executionId, @RequestParam String variableNames, @RequestParam(value = "scope", required = false) String scope); + + @Operation(summary = "查询指定审批流程是否能打印,打印开关是否开启,是否存在打印模板") + @GetMapping("/api/print/admin/template/exists") + @InvokeMode(SYNC) + Boolean hasPrintTemplate(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId); + + @Operation(summary = "获取打印模板中可打印的字段") + @PostMapping("/api/print/admin/fields") + @InvokeMode(SYNC) + List getPrintFields(@Validated @RequestBody PrintFieldQueryDTO dto); + + @Operation(summary = "获取指定流程下用于替换打印的相关变量") + @GetMapping("/api/print/admin/field/variables") + @InvokeMode(SYNC) + Map getPrintFieldVariables(@NotBlank(message = "流程实例不能为空") @RequestParam String processInstanceId); + + /** + * 催办 + * + * @param dto + * @return + */ + @Operation(summary = "审批流程催办") + @PostMapping("/api/process/task/remind") + @Manageable + @InvokeMode(SYNC) + Boolean remindTask(@Validated @RequestBody BpmnTaskRemindDTO dto); + + /** + * 添加附件 + * + * @param dto + * @return + */ + @Operation(summary = "添加附件") + @PostMapping("/api/process/task/attachment") + @Manageable + Void addAttachment(@Validated @RequestBody BpmnTaskAttachmentDTO dto); + + /** + * 待审核列表 + */ + @Operation(summary = "待审核列表") + @GetMapping("/api/process/task/page/todo") + @Manageable + @InvokeMode(SYNC) + BpmPageResult getTodoTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto); + + /** + * 已完成的审批列表 + */ + @Operation(summary = "已完成的审批列表") + @GetMapping("/api/process/task/page/done") + @Manageable + @InvokeMode(SYNC) + BpmPageResult getDoneTaskPage(@Validated @RequestBody BpmnTaskPageSearchDTO dto); + + /** + * 获取指定流程实例的审批过程信息 + *

+ * 同一层级结构 + */ + @Operation(summary = "获取指定流程实例的审批过程信息") + @GetMapping("/api/process/task/list/flat") + @Manageable + @InvokeMode(SYNC) + List getTaskListFlatByProcessInstanceId(@NotBlank(message = "流程实例 ID " + "不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + + /** + * 获取指定流程实例的审批过程信息 + *

+ * 分组结构 + */ + @Operation(summary = "获取指定流程实例的审批过程信息") + @GetMapping("/api/process/task/list/group") + @Manageable + @InvokeMode(SYNC) + List getTaskListGroupByProcessInstanceId(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + + /** + * 获取实例正在审核的人列表 + */ + @Operation(summary = "获取实例正在审核的人列表") + @GetMapping("/api/process/task/active/list") + @Manageable + @InvokeMode(SYNC) + List getActiveTasksByProcessInstanceId(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @NotBlank(message = "租户不能为空") @RequestParam String tenantId); + + /** + * 根据实例 ID 和自然人 ID 查询对应待处理的任务 ID + * + * @return + */ + @Operation(summary = "根据实例 ID 和自然人 ID 查询对应待处理的任务 ID") + @GetMapping("/api/process/task/find") + @Manageable + @InvokeMode(SYNC) + String findTaskIdByInstanceIdAndPersonId(@RequestParam(required = false) @NotBlank(message = "流程实例 ID 不能为空") String processInstanceId, @RequestParam(required = false) @NotBlank(message = "自然人 ID 不能为空") String personId); + + /** + * 根据实例 ID列表 和自然人 ID 查询对应待处理的任务 ID + * + * @return + */ + @Operation(summary = "根据实例 ID列表 和自然人 ID 查询对应待处理的任务 ID") + @GetMapping("/api/process/task/batch/find") + @Manageable + @InvokeMode(SYNC) + Map findTaskIdByInstanceIdsAndPersonId(@RequestParam(required = false) @NotEmpty(message = "流程实例 ID列表 不能为空") List processInstanceIds, @RequestParam(required = false) @NotBlank(message = "自然人 ID 不能为空") String personId); + + @PostMapping("/api/form/admin/form/page") + @InvokeMode(SYNC) + List formPage(@Validated @RequestBody FormSearchDTO dto); + + @PostMapping("/api/form/admin/start/form") + @InvokeMode(SYNC) + FormDefinitionVO getFormDefinition(@Validated @RequestBody StartFormSearchDTO dto); + + /** + * 查询指定审批实例的表单模型和数据 + *

+ * dto 中的 processInstanceId 与 taskId,至少有一个属性有值,一般建议直接使用实例 ID。 + * 当传入 taskId 时,将只查询该任务绑定的表单模型和数据。 + * + * @param dto + * @return + */ + @PostMapping("/api/form/admin/instance/render") + @InvokeMode(SYNC) + FormInstanceVO getFormInstance(@Validated @RequestBody FormDetailDTO dto); + + @DeleteMapping("/api/process/instance/super/cancel") + @Manageable + Boolean superCancelProcessInstance(@Validated @RequestBody SuperBpmnProcessInstanceCancelDTO dto); + + /** + * 查询所有的审批流 + * + * @return + */ + @Operation(summary = "查询所有的审批流") + @PostMapping("/api/process/instance/page/all") + @Manageable + @InvokeMode(SYNC) + BpmPageResult getAllProcessInstancePage(@Validated @RequestBody BpmnProcessInstanceAdminPageReqVO dto); + + /** + * 我发起的审批列表 + */ + @Operation(summary = "我发起的审批列表") + @PostMapping("/api/process/instance/page/my") + @Manageable + @InvokeMode(SYNC) + BpmPageResult getMyProcessInstancePage(@Validated @RequestBody BpmnProcessInstanceMyPageReqVO dto); + + /** + * 更新流程定义的状态 + * + * @param processDefinitionId 流程定义的编号 + * @param status 1, "active"; 2, "suspended" + */ + @Operation(summary = "更新指定流程定义的版本的状态, 处于 suspended 状态的流程模型将不能再发起实例") + @PutMapping("/api/process/instance/status/update") + @Manageable + @InvokeMode(SYNC) + Boolean updateProcessStatus(@NotBlank(message = "流程定义 ID 不能为空") @RequestParam String processDefinitionId, @NotNull(message = "状态不能为空") @RequestParam Integer status); + + /** + * 获取审批流程实例的运行图 + * + * @param processInstanceId + * @param tenantId + * @return + */ + @Operation(summary = "获取审批流程实例的运行图") + @GetMapping("/api/process/instance/graphical") + @Manageable + @InvokeMode(SYNC) + ObjectNode processInstanceGraphical(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + + /** + * 推断指定流程实例的所有节点执行顺序 + * + * @return + */ + @Operation(summary = "推断指定流程实例的所有节点执行顺序") + @GetMapping("/api/process/instance/node/forecasting") + @Manageable + @InvokeMode(SYNC) + List processInstanceNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + + /** + * 推断指定流程实例的过滤掉部分节点执行顺序 + * + * @param allNode 如果为真时,相当于调用 {@link ProcessInstanceApi#processInstanceNodeForecast(String, String)} 方法,切会直接丢弃 nodeDefinitionKeys 参数 + * 如果为假时,才结合 nodeDefinitionKeys 过滤掉传入的节点 + * @return + */ + @Operation(summary = "推断指定流程实例的过滤掉部分节点执行顺序") + @GetMapping("/api/process/instance/node/filter/forecasting") + @Manageable + @InvokeMode(SYNC) + List processInstanceFilterNodeForecast(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam(required = false) String processInstanceId, @Nullable @RequestParam(required = false) String tenantId, @RequestParam(required = false, defaultValue = "false") Boolean allNode, @Nullable @RequestParam(required = false) List nodeDefinitionKeys); + + /** + * 查询实例的租户集合 + * + * @return + */ + @Operation(summary = "查询实例的租户集合") + @GetMapping("/api/process/instance/tenant/ids") + @Manageable + @InvokeMode(SYNC) + List getTenantIds(); + + /** + * 校验指定流程实例下,是否存在指定的审批人正处理待审批 + * + * @return true 是在当前流程实例中,存在指定的审批人 + */ + @Operation(summary = "校验指定流程实例下,是否存在指定的审批人") + @PostMapping("/api/process/instance/check/approver") + @Manageable + @InvokeMode(SYNC) + Boolean checkInstanceApprover(@Validated @RequestBody BpmnProcessInstanceCheckApproverDTO dto); + + /** + * 流程模型列表 + * + * @param dto + * @return + */ + @Operation(summary = "流程模型列表") + @GetMapping("/api/process/model/page") + @InvokeMode(SYNC) + BpmPageResult page(@Validated @RequestBody BpmnModelSearchDTO dto); + + /** + * 创建流程, + * return modelId的主键 + */ + @Operation(summary = "创建流程模型") + @PostMapping("/api/process/model/create") + @InvokeMode(SYNC) + String create(@Validated @RequestBody BpmnModelCreateDTO dto); + + /** + * 通过模型 ID 获取模型 + */ + @Operation(summary = "通过模型ID查询指定流程模型") + @GetMapping("/api/process/model/get") + @InvokeMode(SYNC) + BpmnModelDetailVO getById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId, @RequestParam(required = false) String tenantId); + + /** + * 通过模型 KEY 获取模型 + */ + @Operation(summary = "通过模型KEY查询指定流程模型") + @GetMapping("/api/process/model/getByKey") + @InvokeMode(SYNC) + BpmnModelDetailVO getByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam(required = false) String processModelKey, @NotBlank(message = "租户不能为空") @RequestParam(required = false) String tenantId); + + /** + * 校验是否能更新模型即其关联的流程定义内容 + *

+ * 偏向业务的接口,flowable 引擎是站在流程定义维度进行激活和挂起 + * + * @return true 能更新,表明该模型关联的所有定义版本都是挂起状态 + */ + @Operation(summary = "获取指定模型的扩展属性") + @GetMapping("/api/process/model/ext") + @InvokeMode(SYNC) + BpmnModelExtVO getModelExt(@NotBlank(message = "模型 ID 不能为空") @RequestParam(required = false) String modelId); + + /** + * 修改流程信息 + */ + @Operation(summary = "更新流程模型") + @PutMapping("/api/process/model/update") + @InvokeMode(SYNC) + String update(@RequestBody BpmnModelUpdateDTO dto); + + /** + * 通过模型 ID 部署模型 + * + * @return 部署完成的流程定义Id + */ + @Operation(summary = "通过模型 ID 部署流程模型") + @PostMapping("/api/process/model/deploy") + @InvokeMode(SYNC) + String deployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId, @RequestParam(required = false, defaultValue = "") String modelTenantId, @RequestParam(required = false) String operator); + + /** + * 通过模型 KEY 部署模型 + * + * @return 部署完成的流程定义Id + */ + @Operation(summary = "通过模型 KEY 部署流程模型") + @PostMapping("/api/process/model/deployByKey") + @InvokeMode(SYNC) + String deployByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam(required = false) String processModelKey, @NotBlank(message = "租户不能为空") @RequestParam(required = false) String modelTenantId, @RequestParam(required = false) String operator); + + /** + * 通过模型 ID 取消部署流程模型 + * + * @param processModelId + * @param tenantId + * @param operator + * @return + */ + @Operation(summary = "通过模型 ID 取消部署流程模型") + @PostMapping("/api/process/model/undeploy") + @InvokeMode(SYNC) + Void unDeployById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam(required = false) String processModelId, @RequestParam(required = false, defaultValue = "") String tenantId, @RequestParam(required = false) String operator); + + /** + * 通过模型 ID 删除模型 + */ + @Operation(summary = "删除指定模型 ID 的流程模型") + @DeleteMapping("/api/process/model/delete") + @InvokeMode(SYNC) + Void deleteById(@NotBlank(message = "流程模型 ID 不能为空") @RequestParam String processModelId, @RequestParam(required = false, defaultValue = "") String tenantId); + + /** + * 通过模型 KEY 删除模型 + * + * @param processModelKey + * @param tenantId + * @return + */ + @Operation(summary = "删除指定模型 KEY 的流程模型") + @DeleteMapping("/api/process/model/deleteByKey") + @InvokeMode(SYNC) + Void deleteByKey(@NotBlank(message = "流程模型 KEY 不能为空") @RequestParam String processModelKey, @RequestParam(required = false, defaultValue = "") String tenantId); + + /** + * 通过模型 ID 修改模型状态 + * + * @param modelId + * @param status + * @param operator + * @return + */ + @Operation(summary = "修改模型状态") + @PostMapping("/api/process/model/changeStatus") + @InvokeMode(SYNC) + Boolean changeStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId, @NotNull(message = "状态不能为空") @RequestParam Integer status, @RequestParam(required = false) String operator); + + /** + * 修改模型打印开关状态 + * + * @param modelId + * @param status + * @param operator + * @return + */ + @Operation(summary = "修改模型打印开关状态") + @PostMapping("/api/process/model/print/changeStatus") + @InvokeMode(SYNC) + Boolean changePrintStatus(@NotBlank(message = "模型 ID 不能为空") @RequestParam String modelId, @NotNull(message = "状态不能为空") @RequestParam Integer status, @RequestParam(required = false) String operator); + + /** + * 查询流程模型使用的分类列表 + * + * @return + */ + @Operation(summary = "查询流程模型使用的分类列表") + @GetMapping("/api/process/model/category/ids") + @InvokeMode(SYNC) + List getModelCategoryList(); + + /** + * 查询模型的租户集合 + * + * @return + */ + @Operation(summary = "查询模型的租户集合") + @GetMapping("/api/process/model/tenant/ids") + @InvokeMode(SYNC) + List getModelTenantIds(); + + /** + * 打印模板配置内容更新保存 + * + * @param dto + * @return + */ + @Operation(summary = "打印模板配置内容更新保存") + @PostMapping("/api/process/model/print/template/upsert") + @InvokeMode(SYNC) + Void printTemplateConfig(@Validated @RequestBody PrintTemplateConfigUpsertDTO dto); + + /** + * 获取打印模板配置内容 + * + * @param dto + * @return + */ + @Operation(summary = "获取打印模板配置内容") + @PostMapping("/api/process/model/print/template/config/query") + @InvokeMode(SYNC) + PrintModelDTO getPrintTemplateConfig(@Validated @RequestBody PrintTemplateConfigQueryDTO dto); + + /** + * 代运营充值的打印模板 + * + * @param dto + * @return + */ + @Operation(summary = "代运营重置打印模板") + @PostMapping(value = "/api/process/model/print/template/config/reset") + @InvokeMode(SYNC) + Boolean resetPrintTemplateConfig(@Validated @RequestBody RestPrintTemplateConfigDTO dto); + + /** + * 搜索文档列表 + * + * @param dto + * @return + */ + @Operation(summary = "搜索文档列表") + @PostMapping(value = "/api/process/model/doc/page") + @InvokeMode(SYNC) + BpmPageResult docPage(@Validated @RequestBody DocSearchDTO dto); + + /** + * 获取指定 docIds 文档列表 + * + * @return + */ + @Operation(summary = "获取指定 docIds 文档列表") + @PostMapping(value = "/api/process/model/doc/ids") + @InvokeMode(SYNC) + List docByIds(@Validated @RequestBody DocByIdDTO dto); + + /** + * 获取指定模板的原始文档列表 + * + * @param dto + * @return + */ + @Operation(summary = "根据业务 ID 获取模型文档列表,自动适配公共模板和代运营") + @PostMapping(value = "/api/process/model/doc/list") + @InvokeMode(SYNC) + List docList(@Validated @RequestBody DocQueryDTO dto); + + /** + * 获取关联 HiPrint 类型文档模板内容 + * + * @param fileRelationId + * @return + */ + @Operation(summary = "获取关联 HiPrint 类型文档模板内容") + @PostMapping(value = "/api/process/model/hi-print/content/get") + @InvokeMode(SYNC) + String getHiPrintContent(@RequestParam String fileRelationId); + + /** + * 添加关联文档 + * + * @return + */ + @Operation(summary = "添加关联文档") + @PutMapping(value = "/api/process/model/doc/create") + @InvokeMode(SYNC) + Boolean createDoc(@Validated @RequestBody DocCreateDTO dto); + + /** + * 修改关联文档 + * + * @return + */ + @Operation(summary = "修改关联文档") + @PostMapping(value = "/api/process/model/doc/update") + @InvokeMode(SYNC) + Boolean updateDoc(@Validated @RequestBody DocUpdateDTO dto); + + /** + * 克隆关联文档 + * + * @param docId + * @return + */ + @Operation(summary = "克隆关联文档") + @PostMapping(value = "/api/process/model/doc/clone") + @InvokeMode(SYNC) + Boolean cloneDoc(@RequestParam("id") Long docId); + + /** + * 删除关联文档 + * + * @return + */ + @Operation(summary = "删除指定文档") + @DeleteMapping(value = "/api/process/model/doc/delete") + @InvokeMode(SYNC) + Boolean deleteDoc(@RequestParam("id") Long docId); + + /** + * 关联文档配置排序 + * + * @param dto + * @return + */ + @Operation(summary = "关联文档配置排序") + @PostMapping(value = "/api/process/model/doc/order") + @InvokeMode(SYNC) + Boolean orderDoc(@Validated @RequestBody DocOrderDTO dto); + + /** + * 重置关联文档 + * + * @param dto + * @return + */ + @Operation(summary = "重置关联文档配置") + @PostMapping(value = "/api/process/model/doc/reset") + @InvokeMode(SYNC) + Boolean resetDoc(@Validated @RequestBody DocResetDTO dto); + + /** + * 设置关联文档的停启用状态 + * + * @param dto + * @return + */ + @Operation(summary = "设置关联文档的停启用状态") + @PostMapping(value = "/api/process/model/doc/status") + @InvokeMode(SYNC) + Boolean statusDoc(@Validated @RequestBody DocStatusDTO dto); + + /** + * 设置关联文档的必选状态 + * + * @return + */ + @Operation(summary = "设置关联文档的必选状态") + @PostMapping(value = "/api/process/model/doc/require") + @InvokeMode(SYNC) + Boolean requireDoc(@Validated @RequestBody DocStatusDTO dto); + + /** + * 特殊的查询设置过关联过文档的工作台 ID 集合 + * + * @return + */ + @Operation(summary = "特殊的查询设置过关联过文档的工作台 ID 集合") + @PostMapping(value = "/api/process/model/has/docs/tenantId") + @InvokeMode(SYNC) + List hasFilesTenantIds(@Validated @RequestBody DocTenantQueryDTO dto); + + /** + * 查询管理员 + * @param dto 管理员数据 + * @return 管理员id + */ + @PostMapping("/api/process/admin/query") + @InvokeMode(SYNC) + List queryProcessAdmins(@RequestBody ProcessAdminQueryDTO dto); + + /** + * 查询管理员 + * @param dto 管理员数据 + * @return 管理员id + */ + @PostMapping("/api/process/admin/query/count") + @InvokeMode(SYNC) + Integer queryProcessAdminsCount(@RequestBody ProcessAdminQueryDTO dto); + + /** + * 添加管理员 + * @param dto 管理员数据 + * @return 管理员id + */ + @PostMapping("/api/process/admin/create") + @InvokeMode(SYNC) + Long createProcessAdmin(@RequestBody ProcessAdminCreateDTO dto); + + /** + * 批量添加管理员 + * @param dtos + * @return + */ + @PostMapping("/api/process/admin/batch/create") + @InvokeMode(SYNC) + Void batchCreateProcessAdmin(@RequestBody List dtos); + + /** + * 删除管理员 + * @param id 配置表id + * @return + */ + @DeleteMapping("/api/process/admin/delete") + @InvokeMode(SYNC) + Integer deleteCommonProcessAdmin(@RequestParam Long id); + + /** + * 根据条件删除管理员 + * @param dto 删除条件 + * @return + */ + @DeleteMapping("/api/process/admin/delete/criteria") + @InvokeMode(SYNC) + Integer deleteProcessAdminCriteria(@RequestBody ProcessAdminDeleteDTO dto); + + /** + * 删除管理员 + * @param ids 管理员配置id列表 + * @return + */ + @DeleteMapping("/api/process/admin/batch/delete") + @InvokeMode(SYNC) + Integer batchDeleteProcessAdmin(@RequestBody List ids); + + /** + * 强制使用‘同步’模式调用该方法,请在调用真实方法前调用该方法 + *

+     *   workflowManageService.sync().getTenantIds();
+     * 
+ */ + default WorkflowManageService sync() { + ThreadUtil.set(SYNC); + return this; + } + + /** + * 强制使用‘异步’模式调用该方法,请在调用真实方法前调用该方法 + *
+     *   workflowManageService.async().getTenantIds();
+     * 
+ */ + default WorkflowManageService async() { + ThreadUtil.set(ASYNC); + return this; + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/condition/NonContainerEnvironmentCondition.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/condition/NonContainerEnvironmentCondition.java new file mode 100644 index 000000000..153aafe0c --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/condition/NonContainerEnvironmentCondition.java @@ -0,0 +1,65 @@ +package cn.axzo.workflow.starter.common.condition; + +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.StringUtils; + +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.StarterConstants.DEBUGGING_MQ_SUFFIX; +import static cn.axzo.workflow.common.constant.StarterConstants.K8S_POD_NAME_SPACE; +import static cn.axzo.workflow.common.constant.StarterConstants.MQ_GID_NAME_SEGMENT; +import static cn.axzo.workflow.common.constant.StarterConstants.NACOS_PROFILES_ACTIVE; + +/** + * 用于处理 MQ 的消费者, 在本地启动或在容器中启动时, 能自主控制是否并入统一的消费组 + *

+ * 可查看 {@link WorkflowEngineStarterProperties#joinContainerGroup} 属性, 来了解本类的用途, + * 特别需要注意的是: Starter 是结合 K8S 的命名空间(namespace) 来处理的. + * + * @author wangli + * @since 2024/5/30 22:19 + */ +public class NonContainerEnvironmentCondition implements Condition { + + private static final Logger log = LoggerFactory.getLogger(NonContainerEnvironmentCondition.class); + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment(); + // 依赖 K8S 设置的环境变量信息,如果变量的 key 发生变化,会导致此处的功能可能出现异常 + String myPodNamespace = environment.getProperty(K8S_POD_NAME_SPACE); + String activeProfile = environment.getProperty(NACOS_PROFILES_ACTIVE); + if (!StringUtils.hasText(activeProfile)) { + activeProfile = environment.getProperty("spring.profiles.active", String.class); + } + // 在容器环境时, 强制加入集群消费组 + if (StringUtils.hasText(myPodNamespace)) { + environment.getSystemProperties().put(MQ_GID_NAME_SEGMENT, activeProfile); + return true; + } + + // 优先外部化配置 + Boolean joinContainerGroup = environment.getProperty("workflow.engine.starter.join-container-group", Boolean.class); + if (Objects.isNull(joinContainerGroup)) { + // 获取默认值 + joinContainerGroup = new WorkflowEngineStarterProperties().getJoinContainerGroup(); + } + if (log.isDebugEnabled()) { + log.debug("workflow engine starter join-container-group status: {} ", joinContainerGroup); + } + + String specialId = environment.getProperty("workflow.engine.starter.special-id", String.class); + environment.getSystemProperties().put(MQ_GID_NAME_SEGMENT, + joinContainerGroup ? activeProfile : + activeProfile + (StringUtils.hasText(specialId) ? "_" + specialId : "") + DEBUGGING_MQ_SUFFIX); + + return true; + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/enums/FailHandleTypeEnum.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/enums/FailHandleTypeEnum.java new file mode 100644 index 000000000..0f6d64e32 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/enums/FailHandleTypeEnum.java @@ -0,0 +1,23 @@ +package cn.axzo.workflow.starter.common.enums; + + +import lombok.Getter; + +/** + * 异常处理策略,执行客户端自定义Listener异常处理策略,默认未FAIL_OVER + */ +@Getter +public enum FailHandleTypeEnum { + FAIL_OVER("fail_over", "当前listener执行出错,忽略继续往下执行,可配置重试相关参数,不抛出异常"), + FAIL_FAST("fail_fast", "快速失败,出错直接抛出异常,listener不再往下执行"), + FAIL_BACK("fail_back", "失败自动恢复,在后台记录失败的消息,并按照一定的策略后期再进行重试,目前暂不支持"), + ; + private final String code; + private final String description; + + FailHandleTypeEnum(String code, String description) { + this.code = code; + this.description = description; + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowEngineStarterException.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowEngineStarterException.java new file mode 100644 index 000000000..25e7497dd --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowEngineStarterException.java @@ -0,0 +1,24 @@ +package cn.axzo.workflow.starter.common.exception; + +import cn.axzo.framework.domain.ServiceException; + +/** + * 流程引擎 starter 的异常类 + * + * @author wangli + * @since 2024/5/21 17:57 + */ +public class WorkflowEngineStarterException extends ServiceException { + + public WorkflowEngineStarterException(Throwable cause) { + super(cause); + } + + public WorkflowEngineStarterException(String message) { + super(message); + } + + public WorkflowEngineStarterException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowListenerExecutionException.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowListenerExecutionException.java new file mode 100644 index 000000000..163dccd2e --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowListenerExecutionException.java @@ -0,0 +1,13 @@ +package cn.axzo.workflow.starter.common.exception; + +public class WorkflowListenerExecutionException extends WorkflowEngineStarterException { + + public WorkflowListenerExecutionException(String message) { + super(message); + } + + public WorkflowListenerExecutionException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowNoMethodException.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowNoMethodException.java new file mode 100644 index 000000000..b4a7ffba8 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowNoMethodException.java @@ -0,0 +1,17 @@ +package cn.axzo.workflow.starter.common.exception; + +/** + * 特殊的 invoke 逻辑中,没有特定的方法异常 + * + * @author wangli + * @since 2024/6/14 13:35 + */ +public class WorkflowNoMethodException extends WorkflowEngineStarterException { + public WorkflowNoMethodException(String message) { + super(message); + } + + public WorkflowNoMethodException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowRpcInvokeException.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowRpcInvokeException.java new file mode 100644 index 000000000..4ffde34dc --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowRpcInvokeException.java @@ -0,0 +1,21 @@ +package cn.axzo.workflow.starter.common.exception; + +/** + * Starter 的 RPC 动作调用异常 + * + * @author wangli + * @since 2024/6/18 14:20 + */ +public class WorkflowRpcInvokeException extends WorkflowEngineStarterException { + public WorkflowRpcInvokeException(Throwable cause) { + super(cause); + } + + public WorkflowRpcInvokeException(String message) { + super(message); + } + + public WorkflowRpcInvokeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowUnsupportedException.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowUnsupportedException.java new file mode 100644 index 000000000..09bda2345 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowUnsupportedException.java @@ -0,0 +1,18 @@ +package cn.axzo.workflow.starter.common.exception; + +/** + * 不支持的操作的异常类 + * + * @author wangli + * @since 2024/6/12 15:25 + */ +public class WorkflowUnsupportedException extends WorkflowEngineStarterException { + + public WorkflowUnsupportedException(String message) { + super(message); + } + + public WorkflowUnsupportedException(String message, Throwable cause) { + super(message, cause); + } +} 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 new file mode 100644 index 000000000..be27d6ecb --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/ComplexInvokeClient.java @@ -0,0 +1,290 @@ +package cn.axzo.workflow.starter.feign.ext; + +import cn.axzo.workflow.common.enums.RpcInvokeModeEnum; +import cn.axzo.workflow.common.model.response.mq.WorkflowEngineStarterRpcInvokeDTO; +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import cn.axzo.workflow.starter.common.exception.WorkflowEngineStarterException; +import cn.axzo.workflow.starter.mq.retry.producer.RpcInvokeEventProducer; +import cn.axzo.workflow.starter.util.MD5; +import cn.azxo.framework.common.model.CommonResponse; +import com.alibaba.fastjson.JSON; +import feign.Client; +import feign.MethodMetadata; +import feign.Request; +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.util.StringUtils; +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 java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +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; + +/** + * 适用于 Starter 中复合型的 FeignClient 实现 + *

+ * 如果使用方调用的服务方法是同步,则使用原生的 FeignClient 实现, 否则通过 MQ 事件解耦请求 + * + * @author wangli + * @since 2024/5/28 15:23 + */ +public class ComplexInvokeClient implements Client { + + private final Logger log = LoggerFactory.getLogger(ComplexInvokeClient.class); + private final WorkflowEngineStarterProperties starterProperties; + private final BeanFactory beanFactory; + private final Client feignClient; + + public ComplexInvokeClient(WorkflowEngineStarterProperties starterProperties, + BeanFactory beanFactory, + Client feignClient) { + this.starterProperties = starterProperties; + this.beanFactory = beanFactory; //(RpcInvokeEventProducer) eventProducer; + this.feignClient = feignClient; + } + + @Override + public Response execute(Request request, Request.Options options) throws IOException { + log.debug("ComplexInvokeClient execute... Url: {}", request.url()); + if (useMetaFeign(request)) { + log.debug("use meta feign client: {}", request.url()); + return feignClient.execute(request, options); + } + RpcInvokeModeEnum currentInvokeModeEnum = getInvokeMode(request); + log.debug("[{}] invoke url: {}", currentInvokeModeEnum, request.url()); + if (Objects.equals(SYNC, currentInvokeModeEnum)) { + return feignClient.execute(request, options); + } + return asyncInvoke(request, options); + } + + private boolean useMetaFeign(Request request) { + return starterProperties.getMetaFeign() && request.requestTemplate().feignTarget().type().getName().contains("cn.axzo.workflow.client.feign"); + } + + /** + * 发送 RPC 调用动作的 MQ 事件 + * + * @param request + * @param options + */ + 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()); + + Class[] parameterTypes = metadata.method().getParameterTypes(); + event.setParameterTypesMd5(MD5.encrypt(StringUtils.collectionToCommaDelimitedString(Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.toList())))); + + 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 + private void buildArgs(Request request, MethodMetadata metadata, List args) { + Parameter[] parameters = metadata.method().getParameters(); + Annotation[][] parameterAnnotations = metadata.method().getParameterAnnotations(); + Map> queries = request.requestTemplate().queries(); + byte[] body = request.requestTemplate().body(); + + // 提前解析出 PathVariable 变量,避免后续循环时再解析 + Map pathVariableMap = parsePathVariables(getRequestMappingsValue(metadata.method()), getUrlRemoveDomain(request)); + Queue pathVariableQueue = convertMapValueToQueue(pathVariableMap); + for (int i = 0; i < parameters.length; i++) { + // 目前工作流的所有 API,只用了 RequestParam / RequestBody / PathVariable 三个注解,所以这里只处理这三种 + Parameter parameter = parameters[i]; + Annotation[] annotations = parameterAnnotations[i]; + for (Annotation annotation : annotations) { + if (annotation instanceof RequestParam) { + // RequestParam 支持 Nullable + List values = (List) queries.getOrDefault(parameter.getName(), Collections.emptyList()); + args.add(JSON.toJSONString(values)); + } else if (annotation instanceof PathVariable) { + args.add(pathVariableQueue.poll()); + } else if (annotation instanceof RequestBody) { + args.add(new String(body, UTF_8)); + } + } + } + } + + private String getRequestMappingsValue(Method method) { + Annotation[] annotations = method.getAnnotations(); + for (Annotation annotation : annotations) { + if (annotation instanceof GetMapping) { + GetMapping getMapping = (GetMapping) annotation; + return getMapping.value()[0]; + } else if (annotation instanceof PostMapping) { + PostMapping postMapping = (PostMapping) annotation; + return postMapping.value()[0]; + } else if (annotation instanceof PutMapping) { + PutMapping putMapping = (PutMapping) annotation; + return putMapping.value()[0]; + } else if (annotation instanceof DeleteMapping) { + DeleteMapping deleteMapping = (DeleteMapping) annotation; + return deleteMapping.value()[0]; + } + } + if (log.isDebugEnabled()) { + log.debug("{} not found request mappings", method.getName()); + } + return ""; + } + + private String getUrlRemoveDomain(Request request) { + return request.url().replace(request.requestTemplate().feignTarget().url(), ""); + } + + private Queue convertMapValueToQueue(Map map) { + Queue queue = new LinkedList<>(); + if (CollectionUtils.isEmpty(map)) { + return queue; + } + map.forEach((k, v) -> queue.add(v)); + return queue; + } + + private Map parsePathVariables(String template, String actualPath) { + Map variables = new HashMap<>(); + + // 创建一个正则表达式,用于匹配模板中的占位符 + String regex = template.replaceAll("\\{([^}]+)\\}", "([^/]+)"); + regex = regex.replaceAll("/+", "/"); // 去除可能由于连续占位符产生的多余斜杠 + regex = "^" + regex + "$"; // 锚定整个字符串的匹配 + + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(actualPath); + + if (matcher.find()) { + // 提取占位符名称和对应的值 + Pattern variablePattern = Pattern.compile("\\{([^}]+)\\}"); + Matcher variableMatcher = variablePattern.matcher(template); + int index = 1; // 匹配值的索引从1开始(0是整个匹配的字符串) + while (variableMatcher.find()) { + variables.put(variableMatcher.group(1), matcher.group(index++)); + } + } + + return variables; + } + + private RpcInvokeModeEnum getInvokeMode(Request request) { + Collection invokeModel = request.headers().getOrDefault(STARTER_INVOKE_MODE, + Collections.singletonList(starterProperties.getInvokeMode().name())); + if (CollectionUtils.isEmpty(invokeModel)) { + return starterProperties.getInvokeMode(); + } else if (invokeModel.size() > 1) { + throw new WorkflowEngineStarterException("Multiple invoke mode is not supported"); + } + return RpcInvokeModeEnum.valueOf(invokeModel.iterator().next()); + } + + static Response.Body body = new Response.Body() { + //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() { + return null; + } + + @Override + public boolean isRepeatable() { + return false; + } + + @Override + public InputStream asInputStream() throws IOException { + return inputStream; + } + + @Override + public Reader asReader(Charset charset) throws IOException { + return new InputStreamReader(asInputStream(), charset); + } + + @Override + public void close() throws IOException { + inputStream.close(); + } + }; +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterDecoder.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterDecoder.java new file mode 100644 index 000000000..b548f7257 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterDecoder.java @@ -0,0 +1,100 @@ +package cn.axzo.workflow.starter.feign.ext; + +import cn.axzo.framework.jackson.utility.JSON; +import cn.axzo.workflow.common.model.response.BpmPageResult; +import cn.axzo.workflow.starter.common.exception.WorkflowRpcInvokeException; +import cn.azxo.framework.common.model.CommonResponse; +import com.google.common.collect.Lists; +import feign.Response; +import feign.Util; +import feign.codec.Decoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_W_E; + +/** + * Workflow Engine Starter Complex Invoke Client Decoder + * + * @author wangli + * @since 2024/5/31 14:55 + */ +final class WorkflowEngineStarterDecoder implements Decoder { + private final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterDecoder.class); + final Decoder delegate; + + public WorkflowEngineStarterDecoder(Decoder delegate) { + Objects.requireNonNull(delegate, "Decoder must not be null. "); + this.delegate = delegate; + } + + @Override + public Object decode(Response response, Type type) throws IOException { + if (!isOptional(type)) { + return convertWrapper(response, type); + } + if (response.status() == 404 || response.status() == 204) { + return Optional.empty(); + } + Type enclosedType = Util.resolveLastTypeParameter(type, Optional.class); + return Optional.ofNullable(convertWrapper(response, enclosedType)); + } + + static boolean isOptional(Type type) { + if (!(type instanceof ParameterizedType)) { + return false; + } + ParameterizedType parameterizedType = (ParameterizedType) type; + return parameterizedType.getRawType().equals(Optional.class); + } + + Object convertWrapper(Response response, Type enclosedType) throws IOException { + if (response.request().headers().containsKey(HEADER_W_E)) { + return delegate.decode(response, enclosedType); + } else { + return convert(response, enclosedType); + } + } + + /** + * 这里做返回数据的解析,并处理引擎返回的一些正常的业务异常 + * + * @param response + * @param type + * @return + */ + Object convert(Response response, Type type) throws IOException { + ParameterizedTypeImpl wrappedType; + List> cls = Lists.newArrayList(BpmPageResult.class, Collection.class, List.class, Map.class); + if (type instanceof ParameterizedType && !cls.contains(((ParameterizedType) type).getRawType())) { + wrappedType = (ParameterizedTypeImpl) type; + } else { + wrappedType = ParameterizedTypeImpl.make(CommonResponse.class, new Type[]{type}, null); + } + Object decode = delegate.decode(response, wrappedType); + if (log.isDebugEnabled()) { + log.debug("workflow engine starter response :{}", JSON.toJSONString(decode)); + } + if (decode instanceof CommonResponse) { + CommonResponse commonResponse = (CommonResponse) decode; + if (response.status() == 202) { + log.warn("workflow engine starter rpc invoke return msg: {}", commonResponse.getMsg()); + throw new WorkflowRpcInvokeException(commonResponse.getMsg()); + } else if (response.status() != 200) { + log.error("WorkflowEngineStarterDecoder has error, status:{} msg: {}", response.status(), commonResponse.getMsg()); + } + return commonResponse.getData(); + } + return decode; + } +} 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 new file mode 100644 index 000000000..794b71bac --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterFeignConfiguration.java @@ -0,0 +1,135 @@ +package cn.axzo.workflow.starter.feign.ext; + +import cn.axzo.workflow.common.enums.RpcInvokeModeEnum; +import cn.axzo.workflow.common.util.ThreadUtil; +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import cn.azxo.framework.common.constatns.Constants; +import feign.Client; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import feign.Retryer; +import feign.Target; +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; +import org.springframework.cloud.openfeign.FeignBuilderCustomizer; +import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; +import org.springframework.cloud.openfeign.support.SpringDecoder; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.util.CollectionUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_API_VERSION; +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_HTTP_CLIENT; +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_HTTP_CLIENT_VALUE; +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME; +import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_W_E; +import static cn.axzo.workflow.common.constant.StarterConstants.ENABLE_MANAGEABLE; +import static cn.axzo.workflow.common.constant.StarterConstants.STARTER_INVOKE_MODE; +import static java.util.concurrent.TimeUnit.SECONDS; + +/** + * Starter Feign Client 的自定义的配置 + * + * @author wangli + * @since 2024/5/28 23:17 + */ +public class WorkflowEngineStarterFeignConfiguration { + private final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterFeignConfiguration.class); + + @Bean + public Retryer defaultRetryer() { + return new Retryer.Default(100, SECONDS.toMillis(1), 3); + } + + @Bean + public Client complexInvokeClient(WorkflowEngineStarterProperties starterProperties, + BeanFactory beanFactory, + Client feignClient) { + return new ComplexInvokeClient(starterProperties, beanFactory, feignClient); + } + + @Bean + public Decoder workflowEngineStarterDecoder(ObjectFactory messageConverters) { + return new WorkflowEngineStarterDecoder(new ResponseEntityDecoder(new SpringDecoder(messageConverters))); + } + + @Bean + public RequestInterceptor workflowEngineStarterRequestInterceptor(WorkflowEngineStarterProperties starterProperties, + Environment environment, + @Qualifier("serviceVersion") String serviceVersion) { + return template -> { + // 接入应用上报 + template.header(HEADER_SERVER_NAME, environment.getProperty("spring.application.name")); + resetInvokeMode(starterProperties, template); + // 通过服务端的校验 + Target.HardCodedTarget target = (Target.HardCodedTarget) template.feignTarget(); + String apiClassPath = target.type().getName(); + if (apiClassPath.contains("cn.axzo.workflow.client")) { + template.header(HEADER_W_E, "true"); + } + if (apiClassPath.contains("cn.axzo.workflow.starter.api")) { + template.header(HEADER_HTTP_CLIENT, HEADER_HTTP_CLIENT_VALUE); + template.header(HEADER_API_VERSION, serviceVersion); + } + //链路追踪 + template.header(Constants.CTX_LOG_ID_MDC, MDC.get(Constants.CTX_LOG_ID_MDC)); + template.header(ENABLE_MANAGEABLE, starterProperties.getManageable().toString()); + HttpServletRequest originalRequest = getOriginalRequest(); + if (Objects.nonNull(originalRequest)) { + setRequestParams(template, originalRequest); + } + }; + } + + private void setRequestParams(RequestTemplate template, HttpServletRequest originalRequest) { + String MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME = "pkg"; + String[] packageNames = originalRequest.getParameterValues(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME); + template.query(MICRO_SERVER_RECORD_ERROR_GET_PARAM_NAME, packageNames); + } + + public HttpServletRequest getOriginalRequest() { + try { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return attributes.getRequest(); + } catch (Exception e) { + log.debug("not HttpServletRequest instance bean found"); + return null; + } + } + + private void resetInvokeMode(WorkflowEngineStarterProperties starterProperties, RequestTemplate template) { + Collection invokeModeInHeader = template.headers().getOrDefault(STARTER_INVOKE_MODE, Collections.emptyList()); + template.removeHeader(STARTER_INVOKE_MODE); + // 接口的同步异步模式 + RpcInvokeModeEnum rpcInvokeModeEnum = ThreadUtil.get(); + ThreadUtil.clear(); + if (Objects.isNull(rpcInvokeModeEnum)) { + if (CollectionUtils.isEmpty(invokeModeInHeader)) { + log.debug("Without calling the async or sync methods of the WorkflowCoreService, the default configuration in WorkflowEngineStarterProperties will be loaded."); + rpcInvokeModeEnum = starterProperties.getInvokeMode(); + } else { + rpcInvokeModeEnum = RpcInvokeModeEnum.valueOf(invokeModeInHeader.iterator().next()); + } + } + template.header(STARTER_INVOKE_MODE, rpcInvokeModeEnum.name()); + } + + + @Bean + public FeignBuilderCustomizer workflowEngineFeignBuilderCustomizer() { + return builder -> builder.invocationHandlerFactory(new WorkflowEngineStarterInvocationHandlerFactory()); + } +} 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 new file mode 100644 index 000000000..bcc0d8e43 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterInvocationHandler.java @@ -0,0 +1,102 @@ +package cn.axzo.workflow.starter.feign.ext; + +import cn.axzo.workflow.common.annotation.InvokeMode; +import cn.axzo.workflow.common.util.ThreadUtil; +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Lists; +import feign.InvocationHandlerFactory; +import feign.Target; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.StopWatch; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Workflow Engine Starter Core Service Invocation Handler + * + * @author wangli + * @since 2024/5/29 22:58 + */ +class WorkflowEngineStarterInvocationHandler implements InvocationHandler { + + private static final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterInvocationHandler.class); + private final Target target; + private final Map dispatch; + + public WorkflowEngineStarterInvocationHandler(Target target, Map dispatch) { + this.target = target; + this.dispatch = dispatch; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("equals".equals(method.getName())) { + try { + Object otherHandler = + args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; + return equals(otherHandler); + } catch (IllegalArgumentException e) { + return false; + } + } else if ("hashCode".equals(method.getName())) { + return hashCode(); + } else if ("toString".equals(method.getName())) { + return toString(); + } + + parseInvokeModelAnnotation(method); + StopWatch stopWatch = new StopWatch(); + stopWatch.start(method.getName()); + Object invoke = null; + try { + invoke = dispatch.get(method).invoke(args); + } finally { + stopWatch.stop(); + List ignoreMethods = Lists.newArrayList("sync", "async"); + if (!ignoreMethods.contains(method.getName())) { + if (log.isDebugEnabled()) { + log.debug("Workflow starter Method invoke record: {}.{}, args: {}, result: {} const: {} ms", + target.type().getSimpleName(), method.getName(), JSON.toJSONString(args), JSON.toJSONString(invoke), stopWatch.getTotalTimeMillis()); + } + } + } + return invoke; + } + + private void parseInvokeModelAnnotation(Method method) { + InvokeMode annotation = AnnotationUtils.getAnnotation(method, InvokeMode.class); + if (Objects.nonNull(annotation) && Objects.isNull(ThreadUtil.get())) { + ThreadUtil.set(annotation.value()); + } + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WorkflowEngineStarterInvocationHandler) { + WorkflowEngineStarterInvocationHandler other = (WorkflowEngineStarterInvocationHandler) obj; + return target.equals(other.target); + } + return false; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public String toString() { + return target.toString(); + } + + public Class getTargetClass() { + return target.getClass(); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterInvocationHandlerFactory.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterInvocationHandlerFactory.java new file mode 100644 index 000000000..26a133e30 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/WorkflowEngineStarterInvocationHandlerFactory.java @@ -0,0 +1,21 @@ +package cn.axzo.workflow.starter.feign.ext; + +import feign.InvocationHandlerFactory; +import feign.Target; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * 用于生成 InvocationHandler + * + * @author wangli + * @since 2024/5/29 22:56 + */ +public class WorkflowEngineStarterInvocationHandlerFactory implements InvocationHandlerFactory { + @Override + public InvocationHandler create(Target target, Map dispatch) { + return new WorkflowEngineStarterInvocationHandler(target, dispatch); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/AbstractProcessInstanceEventHandler.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/AbstractProcessInstanceEventHandler.java new file mode 100644 index 000000000..8b5120e7a --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/AbstractProcessInstanceEventHandler.java @@ -0,0 +1,38 @@ +package cn.axzo.workflow.starter.handler; + +import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO; + +/** + * 通过抽象类,将实例的多个结束的方法合并 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/5/27 16:20 + */ +public abstract class AbstractProcessInstanceEventHandler implements ProcessInstanceEventHandler { + + @Override + public void onCompleted(ProcessInstanceDTO dto) { + onFinished(dto, BpmnProcessInstanceResultEnum.APPROVED); + } + + @Override + public void onCancelled(ProcessInstanceDTO dto) { + onFinished(dto, BpmnProcessInstanceResultEnum.CANCELLED); + } + + @Override + public void onRejected(ProcessInstanceDTO dto) { + onFinished(dto, BpmnProcessInstanceResultEnum.REJECTED); + } + + @Override + public void onAborted(ProcessInstanceDTO dto) { + onFinished(dto, BpmnProcessInstanceResultEnum.ABORTED); + } + + public abstract void onFinished(ProcessInstanceDTO dto, BpmnProcessInstanceResultEnum resultEnum); + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/MessageNotificationEventHandler.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/MessageNotificationEventHandler.java new file mode 100644 index 000000000..7899fa51a --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/MessageNotificationEventHandler.java @@ -0,0 +1,92 @@ +package cn.axzo.workflow.starter.handler; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.common.model.response.mq.MessagePushDTO; +import org.springframework.core.Ordered; + +/** + * 引擎广播发送消息的事件, 业务一般无需实现,该事件由引擎的中继服务去处理待办相关动作的。 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/5/27 16:25 + */ +public interface MessageNotificationEventHandler extends Ordered { + + /** + * 针对当前接口的实现进行过滤, 为 true 时,往下执行下面的方法 + * + * @param dto + * @param event + * @param context + * @return + */ + default boolean accept(MessagePushDTO dto, Event event, EventConsumer.Context context) { + return true; + } + + /** + * 站内信推送 + * + * @param dto + */ + default void pushNotice(MessagePushDTO dto) { + } + + /** + * 待办推送 + * + * @param dto + */ + default void pushPending(MessagePushDTO dto) { + } + + /** + * 完成待办 + * + * @param dto + */ + default void completePending(MessagePushDTO dto) { + } + + /** + * 审批失败,恢复待办 + * + * @param dto + */ + default void rollbackPending(MessagePushDTO dto) { + } + + /** + * 抄送流程 + * + * @param dto + */ + default void carbonCopy(MessagePushDTO dto) { + } + + /** + * 完成抄送 + * + * @param dto + */ + default void carbonCopyComplete(MessagePushDTO dto) { + } + + /** + * 短信推送 + * + * @param dto + */ + default void pushSms(MessagePushDTO dto) { + } + + /** + * IM 推送 + * @param dto + */ + default void pushIm(MessagePushDTO dto) {} + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessActivityEventHandler.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessActivityEventHandler.java new file mode 100644 index 000000000..6fa3188f2 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessActivityEventHandler.java @@ -0,0 +1,74 @@ +package cn.axzo.workflow.starter.handler; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.common.model.response.mq.ProcessActivityDTO; +import org.springframework.core.Ordered; + +/** + * 流程节点相关事件 + *

+ * 节点代表“流程配置”中的一个“审批节点”或“业务节点”,流程配置请按照以下路径去查看 + *

+ *  OMS -> 审批流程 -> 审批配置台 -> 流程配置
+ * 
+ *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/5/27 16:25 + */ +public interface ProcessActivityEventHandler extends Ordered { + + /** + * 针对当前接口的实现进行过滤, 为 true 时,往下执行下面的方法 + * + * @param dto + * @param event + * @param context + * @return + */ + default boolean accept(ProcessActivityDTO dto, Event event, EventConsumer.Context context) { + return true; + } + + /** + * 节点已启动 + * + * @param dto 入参 + */ + default void onStart(ProcessActivityDTO dto) { + } + + /** + * 节点等待业务指定审批人 + * + * @param dto 入参 + */ + default void onWaitAssignee(ProcessActivityDTO dto) { + } + + /** + * 业务节点回调 + * + * @param dto + */ + default void onCallback(ProcessActivityDTO dto) { + } + + /** + * 节点已完成 + * + * @param dto 入参 + */ + default void onTake(ProcessActivityDTO dto) { + } + + /** + * 节点已取消 + * + * @param dto 入参 + */ + default void onEnd(ProcessActivityDTO dto) { + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessInstanceEventHandler.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessInstanceEventHandler.java new file mode 100644 index 000000000..6660dcacc --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessInstanceEventHandler.java @@ -0,0 +1,87 @@ +package cn.axzo.workflow.starter.handler; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; +import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO; +import org.springframework.core.Ordered; + +/** + * 流程实例相关事件 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/5/27 16:20 + */ +public interface ProcessInstanceEventHandler extends Ordered { + + /** + * 针对当前接口的实现进行过滤, 为 true 时,往下执行下面的方法 + * + * @param dto + * @param event + * @param context + * @return + */ + default boolean accept(ProcessInstanceDTO dto, Event event, EventConsumer.Context context) { + return true; + } + + /** + * 流程实例创建成功后回调 + * + * @param dto + */ + default void onCreated(ProcessInstanceDTO dto) { + } + + /** + * 流程实例开始运行后回调 + * + * @param dto + */ + default void onStarted(ProcessInstanceDTO dto) { + } + + /** + * 流程实例运行完成(通过)后回调 + *

+ * 注意: 该接口表明流程已经走完正向逻辑,正向逻辑比如:通过、同意等 + * + * @param dto + */ + default void onCompleted(ProcessInstanceDTO dto) { + } + + /** + * 流程实例被“撤回”后回调 + *

+ * 撤回只有发起人能触发 + * + * @param dto + */ + default void onCancelled(ProcessInstanceDTO dto) { + } + + /** + * 流程实例被“驳回”后回调 + *

+ * 审批过程中,有一个审批人或者有节点配置的是“自动驳回”,都能触发该事件。 + * + * @param dto + */ + default void onRejected(ProcessInstanceDTO dto) { + } + + /** + * 流程实例被中止后回调 + *

+ * 一般由接入方主动触发,比如调用了 {@link ProcessInstanceApi#abortProcessInstance(BpmnProcessInstanceAbortDTO)} 方法等 + * + * @param dto + */ + default void onAborted(ProcessInstanceDTO dto) { + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessTaskEventHandler.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessTaskEventHandler.java new file mode 100644 index 000000000..aa2db9e57 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessTaskEventHandler.java @@ -0,0 +1,65 @@ +package cn.axzo.workflow.starter.handler; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.common.model.response.mq.ProcessTaskDTO; +import org.springframework.core.Ordered; + +/** + * 审批任务相关事件 + *

+ * 一个节点(Activity)可以包含 0个或多个任务(Task),它们两者是包含关系 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/5/27 16:21 + */ +public interface ProcessTaskEventHandler extends Ordered { + + /** + * 针对当前接口的实现进行过滤, 为 true 时,往下执行下面的方法 + * + * @param dto + * @param event + * @param context + * @return + */ + default boolean accept(ProcessTaskDTO dto, Event event, EventConsumer.Context context) { + return true; + } + + /** + * 用户任务已指派审核人 + */ + default void onAssigned(ProcessTaskDTO dto) { + } + + /** + * 用户任务已创建,未指派审核人 + */ + default void onCreated(ProcessTaskDTO dto) { + } + + /** + * 用户任务已通过 + *

+ * 仅审核通过一个用户任务时触发, 如果任务是驳回了, 则直接走实例撤回事件 + */ + default void onCompleted(ProcessTaskDTO dto) { + } + + /** + * 用户任务已删除 + *

+ * 删除不代表驳回或拒绝,因为通过也会走该事件 + */ + default void onDeleted(ProcessTaskDTO dto) { + } + + /** + * 用户任务已转交 + * @param dto + */ + default void onTransfer(ProcessTaskDTO dto) {} +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/ListenerExecutor.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/ListenerExecutor.java new file mode 100644 index 000000000..cc504cbad --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/ListenerExecutor.java @@ -0,0 +1,31 @@ +package cn.axzo.workflow.starter.handler.execute; + +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.starter.common.exception.WorkflowEngineStarterException; +import cn.axzo.workflow.starter.handler.execute.interceptor.ExecuteInterceptor; + +import java.util.List; +import java.util.function.Consumer; + +public final class ListenerExecutor { + + private final ExecuteInterceptor firstExecuteInterceptor; + + public ListenerExecutor(List chain) { + this.firstExecuteInterceptor = initInterceptorChain(chain); + } + + public void execute(Consumer command, EventConsumer.Context context, T t) { + firstExecuteInterceptor.execute(this, command, context, t); + } + + private ExecuteInterceptor initInterceptorChain(List chain) { + if (chain == null || chain.isEmpty()) { + throw new WorkflowEngineStarterException("invalid command interceptor chain configuration: " + chain); + } + for (int i = 0; i < chain.size() - 1; i++) { + chain.get(i).setNext(chain.get(i + 1)); + } + return chain.get(0); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/AbstractListenerInterceptor.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/AbstractListenerInterceptor.java new file mode 100644 index 000000000..8fbec537c --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/AbstractListenerInterceptor.java @@ -0,0 +1,16 @@ +package cn.axzo.workflow.starter.handler.execute.interceptor; + +public abstract class AbstractListenerInterceptor implements ExecuteInterceptor { + + protected ExecuteInterceptor next; + + @Override + public ExecuteInterceptor getNext() { + return next; + } + + @Override + public void setNext(ExecuteInterceptor next) { + this.next = next; + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/ExecuteInterceptor.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/ExecuteInterceptor.java new file mode 100644 index 000000000..c7767f514 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/ExecuteInterceptor.java @@ -0,0 +1,15 @@ +package cn.axzo.workflow.starter.handler.execute.interceptor; + +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; + +import java.util.function.Consumer; + +public interface ExecuteInterceptor { + + void execute(ListenerExecutor executor, Consumer consumer, EventConsumer.Context context, T t); + + ExecuteInterceptor getNext(); + + void setNext(ExecuteInterceptor next); +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/ExecutorInvoker.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/ExecutorInvoker.java new file mode 100644 index 000000000..0cbb6c611 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/ExecutorInvoker.java @@ -0,0 +1,34 @@ +package cn.axzo.workflow.starter.handler.execute.interceptor; + +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; +import java.util.function.Consumer; + +public final class ExecutorInvoker extends AbstractListenerInterceptor { + + private static final Logger log = LoggerFactory.getLogger(ExecutorInvoker.class); + + @Override + public void execute(ListenerExecutor executor, Consumer consumer, EventConsumer.Context context, T t) { + if (Objects.isNull(consumer)) { + log.warn("Unsupported message eventCode type, Please check message producer"); + return; + } + consumer.accept(t); + } + + @Override + public ExecuteInterceptor getNext() { + return null; + } + + @Override + public void setNext(ExecuteInterceptor next) { + throw new UnsupportedOperationException("ExecutorInvoker must be the last interceptor in the chain"); + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailBackInterceptor.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailBackInterceptor.java new file mode 100644 index 000000000..cc3cebea7 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailBackInterceptor.java @@ -0,0 +1,21 @@ +package cn.axzo.workflow.starter.handler.execute.interceptor; + +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; + +import java.util.function.Consumer; + +/** + * 失败自动恢复,在后台记录失败的消息,并按照一定的策略后期再进行重试,目前暂不支持 + */ +public final class FailBackInterceptor extends AbstractListenerInterceptor { + @Override + public void execute(ListenerExecutor executor, Consumer consumer, EventConsumer.Context context, T t) { + throw new UnsupportedOperationException("FailBackInterceptor"); + } + + @Override + public void setNext(ExecuteInterceptor next) { + throw new UnsupportedOperationException("FailBackInterceptor"); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailFastInterceptor.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailFastInterceptor.java new file mode 100644 index 000000000..77b46c5a0 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailFastInterceptor.java @@ -0,0 +1,24 @@ +package cn.axzo.workflow.starter.handler.execute.interceptor; + +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.starter.common.exception.WorkflowListenerExecutionException; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; + +import java.util.function.Consumer; + +/** + * 快速失败,出错直接抛出异常,listener不再往下执行 + */ +public final class FailFastInterceptor extends AbstractListenerInterceptor { + + @Override + public void execute(ListenerExecutor executor, Consumer consumer, EventConsumer.Context context, T t) { + try { + getNext().execute(executor, consumer, context, t); + } catch (Throwable e) { + throw new WorkflowListenerExecutionException( + "Failed to invoke the method. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e); + } + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailOverInterceptor.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailOverInterceptor.java new file mode 100644 index 000000000..a019b6e56 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/FailOverInterceptor.java @@ -0,0 +1,74 @@ +package cn.axzo.workflow.starter.handler.execute.interceptor; + +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.starter.BroadcastListenerProperties; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.Consumer; + +/** + * 当前listener执行出错,忽略继续往下执行,可配置重试相关参数,不抛出异常 + */ +public final class FailOverInterceptor extends AbstractListenerInterceptor { + private static final Logger log = LoggerFactory.getLogger(FailOverInterceptor.class); + + private int numOfRetries = 3; + private int waitTimeInMs = 1500; + private int waitIncreaseFactor = 3; + + public FailOverInterceptor() { + } + + public FailOverInterceptor(BroadcastListenerProperties broadcast) { + this.numOfRetries = 0 == Math.abs(broadcast.getNumOfRetries()) ? this.numOfRetries : Math.abs(broadcast.getNumOfRetries()); + this.waitTimeInMs = Math.abs(broadcast.getWaitTimeInMs()); + this.waitIncreaseFactor = Math.abs(broadcast.getWaitIncreaseFactor()); + } + + @Override + public void execute(ListenerExecutor executor, Consumer consumer, EventConsumer.Context context, T t) { + // 避免组件抛出“程序处理超时”的异常信息, 为业务冗余 10 处理时间 + context.setMaxAllowElapsedMillis(calc() + 10_000L); + long waitTime = waitTimeInMs; + int failedAttempts = 0; + do { + if (failedAttempts > 0) { + if (log.isDebugEnabled()) { + log.debug("Waiting for {} ms before retrying the command. retryTimes: {}", waitTime, failedAttempts); + } + waitBeforeRetry(waitTime); + waitTime *= waitIncreaseFactor; + } + try { + getNext().execute(executor, consumer, context, t); + return; + } catch (Throwable e) { + if (failedAttempts == numOfRetries) { + log.error("Workflow Engine Starter caught exception: {}", e.getMessage(), e); + throw e; + } + } + failedAttempts++; + } while (failedAttempts <= numOfRetries); + } + + private Long calc() { + long waitTime = waitTimeInMs; + for (int failedAttempts = 0; failedAttempts < numOfRetries; failedAttempts++) { + // 更新等待时间 + waitTime *= waitIncreaseFactor; + } + // 返回最后一次等待时间(注意:这可能不是最终你想要的逻辑,取决于你的具体需求) + return waitTime; + } + + private void waitBeforeRetry(long waitTime) { + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + log.debug(" interrupted while waiting for a retry."); + } + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/LogInterceptor.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/LogInterceptor.java new file mode 100644 index 000000000..7eb43810c --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/execute/interceptor/LogInterceptor.java @@ -0,0 +1,44 @@ +package cn.axzo.workflow.starter.handler.execute.interceptor; + +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.starter.common.exception.WorkflowEngineStarterException; +import cn.axzo.workflow.starter.common.exception.WorkflowListenerExecutionException; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import com.alibaba.fastjson.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StopWatch; + +import java.util.function.Consumer; + +public final class LogInterceptor extends AbstractListenerInterceptor { + private static final Logger log = LoggerFactory.getLogger(LogInterceptor.class); + + @Override + public void execute(ListenerExecutor executor, Consumer consumer, EventConsumer.Context context, T t) { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + if (log.isDebugEnabled()) { + log.debug("--- starting handle mq ---- "); + } + if (log.isDebugEnabled()) { + log.debug("messageId: {}, eventCode: {}, messageBody: {}", context.getMsgId(), context.getEventCode().toString(), JSON.toJSONString(t)); + } + try { + getNext().execute(executor, consumer, context, t); + } catch (WorkflowListenerExecutionException e) { + // fail_fast mode , will be ignored + } catch (Throwable e) { + if (e instanceof WorkflowEngineStarterException) { + // 如果业务想使用异常来正确结束事件处理,可以抛出该移除类型 + } else { + throw e; + } + } finally { + stopWatch.stop(); + if (log.isDebugEnabled()) { + log.debug("--- messageId: {} finished ,timeCost:{} ms ---", executor.getClass().getSimpleName(), stopWatch.getTotalTimeMillis()); + } + } + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/MessageNotificationEventFilter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/MessageNotificationEventFilter.java new file mode 100644 index 000000000..32bbe0aa0 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/MessageNotificationEventFilter.java @@ -0,0 +1,18 @@ +package cn.axzo.workflow.starter.handler.filter; + +import cn.axzo.workflow.common.model.response.mq.MessagePushDTO; +import cn.axzo.workflow.starter.handler.MessageNotificationEventHandler; +import cn.axzo.workflow.starter.mq.broadcast.filter.BasicMessageQueueFilter; + +/** + * MessageNotificationEvent 的自定义过滤接口 + *

+ * 该接口的实现,是会全局过滤,比如当应用需要监听多个流程模型时,全局过滤会不精确,所以我们还提供了针对 {@link MessageNotificationEventHandler#filter(MessagePushDTO)} 实例的专属过滤 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/6/5 15:49 + */ +public interface MessageNotificationEventFilter extends BasicMessageQueueFilter { +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessActivityEventFilter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessActivityEventFilter.java new file mode 100644 index 000000000..cd3d56233 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessActivityEventFilter.java @@ -0,0 +1,18 @@ +package cn.axzo.workflow.starter.handler.filter; + +import cn.axzo.workflow.common.model.response.mq.ProcessActivityDTO; +import cn.axzo.workflow.starter.handler.ProcessActivityEventHandler; +import cn.axzo.workflow.starter.mq.broadcast.filter.BasicMessageQueueFilter; + +/** + * ProcessActivityEvent 自定义的过滤接口 + *

+ * 该接口的实现,是会全局过滤,比如当应用需要监听多个流程模型时,全局过滤会不精确,所以我们还提供了针对 {@link ProcessActivityEventHandler#filter(ProcessActivityDTO)} 实例的专属过滤 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/6/5 15:49 + */ +public interface ProcessActivityEventFilter extends BasicMessageQueueFilter { +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessInstanceEventFilter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessInstanceEventFilter.java new file mode 100644 index 000000000..310807c08 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessInstanceEventFilter.java @@ -0,0 +1,18 @@ +package cn.axzo.workflow.starter.handler.filter; + +import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO; +import cn.axzo.workflow.starter.handler.ProcessInstanceEventHandler; +import cn.axzo.workflow.starter.mq.broadcast.filter.BasicMessageQueueFilter; + +/** + * ProcessInstanceEvent 自定义的过滤接口 + *

+ * 该接口的实现,是会全局过滤,比如当应用需要监听多个流程模型时,全局过滤会不精确,所以我们还提供了针对 {@link ProcessInstanceEventHandler#filter(ProcessInstanceDTO)} 实例的专属过滤 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/6/5 15:44 + */ +public interface ProcessInstanceEventFilter extends BasicMessageQueueFilter { +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessTaskEventFilter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessTaskEventFilter.java new file mode 100644 index 000000000..3ede54f29 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/ProcessTaskEventFilter.java @@ -0,0 +1,18 @@ +package cn.axzo.workflow.starter.handler.filter; + +import cn.axzo.workflow.common.model.response.mq.ProcessTaskDTO; +import cn.axzo.workflow.starter.handler.ProcessTaskEventHandler; +import cn.axzo.workflow.starter.mq.broadcast.filter.BasicMessageQueueFilter; + +/** + * ProcessTaskEvent 自定义的过滤接口 + *

+ * 该接口的实现,是会全局过滤,比如当应用需要监听多个流程模型时,全局过滤会不精确,所以我们还提供了针对 {@link ProcessTaskEventHandler#filter(ProcessTaskDTO)} 实例的专属过滤 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE) + * + * @author wangli + * @since 2024/6/5 15:50 + */ +public interface ProcessTaskEventFilter extends BasicMessageQueueFilter { +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/global/BroadcastMessageQueueFilter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/global/BroadcastMessageQueueFilter.java new file mode 100644 index 000000000..d17283353 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/filter/global/BroadcastMessageQueueFilter.java @@ -0,0 +1,23 @@ +package cn.axzo.workflow.starter.handler.filter.global; + +import org.apache.rocketmq.common.message.MessageExt; +import org.springframework.core.Ordered; + +/** + * 对流程引擎广播的事件进行全局过滤,业务可扩展的 MQ 消息过滤接口 + *

+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer.MIN_VALUE - Integer.MAX_VALUE) + * + * @author wangli + * @since 2024/6/4 17:04 + */ +public interface BroadcastMessageQueueFilter extends Ordered { + + /** + * @param message MQ 消息元数据 + * @param jsonData MQ 消息中的 payload,是 JSON 格式的字符串 + * @return 返回 true 时,将跳过该 {@link MessageExt} 消息,不进行消费 + */ + boolean doFilter(MessageExt message, String jsonData); + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/monitor/BroadcastDLQReporter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/monitor/BroadcastDLQReporter.java new file mode 100644 index 000000000..9305f449c --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/monitor/BroadcastDLQReporter.java @@ -0,0 +1,24 @@ +package cn.axzo.workflow.starter.handler.monitor; + +import org.apache.rocketmq.common.admin.TopicOffset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 广播死信队列监控的报告器, 默认仅仅是打印异常。 + *

+ * 可以用它来发生钉钉之类的。 + *

+ * 注意:该接口目前不是为了消费死信队列中的消息的。后续必要时,再开放死信队列的消费扩展点 + * + * @author wangli + * @since 2024/6/14 17:04 + */ +public interface BroadcastDLQReporter { + Logger log = LoggerFactory.getLogger(BroadcastDLQReporter.class); + + default void process(TopicOffset v, String dlqName) { + long dlqCount = v.getMaxOffset() - v.getMinOffset(); + log.error("found Broadcast MQ DLQ, count: {}", dlqCount); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/monitor/RpcDLQReporter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/monitor/RpcDLQReporter.java new file mode 100644 index 000000000..ceeffc63e --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/monitor/RpcDLQReporter.java @@ -0,0 +1,25 @@ +package cn.axzo.workflow.starter.handler.monitor; + +import org.apache.rocketmq.common.admin.TopicOffset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * RPC 重试的死信队列监控报告器, 默认仅仅是打印异常 + *

+ * 可以用它来发生钉钉之类的. + *

+ * 注意:该接口目前不是为了消费死信队列中的消息的。后续必要时,再开放死信队列的消费扩展点 + * + * @author wangli + * @since 2024/6/14 17:01 + */ + +public interface RpcDLQReporter { + Logger log = LoggerFactory.getLogger(RpcDLQReporter.class); + + default void process(TopicOffset v, String dlqName) { + long dlqCount = v.getMaxOffset() - v.getMinOffset(); + log.error("found rpc MQ DLQ, count: {}", dlqCount); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/AbstractInnerWorkflowListener.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/AbstractInnerWorkflowListener.java new file mode 100644 index 000000000..cb2dbeb27 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/AbstractInnerWorkflowListener.java @@ -0,0 +1,83 @@ +package cn.axzo.workflow.starter.mq.broadcast.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import cn.axzo.workflow.starter.mq.broadcast.filter.BasicMessageQueueFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.core.Ordered; +import org.springframework.util.CollectionUtils; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class AbstractInnerWorkflowListener, Target> implements InnerWorkflowListener { + + private static final Logger log = LoggerFactory.getLogger(AbstractInnerWorkflowListener.class); + + /** + * 接入方对 cn/axzo/workflow/starter/handler/ 包下的 EventHandler 实现 + */ + protected final List businessListeners; + /** + * 接入方对 cn/axzo/workflow/starter/handler/filter/ 包下的EventFilter 实现 + */ + protected final List businessFilters; + protected final ListenerExecutor listenerExecutor; + + + public AbstractInnerWorkflowListener(ListenerExecutor listenerExecutor, + ObjectProvider> handlerProvider, + ObjectProvider> filterProvider) { + this.listenerExecutor = listenerExecutor; + this.businessListeners = getOrderedBusinessListeners(handlerProvider); + this.businessFilters = getOrderedBusinessFilters(filterProvider); + } + + private List getOrderedBusinessFilters(ObjectProvider> filterProvider) { + return filterProvider.getIfAvailable(Collections::emptyList) + .stream() + .sorted(Comparator.comparingInt(Ordered::getOrder)) + .collect(Collectors.toList()); + } + + protected List getOrderedBusinessListeners(ObjectProvider> handlerProvider) { + return handlerProvider.getIfAvailable(Collections::emptyList) + .stream() + .sorted(Comparator.comparingInt(Ordered::getOrder)) + .collect(Collectors.toList()); + } + + @Override + public void handleEvent(Event event, EventConsumer.Context context) { + Target convert = convert(event); + + if (!CollectionUtils.isEmpty(businessListeners)) { + for (F filter : businessFilters) { + if (filter.doFilter(event, context, convert)) { + if (log.isDebugEnabled()) { + log.debug("【{}】filtered message, messageId: {}", filter.getClass().getSimpleName(), context.getMsgId()); + } + return; + } + } + } + + onEvent(convert, event, context); + } + + protected abstract Target convert(Event event); + + protected abstract void onEvent(Target data, Event event, EventConsumer.Context context); + + @Override + public boolean dispatch(Event event) { + return getSupportEventCodes().contains(event.getEventCode()); + } + + protected abstract List getSupportEventCodes(); +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerActivityEventListener.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerActivityEventListener.java new file mode 100644 index 000000000..02eeb58a3 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerActivityEventListener.java @@ -0,0 +1,79 @@ +package cn.axzo.workflow.starter.mq.broadcast.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.common.enums.ProcessActivityEventEnum; +import cn.axzo.workflow.common.model.response.mq.ProcessActivityDTO; +import cn.axzo.workflow.starter.handler.ProcessActivityEventHandler; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import cn.axzo.workflow.starter.handler.filter.ProcessActivityEventFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author wangli + * @since 2024/5/21 15:51 + */ +public class InnerActivityEventListener extends AbstractInnerWorkflowListener { + private final Logger log = LoggerFactory.getLogger(InnerActivityEventListener.class); + + public final static Supplier> SUPPORTED_EVENT_CODES_SUPPLIER = () -> + Arrays.stream(ProcessActivityEventEnum.values()).map(ProcessActivityEventEnum::getEventCode) + .collect(Collectors.toList()); + + public InnerActivityEventListener(ListenerExecutor executor, + ObjectProvider> provider, + ObjectProvider> filterProvider) { + super(executor, provider, filterProvider); + } + + @Override + protected ProcessActivityDTO convert(Event event) { + return event.normalizedData(ProcessActivityDTO.class); + } + + @Override + public void onEvent(ProcessActivityDTO dto, Event event, EventConsumer.Context context) { + log.debug("【{}】new message begin processing, messageId: {}", + this.getClass().getSimpleName(), context.getMsgId()); + ProcessActivityEventEnum type = dto.getType(); + for (ProcessActivityEventHandler activityListener : businessListeners) { + Consumer consumer = null; + switch (type) { + case PROCESS_ACTIVITY_START: + consumer = activityListener::onStart; + break; + case PROCESS_ACTIVITY_WAIT_ASSIGNEE: + consumer = activityListener::onWaitAssignee; + break; + case PROCESS_ACTIVITY_CALLBACK: + consumer = activityListener::onCallback; + break; + case PROCESS_ACTIVITY_TAKE: + consumer = activityListener::onTake; + break; + case PROCESS_ACTIVITY_END: + consumer = activityListener::onEnd; + break; + default: + log.warn("unknown process activity event type: {}", type); + } + if (activityListener.accept(dto, event, context)) { + listenerExecutor.execute(consumer, context, dto); + } + } + } + + @Override + protected List getSupportEventCodes() { + return SUPPORTED_EVENT_CODES_SUPPLIER.get(); + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerInstanceEventListener.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerInstanceEventListener.java new file mode 100644 index 000000000..4da4f1b0f --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerInstanceEventListener.java @@ -0,0 +1,82 @@ +package cn.axzo.workflow.starter.mq.broadcast.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.common.enums.ProcessInstanceEventEnum; +import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO; +import cn.axzo.workflow.starter.handler.ProcessInstanceEventHandler; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import cn.axzo.workflow.starter.handler.filter.ProcessInstanceEventFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author wangli + * @since 2024/5/21 15:51 + */ +public class InnerInstanceEventListener extends AbstractInnerWorkflowListener { + private final Logger log = LoggerFactory.getLogger(InnerInstanceEventListener.class); + + public final static Supplier> SUPPORTED_EVENT_CODES_SUPPLIER = () -> + Arrays.stream(ProcessInstanceEventEnum.values()).map(ProcessInstanceEventEnum::getEventCode) + .collect(Collectors.toList()); + + public InnerInstanceEventListener(ListenerExecutor listenerExecutor, + ObjectProvider> handlerProvider, + ObjectProvider> filterProvider) { + super(listenerExecutor, handlerProvider, filterProvider); + } + + @Override + protected ProcessInstanceDTO convert(Event event) { + return event.normalizedData(ProcessInstanceDTO.class); + } + + @Override + public void onEvent(ProcessInstanceDTO dto, Event event, EventConsumer.Context context) { + log.debug("【{}】new message begin processing, messageId: {}", + this.getClass().getSimpleName(), context.getMsgId()); + ProcessInstanceEventEnum type = dto.getType(); + for (ProcessInstanceEventHandler instanceListener : businessListeners) { + Consumer consumer = null; + switch (type) { + case PROCESS_INSTANCE_CREATED: + consumer = instanceListener::onCreated; + break; + case PROCESS_INSTANCE_STARTED: + consumer = instanceListener::onStarted; + break; + case PROCESS_INSTANCE_CANCELLED: + consumer = instanceListener::onCancelled; + break; + case PROCESS_INSTANCE_ABORTED: + consumer = instanceListener::onAborted; + break; + case PROCESS_INSTANCE_COMPLETED: + consumer = instanceListener::onCompleted; + break; + case PROCESS_INSTANCE_REJECTED: + consumer = instanceListener::onRejected; + break; + default: + log.warn("unknown process activity event type: {}", type); + } + if (instanceListener.accept(dto, event, context)) { + listenerExecutor.execute(consumer, context, dto); + } + } + } + + @Override + protected List getSupportEventCodes() { + return SUPPORTED_EVENT_CODES_SUPPLIER.get(); + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerNotificationEventListener.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerNotificationEventListener.java new file mode 100644 index 000000000..7fdeb435b --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerNotificationEventListener.java @@ -0,0 +1,85 @@ +package cn.axzo.workflow.starter.mq.broadcast.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum; +import cn.axzo.workflow.common.model.response.mq.MessagePushDTO; +import cn.axzo.workflow.starter.handler.MessageNotificationEventHandler; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import cn.axzo.workflow.starter.handler.filter.MessageNotificationEventFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author wangli + * @since 2024/5/21 15:53 + */ +public class InnerNotificationEventListener extends AbstractInnerWorkflowListener { + private final static Logger log = LoggerFactory.getLogger(InnerNotificationEventListener.class); + + public final static Supplier> SUPPORTED_EVENT_CODES_SUPPLIER = () -> + Arrays.stream(ProcessMessagePushEventEnum.values()).map(ProcessMessagePushEventEnum::getEventCode) + .collect(Collectors.toList()); + + public InnerNotificationEventListener(ListenerExecutor executor, ObjectProvider> provider, ObjectProvider> filterProvider) { + super(executor, provider, filterProvider); + } + + @Override + protected MessagePushDTO convert(Event event) { + return event.normalizedData(MessagePushDTO.class); + } + + @Override + public void onEvent(MessagePushDTO dto, Event event, EventConsumer.Context context) { + log.debug("【{}】new message begin processing, messageId: {}", + this.getClass().getSimpleName(), context.getMsgId()); + ProcessMessagePushEventEnum type = dto.getType(); + for (MessageNotificationEventHandler noticeListener : businessListeners) { + Consumer consumer = null; + switch (type) { + case PROCESS_PUSH_NOTICE: + consumer = noticeListener::pushNotice; + break; + case PROCESS_PUSH_PENDING: + consumer = noticeListener::pushPending; + break; + case PROCESS_PUSH_PENDING_COMPLETE: + consumer = noticeListener::completePending; + break; + case PROCESS_PUSH_PENDING_ROLLBACK: + consumer = noticeListener::rollbackPending; + break; + case PROCESS_CARBON_COPY: + consumer = noticeListener::carbonCopy; + break; + case PROCESS_CARBON_COPY_COMPLETE: + consumer = noticeListener::carbonCopyComplete; + break; + case PROCESS_PUSH_SMS: + consumer = noticeListener::pushSms; + break; + case PROCESS_PUSH_IM: + consumer = noticeListener::pushIm; + default: + log.warn("unknown message event type: {}", type); + } + if (noticeListener.accept(dto, event, context)) { + listenerExecutor.execute(consumer, context, dto); + } + } + } + + @Override + protected List getSupportEventCodes() { + return SUPPORTED_EVENT_CODES_SUPPLIER.get(); + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerTaskEventListener.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerTaskEventListener.java new file mode 100644 index 000000000..da5834898 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerTaskEventListener.java @@ -0,0 +1,77 @@ +package cn.axzo.workflow.starter.mq.broadcast.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.common.enums.ProcessTaskEventEnum; +import cn.axzo.workflow.common.model.response.mq.ProcessTaskDTO; +import cn.axzo.workflow.starter.handler.ProcessTaskEventHandler; +import cn.axzo.workflow.starter.handler.execute.ListenerExecutor; +import cn.axzo.workflow.starter.handler.filter.ProcessTaskEventFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author wangli + * @since 2024/5/21 15:51 + */ +public class InnerTaskEventListener extends AbstractInnerWorkflowListener { + private final Logger log = LoggerFactory.getLogger(InnerTaskEventListener.class); + + public final static Supplier> SUPPORTED_EVENT_CODES_SUPPLIER = () -> + Arrays.stream(ProcessTaskEventEnum.values()).map(ProcessTaskEventEnum::getEventCode) + .collect(Collectors.toList()); + + public InnerTaskEventListener(ListenerExecutor executor, ObjectProvider> provider, ObjectProvider> filterProvider) { + super(executor, provider, filterProvider); + } + + @Override + protected ProcessTaskDTO convert(Event event) { + return event.normalizedData(ProcessTaskDTO.class); + } + + @Override + public void onEvent(ProcessTaskDTO dto, Event event, EventConsumer.Context context) { + log.debug("【{}】new message begin processing, messageId: {}", + this.getClass().getSimpleName(), context.getMsgId()); + ProcessTaskEventEnum type = dto.getType(); + for (ProcessTaskEventHandler taskListener : businessListeners) { + Consumer consumer = null; + switch (type) { + case PROCESS_TASK_CREATED: + consumer = taskListener::onCreated; + break; + case PROCESS_TASK_COMPLETED: + consumer = taskListener::onCompleted; + break; + case PROCESS_TASK_ASSIGNED: + consumer = taskListener::onAssigned; + break; + case PROCESS_TASK_DELETED: + consumer = taskListener::onDeleted; + break; + case PROCESS_TASK_TRANSFER: + consumer = taskListener::onTransfer; + break; + default: + log.warn("unknown task event type: {}", type); + } + if (taskListener.accept(dto, event, context)) { + listenerExecutor.execute(consumer, context, dto); + } + } + } + + @Override + protected List getSupportEventCodes() { + return SUPPORTED_EVENT_CODES_SUPPLIER.get(); + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerWorkflowListener.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerWorkflowListener.java new file mode 100644 index 000000000..03940f45d --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/InnerWorkflowListener.java @@ -0,0 +1,12 @@ +package cn.axzo.workflow.starter.mq.broadcast.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; + +public interface InnerWorkflowListener { + + void handleEvent(Event event, EventConsumer.Context context); + + boolean dispatch(Event event); +} + diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/WorkflowEngineBroadcastEventListener.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/WorkflowEngineBroadcastEventListener.java new file mode 100644 index 000000000..d3c09b78b --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/consumer/WorkflowEngineBroadcastEventListener.java @@ -0,0 +1,69 @@ +package cn.axzo.workflow.starter.mq.broadcast.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.framework.rocketmq.EventHandler; +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import com.alibaba.fastjson.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Objects; + +/** + * 流程引擎服务广播的事件监听器 + * + * @author wangli + * @since 2024/5/21 15:50 + */ +public class WorkflowEngineBroadcastEventListener implements EventHandler, InitializingBean { + private final Logger log = LoggerFactory.getLogger(WorkflowEngineBroadcastEventListener.class); + private final EventConsumer eventConsumer; + private final WorkflowEngineStarterProperties starterProperties; + + private final List workflowListeners; + + public WorkflowEngineBroadcastEventListener(EventConsumer eventConsumer, WorkflowEngineStarterProperties workflowEngineStarterProperties, List workflowListeners) { + this.eventConsumer = eventConsumer; + this.starterProperties = workflowEngineStarterProperties; + this.workflowListeners = workflowListeners; + } + + @Override + public void onEvent(Event event, EventConsumer.Context context) { + if (Objects.isNull(event) || Objects.isNull(event.getEventCode())) { + log.warn("illegal event code: {}", JSON.toJSONString(event)); + return; + } + + try { + JSON.parse(JSON.toJSONString(event.getData())); + } catch (Exception e) { + log.warn("event data json format error, will be ignored, messageId: {}", context.getMsgId()); + return; + } + + if (CollectionUtils.isEmpty(workflowListeners)) { + log.warn("no business listeners implementation found, please check @Component annotation"); + return; + } + + for (InnerWorkflowListener workflowListener : workflowListeners) { + if (workflowListener.dispatch(event)) { + workflowListener.handleEvent(event, context); + } + } + } + + @Override + public void afterPropertiesSet() { + eventConsumer.registerHandlers(InnerActivityEventListener.SUPPORTED_EVENT_CODES_SUPPLIER.get(), this); + eventConsumer.registerHandlers(InnerInstanceEventListener.SUPPORTED_EVENT_CODES_SUPPLIER.get(), this); + eventConsumer.registerHandlers(InnerNotificationEventListener.SUPPORTED_EVENT_CODES_SUPPLIER.get(), this); + eventConsumer.registerHandlers(InnerTaskEventListener.SUPPORTED_EVENT_CODES_SUPPLIER.get(), this); + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/BasicMessageQueueFilter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/BasicMessageQueueFilter.java new file mode 100644 index 000000000..aa3a31af6 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/BasicMessageQueueFilter.java @@ -0,0 +1,31 @@ +package cn.axzo.workflow.starter.mq.broadcast.filter; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.workflow.common.constant.BpmnConstants; +import org.springframework.core.Ordered; + +/** + * 一个基于某个具体的 MQ 消息的过滤接口 + * + * @author wangli + * @since 2024/6/5 16:08 + */ +public interface BasicMessageQueueFilter extends Ordered { + + /** + * 基于具体的 {@link Event.EventCode#module} 下的自定义的 MQ 过滤器 + * + * @param event 事件原对象 + * @param context event 上下文对象,包含 msgId,headers 等信息,其中
+ * 有 MQ 消息归属哪个应用,使用 {@link BpmnConstants#MQ_OWNERSHIP_APPLICATION} 获取值;
+ * 还有 MQ 消息归属哪个业务 ID,使用 {@link BpmnConstants#MQ_OWNERSHIP_PROCESS_DEFINITION_KEY} 获取值. + *

获取归属应用示例: + *

String owner_application = new String(context.getHeaders().getOrDefault(MQ_OWNERSHIP_APPLICATION, new byte[0]), StandardCharsets.UTF_8);
+ * @param data payload 被转换后的目标对象 + * @return 返回 true 时,将跳过该 {@link Event} 消息,不进行消费 + */ + default boolean doFilter(Event event, EventConsumer.Context context, T data) { + return false; + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterDefinitionKey.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterDefinitionKey.java new file mode 100644 index 000000000..a08e15f6b --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterDefinitionKey.java @@ -0,0 +1,45 @@ +package cn.axzo.workflow.starter.mq.broadcast.filter; + +import cn.axzo.workflow.starter.BroadcastListenerProperties; +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import org.apache.rocketmq.common.message.MessageExt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +import java.util.Map; + +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_PROCESS_DEFINITION_KEY; + +/** + * 对 {@link BroadcastListenerProperties#filterProcessDefinitionKeys} 的实现 + * + * @author wangli + * @since 2024/6/4 17:06 + */ +public class InnerFilterDefinitionKey implements InnerMessageQueueHandleBeforeFilter { + private final Logger log = LoggerFactory.getLogger(InnerFilterDefinitionKey.class); + private final WorkflowEngineStarterProperties properties; + + public InnerFilterDefinitionKey(WorkflowEngineStarterProperties properties) { + this.properties = properties; + } + + @Override + public boolean doFilter(MessageExt message) { + if (!properties.getBroadcast().getEnableFilterDefinitionKey()) { + return false; + } + if (CollectionUtils.isEmpty(properties.getBroadcast().getFilterProcessDefinitionKeys())) { + log.warn("【{}】Your system has the filter process-definition-key enabled, but `BroadcastListenerConfigurationProperties.filterProcessDefinitionKeys` is empty", this.getClass().getSimpleName()); + return false; + } + Map headers = message.getProperties(); + String processDefinitionKey = headers.getOrDefault(MQ_OWNERSHIP_PROCESS_DEFINITION_KEY, ""); + if (properties.getBroadcast().getFilterProcessDefinitionKeys().contains(processDefinitionKey)) { + return false; + } + log.debug("【{}】The broadcast message does not belong to the key of interest, will be ignored. messageId: {}, message process-definition-key: {}", this.getClass().getSimpleName(), message.getMsgId(), processDefinitionKey); + return true; + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterExtension.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterExtension.java new file mode 100644 index 000000000..dfb5b3b3a --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterExtension.java @@ -0,0 +1,59 @@ +package cn.axzo.workflow.starter.mq.broadcast.filter; + +import cn.axzo.workflow.starter.handler.filter.global.BroadcastMessageQueueFilter; +import org.apache.rocketmq.common.message.MessageExt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.core.Ordered; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * 用于扩展业务自己的 MQ 事件过滤器 + * + * @author wangli + * @since 2024/6/4 17:10 + */ +public class InnerFilterExtension implements InnerMessageQueueHandleBeforeFilter { + private final Logger log = LoggerFactory.getLogger(InnerFilterExtension.class); + + private final List businessFilters; + + public InnerFilterExtension(ObjectProvider> businessFilterProvider) { + this.businessFilters = businessFilterProvider.getIfAvailable(Collections::emptyList) + .stream().sorted(Comparator.comparing(Ordered::getOrder)) + .collect(Collectors.toList()); + } + + @Override + public boolean doFilter(MessageExt message) { + if (businessFilters.isEmpty()) { + log.debug("【{}】business extension filter is empty", this.getClass().getSimpleName()); + return false; + } + for (BroadcastMessageQueueFilter businessFilter : businessFilters) { + if (businessFilter.doFilter(message, new String(message.getBody(), UTF_8))) { + return true; + } + } + return false; + } + + + private Class getParameterizedTypeClass(BroadcastMessageQueueFilter filter) { + Type type = filter.getClass().getGenericInterfaces()[0]; + if (type instanceof ParameterizedType) { + return (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + } + return null; + } + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterMQOwnerShip.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterMQOwnerShip.java new file mode 100644 index 000000000..1ccfd2f85 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerFilterMQOwnerShip.java @@ -0,0 +1,50 @@ +package cn.axzo.workflow.starter.mq.broadcast.filter; + +import cn.axzo.workflow.starter.BroadcastListenerProperties; +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import org.apache.rocketmq.common.message.MessageExt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.util.Map; + +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_APPLICATION; + +/** + * 对 {@link BroadcastListenerProperties#filterApplicationNames} 的实现 + * + * @author wangli + * @since 2024/6/4 17:06 + */ +public class InnerFilterMQOwnerShip implements InnerMessageQueueHandleBeforeFilter { + private final Logger log = LoggerFactory.getLogger(InnerFilterMQOwnerShip.class); + + private final WorkflowEngineStarterProperties properties; + + public InnerFilterMQOwnerShip(WorkflowEngineStarterProperties properties, String applicationName) { + this.properties = properties; + this.properties.getBroadcast().getFilterApplicationNames().add(applicationName); + } + + @Override + public boolean doFilter(MessageExt message) { + if (!properties.getBroadcast().getEnableFilterApplicationName()) { + return false; + } + Map headers = message.getProperties(); + String mqOwnerShip = headers.getOrDefault(MQ_OWNERSHIP_APPLICATION, null); + + if (!StringUtils.hasText(mqOwnerShip)) { + log.debug("【{}】The broadcast message does not attribute the message, will not be ignored. messageId: {}", this.getClass().getSimpleName(), message.getMsgId()); + return false; + } else { + if (properties.getBroadcast().getFilterApplicationNames().contains(mqOwnerShip)) { + return false; + } else { + log.debug("【{}】The broadcast message does not attribute the message, will be ignored. messageId: {}, mq-ownership-application: {}", this.getClass().getSimpleName(), message.getMsgId(), mqOwnerShip); + return true; + } + } + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerMessageQueueHandleBeforeFilter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerMessageQueueHandleBeforeFilter.java new file mode 100644 index 000000000..454e1617f --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/broadcast/filter/InnerMessageQueueHandleBeforeFilter.java @@ -0,0 +1,21 @@ +package cn.axzo.workflow.starter.mq.broadcast.filter; + +import org.apache.rocketmq.common.message.MessageExt; + +/** + * 过滤 MQ 消息, 不再对其进行消费 + * + * @author wangli + * @since 2024/6/4 17:12 + */ +public interface InnerMessageQueueHandleBeforeFilter { + + /** + * 传入 RocketMQ 底层的消息对象,对消费过程中的消息过滤 + * + * @param message + * @return 为 true 时,代表需要被过滤 + */ + boolean doFilter(MessageExt message); + +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/AlertBroadcastDLQReporter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/AlertBroadcastDLQReporter.java new file mode 100644 index 000000000..97a7a24ae --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/AlertBroadcastDLQReporter.java @@ -0,0 +1,75 @@ +package cn.axzo.workflow.starter.mq.monitor; + +import cn.axzo.workflow.starter.handler.monitor.BroadcastDLQReporter; +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.apache.rocketmq.common.admin.TopicOffset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; + +/** + * Starter 内置的 Broadcast DLQ 钉钉通知 + * + * @author wangli + * @since 2024/6/19 11:04 + */ +public class AlertBroadcastDLQReporter implements BroadcastDLQReporter { + + private static final Logger log = LoggerFactory.getLogger(AlertBroadcastDLQReporter.class); + private final String dingtalk_robot_webhook = "https://oapi.dingtalk.com/robot/send?access_token=341ee2907f3ebc15dc495fb7771a646230058710999fec7838066c109849878e"; + private final String profile; + private final String applicationName; + + public AlertBroadcastDLQReporter(Environment environment) { + this.profile = environment.getProperty("spring.profiles.active"); + this.applicationName = environment.getProperty("spring.application.name"); + } + + @Override + public void process(TopicOffset v, String dlqName) { + log.warn("found DLQ"); + long count = v.getMaxOffset() - v.getMinOffset(); + if (count > 0) { + sendDingTalk(count, dlqName); + } + } + + @SneakyThrows + public void sendDingTalk(Long count, String dlqName) { + DingTalkClient client = new DefaultDingTalkClient(dingtalk_robot_webhook); + OapiRobotSendRequest request = new OapiRobotSendRequest(); + + request.setMsgtype("markdown"); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle("Notice Env: " + profile); + markdown.setText("#### [" + profile + "]环境[" + applicationName + "]发现“DLQ”数据(Rpc)\n" + + "> ###### 1. 如需查看详情,[请点击这里](" + findDomain() + "m) \n" + + "> ###### 2. 如需调整 MQ DLQ 的监控状态: \n" + + "> ###### 3. DLQ名称:" + dlqName + " \n" + + "[关闭监控](" + findDomain() + "m/set?status=false) \n | " + + "[开启监控](" + findDomain() + "m/set?status=true) \n" + + "> 积累条数:" + count + " \n"); + request.setMarkdown(markdown); + log.warn(markdown.getText()); + OapiRobotSendResponse response = client.execute(request); + } + + private String findDomain() { + switch (profile) { + case "dev": + return "https://dev-app.axzo.cn/" + applicationName + "/web/we/s/"; + case "test": + return "https://test-api.axzo.cn/" + applicationName + "/web/we/s/"; + case "pre": + return "https://pre-api.axzo.cn/" + applicationName + "/web/we/s/"; + case "live": + return "https://live-api.axzo.cn/" + applicationName + "/web/we/s/"; + default: + return "https://api.axzo.cn/" + applicationName + "/web/we/s/"; + } + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/AlertRcpDLQReporter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/AlertRcpDLQReporter.java new file mode 100644 index 000000000..3619f7f83 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/AlertRcpDLQReporter.java @@ -0,0 +1,75 @@ +package cn.axzo.workflow.starter.mq.monitor; + +import cn.axzo.workflow.starter.handler.monitor.RpcDLQReporter; +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.apache.rocketmq.common.admin.TopicOffset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; + +/** + * Starter 内置的 Rpc DLQ 钉钉通知 + * + * @author wangli + * @since 2024/6/19 11:04 + */ +public class AlertRcpDLQReporter implements RpcDLQReporter { + + private static final Logger log = LoggerFactory.getLogger(AlertRcpDLQReporter.class); + private final String dingtalk_robot_webhook = "https://oapi.dingtalk.com/robot/send?access_token=341ee2907f3ebc15dc495fb7771a646230058710999fec7838066c109849878e"; + private final String profile; + private final String applicationName; + + public AlertRcpDLQReporter(Environment environment) { + this.profile = environment.getProperty("spring.profiles.active"); + this.applicationName = environment.getProperty("spring.application.name"); + } + + @Override + public void process(TopicOffset v, String dlqName) { + log.warn("found DLQ"); + long count = v.getMaxOffset() - v.getMinOffset(); + if (count > 0) { + sendDingTalk(count, dlqName); + } + } + + @SneakyThrows + public void sendDingTalk(Long count, String dlqName) { + DingTalkClient client = new DefaultDingTalkClient(dingtalk_robot_webhook); + OapiRobotSendRequest request = new OapiRobotSendRequest(); + + request.setMsgtype("markdown"); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle("Notice Env: " + profile); + markdown.setText("#### [" + profile + "]环境[" + applicationName + "]发现“DLQ”数据(Rpc)\n" + + "> ###### 1. 如需查看详情,[请点击这里](" + findDomain() + "m) \n" + + "> ###### 2. 如需调整 MQ DLQ 的监控状态: \n" + + "> ###### 3. DLQ名称:" + dlqName + " \n" + + "[关闭监控](" + findDomain() + "m/set?status=false) \n | " + + "[开启监控](" + findDomain() + "m/set?status=true) \n" + + "> 积累条数:" + count + " \n"); + request.setMarkdown(markdown); + log.warn(markdown.getText()); + OapiRobotSendResponse response = client.execute(request); + } + + private String findDomain() { + switch (profile) { + case "dev": + return "https://dev-app.axzo.cn/" + applicationName + "/web/we/s/"; + case "test": + return "https://test-api.axzo.cn/" + applicationName + "/web/we/s/"; + case "pre": + return "https://pre-api.axzo.cn/" + applicationName + "/web/we/s/"; + case "live": + return "https://live-api.axzo.cn/" + applicationName + "/web/we/s/"; + default: + return "https://api.axzo.cn/" + applicationName + "/web/we/s/"; + } + } +} 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 new file mode 100644 index 000000000..e3d9d2c27 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/WorkflowEngineStarterDefaultMQMonitor.java @@ -0,0 +1,158 @@ +package cn.axzo.workflow.starter.mq.monitor; + +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import cn.axzo.workflow.starter.handler.monitor.BroadcastDLQReporter; +import cn.axzo.workflow.starter.handler.monitor.RpcDLQReporter; +import lombok.SneakyThrows; +import org.apache.rocketmq.common.admin.TopicStatsTable; +import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.env.Environment; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +import static cn.axzo.workflow.common.constant.StarterConstants.K8S_POD_NAME_SPACE; +import static cn.axzo.workflow.common.constant.StarterConstants.MQ_GID_NAME_SEGMENT; +import static cn.axzo.workflow.starter.mq.monitor.console.WorkflowEngineStarterMQMonitorController.BROADCAST_CONSUMER_GROUP; +import static cn.axzo.workflow.starter.mq.monitor.console.WorkflowEngineStarterMQMonitorController.RPC_RETRY_CONSUMER_GROUP; + +/** + * 用于监控 Starter 关注的 Topic 相关的 Producer 与 Consumer 信息 + * + * @author wangli + * @since 2024/6/6 10:04 + */ +public class WorkflowEngineStarterDefaultMQMonitor implements SmartLifecycle { + + private static final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterDefaultMQMonitor.class); + public static final String DLQ_PREFIX = "%DLQ%"; + private static final String BROADCAST = "broadcast"; + private static final String RPC = "rpc"; + /** + * 可能为 null + */ + private final DefaultMQAdminExt defaultMQAdminExt; + private final Environment environment; + private final ThreadPoolTaskScheduler taskScheduler; + private BroadcastDLQReporter broadcastDLQProcessor; + private RpcDLQReporter rpcDLQProcessor; + private final WorkflowEngineStarterProperties starterProperties; + private final AtomicBoolean running = new AtomicBoolean(false); + + public WorkflowEngineStarterDefaultMQMonitor(ObjectProvider mqAdminExtObjectProvider, + ObjectProvider broadcastDLQProcessorObjectProvider, + ObjectProvider rpcDLQProcessorObjectProvider, + WorkflowEngineStarterProperties workflowEngineStarterProperties, + Environment environment) { + this.defaultMQAdminExt = mqAdminExtObjectProvider.getIfAvailable(); + this.environment = environment; + if (workflowEngineStarterProperties.getAlert()) { + this.broadcastDLQProcessor = new AlertBroadcastDLQReporter(environment); + this.rpcDLQProcessor = new AlertRcpDLQReporter(environment); + } else { + this.broadcastDLQProcessor = broadcastDLQProcessorObjectProvider.getIfAvailable(() -> new BroadcastDLQReporter() { + }); + this.rpcDLQProcessor = rpcDLQProcessorObjectProvider.getIfAvailable(() -> new RpcDLQReporter() { + }); + } + this.starterProperties = workflowEngineStarterProperties; + this.taskScheduler = init(); + } + + private ThreadPoolTaskScheduler init() { + ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + taskScheduler.setBeanName("Workflow-Engine-Watch-MQ-Scheduler"); + taskScheduler.initialize(); + return taskScheduler; + } + + @SneakyThrows + public void mqWatch() { + mqWatch(BROADCAST); + mqWatch(RPC); + } + + @SneakyThrows + public void mqWatch(String type) { + String segment = environment.getProperty(MQ_GID_NAME_SEGMENT); + String applicationName = environment.getProperty("spring.application.name"); + // 周期性比对 + if (Objects.equals(type, BROADCAST)) { + compareDLQ(String.format(BROADCAST_CONSUMER_GROUP, applicationName, segment), type); + } else if (Objects.equals(type, RPC)) { + compareDLQ(String.format(RPC_RETRY_CONSUMER_GROUP, applicationName, segment), type); + } + } + + private void compareDLQ(String consumerGroupName, String dqlType) { + log.info("current broadcast consumer group is :{}", consumerGroupName); + String myPodNamespace = environment.getProperty(K8S_POD_NAME_SPACE); + if (!StringUtils.hasText(myPodNamespace)) { + return; + } + try { + TopicStatsTable table = defaultMQAdminExt.examineTopicStats(DLQ_PREFIX + consumerGroupName); + if (Objects.isNull(table) || CollectionUtils.isEmpty(table.getOffsetTable())) { + return; + } + table.getOffsetTable().forEach((k, v) -> { + if (Objects.equals(DLQ_PREFIX + consumerGroupName, k.getTopic())) { + if (BROADCAST.equals(dqlType)) { + broadcastDLQProcessor.process(v, DLQ_PREFIX + consumerGroupName); + } else if (RPC.equals(dqlType)) { + rpcDLQProcessor.process(v, DLQ_PREFIX + consumerGroupName); + } + } + }); + } catch (Exception e) { + log.warn("该异常信息是正常的,不影响服务启动!"); + } + } + + public void setBroadcastDLQProcessor(BroadcastDLQReporter broadcastDLQProcessor) { + this.broadcastDLQProcessor = broadcastDLQProcessor; + } + + public void setRpcDLQProcessor(RpcDLQReporter rpcDLQProcessor) { + this.rpcDLQProcessor = rpcDLQProcessor; + } + + @Override + public boolean isAutoStartup() { + if (starterProperties.getEnableDlqMonitor() && starterProperties.getRpcMonitorStatus()) { + taskScheduler.scheduleWithFixedDelay(() -> mqWatch(RPC), starterProperties.getDlqMonitorIntervalInMs()); + } + if (starterProperties.getEnableDlqMonitor() && starterProperties.getBroadcast().getMonitorStatus()) { + taskScheduler.scheduleWithFixedDelay(() -> mqWatch(BROADCAST), starterProperties.getDlqMonitorIntervalInMs()); + } + return false; + } + + @Override + public void start() { + if (running.compareAndSet(false, true)) { + log.info("starting workflow engine mq monitor"); + taskScheduler.scheduleWithFixedDelay(this::mqWatch, 30000); + } + } + + @Override + public void stop() { + if (running.compareAndSet(true, false)) { + log.info("destroying workflow engine mq monitor"); + taskScheduler.shutdown(); + } + } + + @Override + public boolean isRunning() { + return this.running.get(); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/console/WorkflowEngineStarterMQMonitorController.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/console/WorkflowEngineStarterMQMonitorController.java new file mode 100644 index 000000000..eeb98074c --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/monitor/console/WorkflowEngineStarterMQMonitorController.java @@ -0,0 +1,160 @@ +package cn.axzo.workflow.starter.mq.monitor.console; + +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import cn.axzo.workflow.starter.handler.monitor.BroadcastDLQReporter; +import cn.axzo.workflow.starter.handler.monitor.RpcDLQReporter; +import cn.axzo.workflow.starter.mq.monitor.AlertBroadcastDLQReporter; +import cn.axzo.workflow.starter.mq.monitor.AlertRcpDLQReporter; +import cn.axzo.workflow.starter.mq.monitor.WorkflowEngineStarterDefaultMQMonitor; +import cn.azxo.framework.common.model.CommonResponse; +import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static cn.axzo.workflow.common.constant.StarterConstants.MQ_GID_NAME_SEGMENT; +import static cn.axzo.workflow.starter.StarterRPCInvokeMQConfiguration.DEFAULT_EVENT; +import static cn.axzo.workflow.starter.mq.monitor.WorkflowEngineStarterDefaultMQMonitor.DLQ_PREFIX; + +/** + * Workflow Engine Starter MessageQueue Monitor Controller + * + * @author wangli + * @since 2024/6/6 10:04 + */ +@RestController +@RequestMapping("/web/we/s") +public class WorkflowEngineStarterMQMonitorController { + private static final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterMQMonitorController.class); + @Resource + private WorkflowEngineStarterProperties starterProperties; + @Resource + private ObjectProvider mqAdminExtObjectProvider; + @Resource + private ObjectProvider monitorObjectProvider; + @Resource + private ObjectProvider broadcastDLQProcessorObjectProvider; + @Resource + private ObjectProvider rpcDLQProcessorObjectProvider; + @Resource + private Environment environment; + @Value("${spring.application.name}") + private String applicationName; + @Value("${spring.profiles.active}") + private String activeProfile; + @Resource + @Qualifier("serviceVersion") + private String serviceVersion; + public static String BROADCAST_CONSUMER_GROUP = "GID_%s_workflow_engine_%s_consumer"; + public static String RPC_RETRY_CONSUMER_GROUP = "GID_%s_workflow_engine_starter_%s_consumer"; + + @GetMapping("/m") + public CommonResponse> monitor() { + String topic = DEFAULT_EVENT + activeProfile; + Map result = new HashMap<>(); + if (!starterProperties.getEnableDlqMonitor()) { + result.put("Info", "未开启·死信队列·的监控,如需,请设置 workflow.engine.starter.enableDlqMonitor = true 后再重试!"); + } else { + mqAdminExtObjectProvider.ifAvailable(defaultMQAdminExt -> { + String segment = environment.getProperty(MQ_GID_NAME_SEGMENT); + String broadcastConsumer = String.format(BROADCAST_CONSUMER_GROUP, applicationName, segment); + String rpcConsumer = String.format(RPC_RETRY_CONSUMER_GROUP, applicationName, segment); + try { + result.put("Engine Broadcast MQ", defaultMQAdminExt.examineConsumeStats(broadcastConsumer)); + } catch (Exception e) { + log.warn("monitor controller examineConsumeStats broadcast error: {}", e.getMessage(), e); + } + try { + result.put("Starter RPC MQ", defaultMQAdminExt.examineConsumeStats(rpcConsumer)); + } catch (Exception e) { + log.warn("monitor controller examineConsumeStats rpc error: {}", e.getMessage(), e); + } +// result.put("BrokerClusterInfo", defaultMQAdminExt.examineBrokerClusterInfo()); +// result.put("TopicClusterList", defaultMQAdminExt.getTopicClusterList(topic)); + try { + result.put("TopicRouteInfo", defaultMQAdminExt.examineTopicRouteInfo(topic)); + } catch (Exception e) { + log.warn("monitor controller examineTopicRouteInfo error: {}", e.getMessage(), e); + } +// result.put("TopicStats", defaultMQAdminExt.examineTopicStats(topic)); + try { + result.put("Broadcast-DLQ", defaultMQAdminExt.examineTopicStats(DLQ_PREFIX + broadcastConsumer)); + } catch (Exception e) { + log.warn("monitor controller examineTopicStats broadcast error: {}", e.getMessage(), e); + } + try { + result.put("RPC-DLQ", defaultMQAdminExt.examineTopicStats(DLQ_PREFIX + rpcConsumer)); + } catch (Exception e) { + log.warn("monitor controller examineTopicStats rpc error: {}", e.getMessage(), e); + } + + }); + } + return CommonResponse.success(result); + } + + /** + * @param status + * @return + */ + @GetMapping("/m/set") + public CommonResponse changeMonitorState(@RequestParam("status") Boolean status) { + WorkflowEngineStarterDefaultMQMonitor monitor = monitorObjectProvider.getIfAvailable(); + if (Objects.isNull(monitor)) { + return CommonResponse.success("未开启·死信队列·的监控,如需,请设置 workflow.engine.starter.enableDlqMonitor = true 后再重试!"); + } + if (status) { + if (!monitor.isRunning()) { + monitor.start(); + } + return CommonResponse.success("已临时开始 MQ 死信队列监控!" + + "在应用重启前,将持续监控。重启后,将根据环境变量来确定是否监控。"); + } else { + if (monitor.isRunning()) { + monitor.stop(); + } + return CommonResponse.success("已临时关闭 MQ 死信队列监控!" + + "在应用重启前,将不再监控。重启后,将根据环境变量配置来确定是否监控。"); + } + } + + @GetMapping("/m/a/set") + public CommonResponse changeDLQAlert(@RequestParam("status") Boolean status) { + WorkflowEngineStarterDefaultMQMonitor monitor = monitorObjectProvider.getIfAvailable(); + if (Objects.isNull(monitor)) { + return CommonResponse.success("未开启·死信队列·的监控,如需,请设置 workflow.engine.starter.enableDlqMonitor = true 后再重试!"); + } + if (status) { + monitor.setBroadcastDLQProcessor(new AlertBroadcastDLQReporter(environment)); + monitor.setRpcDLQProcessor(new AlertRcpDLQReporter(environment)); + return CommonResponse.success("开启 DLQ 钉钉通知"); + } else { + monitor.setBroadcastDLQProcessor(broadcastDLQProcessorObjectProvider.getIfAvailable(() -> new BroadcastDLQReporter() { + })); + monitor.setRpcDLQProcessor(rpcDLQProcessorObjectProvider.getIfAvailable(() -> new RpcDLQReporter() { + })); + return CommonResponse.success("以关闭 DLQ 钉钉通知"); + } + } + + @GetMapping("/v") + public CommonResponse consoleVersion(@RequestParam(value = "fix", required = false, defaultValue = "false") Boolean showLastFix) { + String str = "当前 workflow-engine version: " + serviceVersion; + if (showLastFix) { + str += "fix: cn.axzo.workflow.starter.mq.retry.consumer.WorkflowEngineStarterRetryEventListener.multiMethodCache"; + } + return CommonResponse.success(str); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/retry/consumer/WorkflowEngineStarterRetryEventListener.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/retry/consumer/WorkflowEngineStarterRetryEventListener.java new file mode 100644 index 000000000..22daebf03 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/retry/consumer/WorkflowEngineStarterRetryEventListener.java @@ -0,0 +1,198 @@ +package cn.axzo.workflow.starter.mq.retry.consumer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventConsumer; +import cn.axzo.framework.rocketmq.EventHandler; +import cn.axzo.workflow.common.enums.WorkflowEngineEventEnum; +import cn.axzo.workflow.common.model.response.mq.WorkflowEngineStarterRpcInvokeDTO; +import cn.axzo.workflow.common.util.ThreadUtil; +import cn.axzo.workflow.starter.WorkflowEngineStarterProperties; +import cn.axzo.workflow.starter.api.WorkflowCoreService; +import cn.axzo.workflow.starter.api.WorkflowManageService; +import cn.axzo.workflow.starter.common.exception.WorkflowNoMethodException; +import cn.axzo.workflow.starter.util.MD5; +import com.alibaba.fastjson.JSON; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.env.Environment; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.ConnectException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC; + +/** + * RPC 动作事件的 MQ 消费者(集成业务系统中的自产自销) + * + * @author wangli + * @since 2024/5/21 16:28 + */ +public class WorkflowEngineStarterRetryEventListener implements EventHandler, InitializingBean { + private final Logger log = LoggerFactory.getLogger(WorkflowEngineStarterRetryEventListener.class); + private final EventConsumer workflowEngineStarterEventConsumer; + private final Environment environment; + private final WorkflowEngineStarterProperties starterProperties; + private final String currentApplicationName; + private WorkflowCoreService workflowCoreService; + private WorkflowManageService workflowManageService; + private final Map> multiMethodCache = new HashMap<>(); + + class InterfaceMapping { + private final Object interfaceObject; + private final Method method; + + public InterfaceMapping(Object interfaceObject, Method method) { + this.interfaceObject = interfaceObject; + this.method = method; + } + + public Object getInterfaceObject() { + return interfaceObject; + } + + public Method getMethod() { + return method; + } + } + + public WorkflowEngineStarterRetryEventListener(EventConsumer workflowEngineStarterEventConsumer, + Environment environment, + WorkflowEngineStarterProperties starterProperties, + ObjectProvider workflowCoreServiceObjectProvider, + ObjectProvider workflowManageServiceObjectProvider) { + this.workflowEngineStarterEventConsumer = workflowEngineStarterEventConsumer; + this.environment = environment; + this.starterProperties = starterProperties; + this.currentApplicationName = environment.getProperty("spring.application.name"); + workflowCoreServiceObjectProvider.ifAvailable(core -> { + this.workflowCoreService = core; + parseWorkflowCoreService(); + }); + workflowManageServiceObjectProvider.ifAvailable(manage -> { + this.workflowManageService = manage; + parseWorkflowManageService(); + }); + } + + + private void parseWorkflowCoreService() { + Class coreService = (Class) workflowCoreService.getClass().getGenericInterfaces()[0]; + FeignClient feignClient = AnnotationUtils.findAnnotation(coreService, FeignClient.class); + if (Objects.isNull(feignClient)) { + throw new IllegalStateException("WorkflowCoreService 配置错误,没有找到 FeignClient 注解"); + } + + Method[] methods = coreService.getDeclaredMethods(); + for (Method method : methods) { + String parameterTypesMd5 = MD5.encrypt(StringUtils.collectionToCommaDelimitedString(Arrays.stream(method.getParameterTypes()).map(Class::getName).collect(Collectors.toList()))); + Map methodCache = multiMethodCache.getOrDefault(method.getName(), new HashMap<>()); + methodCache.put(parameterTypesMd5, new InterfaceMapping(workflowCoreService, method)); + multiMethodCache.put(method.getName(), methodCache); + } + } + + private void parseWorkflowManageService() { + Class manageService = (Class) workflowManageService.getClass().getGenericInterfaces()[0]; + FeignClient feignClient = AnnotationUtils.findAnnotation(manageService, FeignClient.class); + if (Objects.isNull(feignClient)) { + throw new IllegalStateException("WorkflowCoreService 配置错误,没有找到 FeignClient 注解"); + } + + Method[] methods = manageService.getDeclaredMethods(); + for (Method method : methods) { + String parameterTypesMd5 = MD5.encrypt(StringUtils.collectionToCommaDelimitedString(Arrays.stream(method.getParameterTypes()).map(Class::getName).collect(Collectors.toList()))); + Map methodCache = multiMethodCache.getOrDefault(method.getName(), new HashMap<>()); + methodCache.put(parameterTypesMd5, new InterfaceMapping(workflowManageService, method)); + multiMethodCache.put(method.getName(), methodCache); + } + } + + @SneakyThrows + @Override + public void onEvent(Event event, EventConsumer.Context context) { + log.info("WorkflowEngineClientRetryEventListener onEvent: {}", event.toPrettyJsonString()); + WorkflowEngineStarterRpcInvokeDTO dto = event.normalizedData(WorkflowEngineStarterRpcInvokeDTO.class); + + Map methodCache = multiMethodCache.getOrDefault(dto.getMethodName(), new HashMap<>()); + if (CollectionUtils.isEmpty(methodCache)) { + throw new WorkflowNoMethodException("Not methodCache found: " + dto.getMethodName()); + } + InterfaceMapping mapping = methodCache.getOrDefault(dto.getParameterTypesMd5(), null); + if (Objects.isNull(mapping)) { + throw new WorkflowNoMethodException("Not method found: " + dto.getMethodName()); + } + try { + // 事件处理 RPC 请求, 强制使用同步模式 + ThreadUtil.set(SYNC); + Object[] args = convertToActualArgs(mapping.getMethod(), dto.getParameters()); + if (log.isDebugEnabled()) { + log.debug("event rpc request args: {}", JSON.toJSONString(args)); + } + Object invoke = mapping.getMethod().invoke(mapping.getInterfaceObject(), args); + if (log.isDebugEnabled()) { + log.debug("Event Invoke Result: {}", JSON.toJSONString(invoke)); + } + log.info("WorkflowEngineClientRetryEventListener onEvent end!"); + } catch (Throwable e) { + // 能抛出异常目前只有两种情况, 一个是网络异常, 另一个是对端服务内部异常 + Throwable cause = getRealCause(e); + log.warn("onEvent Invoke root exception cause by: {}", cause.getMessage(), cause); + if (cause instanceof ConnectException) { + // 而只有当网络异常时, 利用 RocketMQ 的重试机制 + throw cause; + } + } + } + + private Object[] convertToActualArgs(Method method, List parameters) { + Object[] args = new Object[parameters.size()]; + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + for (int i = 0; i < parameters.size(); i++) { + Class parameterType = parameterTypes[i]; + Annotation[] annotations = parameterAnnotations[i]; + for (Annotation annotation : annotations) { + if (annotation instanceof RequestParam) { + List arg = JSON.parseObject(parameters.get(i), List.class); + args[i] = Objects.equals(parameterType, List.class) ? arg : CollectionUtils.isEmpty(arg) ? null : arg.get(0); + } else if (annotation instanceof PathVariable) { + args[i] = parameters.get(i); + } else if (annotation instanceof RequestBody) { + args[i] = JSON.parseObject(parameters.get(i), parameterType); + } + } + } + return args; + } + + private Throwable getRealCause(Throwable error) { + while (Objects.nonNull(error.getCause())) { + // TODO 如果有必要, 可以在这里对异常进行判断/过滤之类的动作, 目前暂未发现需要处理的场景, + // 后续如果发现, 则需要在此处进行扩展, 注意不是 HardCode, 而是用接口扩展 + error = error.getCause(); + } + return error; + } + + @Override + public void afterPropertiesSet() { + workflowEngineStarterEventConsumer.registerHandler(WorkflowEngineEventEnum.WORKFLOW_ENGINE_STARTER.getEventCode(currentApplicationName), this); + } +} diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/retry/producer/RpcInvokeEventProducer.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/retry/producer/RpcInvokeEventProducer.java new file mode 100644 index 000000000..de7f2fc80 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/retry/producer/RpcInvokeEventProducer.java @@ -0,0 +1,132 @@ +package cn.axzo.workflow.starter.mq.retry.producer; + +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.RocketMQEventProducer; +import cn.axzo.framework.rocketmq.utils.TraceUtils; +import cn.axzo.workflow.common.enums.WorkflowEngineEventEnum; +import cn.axzo.workflow.common.model.response.mq.WorkflowEngineStarterRpcInvokeDTO; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import lombok.NonNull; +import org.apache.commons.collections4.ListUtils; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; + +import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_APPLICATION; + +/** + * RPC 的调用动作的 MQ 事件生产者 + * + * @author wangli + * @since 2024/5/22 10:02 + */ +public class RpcInvokeEventProducer extends RocketMQEventProducer { + private final Logger log = LoggerFactory.getLogger(RpcInvokeEventProducer.class); + + private BiConsumer> sendBeforeCallback; + private BiConsumer> rollbackHandler; + private final String applicationName; + + public RpcInvokeEventProducer(RocketMQTemplate rocketMQTemplate, String defaultModule, + String appName, Context defaultContext, + BiConsumer> sendBeforeCallback, + BiConsumer> sendAfterCallback, + BiConsumer> rollbackHandler) { + super(rocketMQTemplate, defaultModule, appName, defaultContext, sendAfterCallback); + this.applicationName = appName; + this.sendBeforeCallback = sendBeforeCallback; + this.rollbackHandler = rollbackHandler; + } + + @Override + public void send(@NonNull Event event, @NonNull Context context) { + if (log.isDebugEnabled()) { + log.debug("发送事件内容: {}", event.toPrettyJsonString()); + } + if (sendBeforeCallback != null) { + sendBeforeCallback.accept(event, context); + } + // XXX:不要在send的时候修改event的值,有副作用。 + // 例如:当将同一个event发送到不同的topic的时候,buildSchemaHash会用不同的topic赋值两次,导致一些异常case + Event copiedEvent = Event.builder().build(); + BeanUtils.copyProperties(event, copiedEvent); + if (Strings.isNullOrEmpty(copiedEvent.getTargetId())) { + log.warn("targetId of event is black, best practice of targetId is present, event = {}", event.toJsonString()); + } + + if (copiedEvent.getData() == null) { + log.warn("data of event is empty, best practice of data must present, event = {}", event.toJsonString()); + } + Preconditions.checkArgument(!Strings.isNullOrEmpty(copiedEvent.getEventModule()), "eventModule不能为空"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(copiedEvent.getEventName()), "eventName不能为空"); + + // 复制一份 context,并加入链路跟踪信息 traceId + HashMap newHeaders = Maps.newHashMap(Optional.ofNullable(context.getHeaders()).orElse(ImmutableMap.of())); + newHeaders.put(TraceUtils.TRACE_ID, TraceUtils.getOrCreateTraceId()); + newHeaders.put(TraceUtils.CTX_LOG_ID, TraceUtils.getOrCreateTraceId()); + newHeaders.put(TraceUtils.TRACE_ID_IN_MDC, TraceUtils.getOrCreateTraceId()); + newHeaders.put(MQ_OWNERSHIP_APPLICATION, applicationName); + final Context copiedContext = context.toBuilder().headers(newHeaders).build(); + + Runnable runnable = () -> { + try { + getSender().accept(copiedEvent, copiedContext); + } catch (Exception e) { + log.error("====MQ PRODUCER ====, context={}, message = {}", copiedContext, copiedEvent.toPrettyJsonString(), e); + throw e; + } + }; + + Runnable rollbackRunnable = () -> { + try { + getRollbackHandler().accept(copiedEvent, copiedContext); + } catch (Exception e) { + // ignore + } + }; + if (copiedContext.isTransactional()) { + // https://www.jianshu.com/p/59891ede5f90 + log.info("runnable is transaction event={}", copiedEvent.toJsonString()); + runnable.run(); + } else { + // 并发会导致事件时序出现问题. 所以串行执行 + log.info("runnable not transaction event={}", copiedEvent.toJsonString()); + getAfterCommitExecutor().executeAndRollback(runnable, rollbackRunnable); + } + + if (log.isDebugEnabled()) { + List runnables = ListUtils.emptyIfNull(getAfterCommitExecutor().getRunnables()); + log.debug("runnables.size(): {}", runnables.size()); + } + } + + @Override + public BiConsumer> getRollbackHandler() { + return rollbackHandler; + } + + /** + * 发送 RPC 调用的事件 + * + * @param eventEnum {@link WorkflowEngineEventEnum} + * @param data {@link WorkflowEngineStarterRpcInvokeDTO} + */ + public void send(WorkflowEngineEventEnum eventEnum, WorkflowEngineStarterRpcInvokeDTO data) { + send(Event.builder() + .shardingKey(applicationName) + .eventCode(eventEnum.getEventCode(applicationName)) + .targetId(data.getMethodName()) + .targetType(eventEnum.getTag()) + .data(data) + .build()); + } +} 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..cb2b110d6 --- /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 + // 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/java/cn/axzo/workflow/starter/util/MD5.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/util/MD5.java new file mode 100644 index 000000000..07fd12d59 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/util/MD5.java @@ -0,0 +1,46 @@ +package cn.axzo.workflow.starter.util; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Starter 专用的 MD5小工具 + * + * @author wangli + * @since 2025-03-05 16:45 + */ +public class MD5 { + + public static String encrypt(String data) { + if (data == null) { + return null; + } + try { + // 获取 MD5 算法实例 + MessageDigest md = MessageDigest.getInstance("MD5"); + // 将输入字符串转换为字节数组并进行加密 + byte[] digest = md.digest(data.getBytes(StandardCharsets.UTF_8)); + + // 将字节数组转换为 BigInteger + BigInteger bigInt = new BigInteger(1, digest); + // 将 BigInteger 转换为十六进制字符串 + String hashText = bigInt.toString(16); + // 不足 32 位时前面补 0 + while (hashText.length() < 32) { + hashText = "0" + hashText; + } + return hashText; + } catch (NoSuchAlgorithmException e) { + // 处理算法不可用的异常 + e.printStackTrace(); + return null; + } + } + + public static void main(String[] args) { + String encrypt = encrypt("123"); + System.out.println("encrypt = " + encrypt); + } +} 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 new file mode 100644 index 000000000..0af4c06d5 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/resources/META-INF/application.yml.demo @@ -0,0 +1,22 @@ +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,可调用受限访问接口 + enable-dlq-monitor: true # 是否启用 starter 中使用到的死信队列监控功能,仅是启动功能,未真实监控,需要配合 monitor-status = true 使用 + monitor-status: false # 应用启动后,如果该属性为 true,则真实启动监控,否则不启动 + dql-monitor-interval-in-ms: 4 * 60 * 60 * 1000 # 周期性获取死信队列监控信息的间隔时间 + broadcast: # 关于 Starter 中监听引擎广播的事件相关配置 + enable-filter-application-name: false # 过滤广播事件,只接收来自指定应用名的创建的流程的广播事件,默认 false,不进行过滤。如果为 true,则将配合 filter-application-names 参数进行广播事件的过滤。该过滤功能的前提是使用了 1.4.0 及以上版本的 workflow-engine-spring-boot-starter.jar + filter-application-names: # 配合 enable-filter-application-name 属性,当它为 true 时,该参数生效。设置的值为需要消费的 MQ 事件 + - 'workflow-engine' + enable-filter-definition-key: false # 过滤广播事件,只接收指定流程定义的创建的广播事件,默认 false,不进行过滤。如果为 true,则将配合 filter-process-definition-keys 参数进行广播事件的过滤。 + filter-process-definition-keys: # 配合 enable-filter-definition-key 属性,当它为 true 时,该参数生效。设置的值为需要消费的 MQ 事件 + - '1' + fail-handle-type: fail_over # 广播事件失败时,如何处理。可选值:fail_over(会按下面三个属性进行重试)、fail_fast(直接打印异常,跳过该消息的消费) + monitor-status: false # 广播 MQ 的私信队列监控 + num-of-retries: # 当 fail-handle-type 为 fail_over 时,广播事件失败时,重试次数 + wait-increase-factor: # 当 fail-handle-type 为 fail_over 时,广播事件失败时,重试间隔时间增长因子 + wait-time-in-ms: # 当 fail-handle-type 为 fail_over 时,广播事件失败时,重试间隔时间 diff --git a/workflow-engine-spring-boot-starter/src/main/resources/META-INF/spring.factories b/workflow-engine-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..272293ebe --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.axzo.workflow.starter.WorkflowEngineStarterAutoConfiguration diff --git a/workflow-engine-spring-boot-starter/src/main/resources/imgs/business.png b/workflow-engine-spring-boot-starter/src/main/resources/imgs/business.png new file mode 100644 index 000000000..09051ab51 Binary files /dev/null and b/workflow-engine-spring-boot-starter/src/main/resources/imgs/business.png differ diff --git a/workflow-engine-spring-boot-starter/src/main/resources/imgs/process-model.png b/workflow-engine-spring-boot-starter/src/main/resources/imgs/process-model.png new file mode 100644 index 000000000..722b88b58 Binary files /dev/null and b/workflow-engine-spring-boot-starter/src/main/resources/imgs/process-model.png differ diff --git a/workflow-engine-support/pom.xml b/workflow-engine-support/pom.xml new file mode 100644 index 000000000..1c04d54f7 --- /dev/null +++ b/workflow-engine-support/pom.xml @@ -0,0 +1,44 @@ + + 4.0.0 + + cn.axzo.workflow + workflow-engine + ${revision} + + ${revision} + workflow-engine-support + jar + Workflow Engine Support + + + com.github.javaparser + javaparser-core + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + run-custom-code + process-classes + + java + + + cn.axzo.workflow.support.api.CodeGenerator + + + + compile + + + + + + + diff --git a/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/CodeGenerator.java b/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/CodeGenerator.java new file mode 100644 index 000000000..3f3861f92 --- /dev/null +++ b/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/CodeGenerator.java @@ -0,0 +1,17 @@ +package cn.axzo.workflow.support.api; + +/** + * Maven plugin execution entrance + * + * @author wangli + * @since 2024/6/12 10:56 + */ +public class CodeGenerator { + + public static void main(String[] args) throws Exception { + + CoreServiceCodeGeneration.generate(); + ManageServiceCodeGeneration.generate(); + + } +} 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 new file mode 100644 index 000000000..5c4323ff9 --- /dev/null +++ b/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/CoreServiceCodeGeneration.java @@ -0,0 +1,183 @@ +package cn.axzo.workflow.support.api; + +import com.github.javaparser.ParserConfiguration; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.Modifier; +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.expr.ClassExpr; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.ast.expr.StringLiteralExpr; +import com.github.javaparser.ast.expr.ThisExpr; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.ExpressionStmt; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.Type; +import com.github.javaparser.printer.DefaultPrettyPrinter; +import com.github.javaparser.printer.Printer; +import com.github.javaparser.utils.SourceRoot; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +/** + * 生成 WorkflowCoreService 接口 + * + * @author wangli + * @since 2024/6/7 17:14 + */ +public class CoreServiceCodeGeneration { + + public static void generate() throws Exception { + String newClassName = "WorkflowCoreService"; +// CompilationUnit originFile = parseOriginFile(newClassName); + CompilationUnit testGeneric = genericCode("cn.axzo.workflow.starter.api", newClassName); + writeToStarter(testGeneric, newClassName); + + } + + private static CompilationUnit parseOriginFile(String newClassName) throws Exception { + String projectRootDir = System.getProperty("user.dir"); + Path sourceCodeRoot = Paths.get(projectRootDir, "/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/"); + SourceRoot sourceRoot = new SourceRoot(sourceCodeRoot); + sourceRoot.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8); + Printer printer = new DefaultPrettyPrinter(); + sourceRoot.setPrinter(printer::print); + sourceRoot.tryToParse(); + + List compilationUnits = sourceRoot.getCompilationUnits(); + for (CompilationUnit cu : compilationUnits) { + Optional primaryTypeName = cu.getPrimaryTypeName(); + if (primaryTypeName.isPresent() && primaryTypeName.get().equals(newClassName)) { + System.out.println("compilationUnit = " + cu); + return cu; + } + } + return null; + } + + private static void writeToStarter(CompilationUnit cu, String newClassName) { + String projectRootDir = System.getProperty("user.dir"); + String genericFilePath = Paths.get(projectRootDir, "/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/", newClassName + ".java").toString(); + try (FileWriter fileWriter = new FileWriter(genericFilePath)) { + fileWriter.append(cu.toString()); + } catch (IOException e) { + // nothing to do + } + } + + private static CompilationUnit genericCode(String pkg, String newClassName) throws Exception { + CompilationUnit cu = new CompilationUnit(pkg); + ClassOrInterfaceDeclaration interfaceDeclaration = setCommon(cu, newClassName); + + addMethods(interfaceDeclaration, cu); + addDefaultMethods(interfaceDeclaration); + return cu; + } + + private static void addDefaultMethods(ClassOrInterfaceDeclaration interfaceDeclaration) { + MethodDeclaration sync = createDefaultMethod("sync", interfaceDeclaration); + sync.setJavadocComment("强制使用‘同步’模式调用该方法,请在调用真实方法前调用该方法\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" +
+            "
"); + } + + private static MethodDeclaration createDefaultMethod(String methodName, ClassOrInterfaceDeclaration interfaceDeclaration) { + // 将sync方法添加到WorkflowCoreService类中 + MethodDeclaration methodDeclaration = interfaceDeclaration.addMethod(methodName, Modifier.Keyword.DEFAULT); + methodDeclaration.setType(new ClassOrInterfaceType("WorkflowCoreService")); + + // 创建方法体 + BlockStmt methodBody = new BlockStmt(); + + MethodCallExpr setMethodCall = new MethodCallExpr(new NameExpr("ThreadUtil"), "set"); + setMethodCall.addArgument(new NameExpr(methodName.toUpperCase())); // 使用NameExpr来表示枚举常量 + ExpressionStmt setStatement = new ExpressionStmt(setMethodCall); + + methodBody.addStatement(setStatement); + // 创建return this;的语句 + ReturnStmt returnStmt = new ReturnStmt(new ThisExpr()); + methodBody.addStatement(returnStmt); + + // 将方法体设置到sync方法中 + methodDeclaration.setBody(methodBody); + return methodDeclaration; + } + + private static void addMethods(ClassOrInterfaceDeclaration interfaceDeclaration, CompilationUnit cu) throws Exception { + String projectRootDir = System.getProperty("user.dir"); + Path sourceCodeRoot = Paths.get(projectRootDir, "/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/"); + SourceRoot sourceRoot = new SourceRoot(sourceCodeRoot); + sourceRoot.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8); + Printer printer = new DefaultPrettyPrinter(); + sourceRoot.setPrinter(printer::print); + sourceRoot.tryToParse(); + + List compilationUnits = sourceRoot.getCompilationUnits(); + NodeList> targetMethods = interfaceDeclaration.getMembers(); + for (CompilationUnit apiCU : compilationUnits) { + if (!apiCU.getPrimaryType().filter(e -> e.getAnnotationByName("Manageable").isPresent()).isPresent()) { + addImports(apiCU, cu); + // 类上含有 @Manageable,不进行解析 + String interfaceName = apiCU.getPrimaryTypeName().get(); + List methods = apiCU.getInterfaceByName(interfaceName).get().findAll(MethodDeclaration.class); + for (MethodDeclaration method : methods) { + if (!method.getAnnotationByName("Manageable").isPresent()) { + MethodDeclaration methodDeclaration = method.clone(); + methodDeclaration.setType(changeReturnType(methodDeclaration.getType())); + targetMethods.add(methodDeclaration); + } + } + } + } + } + + private static Type changeReturnType(Type type) { + if (type instanceof ClassOrInterfaceType && type.asClassOrInterfaceType().getTypeArguments().isPresent()) { + NodeList types = type.asClassOrInterfaceType().getTypeArguments().orElse(new NodeList<>()); + if (types.size() != 1) { + throw new RuntimeException("workflow-engine-api 中的接口返回类型有误"); + } + return types.getFirst().get(); + } + return type; + } + + private static void addImports(CompilationUnit apiCU, CompilationUnit cu) { + for (ImportDeclaration sourceImport : apiCU.getImports()) { + cu.addImport(sourceImport.clone()); + } + } + + private static ClassOrInterfaceDeclaration setCommon(CompilationUnit cu, String newClassName) { + ClassOrInterfaceDeclaration classOrInterfaceDeclaration = cu.addInterface(newClassName).setPublic(true); + 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")) + .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); + cu.addImport("cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC", true, false); + return classOrInterfaceDeclaration; + } +} 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 new file mode 100644 index 000000000..077d798c4 --- /dev/null +++ b/workflow-engine-support/src/main/java/cn/axzo/workflow/support/api/ManageServiceCodeGeneration.java @@ -0,0 +1,174 @@ +package cn.axzo.workflow.support.api; + +import com.github.javaparser.ParserConfiguration; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.Modifier; +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.expr.ClassExpr; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.ast.expr.StringLiteralExpr; +import com.github.javaparser.ast.expr.ThisExpr; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.ExpressionStmt; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.Type; +import com.github.javaparser.printer.DefaultPrettyPrinter; +import com.github.javaparser.printer.Printer; +import com.github.javaparser.utils.SourceRoot; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +/** + * 生成 WorkflowManageService 接口 + * + * @author wangli + * @since 2024/6/11 17:38 + */ +public class ManageServiceCodeGeneration { + + public static void generate() throws Exception { + String newClassName = "WorkflowManageService"; + + CompilationUnit testGeneric = genericCode("cn.axzo.workflow.starter.api", newClassName); + writeToStarter(testGeneric, newClassName); + + } + + private static void writeToStarter(CompilationUnit cu, String newClassName) { + String projectRootDir = System.getProperty("user.dir"); + String genericFilePath = Paths.get(projectRootDir, "/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/", newClassName + ".java").toString(); + try (FileWriter fileWriter = new FileWriter(genericFilePath)) { + fileWriter.append(cu.toString()); + } catch (IOException e) { + // nothing to do + } + } + + private static CompilationUnit genericCode(String pkg, String newClassName) throws Exception { + CompilationUnit cu = new CompilationUnit(pkg); + ClassOrInterfaceDeclaration interfaceDeclaration = setCommon(cu, newClassName); + + addMethods(interfaceDeclaration, cu); + addDefaultMethods(interfaceDeclaration); + return cu; + } + + private static void addDefaultMethods(ClassOrInterfaceDeclaration interfaceDeclaration) { + MethodDeclaration sync = createDefaultMethod("sync", interfaceDeclaration); + sync.setJavadocComment("强制使用‘同步’模式调用该方法,请在调用真实方法前调用该方法\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" +
+            "
"); + } + + private static MethodDeclaration createDefaultMethod(String methodName, ClassOrInterfaceDeclaration interfaceDeclaration) { + // 将sync方法添加到WorkflowCoreService类中 + MethodDeclaration methodDeclaration = interfaceDeclaration.addMethod(methodName, Modifier.Keyword.DEFAULT); + methodDeclaration.setType(new ClassOrInterfaceType("WorkflowManageService")); + + // 创建方法体 + BlockStmt methodBody = new BlockStmt(); + + MethodCallExpr setMethodCall = new MethodCallExpr(new NameExpr("ThreadUtil"), "set"); + setMethodCall.addArgument(new NameExpr(methodName.toUpperCase())); // 使用NameExpr来表示枚举常量 + ExpressionStmt setStatement = new ExpressionStmt(setMethodCall); + + methodBody.addStatement(setStatement); + // 创建return this;的语句 + ReturnStmt returnStmt = new ReturnStmt(new ThisExpr()); + methodBody.addStatement(returnStmt); + + // 将方法体设置到sync方法中 + methodDeclaration.setBody(methodBody); + return methodDeclaration; + } + + private static void addMethods(ClassOrInterfaceDeclaration interfaceDeclaration, CompilationUnit cu) throws Exception { + String projectRootDir = System.getProperty("user.dir"); + Path sourceCodeRoot = Paths.get(projectRootDir, "/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/"); + SourceRoot sourceRoot = new SourceRoot(sourceCodeRoot); + sourceRoot.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8); + Printer printer = new DefaultPrettyPrinter(); + sourceRoot.setPrinter(printer::print); + sourceRoot.tryToParse(); + + List compilationUnits = sourceRoot.getCompilationUnits(); + NodeList> targetMethods = interfaceDeclaration.getMembers(); + for (CompilationUnit apiCU : compilationUnits) { + // 如果类上包含 @Manageable 注解,则直接将所有方法解析进新目标接口中 + if (apiCU.getPrimaryType().filter(e -> e.getAnnotationByName("Manageable").isPresent()).isPresent()) { + addImports(apiCU, cu); + // 类上含有 @Manageable,不进行解析 + addMethod0(apiCU, targetMethods, false); + } else { + addImports(apiCU, cu); + addMethod0(apiCU, targetMethods, true); + } + } + } + + private static void addMethod0(CompilationUnit apiCU, NodeList> targetMethods, boolean containAnnotation) { + String interfaceName = apiCU.getPrimaryTypeName().get(); + List methods = apiCU.getInterfaceByName(interfaceName).get().findAll(MethodDeclaration.class); + for (MethodDeclaration method : methods) { + if (containAnnotation && method.getAnnotationByName("Manageable").isPresent()) { + MethodDeclaration methodDeclaration = method.clone(); + methodDeclaration.setType(changeReturnType(methodDeclaration.getType())); + targetMethods.add(methodDeclaration); + } else if (!containAnnotation && !method.getAnnotationByName("Manageable").isPresent()) { + MethodDeclaration methodDeclaration = method.clone(); + methodDeclaration.setType(changeReturnType(methodDeclaration.getType())); + targetMethods.add(methodDeclaration); + } + } + } + + private static Type changeReturnType(Type type) { + if (type instanceof ClassOrInterfaceType && type.asClassOrInterfaceType().getTypeArguments().isPresent()) { + NodeList types = type.asClassOrInterfaceType().getTypeArguments().orElse(new NodeList<>()); + if (types.size() != 1) { + throw new RuntimeException("workflow-engine-api 中的接口返回类型有误"); + } + return types.getFirst().get(); + } + return type; + } + + private static void addImports(CompilationUnit apiCU, CompilationUnit cu) { + for (ImportDeclaration sourceImport : apiCU.getImports()) { + cu.addImport(sourceImport.clone()); + } + } + + private static ClassOrInterfaceDeclaration setCommon(CompilationUnit cu, String newClassName) { + ClassOrInterfaceDeclaration classOrInterfaceDeclaration = cu.addInterface(newClassName).setPublic(true); + 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")) + .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); + cu.addImport("cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC", true, false); + return classOrInterfaceDeclaration; + } +}