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

Spring Boot中如何同时追踪自研API与第三方API调用

解决方案:在RestTemplateInterceptor中获取自研API请求信息

以下是几种适配Spring Boot 3.1.0的实用方案,可实现自研API与第三方API调用信息的关联追踪:

方法一:利用Spring内置RequestContextHolder直接获取

Spring的RequestContextHolder会将当前请求的HttpServletRequest绑定到线程上下文(同步调用场景默认生效),可直接在RestTemplateInterceptor中提取自研API的请求信息:

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

public class RestTemplateTrackingInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // 获取自研API的请求上下文
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest selfRequest = attributes.getRequest();
            // 提取自研API核心请求信息
            String selfApiUrl = selfRequest.getRequestURL().toString();
            String selfHttpMethod = selfRequest.getMethod();
            String clientIp = selfRequest.getRemoteAddr();

            // 执行第三方API调用并计算耗时
            long startTime = System.currentTimeMillis();
            ClientHttpResponse response = execution.execute(request, body);
            long duration = System.currentTimeMillis() - startTime;

            // 关联自研与第三方API信息,存入数据库
            saveTrackingRecord(selfApiUrl, selfHttpMethod, clientIp,
                               request.getURI().toString(), request.getMethod().name(),
                               response.getStatusCode().value(), duration);
            return response;
        }
        // 非请求触发的RestTemplate调用(如定时任务),直接执行不记录关联信息
        return execution.execute(request, body);
    }

    // 自行实现写入API_CALLS_TRACKING表的逻辑
    private void saveTrackingRecord(String selfApiUrl, String selfHttpMethod, String clientIp,
                                    String thirdPartyUrl, String thirdPartyMethod, int statusCode, long duration) {
        // Oracle数据库插入操作
    }
}

注意事项

  • 同步调用场景下直接生效;若使用@Async异步调用第三方API,需手动传递请求上下文:
    // 在控制器方法中开启异步时传递上下文
    RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
    CompletableFuture.runAsync(() -> {
        RequestContextHolder.setRequestAttributes(attributes);
        // RestTemplate调用逻辑
    });
    
  • 必须处理RequestAttributes为null的情况,避免空指针异常。

方法二:自定义ThreadLocal存储请求信息

如果需要自定义存储字段或场景特殊,可通过ThreadLocal在自研API入口传递请求信息:

1. 定义ThreadLocal工具类

public class RequestInfoHolder {
    private static final ThreadLocal<RequestInfo> THREAD_LOCAL = new ThreadLocal<>();

    public static void setRequestInfo(RequestInfo info) {
        THREAD_LOCAL.set(info);
    }

    public static RequestInfo getRequestInfo() {
        return THREAD_LOCAL.get();
    }

    public static void clear() {
        THREAD_LOCAL.remove();
    }

    // 自定义请求信息实体类
    public static class RequestInfo {
        private String apiUrl;
        private String httpMethod;
        private String clientIp;

        public RequestInfo(String apiUrl, String httpMethod, String clientIp) {
            this.apiUrl = apiUrl;
            this.httpMethod = httpMethod;
            this.clientIp = clientIp;
        }

        // getter方法省略
    }
}

2. 在自研API控制器中存入信息

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class CrawlController {

    private final RestTemplate restTemplate;
    private final TrackingService trackingService;

    // 构造注入依赖
    public CrawlController(RestTemplate restTemplate, TrackingService trackingService) {
        this.restTemplate = restTemplate;
        this.trackingService = trackingService;
    }

    @GetMapping("/crawl")
    public String crawl(HttpServletRequest request) {
        RequestInfoHolder.RequestInfo requestInfo = new RequestInfoHolder.RequestInfo(
                request.getRequestURL().toString(),
                request.getMethod(),
                request.getRemoteAddr()
        );
        RequestInfoHolder.setRequestInfo(requestInfo);

        try {
            // 调用第三方API
            String thirdPartyResponse = restTemplate.getForObject("https://third-party.com/data", String.class);
            // 记录自研API调用结果
            trackingService.saveSelfApiRecord(requestInfo, "SUCCESS");
            return thirdPartyResponse;
        } catch (Exception e) {
            trackingService.saveSelfApiRecord(requestInfo, "FAILURE");
            throw e;
        } finally {
            // 必须清理ThreadLocal,防止内存泄漏
            RequestInfoHolder.clear();
        }
    }
}

3. 在RestTemplateInterceptor中提取信息

import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

public class RestTemplateTrackingInterceptor implements ClientHttpRequestInterceptor {

    private final TrackingService trackingService;

    public RestTemplateTrackingInterceptor(TrackingService trackingService) {
        this.trackingService = trackingService;
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        long startTime = System.currentTimeMillis();
        ClientHttpResponse response = null;
        RequestInfoHolder.RequestInfo selfRequestInfo = RequestInfoHolder.getRequestInfo();
        try {
            response = execution.execute(request, body);
            return response;
        } finally {
            if (selfRequestInfo != null) {
                long duration = System.currentTimeMillis() - startTime;
                int statusCode = response != null ? response.getStatusCode().value() : -1;
                String thirdPartyStatus = statusCode >= 200 && statusCode < 300 ? "SUCCESS" : "FAILURE";
                // 记录第三方API追踪信息
                trackingService.saveThirdPartyRecord(selfRequestInfo,
                        request.getURI().toString(),
                        request.getMethod().name(),
                        thirdPartyStatus,
                        duration);
            }
        }
    }
}

注意事项

  • 务必在finally块中清理ThreadLocal,避免线程池场景下的内存泄漏。
  • 可根据需求灵活扩展RequestInfo的字段。

方法三:结合过滤器统一收集请求信息

若多个API需要追踪,可通过Filter统一拦截自研API请求,减少控制器重复代码:

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;

public class RequestTrackingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest httpRequest) {
            RequestInfoHolder.RequestInfo requestInfo = new RequestInfoHolder.RequestInfo(
                    httpRequest.getRequestURL().toString(),
                    httpRequest.getMethod(),
                    httpRequest.getRemoteAddr()
            );
            RequestInfoHolder.setRequestInfo(requestInfo);
            try {
                chain.doFilter(request, response);
            } finally {
                RequestInfoHolder.clear();
            }
        } else {
            chain.doFilter(request, response);
        }
    }
}

注册过滤器到Spring Boot:

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<RequestTrackingFilter> requestTrackingFilter() {
        FilterRegistrationBean<RequestTrackingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new RequestTrackingFilter());
        // 指定需要拦截的API路径
        registrationBean.addUrlPatterns("/crawl");
        return registrationBean;
    }
}

内容的提问来源于stack exchange,提问作者ginbarca

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 00:14:52