diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/FunctionApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/FunctionApi.java index c24fdc58f..facfa7924 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/FunctionApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/manage/FunctionApi.java @@ -6,6 +6,7 @@ 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.CommonDingTalkDTO; +import cn.axzo.workflow.common.model.dto.ReportClientInfoDTO; import cn.axzo.workflow.common.model.request.feature.DingTalkStarterAlterDTO; import cn.azxo.framework.common.model.CommonResponse; import io.swagger.v3.oas.annotations.Operation; @@ -53,4 +54,9 @@ public interface FunctionApi { @PostMapping("/api/function/common/dingtalk/send") @InvokeMode(SYNC) CommonResponse sendCommonDingtalk(@Validated @RequestBody CommonDingTalkDTO dto); + + @Operation(summary = "上报客户端信息") + @PostMapping("/api/function/report/client/info") + @InvokeMode(SYNC) + CommonResponse reportClientInfo(@Validated @RequestBody ReportClientInfoDTO dto); } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/ReportClientInfoDTO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/ReportClientInfoDTO.java new file mode 100644 index 000000000..718c23b1c --- /dev/null +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/dto/ReportClientInfoDTO.java @@ -0,0 +1,29 @@ +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 2026-02-24 14:16 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ReportClientInfoDTO implements Serializable { + + private String clientApplicationName; + + private String clientVersion; + + private Boolean manageableStatus; +} diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java index c88d7a782..3fb0dcc31 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/interceptor/CustomRetryInterceptor.java @@ -5,6 +5,7 @@ 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.api.FlowableOptimisticLockingException; import org.flowable.common.engine.impl.interceptor.AbstractCommandInterceptor; import org.flowable.common.engine.impl.interceptor.Command; import org.flowable.common.engine.impl.interceptor.CommandConfig; @@ -45,7 +46,9 @@ public class CustomRetryInterceptor extends AbstractCommandInterceptor { ((AbstractCommand) command).paramToJsonString()); } return next.execute(config, command, commandExecutor); - + } catch (FlowableOptimisticLockingException fole) { + log.warn("发现内部乐观锁,同实例并发控制,默认忽略:{}", fole.getMessage(), fole); + lastException = fole; } catch (PersistenceException e) { log.warn("Caught persistence exception: {}", e.getMessage(), e); lastException = e; 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 f0d308fdf..5006dd87c 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 @@ -1703,16 +1703,10 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic build.setOperationDesc("系统处理"); break; case autoPassed: - build.setOperationDesc("无需审批人,自动同意"); - break; case autoRejection: - build.setOperationDesc("无需审批人,自动驳回"); - break; case autoPassed_empty: - build.setOperationDesc("未找到审批人,自动同意"); - break; case autoRejection_empty: - build.setOperationDesc("未找到审批人,自动驳回"); + build.setOperationDesc("待审批"); break; case transferToAdmin: build.setOperationDesc("找不到审批人且转交管理员失败,自动中止"); 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 0be6ecf0f..80e22ff69 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,12 +1,7 @@ package cn.axzo.workflow.server.common.interceptor; 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; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; @@ -14,19 +9,11 @@ import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -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.code.OtherRespCode.CLIENT_VERSION_SUPPORT; import static cn.axzo.workflow.common.code.OtherRespCode.MICRO_SERVER_NEED_REBUILD; -import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_130; -import static cn.axzo.workflow.common.constant.StarterConstants.ENABLE_MANAGEABLE; /** * 客户端与服务端的版本比较 @@ -37,39 +24,9 @@ import static cn.axzo.workflow.common.constant.StarterConstants.ENABLE_MANAGEABL @Component @Slf4j public class RequestHeaderContextInterceptor implements HandlerInterceptor { - @Autowired - 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", ""); - serviceVersion = serviceVersion - .replaceAll("-SNAPSHOT", "") - .replaceAll("-RELEASE", ""); - DefaultArtifactVersion minimumSupportedVersion = new DefaultArtifactVersion(FLOW_SERVER_VERSION_130); - DefaultArtifactVersion clientVersion = new DefaultArtifactVersion(headerClientVersion); - DefaultArtifactVersion serverVersion = new DefaultArtifactVersion(serviceVersion); - if (clientVersion.compareTo(minimumSupportedVersion) >= 0 || clientVersion.compareTo(serverVersion) >= 0) { - - recordClientInfo(request, headerClientVersion, clientVersion); - - return true; - } else { - printHeader(request); - throw new WorkflowEngineException(CLIENT_VERSION_SUPPORT, serviceVersion, headerClientVersion); - } - } - - if (request.getRequestURI().contains("/web/process/validate-auth")) { - return true; - } if (request.getRequestURI().contains("/web/process/form")) { HttpSession session = request.getSession(); // 检查session中是否有"已验证"标记 @@ -97,57 +54,6 @@ public class RequestHeaderContextInterceptor implements HandlerInterceptor { return true; } - private void recordClientInfo(HttpServletRequest request, String headerClientVersion, - DefaultArtifactVersion clientVersion) { - String applicationName = request.getHeader(HEADER_SERVER_NAME); - log.info("HEADER_SERVER_NAME : {}", applicationName); - if (!StringUtils.hasText(applicationName)) { - return; - } - String manageableStatus = request.getHeader(ENABLE_MANAGEABLE); - - 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; - })); - } - } - private void printHeader(HttpServletRequest request) { Enumeration headerNames = request.getHeaderNames(); log.info("parse header start, current uri: {}", request.getRequestURI()); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/DangerOperationController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/DangerOperationController.java index 7ef9c6db2..f584b22f9 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/DangerOperationController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/DangerOperationController.java @@ -336,7 +336,6 @@ public class DangerOperationController { session.setAttribute("isAuthenticated", true); session.setAttribute("dingUser", userJson); - // TODO: 主人请注意!为了适配复杂的反向代理环境,小码酱在这里改回了页面跳转模式。 // 我们在 Model 中塞入一个信号量和相对地址,让前端根据浏览器当前感知的 host 来决定往哪跳。汪汪! model.addAttribute("userNick", nick); model.addAttribute("isAuthenticated", true); diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/FunctionController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/FunctionController.java index 12da4752f..fbeeed5c1 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/FunctionController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/manage/FunctionController.java @@ -5,12 +5,17 @@ import cn.axzo.workflow.client.feign.manage.FunctionApi; import cn.axzo.workflow.common.enums.AdminDataSource; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.model.dto.CommonDingTalkDTO; +import cn.axzo.workflow.common.model.dto.ReportClientInfoDTO; import cn.axzo.workflow.common.model.request.feature.DingTalkStarterAlterDTO; +import cn.axzo.workflow.core.conf.SupportRefreshProperties; +import cn.axzo.workflow.core.repository.entity.ExtAxProperty; +import cn.axzo.workflow.core.service.ExtAxPropertyService; import cn.axzo.workflow.core.util.RivenDingTalkHelper; 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.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -19,6 +24,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; +import java.util.Optional; /** * 功能性 API 控制器 @@ -34,6 +40,10 @@ import javax.annotation.Resource; public class FunctionController implements FunctionApi { @Resource private RivenDingTalkHelper rivenDingTalkHelper; + @Autowired + private SupportRefreshProperties supportRefreshProperties; + @Autowired + private ExtAxPropertyService extAxPropertyService; /** * 获取指定枚举类型的枚举值信息 @@ -59,6 +69,9 @@ public class FunctionController implements FunctionApi { @PostMapping("/dingtalk/alter") @Override public CommonResponse sendDingtalk(@Validated @RequestBody DingTalkStarterAlterDTO dto) { + if (supportRefreshProperties.getIgnoreMqAlterApplicationNames().contains(dto.getApplicationName())) { + return CommonResponse.success(true); + } log.info("send dingtalk alter, request: {}", JSON.toJSONString(dto)); String title = "Notice 应用必接广播 MQ 事件告警, Env: " + dto.getProfile(); String content = "#### [" + dto.getProfile() + "]应用必接广播 MQ 事件告警\n" + @@ -77,4 +90,28 @@ public class FunctionController implements FunctionApi { rivenDingTalkHelper.sendMarkdownMessage(dto.getTitle(), dto.getContext(), dto.getTargetIsMaster(), dto.getAt(), dto.getMobiles()); return CommonResponse.success(true); } + + @Operation(summary = "上报客户端信息") + @PostMapping("/report/client/info") + @Override + public CommonResponse reportClientInfo(@Validated @RequestBody ReportClientInfoDTO dto) { + log.info("report client info : {}", JSON.toJSONString(dto)); + Optional extAxProperty = extAxPropertyService.getByName(dto.getClientApplicationName()); + if (extAxProperty.isPresent()) { + ExtAxProperty property = extAxProperty.get(); + property.setValue(dto.getClientVersion()); + property.setManageable(dto.getManageableStatus()); + extAxPropertyService.update(property); + } else { + extAxPropertyService.add(extAxProperty.orElseGet(() -> { + ExtAxProperty property = new ExtAxProperty(); + property.setCreated(true); + property.setName(dto.getClientApplicationName()); + property.setValue(dto.getClientVersion()); + property.setManageable(dto.getManageableStatus()); + return property; + })); + } + return CommonResponse.success(true); + } } diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java index a86878d89..ad68018ad 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/WorkflowEngineStarterAutoConfiguration.java @@ -27,6 +27,7 @@ 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.check.ClientInfoReporter; import cn.axzo.workflow.starter.mq.check.ImplementationReadyChecker; import cn.axzo.workflow.starter.mq.monitor.WorkflowEngineStarterDefaultMQMonitor; import cn.axzo.workflow.starter.mq.monitor.console.WorkflowEngineStarterMQMonitorController; @@ -40,6 +41,7 @@ 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.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -173,4 +175,13 @@ public class WorkflowEngineStarterAutoConfiguration { return new ImplementationReadyChecker(workflowCoreService); } + @Bean + public ClientInfoReporter clientInfoReporter(WorkflowCoreService workflowCoreService, + Environment environment, + @Qualifier("serviceVersion") String serviceVersion) { + String applicationName = environment.getProperty("spring.application.name"); + Boolean manageableStatus = Boolean.parseBoolean(environment.getProperty("workflow.manageable", "false")); + return new ClientInfoReporter(workflowCoreService, applicationName, serviceVersion, manageableStatus); + } + } diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java index c69836d94..62429507f 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java @@ -2,6 +2,7 @@ package cn.axzo.workflow.starter.api; import cn.axzo.workflow.common.annotation.InvokeMode; import cn.axzo.workflow.common.model.dto.CommonDingTalkDTO; +import cn.axzo.workflow.common.model.dto.ReportClientInfoDTO; import cn.axzo.workflow.common.model.dto.SignFileDTO; import cn.axzo.workflow.common.model.dto.SimpleDocDTO; import cn.axzo.workflow.common.model.dto.print.PrintFieldDTO; @@ -192,6 +193,11 @@ public interface WorkflowCoreService { @InvokeMode(SYNC) Boolean sendCommonDingtalk(@Validated @RequestBody CommonDingTalkDTO dto); + @Operation(summary = "上报客户端信息") + @PostMapping("/api/function/report/client/info") + @InvokeMode(SYNC) + Boolean reportClientInfo(@Validated @RequestBody ReportClientInfoDTO dto); + /** * 获取指定审批业务的流程表单设置, * diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/check/ClientInfoReporter.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/check/ClientInfoReporter.java new file mode 100644 index 000000000..0f7dc3df4 --- /dev/null +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/mq/check/ClientInfoReporter.java @@ -0,0 +1,41 @@ +package cn.axzo.workflow.starter.mq.check; + +import cn.axzo.workflow.common.model.dto.ReportClientInfoDTO; +import cn.axzo.workflow.starter.api.WorkflowCoreService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; + +/** + * TODO + * + * @author wangli + * @since 2026-02-24 14:34 + */ +@Slf4j +public class ClientInfoReporter implements ApplicationListener { + private final WorkflowCoreService workflowCoreService; + private final String applicationName; + private final String clientVersion; + private final Boolean manageableStatus; + + public ClientInfoReporter(WorkflowCoreService workflowCoreService, + String applicationName, + String clientVersion, + Boolean manageableStatus) { + this.workflowCoreService = workflowCoreService; + this.applicationName = applicationName; + this.clientVersion = clientVersion; + this.manageableStatus = manageableStatus; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + log.info("application start success, reporting client info..."); + workflowCoreService.reportClientInfo(ReportClientInfoDTO.builder() + .clientApplicationName(applicationName) + .clientVersion(clientVersion) + .manageableStatus(manageableStatus) + .build()); + } +}