SpringCloud-Feign客户端实战

Feign 客户端实战教程

bash 复制代码
📁 需要创建的文件
com/payslip/feign/
├── WeatherClient.java          # Feign接口
├── WeatherResult.java          # 响应对象
└── WeatherFeignConfig.java     # Feign配置(可选)

com/payslip/service/
└── WeatherService.java         # 业务层

com/payslip/api/
└── WeatherController.java      # 控制器(测试用)

1.WeatherClient.java(Feign接口)

java 复制代码
package com.payslip.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 天气服务 Feign 客户端
 * 
 * 【核心知识点】
 * 1. @FeignClient 声明这是一个Feign客户端
 * 2. value: 客户端名称,用于标识
 * 3. url: 目标服务地址(也可以从配置文件读取:${api.weather.url})
 * 4. configuration: 自定义配置类(可选)
 */
@FeignClient(
    value = "weatherClient",
    url = "${api.weather.url:https://api.openweathermap.org}",  // 从配置读取,默认值兜底
    configuration = WeatherFeignConfig.class
)
public interface WeatherClient {
    
    /**
     * 获取天气信息
     * 
     * 【注解说明】
     * @GetMapping: 对应HTTP GET请求
     * @RequestParam: 对应URL查询参数 ?q=Beijing&appid=xxx
     * 
     * 最终请求: GET https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=xxx
     */
    @GetMapping("/data/2.5/weather")
    WeatherResult getWeather(
        @RequestParam("q") String city,        // 城市名
        @RequestParam("appid") String apiKey   // API密钥
    );
    
    /**
     * 获取天气预报(扩展示例)
     */
    @GetMapping("/data/2.5/forecast")
    WeatherResult getForecast(
        @RequestParam("q") String city,
        @RequestParam("appid") String apiKey,
        @RequestParam(value = "cnt", required = false) Integer count  // 可选参数
    );
}

2.WeatherResult.java(响应对象)

java 复制代码
package com.payslip.feign;
import lombok.Data;
import java.util.List;

/**
 * 天气API响应对象
 * 
 * 【核心知识点】
 * 1. 字段名要和API返回的JSON字段对应
 * 2. 可以只定义需要的字段,其他字段会被忽略
 * 3. 嵌套对象用内部类或单独的类
 */
@Data
public class WeatherResult {
    
    /** 城市名 */
    private String name;
    
    /** 主要天气数据 */
    private Main main;
    
    /** 天气描述列表 */
    private List<Weather> weather;
    
    /** 风力信息 */
    private Wind wind;
    
    /** 响应码(200表示成功) */
    private Integer cod;
    
    /**
     * 主要数据(温度、湿度等)
     */
    @Data
    public static class Main {
        /** 当前温度(开尔文,需要转换) */
        private Double temp;
        
        /** 体感温度 */
        private Double feels_like;
        
        /** 最低温度 */
        private Double temp_min;
        
        /** 最高温度 */
        private Double temp_max;
        
        /** 湿度(百分比) */
        private Integer humidity;
        
        /** 气压 */
        private Integer pressure;
    }
    
    /**
     * 天气描述
     */
    @Data
    public static class Weather {
        private Integer id;
        /** 天气类型:Clear, Clouds, Rain等 */
        private String main;
        /** 详细描述 */
        private String description;
        /** 图标代码 */
        private String icon;
    }
    
    /**
     * 风力信息
     */
    @Data
    public static class Wind {
        /** 风速 */
        private Double speed;
        /** 风向(角度) */
        private Integer deg;
    }
    
    // ========== 工具方法 ==========
    
    /**
     * 开尔文转摄氏度
     */
    public Double getTempCelsius() {
        if (main != null && main.getTemp() != null) {
            return main.getTemp() - 273.15;
        }
        return null;
    }
    
    /**
     * 判断请求是否成功
     */
    public boolean isSuccess() {
        return cod != null && cod == 200;
    }
}

3.WeatherFeignConfig.java(Feign配置)

java 复制代码
package com.payslip.feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Weather Feign 客户端配置
 * 
 * 【核心知识点】
 * 1. 配置类不需要加 @Configuration(Feign会自动扫描)
 * 2. 可以配置:日志级别、请求拦截器、错误处理器等
 */
@Slf4j
public class WeatherFeignConfig {
    
    /**
     * 日志级别
     * NONE: 不记录
     * BASIC: 只记录请求方法、URL、响应状态码、执行时间
     * HEADERS: 记录请求和响应的头信息
     * FULL: 记录所有(包括请求体和响应体)
     */
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.BASIC;
    }
    
    /**
     * 请求拦截器
     * 在每个请求发送前执行,可以添加公共参数、Header等
     */
    @Bean
    public RequestInterceptor requestInterceptor() {
        return template -> {
            // 添加公共Header
            template.header("Accept", "application/json");
            template.header("User-Agent", "PayslipApp/1.0");
            
            // 记录请求日志
            log.info("Feign请求: {} {}", template.method(), template.url());
        };
    }
    
    /**
     * 错误处理器
     * 处理HTTP错误响应
     */
    @Bean
    public ErrorDecoder errorDecoder() {
        return (methodKey, response) -> {
            log.error("Feign调用失败: {} - {}", response.status(), methodKey);
            
            switch (response.status()) {
                case 401:
                    return new RuntimeException("API密钥无效");
                case 404:
                    return new RuntimeException("城市不存在");
                case 429:
                    return new RuntimeException("请求过于频繁");
                default:
                    return new RuntimeException("天气服务调用失败: " + response.status());
            }
        };
    }
}

4.WeatherService.java(业务层)

java 复制代码
package com.payslip.service;
import com.payslip.feign.WeatherClient;
import com.payslip.feign.WeatherResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * 天气服务
 */
@Service
@Slf4j
public class WeatherService {
    
    @Autowired
    private WeatherClient weatherClient;
    
    /** 从配置文件读取API密钥 */
    @Value("${api.weather.key:your_api_key}")
    private String apiKey;
    
    /**
     * 获取天气信息
     * 
     * @param city 城市名(英文,如:Beijing, Shanghai)
     * @return 天气结果
     */
    public WeatherResult getWeather(String city) {
        log.info("查询天气: city={}", city);
        
        try {
            // 调用Feign客户端(像调用本地方法一样!)
            WeatherResult result = weatherClient.getWeather(city, apiKey);
            
            if (result.isSuccess()) {
                log.info("天气查询成功: {} - {}°C", 
                    result.getName(), 
                    String.format("%.1f", result.getTempCelsius()));
            }
            
            return result;
            
        } catch (Exception e) {
            log.error("天气查询失败: city={}, error={}", city, e.getMessage());
            throw new RuntimeException("天气查询失败: " + e.getMessage());
        }
    }
    
    /**
     * 获取天气信息(带缓存,进阶版)
     */
    // @Cacheable(value = "weather", key = "#city", unless = "#result == null")
    public WeatherResult getWeatherWithCache(String city) {
        return getWeather(city);
    }
}

5.WeatherController.java(测试接口)

java 复制代码
package com.payslip.api;
import com.payslip.feign.WeatherResult;
import com.payslip.service.WeatherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * 天气接口(测试用)
 */
@RestController
@RequestMapping("/api/weather")
public class WeatherController {
    
    @Autowired
    private WeatherService weatherService;
    
    /**
     * 查询天气
     * GET /api/weather?city=Beijing
     */
    @GetMapping
    public Map<String, Object> getWeather(@RequestParam String city) {
        Map<String, Object> response = new HashMap<>();
        
        try {
            WeatherResult result = weatherService.getWeather(city);
            
            response.put("code", 0);
            response.put("message", "success");
            response.put("data", Map.of(
                "city", result.getName(),
                "temp", String.format("%.1f°C", result.getTempCelsius()),
                "humidity", result.getMain().getHumidity() + "%",
                "weather", result.getWeather().get(0).getDescription()
            ));
            
        } catch (Exception e) {
            response.put("code", -1);
            response.put("message", e.getMessage());
        }
        
        return response;
    }
}

6.配置文件(application.yml 或 bootstrap.yml)

java 复制代码
# 天气API配置
api:
  weather:
    url: https://api.openweathermap.org
    key: your_api_key_here  # 去 openweathermap.org 注册获取
# Feign日志配置(需要配合logging.level使用)
logging:
  level:
    com.payslip.feign.WeatherClient: DEBUG

7.核心知识点总结

java 复制代码
Feign 注解对照表
| 注解   | 作用   | 示例   | |------|------|------| 
@FeignClient | 声明Feign客户端 | @FeignClient(value="xxx", url="xxx") 
@GetMapping | GET请求 | @GetMapping("/path")  
@PostMapping | POST请求 | @PostMapping("/path")  
@RequestParam | URL查询参数 | ?key=value 
@PathVariable | 路径参数 | /user/{id}  
@RequestBody | 请求体(JSON) | POST的body  
@RequestHeader | 请求头 | Authorization: xxx 

8.Feign 工作流程

java 复制代码
1. 你调用接口方法
   weatherClient.getWeather("Beijing", "xxx")
        ↓
2. Feign动态代理拦截
        ↓
3. 解析注解,构建HTTP请求
   GET https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=xxx
        ↓
4. RequestInterceptor 添加公共Header
        ↓
5. 发送HTTP请求
        ↓
6. 接收响应,JSON反序列化为 WeatherResult
        ↓
7. 返回结果

9.常见问题

java 复制代码
404错误 → 检查URL和路径是否正确
序列化失败 → 检查响应对象字段名是否和JSON对应
连接超时 → 配置Feign超时时间
找不到Bean → 确保启动类加了 @EnableFeignClients

10.启用 Feign

java 复制代码
确保启动类有这个注解:
@SpringBootApplication
@EnableFeignClients  // 启用Feign客户端扫描
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
相关推荐
superman超哥1 小时前
仓颉语言中原子操作的封装深度剖析与无锁编程实践
c语言·开发语言·后端·python·仓颉
阿杰AJie1 小时前
Docker 容器启动的全方位方法汇总
后端
sdguy2 小时前
在 Windows 上正确安装 OpenAI Codex CLI:一次完整的 pnpm 全局环境修复实录
后端·openai
shiwulou12 小时前
PbRL | 近两年论文阅读的不完全总结
后端
yuniko-n2 小时前
【MySQL】通俗易懂的 MVCC 与事务
数据库·后端·sql·mysql
今天过得怎么样2 小时前
彻底搞懂 Spring Boot 中 properties 和 YAML 的区别
后端
qq_12498707532 小时前
基于springboot的幼儿园家校联动小程序的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·spring·微信小程序·小程序
后端小张3 小时前
【Java 进阶】深入理解Redis:从基础应用到进阶实践全解析
java·开发语言·数据库·spring boot·redis·spring·缓存
楠枬3 小时前
Nacos
java·spring·spring cloud·微服务