OpenFeign调用第三方接口更加优雅

OpenFeign 提供了一种声明式的 HTTP 客户端方式,通过接口和注解的方式调用第三方 API,将远程 HTTP 调用转化为简单的 Java 方法调用,从而大大简化了代码的编写和维护。关键点包括:

  • 简单易用:只需声明接口及对应的映射关系,开发者无需关注底层的 HTTP 交互细节。

  • 统一配置:可以通过配置类统一管理请求头、超时、日志级别等设置,确保各个调用场景的一致性。

  • 优雅的容错机制:结合 fallback 机制和异常处理,可在调用失败时返回默认数据或进行相应的降级处理,提升系统的健壮性。

  • 灵活的日志记录:通过 AOP 或自定义注解的方式,可以灵活控制是否记录调用日志,并将关键信息(如请求参数、响应结果、耗时等)记录到 MongoDB 等持久化存储,便于后期追踪和问题排查。

OpenFeign 实现

  • OpenFeign 接口定义ILServiceClient 示例,部分方法使用了注解

  • Feign 配置LFeignConfig 中可以配置拦截器、日志级别等

  • Fallback/FallbackFactory:通过在 FeignClient 注解中添加 fallback 或 fallbackFactory 属性,实现降级策略。
  • 自定义日志注解@LogFeignCall 用于标记需要记录日志的方法

  • MongoDB 日志实体:实体类 FeignCallLog,记录接口调用的各种信息

  • AOP 切面FeignCallLogAspect 拦截标记方法,记录调用前后状态、耗时、异常信息,并写入 MongoDB


Feign 配置:LFeignConfig.java

import feign.Logger;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
@Configuration
public class LFeignConfig {
​
    // 示例:统一添加请求头(如鉴权信息)
    @Bean
    public RequestInterceptor headerInterceptor() {
        return requestTemplate -> {
            // 如需要:requestTemplate.header("Authorization", "Bearer your_token");
            requestTemplate.header("X-Custom-Header", "customValue");
        };
    }
​
    // 设置 Feign 的日志级别为 FULL,便于排查问题
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

 


Feign 接口定义:ILServiceClient.java

import com.example.demo.annotation.LogFeignCall;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@FeignClient(name = "lServiceClient",
             path = "${api.l.path:lService}",
             url = "${api.l.url:}",
             configuration = com.example.demo.config.LFeignConfig.class,
             fallback = LServiceFallback.class)
public interface ILServiceClient {

    @LogFeignCall("创建")
    @PostMapping(value = "/city/create", consumes = MediaType.APPLICATION_JSON_VALUE)
    String create(@RequestBody Map<String, Object> json,
                         @RequestHeader Map<String, String> headers);

    @PostMapping(value = "/city/delete", consumes = MediaType.APPLICATION_JSON_VALUE)
    String delete(@RequestBody Map<String, Object> json,
                            @RequestHeader Map<String, String> headers);
}

容错机制:fallback 类

@Component
public class LServiceFallback implements ILServiceClient {
    @Override
    public String create(Map<String, Object> json, Map<String, String> headers) {
        return "Fallback: 创建制证失败";
    }

    @Override
    public String delete(Map<String, Object> json, Map<String, String> headers) {
        return "Fallback: 删除附件失败";
    }

}

 


自定义日志注解:LogFeignCall.java

import java.lang.annotation.*;
​
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogFeignCall {
    // 可选描述信息,如接口名称或用途
    String value() default "";
}

 


日志实体类:FeignCallLog.java

import java.time.LocalDateTime;
​
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
​
import lombok.Data;
​
@Data
@Document("feign_call_log")
public class FeignCallLog {
    @Id
    private String id;
​
    /** 接口描述(通过注解传入) */
    private String apiName;
    /** 调用方法:类名.方法名 */
    private String methodName;
    /** 请求参数(序列化后的字符串或对象) */
    private Object requestArgs;
    /** 响应内容 */
    private Object response;
    /** 调用是否成功 */
    private boolean success;
    /** HTTP 状态码,如自定义或标准状态码 */
    private int httpStatus;
    /** 异常信息(如果发生异常) */
    private String errorMessage;
    /** 调用耗时(毫秒) */
    private Long elapsedTimeMs;
    /** 日志记录时间 */
    private LocalDateTime timestamp;
}

 


Feign日志AOP 切面:FeignCallLogAspect.java

import com.example.demo.annotation.LogFeignCall;
import com.example.demo.entity.FeignCallLog;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
​
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
​
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class FeignCallLogAspect {
​
    private final MongoTemplate mongoTemplate;
​
    @Around("@annotation(logFeignCall)")
    public Object around(ProceedingJoinPoint joinPoint, LogFeignCall logFeignCall) throws Throwable {
        FeignCallLog logEntry = new FeignCallLog();
        logEntry.setApiName(logFeignCall.value());
        logEntry.setTimestamp(LocalDateTime.now());
​
        Signature signature = joinPoint.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            String className = method.getDeclaringClass().getSimpleName();
            logEntry.setMethodName(className + "." + method.getName());
        } else {
            logEntry.setMethodName(signature.toString());
        }
​
        // 序列化请求参数(这里只是简单调用 Arrays.toString(),可根据实际情况自定义)
        Object[] args = joinPoint.getArgs();
        logEntry.setRequestArgs(args != null ? Arrays.toString(args) : "无参数");
​
        long start = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
​
            logEntry.setResponse(result != null ? result.toString() : "null");
            logEntry.setSuccess(true);
            logEntry.setHttpStatus(HttpStatus.OK.value());
            return result;
        } catch (Exception ex) {
            logEntry.setSuccess(false);
            logEntry.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            logEntry.setErrorMessage(ExceptionUtils.getStackTrace(ex));
            throw ex;
        } finally {
            long end = System.currentTimeMillis();
            logEntry.setElapsedTimeMs(end - start);
            // 将日志记录到 MongoDB
            mongoTemplate.save(logEntry);
            log.debug("Feign调用日志记录: {}", logEntry);
        }
    }
}

 


posted @ 2025-04-15 10:27  ~落辰~  阅读(115)  评论(0)    收藏  举报