1. 程式人生 > >Spring Cloud中FeignClient新增上傳檔案功能

Spring Cloud中FeignClient新增上傳檔案功能

專案概況:Spring Cloud搭的微服務,使用了eureka,FeignClient,現在遇到FeignClient呼叫介面時不支援上傳檔案,

百度到兩種方案,一種是使用feign-form和feign-form-spring庫來做,原始碼地址:https://github.com/OpenFeign/feign-form。

具體的使用方法是加入maven依賴

        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form-spring</artifactId>
            <version>3.2.2</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form</artifactId>
            <version>3.2.2</version>
        </dependency>

注入SpringFormEncoder類

    @Bean
    @Primary
    @Scope("prototype")
    public Encoder multipartFormEncoder() {
        return new SpringFormEncoder();
    }

FeignClient接口裡方法引數是檔案型別的要用@RequestPart註解,且要設定ContentType為multipart/form-data

    @ResponseBody
    @RequestMapping(value = "/ctstestcase/updateTestCase", method = {RequestMethod.POST}, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    Map<String, Object> updateTestCase(@RequestParam("testcaseId") String testcaseId,
            @RequestParam("name") String name, @RequestParam("assignId") String assignId,
            @RequestParam("areaId") String areaId, @RequestParam("state") Integer state,
            @RequestParam("iterationId") String iterationId,@RequestParam("priority") Integer priority,
            @RequestParam("moduleId") String moduleId,  @RequestParam("executionType") Integer executionType,
            @RequestParam("summary") String summary, @RequestParam("tcsteps") String tcsteps,
            @RequestParam("relations") String relations,@RequestParam("attachments") String attachments,
            @RequestPart("files") MultipartFile[] files);

但遇到一個問題,就是不支援檔案陣列型別,我看了原始碼,發現原始碼裡底層是有對MultipartFile[]型別的支援的,原始碼中有個類叫SpringManyMultipartFilesWriter,是專門針對檔案陣列型別進行操作的,但是配置到專案裡的SpringFormEncoder類裡卻沒有對檔案陣列型別的判斷,以致不能支援檔案陣列的上傳.。

SpringManyMultipartFilesWriter原始碼:

@FieldDefaults(level = PRIVATE, makeFinal = true)
public class SpringManyMultipartFilesWriter extends AbstractWriter {

  SpringSingleMultipartFileWriter fileWriter = new SpringSingleMultipartFileWriter();

  @Override
  public void write (Output output, String boundary, String key, Object value) throws Exception {
    if (value instanceof MultipartFile[]) {
      val files = (MultipartFile[]) value;
      for (val file : files) {
        fileWriter.write(output, boundary, key, file);
      }
    } else if (value instanceof Iterable) {
      val iterable = (Iterable<?>) value;
      for (val file : iterable) {
        fileWriter.write(output, boundary, key, file);
      }
    }
  }

  @Override
  public boolean isApplicable (Object value) {
    if (value == null) {
      return false;
    }
    if (value instanceof MultipartFile[]) {
      return true;
    }
    if (value instanceof Iterable) {
      val iterable = (Iterable<?>) value;
      val iterator = iterable.iterator();
      if (iterator.hasNext() && iterator.next() instanceof MultipartFile) {
        return true;
      }
    }
    return false;
  }

SpringFormEncoder原始碼:

public class SpringFormEncoder extends FormEncoder {

  /**
   * Constructor with the default Feign's encoder as a delegate.
   */
  public SpringFormEncoder () {
    this(new Encoder.Default());
  }

  /**
   * Constructor with specified delegate encoder.
   *
   * @param delegate  delegate encoder, if this encoder couldn't encode object.
   */
  public SpringFormEncoder (Encoder delegate) {
    super(delegate);

    val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
    processor.addWriter(new SpringSingleMultipartFileWriter());
    processor.addWriter(new SpringManyMultipartFilesWriter());
  }

  @Override
  public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
    if (!bodyType.equals(MultipartFile.class)) {
      super.encode(object, bodyType, template);
      return;
    }

    val file = (MultipartFile) object;
    val data = singletonMap(file.getName(), object);
    super.encode(data, MAP_STRING_WILDCARD, template);
  }
}

從上面SpringFormEncoder的原始碼上可以看到SpringFormEncoder類構造時把SpringManyMultipartFilesWriter例項新增到了處理器列表裡了,但是在encode方法裡又只判斷了MultipartFile型別,沒有判斷陣列型別,這就比較奇怪了,底層有對陣列的支援但上層卻缺少了相應判斷,而且在原始碼裡的test包裡也沒有對檔案陣列型別的測試,難道只是encode方法裡漏掉了?還是說那個檔案陣列的支援有問題?所以encode方法裡才沒有加入對其的判斷?

於是我先試著對encode方法進行擴充套件加入對檔案陣列的判斷,應該就可以支援檔案陣列的上傳了,於是把SpringFormEncoder類原始碼複製出來重新命名為FeignSpringFormEncoder,原始碼如下:

public class FeignSpringFormEncoder extends FormEncoder {

    /**
     * Constructor with the default Feign's encoder as a delegate.
     */
    public FeignSpringFormEncoder() {
        this(new Encoder.Default());
    }


    /**
     * Constructor with specified delegate encoder.
     *
     * @param delegate delegate encoder, if this encoder couldn't encode object.
     */
    public FeignSpringFormEncoder(Encoder delegate) {
        super(delegate);

        val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
        processor.addWriter(new SpringSingleMultipartFileWriter());
        processor.addWriter(new SpringManyMultipartFilesWriter());
    }


    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (bodyType.equals(MultipartFile.class)) {
            val file = (MultipartFile) object;
            val data = singletonMap(file.getName(), object);
            super.encode(data, MAP_STRING_WILDCARD, template);
            return;
        } else if (bodyType.equals(MultipartFile[].class)) {
            val file = (MultipartFile[]) object;
            if(file != null) {
                val data = singletonMap(file.length == 0 ? "" : file[0].getName(), object);
                super.encode(data, MAP_STRING_WILDCARD, template);
                return;
            }
        }
        super.encode(object, bodyType, template);
    }
}

經過測試,已經可以支援檔案陣列了,完美解決。

 這裡再順便說一下當時還百度到另一個解決檔案上傳的方案,這個方案就不細說了,直接上我用到的那個開原始碼的地址:https://github.com/pcan/feign-client-test

這個我試過也是可以解決檔案上傳問題的,但問題是FeignClient不能用SpringMVC的註解,得用Feign自帶的註解,也因此我才擴充套件了第一種方法來做的檔案上傳功能。