feat(REQ-4418) - 提交

This commit is contained in:
wangli 2025-09-03 15:00:40 +08:00
parent 344bdfd2ee
commit 033b938766
4 changed files with 123 additions and 46 deletions

View File

@ -158,9 +158,8 @@ public class WorkflowEngineStarterAutoConfiguration {
} }
@Bean @Bean
public ImplementationReadyChecker implementationReadyChecker(WorkflowCoreService workflowCoreService, public ImplementationReadyChecker implementationReadyChecker(WorkflowCoreService workflowCoreService) {
Environment environment) { return new ImplementationReadyChecker(workflowCoreService);
return new ImplementationReadyChecker(workflowCoreService, environment);
} }
// @Bean // @Bean

View File

@ -1,8 +1,6 @@
package cn.axzo.workflow.starter.api; package cn.axzo.workflow.starter.api;
import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.InvokeMode;
import cn.axzo.workflow.common.enums.AdminDataSource;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.dto.SignFileDTO; import cn.axzo.workflow.common.model.dto.SignFileDTO;
import cn.axzo.workflow.common.model.dto.SimpleDocDTO; import cn.axzo.workflow.common.model.dto.SimpleDocDTO;
import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO; import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO;
@ -141,16 +139,12 @@ public interface WorkflowCoreService {
@InvokeMode(SYNC) @InvokeMode(SYNC)
Boolean setTimeoutCallback(@Validated @RequestBody BpmnActivityTimeoutCallbackDTO dto); Boolean setTimeoutCallback(@Validated @RequestBody BpmnActivityTimeoutCallbackDTO dto);
@Operation(summary = "获取指定枚举类型的枚举值信息") /**
@GetMapping("/api/function/enum/admin-data-source/get") * 用于 Starter 检测必接事件的告警
@InvokeMode(SYNC) *
AdminDataSource getAdminDataSourceEnum(@RequestParam String enumValue); * @param dto
* @return
@Operation(summary = "获取指定枚举类型的枚举值信息") */
@GetMapping("/api/function/enum/process-instance-result/get")
@InvokeMode(SYNC)
BpmnProcessInstanceResultEnum getFileTypeEnum(@RequestParam String enumValue);
@Operation(summary = "发送钉钉消息") @Operation(summary = "发送钉钉消息")
@PostMapping("/api/function/dingtalk/alter") @PostMapping("/api/function/dingtalk/alter")
@InvokeMode(SYNC) @InvokeMode(SYNC)

View File

@ -2,6 +2,8 @@ package cn.axzo.workflow.starter.api;
import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.annotation.InvokeMode;
import cn.axzo.workflow.common.annotation.Manageable; import cn.axzo.workflow.common.annotation.Manageable;
import cn.axzo.workflow.common.enums.AdminDataSource;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO; import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO;
import cn.axzo.workflow.common.model.request.admin.ProcessAdminCreateDTO; 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.ProcessAdminDeleteDTO;
@ -194,6 +196,18 @@ public interface WorkflowManageService {
@InvokeMode(SYNC) @InvokeMode(SYNC)
Boolean trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId); Boolean trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId);
@Operation(summary = "获取指定枚举类型的枚举值信息")
@GetMapping("/api/function/enum/admin-data-source/get")
@Manageable
@InvokeMode(SYNC)
AdminDataSource getAdminDataSourceEnum(@RequestParam String enumValue);
@Operation(summary = "获取指定枚举类型的枚举值信息")
@GetMapping("/api/function/enum/process-instance-result/get")
@Manageable
@InvokeMode(SYNC)
BpmnProcessInstanceResultEnum getFileTypeEnum(@RequestParam String enumValue);
@PostMapping("/api/form/admin/form/page") @PostMapping("/api/form/admin/form/page")
@InvokeMode(SYNC) @InvokeMode(SYNC)
@Manageable @Manageable

View File

@ -1,15 +1,23 @@
package cn.axzo.workflow.starter.mq.check; package cn.axzo.workflow.starter.mq.check;
import cn.axzo.framework.rocketmq.DefaultEventConsumer;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.framework.rocketmq.EventHandlerRepository;
import cn.axzo.workflow.common.enums.ProcessInstanceEventEnum;
import cn.axzo.workflow.common.model.request.feature.DingTalkStarterAlterDTO; import cn.axzo.workflow.common.model.request.feature.DingTalkStarterAlterDTO;
import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO; import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO;
import cn.axzo.workflow.starter.api.WorkflowCoreService; import cn.axzo.workflow.starter.api.WorkflowCoreService;
import cn.axzo.workflow.starter.handler.ProcessInstanceEventHandler; import cn.axzo.workflow.starter.handler.ProcessInstanceEventHandler;
import com.google.common.collect.ListMultimap;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -34,28 +42,60 @@ public class ImplementationReadyChecker implements ApplicationListener<Applicati
"onAborted" "onAborted"
); );
private static final List<Event.EventCode> EVENT_CODES = Arrays.asList(
ProcessInstanceEventEnum.PROCESS_INSTANCE_COMPLETED.getEventCode(),
ProcessInstanceEventEnum.PROCESS_INSTANCE_CANCELLED.getEventCode(),
ProcessInstanceEventEnum.PROCESS_INSTANCE_REJECTED.getEventCode(),
ProcessInstanceEventEnum.PROCESS_INSTANCE_ABORTED.getEventCode()
);
private final WorkflowCoreService workflowCoreService; private final WorkflowCoreService workflowCoreService;
private final Environment environment;
public ImplementationReadyChecker(WorkflowCoreService workflowCoreService, public ImplementationReadyChecker(WorkflowCoreService workflowCoreService) {
Environment environment) {
this.workflowCoreService = workflowCoreService; this.workflowCoreService = workflowCoreService;
this.environment = environment;
} }
@Override @Override
public void onApplicationEvent(ApplicationReadyEvent event) { public void onApplicationEvent(ApplicationReadyEvent event) {
ApplicationContext context = event.getApplicationContext(); ApplicationContext context = event.getApplicationContext();
// 获取所有实现了目标接口的Bean boolean[] methodImplemented = checkUnImplementedMethodsWithInterface(context);
Map<String, ProcessInstanceEventHandler> handlers = context.getBeansOfType(TARGET_INTERFACE); if (getBinaryResult(methodImplemented) == 15) {
if (handlers.isEmpty()) { log.info("Congratulations, passed the verification");
log.warn("未找到实现 {} 接口的Bean, 请确保至少有一个实现类被正确扫描和注册。", TARGET_INTERFACE.getName()); log.info("祝贺,已通过必接事件的校验!但仍然请确保您的实现类逻辑正确无误,不允许出现空实现,否则生产问题自行负责!");
return;
}
log.info("将继续通过 EventConsumer 进行检查...");
methodImplemented = checkUnImplementedMethodsWithEventConsumer(context);
if (getBinaryResult(methodImplemented) == 15) {
log.info("Congratulations, passed the verification");
log.info("祝贺,已通过必接事件的校验!但仍然请确保您的实现类逻辑正确无误,不允许出现空实现,否则生产问题自行负责!");
} else {
log.error("----------------------------------------");
log.error("警告:未通过必接事件的校验!未实现的方法:{},请确保使用了实现了 {} 接口的Bean被Spring扫描或者在EventConsumer注册了 ProcessInstanceEventEnum 的 EventCode。", String.join(", ", getUnImplementedMethods(methodImplemented)), TARGET_INTERFACE.getName());
log.error("----------------------------------------");
sendAlter(context, getUnImplementedMethods(methodImplemented));
} }
}
private void sendAlter(ApplicationContext context, List<String> unImplementedMethods) {
Environment environment = context.getEnvironment();
DingTalkStarterAlterDTO dto = new DingTalkStarterAlterDTO();
dto.setApplicationName(environment.getProperty("spring.application.name"));
dto.setProfile(environment.getProperty("spring.profiles.active"));
dto.setAlterContent("必接事件实现类检查未通过,未实现的方法: " + String.join(", ", unImplementedMethods) + "。请确保至少有一个实现类覆盖这些方法以处理相应的事件。");
workflowCoreService.sendDingtalk(dto);
}
private static boolean[] checkUnImplementedMethodsWithInterface(ApplicationContext context) {
// 记录每个方法是否被实现 // 记录每个方法是否被实现
boolean[] methodImplemented = new boolean[4]; boolean[] methodImplemented = new boolean[4];
Arrays.fill(methodImplemented, false); Arrays.fill(methodImplemented, false);
// 获取所有实现了目标接口的Bean
Map<String, ProcessInstanceEventHandler> handlers = context.getBeansOfType(TARGET_INTERFACE);
if (handlers.isEmpty()) {
log.warn("未找到实现 {} 接口的Bean ", TARGET_INTERFACE.getName());
return methodImplemented;
}
// 检查每个实现类 // 检查每个实现类
for (ProcessInstanceEventHandler handler : handlers.values()) { for (ProcessInstanceEventHandler handler : handlers.values()) {
@ -75,7 +115,58 @@ public class ImplementationReadyChecker implements ApplicationListener<Applicati
} }
} }
} }
int binaryResult = getBinaryResult(methodImplemented);
List<String> unimplementedMethods = getUnImplementedMethods(methodImplemented);
log.info("实现 {} 接口的Bean数量: {}", TARGET_INTERFACE.getName(), handlers.size());
log.info("方法实现情况:(二进制:{})(十进制:{})", String.format("%4s", Integer.toBinaryString(binaryResult)).replace(' ', '0'), binaryResult);
if (!unimplementedMethods.isEmpty()) {
log.warn("未通过 Starter 提供的 {} 接口实现中找到以下方法的实现: {}。请确保至少有一个实现类覆盖这些方法以处理相应的事件。", TARGET_INTERFACE.getName(), String.join(", ", unimplementedMethods));
}
return methodImplemented;
}
private static boolean[] checkUnImplementedMethodsWithEventConsumer(ApplicationContext context) {
// 记录每个方法是否被实现
boolean[] methodImplemented = new boolean[4];
Arrays.fill(methodImplemented, false);
Map<String, EventConsumer> beansOfType = context.getBeansOfType(EventConsumer.class);
beansOfType.forEach((name, c) -> {
if (c instanceof DefaultEventConsumer) {
try {
Field repoField = DefaultEventConsumer.class.getDeclaredField("handlerRepository");
repoField.setAccessible(true);
EventHandlerRepository repo = (EventHandlerRepository) repoField.get(c);
Field handlersField = EventHandlerRepository.class.getDeclaredField("handlers");
handlersField.setAccessible(true);
@SuppressWarnings("unchecked")
ListMultimap<Event.EventCode, EventHandler> eventHandlers =
(ListMultimap<Event.EventCode, EventHandler>) handlersField.get(repo);
for (int i = 0; i < EVENT_CODES.size(); i++) {
methodImplemented[i] = eventHandlers.keySet().contains(EVENT_CODES.get(i));
}
} catch (Exception e) {
log.warn("通过 EventConsumer 检测必接事件异常:{}", e.getMessage(), e);
}
}
});
return methodImplemented;
}
private static List<String> getUnImplementedMethods(boolean[] methodImplemented) {
// 收集未实现的方法
List<String> unimplementedMethods = new ArrayList<>();
for (int i = 0; i < methodImplemented.length; i++) {
if (!methodImplemented[i]) {
unimplementedMethods.add(METHOD_NAMES.get(i));
}
}
return unimplementedMethods;
}
private static int getBinaryResult(boolean[] methodImplemented) {
// 生成4位二进制数 // 生成4位二进制数
int binaryResult = 0; int binaryResult = 0;
for (int i = 0; i < methodImplemented.length; i++) { for (int i = 0; i < methodImplemented.length; i++) {
@ -85,27 +176,6 @@ public class ImplementationReadyChecker implements ApplicationListener<Applicati
binaryResult += weight; binaryResult += weight;
} }
} }
return binaryResult;
// 收集未实现的方法
List<String> unimplementedMethods = new ArrayList<>();
for (int i = 0; i < methodImplemented.length; i++) {
if (!methodImplemented[i]) {
unimplementedMethods.add(METHOD_NAMES.get(i));
}
}
log.info("实现 {} 接口的Bean数量: {}", TARGET_INTERFACE.getName(), handlers.size());
log.info("方法实现情况:(二进制:{})(十进制:{})", String.format("%4s", Integer.toBinaryString(binaryResult)).replace(' ', '0'), binaryResult);
if (!unimplementedMethods.isEmpty()) {
log.error("以下方法未被任何实现类覆盖: {}。请确保至少有一个实现类覆盖这些方法以处理相应的事件。", String.join(", ", unimplementedMethods));
DingTalkStarterAlterDTO dto = new DingTalkStarterAlterDTO();
dto.setApplicationName(environment.getProperty("spring.application.name"));
dto.setProfile(environment.getProperty("spring.profiles.active"));
dto.setAlterContent("必接事件实现类检查未通过,未实现的方法: " + String.join(", ", unimplementedMethods) + "。请确保至少有一个实现类覆盖这些方法以处理相应的事件。");
workflowCoreService.sendDingtalk(dto);
} else {
log.info("Congratulations, passed the verification");
log.info("祝贺,已通过必接事件的校验!但仍然请确保您的实现类逻辑正确无误,不允许出现空实现,否则生产问题自行负责!");
}
} }
} }