Merge branch 'feature/improve_starter' into pre

This commit is contained in:
wangli 2026-02-24 15:07:58 +08:00
commit be7c81559f
9 changed files with 134 additions and 96 deletions

View File

@ -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<Boolean> sendCommonDingtalk(@Validated @RequestBody CommonDingTalkDTO dto);
@Operation(summary = "上报客户端信息")
@PostMapping("/api/function/report/client/info")
@InvokeMode(SYNC)
CommonResponse<Boolean> reportClientInfo(@Validated @RequestBody ReportClientInfoDTO dto);
}

View File

@ -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;
}

View File

@ -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<T>) 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;

View File

@ -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<String> 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> 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> 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> 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<String> headerNames = request.getHeaderNames();
log.info("parse header start, current uri: {}", request.getRequestURI());

View File

@ -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);

View File

@ -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<Boolean> 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<Boolean> reportClientInfo(@Validated @RequestBody ReportClientInfoDTO dto) {
log.info("report client info : {}", JSON.toJSONString(dto));
Optional<ExtAxProperty> 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
/**
* 获取指定审批业务的流程表单设置
*

View File

@ -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<ApplicationReadyEvent> {
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());
}
}