一、前提条件和背景
1. 前提
已经部署好Nacos,本文以192.168.101.65:8848为例。
2. 背景
有两个微服务media和content,都已经注册到Nacos。
后者通过引用Feign实现远程调用前者。
两个微服务都被分为3个子模块:api、service、model,对应三层架构。
请根据自身情况出发阅读本文。
二、Feign模块
1. 依赖引入
首先需要Feign依赖和扩展。
language-xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | <!--   openfeign     --> <dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-starter-loadbalancer</artifactId>     <version>4.1.0</version> </dependency> <dependency>     <groupId>io.github.openfeign</groupId>     <artifactId>feign-httpclient</artifactId> </dependency> <!--feign支持Multipart格式传参--> <dependency>     <groupId>io.github.openfeign.form</groupId>     <artifactId>feign-form</artifactId>     <version>3.8.0</version> </dependency> <dependency>     <groupId>io.github.openfeign.form</groupId>     <artifactId>feign-form-spring</artifactId>     <version>3.8.0</version> </dependency>
   | 
 
需要测试依赖(可选),为了MockMultipartFile类才引入的,非必需功能。
language-xml1 2 3 4 5 6 7
   | <!--    测试    --> <dependency> 	<groupId>org.springframework</groupId> 	<artifactId>spring-test</artifactId> 	<version>6.1.2</version> 	<scope>compile</scope> </dependency>
   | 
 
其次需要涉及到的微服务的数据模型,根据个人情况而定。
如果只想要它们的数据模型,而不引入不必要的依赖,可以使用通配符*全部过滤掉。
language-xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | <!--   数据模型pojo     --> <dependency>     <groupId>com.xuecheng</groupId>     <artifactId>xuecheng-plus-media-model</artifactId>     <version>0.0.1-SNAPSHOT</version>      <exclusions>           <exclusion>               <groupId>*</groupId>               <artifactId>*</artifactId>           </exclusion>       </exclusions> </dependency> <dependency>     <groupId>com.xuecheng</groupId>     <artifactId>xuecheng-plus-content-model</artifactId>     <version>0.0.1-SNAPSHOT</version>      <exclusions>          <exclusion>              <groupId>*</groupId>              <artifactId>*</artifactId>          </exclusion>      </exclusions> </dependency>
   | 
 
2. application.yaml配置
填入以下内容,大抵为超时熔断处理。(可选),甚至可以留空。
language-yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | feign:   hystrix:     enabled: true   circuitbreaker:     enabled: true hystrix:   command:     default:       execution:         isolation:           thread:             timeoutInMilliseconds: 30000 ribbon:   ConnectTimeout: 60000   ReadTimeout: 60000   MaxAutoRetries: 0   MaxAutoRetriesNextServer: 1
   | 
 
3. 扩展支持MultipartFile
新建一个配置类,如下,
主要是Encoder feignEncoder()使得Feign支持MultipartFile类型传输。
MultipartFile getMultipartFile(File file)是一个工具方法,和配置无关。
language-java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | @Configuration public class MultipartSupportConfig {    
      @Autowired     private ObjectFactory<HttpMessageConverters> messageConverters;
      @Bean     @Primary//注入相同类型的bean时优先使用     @Scope("prototype")     public Encoder feignEncoder() {             return new SpringFormEncoder(new SpringEncoder(messageConverters));     }
      //将file转为Multipart     public static MultipartFile getMultipartFile(File file) {             try {                 byte[] content = Files.readAllBytes(file.toPath());             MultipartFile multipartFile = new MockMultipartFile(file.getName(),                     file.getName(), Files.probeContentType(file.toPath()), content);             return multipartFile;         } catch (IOException e) {                 e.printStackTrace();             XueChengPlusException.cast("File->MultipartFile转化失败");             return null;         }     } }
   | 
 
新建一个类,如下。
@FeignClient(value)要和服务名称对上,即media模块spring.application.name=media-api。
@FeignClient(path)要和服务前缀路径对上,即media模块server.servlet.context-path=/media。
然后MediaClient中的方法定义尽量和media模块对应的controller函数保持一致。
language-java1 2 3 4 5 6 7 8 9 10 11
   | @FeignClient(value = "media-api", path = "/media") public interface MediaClient {    
      @RequestMapping(value = "/upload/coursefile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)     public UploadFileResultDto upload(             @RequestPart("filedata") MultipartFile file,             @RequestParam(value = "objectName", required = false) String objectName     ); }
 
   | 
 
被调用方media模块无需做什么修改。
四、Content模块
测试在content-api上操作。
1. 引入依赖
content模块需要引入刚才feign模块的依赖。
language-xml1 2 3
   | <dependency>    <!-- 根据自身情况引入 --> </dependency>
   | 
 
2. 启用FeignClient
在启动类上加上@EnableFeignClients注解。
3. 测试
新建测试类,如下
language-java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | package com.xuecheng.content.service.jobhandler;
  import com.xuecheng.feign.client.MediaClient; import com.xuecheng.media.model.dto.UploadFileResultDto; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile;
  @SpringBootTest class CoursePublishTaskTest {         @Autowired     MediaClient mediaClient;
      @Test     void generateCourseHtml() {             MultipartFile file = new MockMultipartFile(                 "filedata",                 "filename.txt",                 "text/plain",                 "Some dataset...".getBytes()         );         UploadFileResultDto upload = mediaClient.upload(file, "/static-test/t-1");         System.out.println(upload);     } }
   | 
 
启动Media模块,启动测试方法,
具体的Debug和检验,可以通过Media模块对应的controller函数打印日志,检查是否通过MediaClient 被触发。
五、需要澄清的几点
Feign模块不需要注册到Nacos且不需要服务发现 :
正确。feign-client模块只是一个包含Feign客户端接口的库,它自身并不是一个独立的微服务。因此,它不需要注册到Nacos,也不需要服务发现功能。这个模块只是被其他微服务模块(如content模块)作为依赖引入。这样做的主要目的是为了代码的重用和解耦,允许任何微服务通过引入这个依赖来调用其他服务。
 
只有调用者(如content模块)需要使用@EnableFeignClients注解,被调用者(如media模块)不需要 :
正确。@EnableFeignClients注解是用来启用Feign客户端的,它告诉Spring Cloud这个服务将会使用Feign来进行远程服务调用。因此,只有需要使用Feign客户端的服务(在这个例子中是content模块)需要添加这个注解。而被调用的服务(如media模块),只需作为普通的Spring Boot应用运行,提供REST API即可,无需使用@EnableFeignClients。
 
如何在服务间共享数据模型(如DTOs)而不引入不必要的依赖。
解决这个问题的一种方法是创建一个共享的库或模块,这个库包含所有服务共享的数据模型。另一种使用依赖剥离,使用通配符(*)可以排除pom.xml中特定依赖的所有传递性依赖。