+ * 当模型中使用了“业务节点”,且设置了“不设置审批人”模式,则当业务监听到 PROCESS_ACTIVITY_START 事件时,可通过该接口推动流程继续运行
*/
- @GetMapping("/api/process/activity/old/trigger")
- CommonResponse
+ * 当模型中使用了“业务节点”,且设置了“业务指定审批人”模式,则当业务监听到 PROCESS_ACTIVITY_WAIT_ASSIGNEE 事件时,可通过该接口设置动态设置审批人
+ *
+ * 注意:如果调用接口时,传入的审批人集合为空,流程引擎将对该审批流程实例自动中止。
*
* @param dto
* @return
*/
@PostMapping("/api/process/activity/assignee/set")
+ @Operation(summary = "业务节点设置审批人,不支持重复调用设置审批人,需一次性传入所有审批人")
CommonResponse
+ * 例如: {@link ProcessActivityApi#setAssignee(BpmnActivitySetAssigneeDTO)} 于 {@link ProcessInstanceApi#abortProcessInstance(BpmnProcessInstanceAbortDTO)}
+ * 两个接口并发访问时,由于 abort 先执行完,并提交事务后,setAssignee 方法内虽然有判断实例状态,但最后事务提交时突然发现实例状态被中止了就会抛出异常。
+ *
+ * @author wangli
+ * @since 2024/6/20 09:42
+ */
+@Component
+public class SQLIntegrityConstraintViolationExceptionHandlerAdvice extends AbstractExceptionApiResultHandler
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
index 1da62614e..f6a54b77a 100644
--- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEventListener.java
+++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/activity/RocketMqBpmActivityEventListener.java
@@ -25,15 +25,19 @@ 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_PROCESS_DEFINITION_KEY;
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;
@@ -179,13 +183,18 @@ public class RocketMqBpmActivityEventListener extends AbstractBpmnEventListener<
if (!sendMQ) {
return;
}
+ Map {
MessagePushEventImpl messagePushEvent =
MessagePushEventBuilder.createEvent(MessagePushEventType.PENDING_COMPLETE, null, noticeConfig,
- event.getProcessInstanceId(), null, null);
+ event.getProcessInstanceId(), parseProcessDefinitionKey(event.getProcessDefinitionId()), null, null);
log.info("发送完成实例下所有待办的消息: {}", JSONUtil.toJsonStr(messagePushEvent));
@@ -87,7 +87,7 @@ public class MessagePushProcessEventListener extends AbstractBpmnEventListener header = new HashMap<>();
+ if (StringUtils.hasText(dto.getProcessDefinitionKey())) {
+ log.warn("record process definition key: {}", dto.getProcessDefinitionKey());
+ header.put(MQ_OWNERSHIP_PROCESS_DEFINITION_KEY, dto.getProcessDefinitionKey());
+ }
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/task/MessagePushTaskEvent_103_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_103_Listener.java
index 8af0e6153..eb07a8387 100644
--- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_103_Listener.java
+++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/MessagePushTaskEvent_103_Listener.java
@@ -114,7 +114,8 @@ public class MessagePushTaskEvent_103_Listener extends AbstractBpmnEventListener
BpmnMetaParserHelper.getNodePendingConfig(userTask).ifPresent(noticeConfig::setPending);
MessagePushEventImpl event = MessagePushEventBuilder.createEvent(MessagePushEventType.PENDING_COMPLETE,
- null, noticeConfig, delegateTask.getProcessInstanceId(), null, delegateTask.getId());
+ null, noticeConfig, delegateTask.getProcessInstanceId(),
+ parseProcessDefinitionKey(delegateTask.getProcessDefinitionId()), null, delegateTask.getId());
log.info("发送完成待办的消息: {}, processInstanceId:{}", JSONUtil.toJsonStr(event), delegateTask.getProcessInstanceId());
eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey());
});
@@ -150,7 +151,8 @@ public class MessagePushTaskEvent_103_Listener extends AbstractBpmnEventListener
noticeConf,
processApproveConf.orElse(new BpmnApproveConf()),
processInstance.getProcessInstanceId(),
- processInstance.getProcessDefinitionId(), userTask.getId(),
+ processInstance.getProcessDefinitionId(),
+ processInstance.getProcessDefinitionKey(), userTask.getId(),
processInstance.getTenantId(), delegateTask.getId());
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_102_Listener.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_102_Listener.java
index 355dbd01d..d56610415 100644
--- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_102_Listener.java
+++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/listener/task/RocketMqBpmnTaskEvent_102_Listener.java
@@ -23,11 +23,14 @@ 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.FLOW_SERVER_VERSION_121;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO;
+import static cn.axzo.workflow.common.constant.BpmnConstants.MQ_OWNERSHIP_PROCESS_DEFINITION_KEY;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ASSIGNEE_SKIP_FLAT;
import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION;
import static cn.axzo.workflow.common.enums.ProcessTaskEventEnum.PROCESS_TASK_ASSIGNED;
@@ -103,9 +106,11 @@ public class RocketMqBpmnTaskEvent_102_Listener extends AbstractBpmnEventListene
public ProcessTaskDTO build(DelegateTask delegateTask, ProcessTaskEventEnum type) {
Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(delegateTask.getProcessDefinitionId()).getMainProcess());
+ String category = getDeployment(delegateTask.getProcessInstanceId()).getCategory();
ProcessTaskDTO dto = new ProcessTaskDTO()
.setType(type)
- .setCategory(getDeployment(delegateTask.getProcessInstanceId()).getCategory())
+ .setCategory(category)
+ .setProcessDefinitionKey(category)
.setProcessTaskId(delegateTask.getId())
.setProcessInstanceId(delegateTask.getProcessInstanceId())
.setCurrentElementKey(delegateTask.getTaskDefinitionKey())
@@ -131,13 +136,20 @@ public class RocketMqBpmnTaskEvent_102_Listener extends AbstractBpmnEventListene
if (!sendMQ) {
return;
}
+ Map
+ * 如果为 true 时,在不对 {@link BroadcastListenerProperties#filterApplicationNames} 设任何值时,默认会将当前应用名加入进去
+ */
+ private Boolean enableFilterApplicationName = false;
+
+ /**
+ * 仅过滤这些应用名称创建的流程实例
+ */
+ private Set
+ * 只有当 {@link BroadcastListenerProperties#enableFilterDefinitionKey} 才生效
+ *
+ * 注意: 如果 enableFilterDefinitionKey = true,但该属性集合为空, 将不会过滤任何消息
+ */
+ private Set
+ * 将整个待发送的事件内容通过 spring 的事件分发器发送出去, 现目前主要是记录 MQ 的发送记录
+ *
+ * @return
+ */
+ private BiConsumer
+ * 将发送前的 MQ 发送记录更新 MQ 组件自己的 MessageId 字段。
+ *
+ * @return
+ */
+ private BiConsumer
+ * 将 MQ 发送记录更新为删除状态,意为这类数据可以不关注,可以物理删除,但该功能还是用逻辑删除。
+ *
+ * @return
+ */
+ private BiConsumer
+ * 全量参数参考:META-INF/application.yml.demo
+ *
+ * @author wangli
+ * @since 2024/5/21 15:24
+ */
+@ConfigurationProperties(prefix = "workflow.engine.starter")
+public class WorkflowEngineStarterProperties {
+ /**
+ * 特殊用途,不建议接入方使用
+ */
+ private Boolean manageable = false;
+
+ /**
+ *
+ * 如果方法上有{@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 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..f7d7aefdf
--- /dev/null
+++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java
@@ -0,0 +1,294 @@
+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.config.CommonFeignConfiguration;
+import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO;
+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;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import javax.validation.constraints.NotBlank;
+import cn.axzo.workflow.common.annotation.InvokeMode;
+import cn.axzo.workflow.common.annotation.Manageable;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCarbonCopyDTO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCheckApproverDTO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
+import cn.axzo.workflow.common.model.response.BpmPageResult;
+import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO;
+import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO;
+import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO;
+import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO;
+import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import javax.annotation.Nullable;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Map;
+import 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.response.bpmn.task.BpmnHistoricTaskInstanceGroupVO;
+import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO;
+import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO;
+import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceVO;
+import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO;
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * Workflow Engine Starter Core Service
+ * 当模型中使用了“业务节点”,且设置了“不设置审批人”模式,则当业务监听到 PROCESS_ACTIVITY_START 事件时,可通过该接口推动流程继续运行
+ */
+ @GetMapping("/api/process/activity/trigger")
+ Boolean trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId);
+
+ /**
+ * 业务节点设置审批人, 不支持重复设置
+ *
+ * 当模型中使用了“业务节点”,且设置了“业务指定审批人”模式,则当业务监听到 PROCESS_ACTIVITY_WAIT_ASSIGNEE 事件时,可通过该接口设置动态设置审批人
+ *
+ * 注意:如果调用接口时,传入的审批人集合为空,流程引擎将对该审批流程实例自动中止。
+ *
+ * @param dto
+ * @return
+ */
+ @PostMapping("/api/process/activity/assignee/set")
+ @Operation(summary = "业务节点设置审批人,不支持重复调用设置审批人,需一次性传入所有审批人")
+ Boolean setAssignee(@Validated @RequestBody BpmnActivitySetAssigneeDTO dto);
+
+ /**
+ * 创建审批流程
+ *
+ *
+ * 偏向业务的接口,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)
+ Void changeStatus(@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
+ * 入参是二选一:当只有 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);
+
+ /**
+ * 获取指定业务分类
+ *
+ * @return
+ */
+ @GetMapping("/api/process/category/get")
+ @InvokeMode(SYNC)
+ CategoryItemVO get(@RequestParam Long id);
+
+ /**
+ * 获取指定业务分类集合
+ *
+ * @param ids
+ * @return
+ */
+ @GetMapping("/api/process/category/getByIds")
+ @InvokeMode(SYNC)
+ List
+ * 同一层级结构
+ */
+ @Operation(summary = "获取指定流程实例的审批过程信息")
+ @GetMapping("/api/process/task/list/flat")
+ @Manageable
+ @InvokeMode(SYNC)
+ List
+ * 分组结构
+ */
+ @Operation(summary = "获取指定流程实例的审批过程信息")
+ @GetMapping("/api/process/task/list/group")
+ @Manageable
+ @InvokeMode(SYNC)
+ List
+ * 可查看 {@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..cf4244523
--- /dev/null
+++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/common/exception/WorkflowEngineStarterException.java
@@ -0,0 +1,22 @@
+package cn.axzo.workflow.starter.common.exception;
+
+/**
+ * 流程引擎 starter 的异常类
+ *
+ * @author wangli
+ * @since 2024/5/21 17:57
+ */
+public class WorkflowEngineStarterException extends RuntimeException {
+
+ 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..017237e0e
--- /dev/null
+++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/feign/ext/ComplexInvokeClient.java
@@ -0,0 +1,245 @@
+package cn.axzo.workflow.starter.feign.ext;
+
+import cn.axzo.framework.rocketmq.EventProducer;
+import cn.axzo.workflow.common.enums.RpcInvokeModeEnum;
+import cn.axzo.workflow.common.model.response.mq.WorkflowEngineStarterRpcInvokeDTO;
+import cn.axzo.workflow.starter.WorkflowEngineStarterProperties;
+import cn.axzo.workflow.starter.common.exception.WorkflowEngineStarterException;
+import cn.axzo.workflow.starter.mq.retry.producer.RpcInvokeEventProducer;
+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.http.HttpStatus;
+import org.springframework.util.CollectionUtils;
+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.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 static cn.axzo.workflow.common.enums.RpcInvokeModeEnum.SYNC;
+import static cn.axzo.workflow.common.enums.WorkflowEngineEventEnum.WORKFLOW_ENGINE_STARTER;
+import static cn.axzo.workflow.common.constant.StarterConstants.STARTER_INVOKE_MODE;
+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 RpcInvokeEventProducer eventProducer;
+ private final Client feignClient;
+
+ public ComplexInvokeClient(WorkflowEngineStarterProperties starterProperties,
+ EventProducer eventProducer,
+ Client feignClient) {
+ this.starterProperties = starterProperties;
+ this.eventProducer = (RpcInvokeEventProducer) eventProducer;
+ this.feignClient = feignClient;
+ }
+
+ @Override
+ public Response execute(Request request, Request.Options options) throws IOException {
+ log.debug("ComplexInvokeClient execute... Url: {}", request.url());
+ RpcInvokeModeEnum currentInvokeModeEnum = getInvokeMode(request);
+ Map
+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE)
+ *
+ * @author wangli
+ * @since 2024/5/27 16:25
+ */
+public interface MessageNotificationEventHandler extends Ordered {
+
+ /**
+ * 站内信推送
+ *
+ * @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) {
+ }
+
+}
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..eeb3c2724
--- /dev/null
+++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessActivityEventHandler.java
@@ -0,0 +1,52 @@
+package cn.axzo.workflow.starter.handler;
+
+import cn.axzo.workflow.common.model.response.mq.ProcessActivityDTO;
+import org.springframework.core.Ordered;
+
+/**
+ * 流程节点相关事件
+ *
+ * 节点代表“流程配置”中的一个“审批节点”或“业务节点”,流程配置请按照以下路径去查看
+ *
+ * 注意:Order 的顺序,遵循值越小越优先。(取值范围:Integer. MIN_VALUE - Integer. MAX_VALUE)
+ *
+ * @author wangli
+ * @since 2024/5/27 16:25
+ */
+public interface ProcessActivityEventHandler extends Ordered {
+
+ /**
+ * 节点已启动
+ *
+ * @param dto 入参
+ */
+ default void onStart(ProcessActivityDTO dto) {
+ }
+
+ /**
+ * 节点等待业务指定审批人
+ *
+ * @param dto 入参
+ */
+ default void onWaitAssignee(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..f427ac927
--- /dev/null
+++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessInstanceEventHandler.java
@@ -0,0 +1,73 @@
+package cn.axzo.workflow.starter.handler;
+
+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 {
+
+ /**
+ * 流程实例创建成功后回调
+ *
+ * @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..abb3d896c
--- /dev/null
+++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/handler/ProcessTaskEventHandler.java
@@ -0,0 +1,45 @@
+package cn.axzo.workflow.starter.handler;
+
+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 {
+
+ /**
+ * 用户任务已指派审核人
+ */
+ default void onAssigned(ProcessTaskDTO dto) {
+ }
+
+ /**
+ * 用户任务已创建,未指派审核人
+ */
+ default void onCreated(ProcessTaskDTO dto) {
+ }
+
+ /**
+ * 用户任务已通过
+ *
+ * 仅审核通过一个用户任务时触发, 如果任务是驳回了, 则直接走实例撤回事件
+ */
+ default void onCompleted(ProcessTaskDTO dto) {
+ }
+
+ /**
+ * 用户任务已删除
+ *
+ * 删除不代表驳回或拒绝,因为通过也会走该事件
+ */
+ default void onDeleted(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..300f9c05f
--- /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.domain.ServiceException;
+import cn.axzo.framework.rocketmq.EventConsumer;
+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> test11() {
+ List
> getTenantIds() {
+ public CommonResponse
> getModelTenantIds() {
log.info("查询模型的租户集合getTenantIds");
return success(bpmnProcessModelService.getTenantIds());
}
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 37c1eba57..0a87ef7e1 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
@@ -39,7 +39,7 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Nullable;
import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
+import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -333,7 +333,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi {
@Operation(summary = "根据实例 ID 和自然人 ID 查询对应待处理的任务 ID")
@GetMapping("/batch/find")
@Override
- public CommonResponse
比如:
+ * 1. 在遇到异常时的处理策略
+ * 2. 过滤事件
+ *
+ *
+ * @author wangli
+ * @since 2024/6/4 14:14
+ */
+public class BroadcastListenerProperties {
+ /**
+ * 是否开启根据应用名过滤 MQ 事件
+ *
+ *
+ * 或者从“审批模板”中“业务 ID”进行查看
+ *
+ *
+ *
+ */
+ private Boolean enableFilterDefinitionKey = false;
+
+ /**
+ * 过滤出 MQ 事件中包含这些业务 ID 的事件
+ *
+ *
+ * 1、FAIL_OVER, 当前listener执行出错,在经历重试后,抛出异常,并将消息加入死信队列,然后继续往下执行(默认策略)
+ * 2、FAIL_FAST, 快速失败,不管内部是什么异常类型,都将吞掉异常,正确结束,不会增加死信队列计数
+ *
+ */
+ 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 Set3、FAIL_BACK, 失败自动恢复,在后台记录失败的消息,并按照一定的策略后期再进行重试,目前暂不支持
+ * > businessFilterProvider;
+ private List
> 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();
+ log.info("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")
+ @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
+ @ConditionalOnProperty(prefix = "workflow.engine.starter", value = "enable-dlq-monitor", havingValue = "true", matchIfMissing = true)
+ public WorkflowEngineStarterDefaultMQMonitor workflowEngineStarterDefaultMQMonitor(ObjectProvider
该参数只针对非容器环境生效
+ * 本地启动时,是否将本地的 MQ 消费者加入集群消费组
+ *
+ * 默认 false, 本地启动应用时, 将创建消息组名称中含有"debugging"的消费组.
+ * 否则, 本地启动应用时, 消费者将加入容器环境, 进行集群消费.
+ *
+ */
+ private Boolean joinContainerGroup = false;
+
+ /**
+ * 该参数只针对非容器环境生效
+ * 配合 joinContainerGroup 使用,且只在 joinContainerGroup = false 时生效
+ *
+ * 在本地有多台开发机同时启动时,又会组成新的集群消费,也会导致消息异常消费,
+ * 所以该参数就是为了创建完全唯一的消费者,避免本地开发机组成集群。
+ *
+ */
+ private String specialId;
+
+ /**
+ * WorkflowCoreService 类中所有方法未标记{@link InvokeMode}注解的方法调用时, 默认采用的模式
+ *
+ *
+ * 如果是同步调用,则直接通过普通 FeignClient 进行调用,
+ * 否则将通过 MQ 将 RPC 调用进行解耦
+ *
+ *
该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口
+ */
+@FeignClient(name = "workflow-engine-starter-core", url = "${axzo.service.workflow-engine:workflow-engine:8080}", configuration = WorkflowEngineStarterFeignConfiguration.class)
+public interface WorkflowCoreService {
+
+ /**
+ * 业务节点唤醒
+ *
+ * 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
+ * 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 dtos
+ * @return
+ */
+ @Operation(summary = "批量同意")
+ @PostMapping("/api/process/task/batch/approve")
+ BatchOperationResultVO batchApproveTask(@Validated @RequestBody List
+ * 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
+ * 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..78809f2da
--- /dev/null
+++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowManageService.java
@@ -0,0 +1,713 @@
+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.config.CommonFeignConfiguration;
+import cn.axzo.workflow.common.annotation.InvokeMode;
+import cn.axzo.workflow.common.annotation.Manageable;
+import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
+import cn.azxo.framework.common.model.CommonResponse;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import java.util.List;
+import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO;
+import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO;
+import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelUpdateDTO;
+import cn.axzo.workflow.common.model.response.BpmPageResult;
+import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelDetailVO;
+import cn.axzo.workflow.common.model.response.bpmn.model.BpmnModelExtVO;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO;
+import cn.axzo.workflow.common.model.request.bpmn.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.BpmnProcessInstanceMyPageReqVO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
+import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO;
+import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO;
+import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO;
+import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO;
+import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import javax.annotation.Nullable;
+import java.util.Map;
+import cn.axzo.workflow.common.model.request.category.CategoryConfigCreateDTO;
+import cn.axzo.workflow.common.model.request.category.CategoryConfigSearchDTO;
+import cn.axzo.workflow.common.model.request.category.CategoryCreateDTO;
+import cn.axzo.workflow.common.model.request.category.CategorySearchDTO;
+import cn.axzo.workflow.common.model.request.category.CategoryUpdateDTO;
+import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO;
+import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
+import org.springframework.web.bind.annotation.PathVariable;
+import cn.axzo.workflow.common.model.request.bpmn.RestBpmnProcessVariable;
+import cn.axzo.workflow.common.model.request.bpmn.task.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.response.bpmn.task.BpmnHistoricTaskInstanceGroupVO;
+import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO;
+import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO;
+import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceVO;
+import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO;
+import javax.validation.constraints.NotEmpty;
+import cn.axzo.workflow.common.model.request.bpmn.definition.BpmnProcessDefinitionUpdateDTO;
+import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessDefinitionPageDTO;
+import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO;
+
+/**
+ * Workflow Engine Starter Management Service
该类是根据 API 动态生成,不同版本可能会开放新的接口,或回收一些旧接口
+ */
+@FeignClient(name = "workflow-engine-starter-manage", url = "${axzo.service.workflow-engine:workflow-engine:8080}", configuration = WorkflowEngineStarterFeignConfiguration.class)
+public interface WorkflowManageService {
+
+ /**
+ * 获取流程操作按钮列表
+ *
+ * @return 流程操作按钮列表
+ */
+ @GetMapping("/api/process/config/button/list")
+ @InvokeMode(SYNC)
+ List
+ * 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 的消费者, 在本地启动或在容器中启动时, 能自主控制是否并入统一的消费组
+ *
+ * OMS -> 审批流程 -> 审批配置台 -> 流程配置
+ *
+ *