perf: 收敛工作流状态与高 IO 节点开销
- 落地 Redis 版本状态、触发租约和定义缓存 - 优化数据批写、插件请求、文件下载与审计日志 - 补齐循环范围校验、轮询兼容和专项测试
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user