You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Boot Admin中使用InstanceExchangeFilterFunction记录Spring WebClient的JSON请求体

如何在Spring Boot Admin中使用InstanceExchangeFilterFunction记录Spring WebClient的JSON请求体

兄弟,我太懂你这个痛点了!在Spring Boot Admin(SBA)里基于Reactor栈拦截请求Payload确实比传统同步过滤器麻烦不少,官方文档这块又没讲透,踩坑很正常。你之前用同步过滤器遇到的“请求体只能读一次”的问题,本质上是因为Reactor用的是流式的请求处理,一旦消费了请求体数据流,后续流程就拿不到了——不过别担心,咱们用Reactor的方式就能解决这个问题。

核心思路:缓存请求体并重构请求

要在InstanceExchangeFilterFunction里安全读取JSON请求体,关键是先把请求体缓存下来,同时要给后续流程留一份可用的请求体。具体来说就是:

  1. 先读取并缓存请求体的字符串内容
  2. 重构一个新的ClientRequest,把缓存的请求体重新写入
  3. 处理缓存的请求体(比如解析JSON、记录审计日志)
  4. 用重构后的请求继续执行后续流程

完整代码示例

下面是针对你的场景(拦截/actuator/loggers/的POST请求,记录日志级别修改)的完整实现:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.ClientRequest;
import de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction;
import java.nio.charset.StandardCharsets;

@Configuration
public class AuditLogInterceptorReactorConfig {

    private static final Logger log = LoggerFactory.getLogger(AuditLogInterceptorReactorConfig.class);
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Bean
    public InstanceExchangeFilterFunction auditLog() {
        return (instance, request, next) -> {
            // 只匹配目标POST请求
            if (HttpMethod.POST.equals(request.method())
                    && request.url().getPath().contains("/actuator/loggers/")) {

                // 1. 缓存请求体为字符串Mono,同时释放缓冲区避免内存泄漏
                Mono<String> requestBodyMono = request.body()
                        .map(dataBuffer -> {
                            byte[] bytes = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(bytes);
                            DataBufferUtils.release(dataBuffer);
                            return new String(bytes, StandardCharsets.UTF_8);
                        })
                        .single();

                // 2. 重构请求,把缓存的请求体重新写入,确保后续流程能正常读取
                ClientRequest modifiedRequest = ClientRequest.from(request)
                        .body((outputMessage, context) -> requestBodyMono
                                .map(body -> outputMessage.getBody()
                                        .write(body.getBytes(StandardCharsets.UTF_8)))
                                .then())
                        .build();

                // 3. 处理请求体:解析JSON并记录审计日志
                return requestBodyMono.flatMap(body -> {
                    String appName = instance.getRegistration().getName();
                    String path = request.url().getPath();
                    String loggerName = path.substring(path.indexOf("/actuator/loggers/") + "/actuator/loggers/".length());

                    try {
                        JsonNode jsonBody = objectMapper.readTree(body);
                        log.info("审计日志:应用[{}]将日志器[{}]的级别修改为[{}]",
                                appName, loggerName, jsonBody.get("configuredLevel").asText());
                    } catch (JsonProcessingException e) {
                        log.error("解析应用[{}]、日志器[{}]的请求体失败", appName, loggerName, e);
                    }

                    // 4. 继续执行后续请求流程
                    return next.exchange(modifiedRequest);
                });
            }

            // 非目标请求直接放行
            return next.exchange(request);
        };
    }
}

关键细节说明

  • 内存泄漏防护:一定要调用DataBufferUtils.release(dataBuffer)释放缓冲区,否则Reactor的内存会被持续占用。
  • 字符编码:明确指定StandardCharsets.UTF_8,避免因编码不一致导致的JSON解析乱码问题。
  • 异常隔离:解析JSON时捕获JsonProcessingException,保证即使请求体格式异常,也不会影响正常的请求处理流程。
  • 路径匹配优化:如果想要更精确的路径匹配,可以用Spring的PathPattern替代contains,比如PathPatternParser.DEFAULT.parse("/actuator/loggers/{loggerName}"),避免误匹配相似路径。

对你问题的直接解答

  1. 能不能在SBA的Reactor exchange里读取请求Payload?
    当然可以!上面的代码就是专门针对SBA的InstanceExchangeFilterFunction场景实现的,完美解决了请求体只能读一次的问题。
  2. 有没有相关示例?
    上面的完整代码就是针对你的JSON请求体场景的示例,直接复制修改就能用。

备注:内容来源于stack exchange,提问作者tero17

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 09:58:03