perf: 收敛工作流状态与高 IO 节点开销

- 落地 Redis 版本状态、触发租约和定义缓存

- 优化数据批写、插件请求、文件下载与审计日志

- 补齐循环范围校验、轮询兼容和专项测试
This commit is contained in:
2026-07-29 00:47:48 +08:00
parent 5ee6065017
commit 1ae8a22afe
103 changed files with 15600 additions and 532 deletions

View File

@@ -6,6 +6,9 @@ import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* Action 请求与响应日志采集配置。
*/
@Component
@ConfigurationProperties(prefix = "easyflow.log.reporter")
public class ActionLogReporterProperties {
@@ -35,6 +38,7 @@ public class ActionLogReporterProperties {
"/css/**",
"/images/**",
"/favicon.ico",
"/api/v1/agent/media/**",
"/actuator/**",
"*.js",
"*.css",
@@ -45,36 +49,75 @@ public class ActionLogReporterProperties {
);
// getter and setter
/**
* 判断 Action 报告是否启用。
*
* @return 是否启用
*/
public boolean isEnabled() {
return enabled;
}
/**
* 设置 Action 报告开关。
*
* @param enabled 是否启用
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* 获取日志采样率。
*
* @return 采样率
*/
public double getSampleRate() {
return sampleRate;
}
/**
* 设置日志采样率。
*
* @param sampleRate 采样率
*/
public void setSampleRate(double sampleRate) {
this.sampleRate = sampleRate;
}
/**
* 获取需要采集的路径模式。
*
* @return 包含路径模式
*/
public List<String> getIncludePatterns() {
return includePatterns;
}
/**
* 设置需要采集的路径模式。
*
* @param includePatterns 包含路径模式
*/
public void setIncludePatterns(List<String> includePatterns) {
this.includePatterns = includePatterns;
}
/**
* 获取禁止缓存正文的路径模式。
*
* @return 排除路径模式
*/
public List<String> getExcludePatterns() {
return excludePatterns;
}
/**
* 设置禁止缓存正文的路径模式。
*
* @param excludePatterns 排除路径模式
*/
public void setExcludePatterns(List<String> excludePatterns) {
this.excludePatterns = excludePatterns;
}
}
}

View File

@@ -3,7 +3,6 @@ package tech.easyflow.log.reporter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -15,7 +14,7 @@ import java.io.IOException;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
/**
* 响应缓存 Filter支持基于路径的排除规则
* 缓存需要记录的请求和响应正文,并绕过流式或敏感路径。
*/
@Component
@Order(HIGHEST_PRECEDENCE)
@@ -27,10 +26,20 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
)
public class ResponseCachingFilter implements Filter {
@Autowired
private ActionLogReporterProperties logProperties;
private final ActionLogReporterProperties logProperties;
/**
* 创建响应缓存过滤器。
*
* @param logProperties 日志采集路径配置
*/
public ResponseCachingFilter(ActionLogReporterProperties logProperties) {
this.logProperties = logProperties;
}
/**
* {@inheritDoc}
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
@@ -39,25 +48,19 @@ public class ResponseCachingFilter implements Filter {
String uri = httpRequest.getRequestURI();
String method = httpRequest.getMethod();
// 1如果是 OPTIONS 请求,跳过(通常预检
// OPTIONS 请求通常预检,不需要缓存正文。
if ("OPTIONS".equalsIgnoreCase(method)) {
chain.doFilter(request, response);
return;
}
// // 检查是否为 SSE 请求
// if (isSseRequest(httpRequest)) {
// chain.doFilter(request, response);
// return;
// }
// 检查是否匹配排除路径
// 流式下载和敏感媒体路径必须在包装响应前排除,避免截断异步响应或缓存文件正文。
if (isExcluded(uri)) {
chain.doFilter(request, response);
return;
}
// 检查是否匹配包含路径(一般为 /**,可省略
// 检查是否匹配包含路径(一般为 /**
if (!isIncluded(uri)) {
chain.doFilter(request, response);
return;
@@ -65,31 +68,45 @@ public class ResponseCachingFilter implements Filter {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpRequest);
if (isSseRequest(httpRequest)) {
// SSE 请求不缓存
// SSE 响应不能经过响应缓存,否则会破坏实时输出。
chain.doFilter(requestWrapper, response);
return;
}
HttpServletResponse httpResponse = (HttpServletResponse) response;
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpResponse);
try {
chain.doFilter(requestWrapper, responseWrapper);
} finally {
responseWrapper.copyBodyToResponse(); // 必须调用
responseWrapper.copyBodyToResponse();
}
}
/**
* 判断请求路径是否禁止缓存。
*
* @param uri 请求路径
* @return 是否排除
*/
private boolean isExcluded(String uri) {
return logProperties.getExcludePatterns().stream().anyMatch(p -> match(uri, p));
}
/**
* 判断请求路径是否需要采集。
*
* @param uri 请求路径
* @return 是否包含
*/
private boolean isIncluded(String uri) {
return logProperties.getIncludePatterns().stream().anyMatch(p -> match(uri, p));
}
/**
* 判断是否为 SSE 请求(基于标准 Accept 头)
* 根据标准 Accept 请求头判断是否为 SSE 请求。
*
* @param request HTTP 请求
* @return 是否为 SSE 请求
*/
private boolean isSseRequest(HttpServletRequest request) {
String accept = request.getHeader("Accept");
@@ -97,8 +114,11 @@ public class ResponseCachingFilter implements Filter {
}
/**
* 简单的路径匹配支持 * 和 **
* 注意:这里简化实现,生产可替换为 AntPathMatcher
* 匹配支持星号和双星号的简单路径模式。
*
* @param path 请求路径
* @param pattern 路径模式
* @return 是否匹配
*/
private boolean match(String path, String pattern) {
if (pattern.equals("/**")) {
@@ -119,4 +139,4 @@ public class ResponseCachingFilter implements Filter {
}
return path.equals(pattern);
}
}
}

View File

@@ -0,0 +1,35 @@
package tech.easyflow.log.reporter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link ResponseCachingFilter} 路径排除行为测试。
*/
public class ResponseCachingFilterTest {
/**
* 验证 Agent 媒体下载保持原始响应,避免缓存包装器截断异步文件流。
*
* @throws Exception Filter 执行失败
*/
@Test
public void agentMediaDownloadShouldBypassResponseCaching() throws Exception {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);
when(request.getMethod()).thenReturn("GET");
when(request.getRequestURI()).thenReturn("/api/v1/agent/media/document/content");
ResponseCachingFilter filter = new ResponseCachingFilter(new ActionLogReporterProperties());
filter.doFilter(request, response, chain);
verify(chain).doFilter(request, response);
}
}