Springcloud Openfegion Usage
本文档描述 Spring Boot 项目如何集成 Nacos 注册中心、使用 OpenFeign 实现服务间调用,以及如何编写共享 API 模块供其他服务接入。
角色说明
在微服务架构中,一个服务可以同时扮演两种角色:
-
服务提供方(Provider)
- 将自身注册到 Nacos
- 暴露 HTTP 接口供其他服务调用
- 需要依赖: spring-cloud-starter-alibaba-nacos-discovery
-
服务调用方(Consumer)
- 从 Nacos 发现其他服务
- 通过 Feign 接口调用远程服务
- 需要依赖: spring-cloud-starter-alibaba-nacos-discovery + spring-cloud-starter-openfeign + wvp-api
项目结构
以 wvp 项目为例,推荐的结构如下:
wvp-GB28181-pro/
├── pom.xml # 主项目 POM,即服务提供方
├── src/ # 主项目源码,含 Controller 等
└── wvp-api/ # 共享 API 模块(独立 jar)
├── pom.xml
└── src/main/java/
└── com/genersoft/iot/vmp/
├── api/feign/
│ └── ChannelGroupFeignClient.java # Feign 接口
├── storager/dao/dto/
│ └── ChannelGroup.java # 响应 DTO
└── gb28181/bean/
└── DeviceChannel.java # 嵌套 DTO
一个标准的pom框架是:把qo、vo、dto对象单独放到一个依赖中,后端服务与Api的模块都依赖它
-
共享 API 模块
- 独立的 Maven jar 模块,不包含业务逻辑
- 仅包含 Feign 接口定义和 DTO 类
- 依赖全部为 provided scope,由调用方提供实际实现
- 服务提供方和调用方各自维护一份 DTO 副本(或通过 API 模块共享)
服务提供方集成
-
pom.xml 依赖
在主项目 pom.xml 中添加:
<!-- 依赖管理 --> <properties> <spring-cloud.version>2021.0.5</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- 服务注册发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.5.0</version> </dependency> <!-- OpenFeign(仅当服务提供方也需要调用其他服务时需要) --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> -
启动类注解
@SpringBootApplication @EnableDiscoveryClient // 注册到 Nacos @EnableFeignClients // 启用 Feign 客户端(调用其他服务时需要) public class VManageBootstrap { public static void main(String[] args) { SpringApplication.run(VManageBootstrap.class, args); } } -
application.yml 配置
spring: application: name: wvp cloud: nacos: discovery: server-addr: 10.100.10.104:8848 namespace: public group: DEFAULT_GROUP username: username password: password -
Controller 接口
服务提供方正常编写 Controller 即可,不需要额外改动:
@RestController @RequestMapping("/api/channel/groups") public class GroupController { // 注意:wvp 返回有另外的封装,统一返回了 WVPResult @GetMapping("/getTreeDataForAi") public List<ChannelGroup> getTreeDataForAi( @RequestParam(required = true) String groupName, @RequestParam(required = false) String deviceId, @RequestParam(required = false) String channelName) { return groupChannelService.getTreeData(null, groupName, deviceId, channelName); } } -
共享 API 模块编写
-
pom.xml
API 模块依赖全部为 provided scope:
<?xml version="1.0"?> <project xmlns="http://maven.apache.org/POM/4.0.0" ...> <modelVersion>4.0.0</modelVersion> <groupId>com.genersoft</groupId> <artifactId>wvp-api</artifactId> <version>1.9.3</version> <packaging>jar</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring-cloud.version>2021.0.5</spring-cloud.version> <lombok.version>1.18.22</lombok.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- @FeignClient, @GetMapping 等注解 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <scope>provided</scope> </dependency> <!-- @Data 注解 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> </dependencies> </project>关键说明:
- provided scope:依赖仅用于编译期,运行时由调用方提供,不会被打包到 API jar 中
- 版本由 spring-cloud-dependencies BOM 管理,确保 Feign 版本与调用方一致
-
Feign 接口定义
package com.genersoft.iot.vmp.api.feign; import com.genersoft.iot.vmp.storager.dao.dto.ChannelGroup; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @FeignClient(name = "wvp", path = "/api/channel/groups", fallback = ChannelGroupFeignClientFallback.class) public interface ChannelGroupFeignClient { @GetMapping("/getTreeDataForAi") WVPResult<List<ChannelGroup>> getTreeDataForAi( @RequestParam("groupName") String groupName, @RequestParam(value = "deviceId", required = false) String deviceId, @RequestParam(value = "channelName", required = false) String channelName); }参数说明:
- name :被调用方注册到 Nacos 的服务名( spring.application.name )
- path :Controller 上的 @RequestMapping 前缀
- 方法签名必须与 Controller 接口完全一致(路径、参数名、参数类型)
-
-
编写 Fallback 实现类
在 API 模块中(或调用方项目中)创建 Fallback 类,实现 Feign 接口并返回兜底数据。以下情况会触发降级,走 Fallback 逻辑:
-
*服务不可用*:被调用方宕机或未注册到 Nacos
-
*响应超时*:Feign 调用超过设定的超时时间
package com.genersoft.iot.vmp.api.feign;
import com.genersoft.iot.vmp.storager.dao.dto.ChannelGroup; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import org.springframework.stereotype.Component;
import java.util.List;
@Component public class ChannelGroupFeignClientFallback implements ChannelGroupFeignClient {
@Override public WVPResult<List<ChannelGroup>> getTreeDataForAi(String groupName, String deviceId, String channelName) { return WVPResult.fail(ErrorCode.ERROR500.getCode(), "channel group 服务暂不可用,请稍后重试"); }}
- Fallback 类必须实现 Feign 接口,且需标注 @Component
- 降级逻辑应尽量简单,避免依赖外部服务
- 返回统一错误码和提示信息,便于前端统一处理
-
-
Feign 超时与重试配置
每个 FeignClient 可以通过 configuration 参数指定独立的配置类,实现超时时间、重试策略等自定义。
-
配置类
在 API 模块中创建 FeignClientConfig :
package com.genersoft.iot.vmp.api.feign; import feign.Request; import feign.Retryer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class FeignClientConfig { @Value("${feign.client.wvp.connect-timeout:2000}") private int connectTimeout; @Value("${feign.client.wvp.read-timeout:3000}") private int readTimeout; @Value("${feign.client.wvp.retryer.period:500}") private long retryPeriod; @Value("${feign.client.wvp.retryer.max-period:2000}") private long retryMaxPeriod; @Value("${feign.client.wvp.retryer.max-attempts:3}") private int retryMaxAttempts; @Bean public Request.Options requestOptions() { return new Request.Options(connectTimeout, TimeUnit.MILLISECONDS, readTimeout, TimeUnit.MILLISECONDS, true); } @Bean public Retryer retryer() { return new Retryer.Default(retryPeriod, retryMaxPeriod, retryMaxAttempts); } } -
在 FeignClient 中引用
@FeignClient(name = "wvp", path = "/api/channel/groups", configuration = FeignClientConfig.class, fallback = ChannelGroupFeignClientFallback.class) public interface ChannelGroupFeignClient { // ... } -
YAML 覆盖示例
feign: client: wvp: connect-timeout: 5000 # 连接超时 5s read-timeout: 10000 # 读取超时 10s retryer: period: 1000 # 初始重试间隔 1s max-period: 5000 # 最大重试间隔 5s max-attempts: 5 # 最多重试 5 次
-
-
发布 API 模块到远程仓库
# 在 wvp 项目根目录执行 cd wvp-api mvn deploy
服务调用方集成
-
步骤一:调用方 pom.xml 添加依赖
<!-- 服务注册发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.5.0</version> </dependency> <!-- OpenFeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- 共享 API 模块 --> <dependency> <groupId>com.genersoft</groupId> <artifactId>wvp-api</artifactId> <version>1.9.3</version> </dependency> -
步骤二:增加登录信息传递
服务之间通过 fegionclient 调用,浏览器请求的 cookie 和 header 并不会传递过去,所幸可以使用 RequestInterceptor 来实现。
RequestInterceptor 是 Feign 原生提供的一个函数式接口,用于在 HTTP 请求发出前对请求模板进行定制修改。 在 Spring Cloud 环境中,你可以通过将它声明为 Bean 来全局生效,或放在 @FeignClient 的 configuration 中局部生效。package com.skytech.infrastructure.config; import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; @Configuration public class FeignHeaderInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); String headerValue = request.getHeader(headerName); template.header(headerName, headerValue); } } } } -
步骤三:调用方 Nacos 配置
spring: application: name: your-service-name cloud: nacos: discovery: server-addr: 10.100.10.104:8848 namespace: public group: DEFAULT_GROUP username: username password: password -
步骤四:启动类添加注解
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients(basePackages = "com.genersoft.iot.vmp.api.feign") public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } } -
步骤五:注入并使用
@Service public class YourService { @Autowired private ChannelGroupFeignClient channelGroupFeignClient; public void queryGroupTree() { WVPResult<List<ChannelGroup>> groups = channelGroupFeignClient .getTreeDataForAi("分组名称", null, null); // 处理返回数据 } }