使用Feign实现微服务间文件传输

在很多时候我们会遇到微服务之间文件传输,很多时候我们可以通过序列化等方式解决(如图片等)。

最近项目中有个excel上传,以及多媒体文件上传,直接报错。

也试了2种解决方式,都不可行。

1.写一个文件Encoder解析器,会出现其他的rest请求出现encoder错误

2.springcloud feign有一个规范,不可以传输2个对象,可以是一个对象带几个参数方式。

那么我们现在需要一种方式,不配置全局的解析器,而是通过Feign Builder 去管理上传文件,这种方式管理起来也较为方便。

引用包

<dependency>
  <groupId>com.netflix.feign</groupId>
  <artifactId>feign-core</artifactId>
  <version>8.17.0</version>
</dependency>
<dependency>
  <groupId>com.netflix.feign</groupId>
  <artifactId>feign-jackson</artifactId>
  <version>8.17.0</version>
</dependency>
<dependency>
  <groupId>com.netflix.feign</groupId>
  <artifactId>feign-slf4j</artifactId>
  <version>8.17.0</version>
</dependency>

调用方式

@ApiOperation(value = "上传Excel", notes = "上传Excel")
@RequestMapping(value = "/imExcel", method = RequestMethod.POST, produces = request_headers)
public ActionResult imExcel(@RequestBody MultipartFile file,@RequestParam("operatorId") Integer operatorId){

  if(file == null || file.isEmpty()|| operatorId==null)
    return new ActionResult<>(ResultType.BAD_REQUEST,"文件与操作用户ID都不能为空");
  String fileName = file.getOriginalFilename();
  if (!fileName.matches("^.+\\.(?i)(xls)$") && !fileName.matches("^.+\\.(?i)(xlsx)$")) {
    return new ActionResult<>(ResultType.BAD_REQUEST,"上传文件格式错误,请上传后缀为.xls或.xlsx的文件");
  }
  Feign.Builder encoder = Feign.builder()
      .decoder(new JacksonDecoder())
      .encoder(new FeignEncoder());
  FileUpload complainFileUpload = encoder.target(FileUpload.class,LABEL_URL);
  return complainFileUpload.imComplainExcel(file,operatorId);

}

文件Encode

import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * @author lxl
 */
public class FeignEncoder implements Encoder {

  private final List<HttpMessageConverter<?>> converters = new RestTemplate().getMessageConverters();
  private final HttpHeaders multipartHeaders = new HttpHeaders();
  private final HttpHeaders jsonHeaders = new HttpHeaders();

  public static final Charset UTF_8 = Charset.forName("UTF-8");

  public FeignEncoder() {
    multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
    jsonHeaders.setContentType(MediaType.APPLICATION_JSON);
  }

  @Override
  public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
    if (isFormRequest(bodyType)) {
      encodeMultipartFormRequest((Map<String, ?>) object, template);
    } else {
      encodeRequest(object, jsonHeaders, template);
    }
  }

  private void encodeMultipartFormRequest(Map<String, ?> formMap, RequestTemplate template) throws EncodeException {
    if (CollectionUtils.isEmpty(formMap)) {
      throw new EncodeException("参数不能为空.");
    }
    LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
    for (Entry<String, ?> entry : formMap.entrySet()) {
      Object value = entry.getValue();
      if (isMultipartFile(value)) {
        map.add(entry.getKey(), encodeMultipartFile((MultipartFile) value));
      } else if (isMultipartFileArray(value)) {
        encodeMultipartFiles(map, entry.getKey(), Arrays.asList((MultipartFile[]) value));
      } else {
        map.add(entry.getKey(), encodeJsonObject(value));
      }
    }
    encodeRequest(map, multipartHeaders, template);
  }

  private boolean isMultipartFile(Object object) {
    return object instanceof MultipartFile;
  }

  private boolean isMultipartFileArray(Object o) {
    return o != null && o.getClass().isArray() && MultipartFile.class.isAssignableFrom(o.getClass().getComponentType());
  }

  /**
   * 设置头
   * @param file
   * @return
   */
  private HttpEntity<?> encodeMultipartFile(MultipartFile file) {
    HttpHeaders filePartHeaders = new HttpHeaders();
    filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    try {
      Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream());
      return new HttpEntity<>(multipartFileResource, filePartHeaders);
    } catch (IOException ex) {
      throw new EncodeException("Cannot encode request.", ex);
    }
  }

  /**
   * 映射
   * @param map
   * @param name
   * @param files
   */
  private void encodeMultipartFiles(LinkedMultiValueMap<String, Object> map, String name, List<? extends MultipartFile> files) {
    HttpHeaders filePartHeaders = new HttpHeaders();
    filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    try {
      for (MultipartFile file : files) {
        Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream());
        map.add(name, new HttpEntity<>(multipartFileResource, filePartHeaders));
      }
    } catch (IOException ex) {
      throw new EncodeException("Cannot encode request.", ex);
    }
  }

  /**
   * {@link HttpEntity} {@code Content-type}
   * {@code application/json}
   *
   * @param o
   * @return
   */
  private HttpEntity<?> encodeJsonObject(Object o) {
    HttpHeaders jsonPartHeaders = new HttpHeaders();
    jsonPartHeaders.setContentType(MediaType.APPLICATION_JSON);
    return new HttpEntity<>(o, jsonPartHeaders);
  }

  /**
   * {@link org.springframework.web.client.RestTemplate}
   *
   * @param value
   * @param requestHeaders
   * @param template
   * @throws EncodeException
   */
  private void encodeRequest(Object value, HttpHeaders requestHeaders, RequestTemplate template) throws EncodeException {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    HttpOutputMessage dummyRequest = new HttpOutputMessageImpl(outputStream, requestHeaders);
    try {
      Class<?> requestType = value.getClass();
      MediaType requestContentType = requestHeaders.getContentType();
      for (HttpMessageConverter<?> messageConverter : converters) {
        if (messageConverter.canWrite(requestType, requestContentType)) {
          ((HttpMessageConverter<Object>) messageConverter).write(
              value, requestContentType, dummyRequest);
          break;
        }
      }
    } catch (IOException ex) {
      throw new EncodeException("Cannot encode request.", ex);
    }
    HttpHeaders headers = dummyRequest.getHeaders();
    if (headers != null) {
      for (Entry<String, List<String>> entry : headers.entrySet()) {
        template.header(entry.getKey(), entry.getValue());
      }
    }
    /*
    使用bytearray方式传输
     */
    template.body(outputStream.toByteArray(), UTF_8);
  }

  /**
   * {@link org.springframework.http.HttpOutputMessage}
   * {@link org.springframework.http.converter.HttpMessageConverter}
   */
  private class HttpOutputMessageImpl implements HttpOutputMessage {

    private final OutputStream body;
    private final HttpHeaders headers;

    public HttpOutputMessageImpl(OutputStream body, HttpHeaders headers) {
      this.body = body;
      this.headers = headers;
    }

    @Override
    public OutputStream getBody() throws IOException {
      return body;
    }

    @Override
    public HttpHeaders getHeaders() {
      return headers;
    }

  }

  static boolean isFormRequest(Type type) {
    return MAP_STRING_WILDCARD.equals(type);
  }

  static class MultipartFileResource extends InputStreamResource {

    private final String filename;
    private final long size;

    public MultipartFileResource(String filename, long size, InputStream inputStream) {
      super(inputStream);
      this.size = size;
      this.filename = filename;
    }

    @Override
    public String getFilename() {
      return this.filename;
    }

    @Override
    public InputStream getInputStream() throws IOException, IllegalStateException {
      return super.getInputStream(); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public long contentLength() throws IOException {
      return size;
    }

  }

}

Feign调用接口

@RequestLine("POST /punish/imExcel")
ActionResult<List<String>> imPunishExcel(@Param("file") MultipartFile file, @Param("operatorId") Integer operatorId);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 使用Spring Cloud Feign上传文件的示例

    最近经常有人问Spring Cloud Feign如何上传文件.有团队的新成员,也有其他公司的兄弟.本文简单做个总结-- 早期的Spring Cloud中,Feign本身是没有上传文件的能力的(1年之前),要想实现这一点,需要自己去编写Encoder 去实现上传.现在我们幸福了很多.因为Feign官方提供了子项目feign-form ,其中实现了上传所需的 Encoder . 注:笔者测试的版本是Edgware.RELEASE.Camden.Dalston同样适应本文所述. 加依赖 <depen

  • spring cloud feign实现远程调用服务传输文件的方法

    实践案例包括两个项目,服务提供者项目名:upload-service,调用服务项目名:upload-client,主要给出两个服务之间的调用过程,文件上传功能不提供 项目框架:spring-boot 2.0.1.RELEASE.spring-cloud Finchley.RELEASE 依赖: <dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form<

  • SpringCloud使用Feign文件上传、下载

    文件上传.下载也是实际项目中会遇到的场景,本篇我们介绍下springcloud中如何使用feign进行文件上传与下载. 还是使用feign 进行http的调用. 一.Feign文件上传 服务提供方java代码: /** * 文件上传 * @param file 文件 * @param fileType * @return */ @RequestMapping(method = RequestMethod.POST, value = "/uploadFile", produces = {

  • 使用Feign扩展包实现微服务间文件上传

    在Spring Cloud 的Feign组件中并不支持文件的传输,会出现这样的错误提示: feign.codec.EncodeException: class [Lorg.springframework.web.multipart.MultipartFile; is not a type supported by this encoder. at feign.codec.Encoder$Default.encode(Encoder.java:90) ~[feign-core-9.5.1.jar:

  • vue使用el-upload上传文件及Feign服务间传递文件的方法

    一.前端代码 <el-upload class="step_content" drag action="string" ref="upload" :multiple="false" :http-request="createAppVersion" :data="appVersion" :auto-upload="false" :limit="1&quo

  • Spring Cloud Feign文件传输的示例代码

    一.配置文件解析器 服务提供者和消费者都需要配置文件解析器,这里使用 commons-fileupload 替换原有的解析器: 依赖: <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> 注入 be

  • Spring Cloud Feign的文件上传实现的示例代码

    在Spring Cloud封装的Feign中并不直接支持传文件,但可以通过引入Feign的扩展包来实现,本来就来具体说说如何实现. 服务提供方(接收文件) 服务提供方的实现比较简单,就按Spring MVC的正常实现方式即可,比如: @RestController public class UploadController { @PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VAL

  • 使用Feign实现微服务间文件传输

    在很多时候我们会遇到微服务之间文件传输,很多时候我们可以通过序列化等方式解决(如图片等). 最近项目中有个excel上传,以及多媒体文件上传,直接报错. 也试了2种解决方式,都不可行. 1.写一个文件Encoder解析器,会出现其他的rest请求出现encoder错误 2.springcloud feign有一个规范,不可以传输2个对象,可以是一个对象带几个参数方式. 那么我们现在需要一种方式,不配置全局的解析器,而是通过Feign Builder 去管理上传文件,这种方式管理起来也较为方便.

  • 使用Feign实现微服务间文件下载

    在使用Feign做服务间调用的时候,当下载大的文件会出现堆栈溢出的情况.另外,文件管理服务(服务提供者)文件下载接口无返回值,是通过HttpServletRespoonse传输的流数据来响应,那么服务消费者该如何接受下载的数据呢? 一. 示例介绍 我们调用feign_upload_second的下载文件接口下载文件,feign_upload_second内部使用feign调用feign_upload_first实现文件下载. 二.feign_upload_first服务提供者 服务提供者下载文件

  • 微服务间调用Retrofit在Spring Cloud Alibaba中的使用

    目录 前置知识 搭建 使用 集成与配置 服务间调用 服务限流 熔断降级 总结 前置知识 在微服务项目中,如果我们想实现服务间调用,一般会选择Feign.之前介绍过一款HTTP客户端工具Retrofit,配合SpringBoot非常好用!其实Retrofit不仅支持普通的HTTP调用,还能支持微服务间的调用,负载均衡和熔断限流都能实现.今天我们来介绍下Retrofit在Spring Cloud Alibaba下的使用,希望对大家有所帮助! SpringBoot实战电商项目mall(50k+star

  • SpringCloud Feign实现微服务之间相互请求问题

    目录 Feign简介 Spring Cloud 组件依赖版本 Feign实现服务之间访问 ☘创建nacos-consumer-feign微服务 创建feign client ☘nacos-provider微服务 Feign微服务之间访问测试 ☘Feign容错机制 上篇文章说了通过RestTemplate实现微服务之间访问:https://www.jb51.net/article/252981.htm,这篇文章将通过Feign实现微服务之间访问.代码基于RestTemplate实现微服务之间访问基

  • 解决feign微服务间的文件上传报错问题

    A微服务调用B服务的上传文件接口报错: the request was rejected because no multipart boundary was found spring cloud版本 H <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>

  • 微服务之间如何通过feign调用接口上传文件

    具体需求: 我们的项目是基于springboot框架的springcloud微服务搭建的,后端服务技术层面整体上分为business服务和core服务,business服务用于作为应用层,直接连接客户端,通常用于聚合数据,core服务用来客户端具体操作不同需求来控制数据库,文件上传是通过客户端上传接口,通过business服务,由服务端调用feign接口,也是第一次做这种文件中转,遇到各种问题,下面是我自己的解决方案,不喜勿喷,代码小白一枚; 一.core服务层接口@requestmapping

  • SpringCloud feign微服务调用之间的异常处理方式

    如何优雅地处理微服务间调用的异常 现在微服务架构盛行,其中spring cloud方案就很具有代表. 那么在微服务之间进行调用,如果被调用的服务挂了,调用方如何感知呢? 一.加上hystrix熔断 在定义feignClient的地方指定熔断,如下图 当被调用服务不可用或者被调用方发生错误的时候,会触发熔断,但是,如果被调用方抛出异常,调用方怎么知道究竟是出了什么问题呢? 那,这就出现了 二.feign全局异常处理 我们不得不提到feign提供的一个接口叫做ErrorDecoder, 是用来处理f

  • Feign实现跨服务文件上传下载

    本文实例为大家分享了Feign实现跨服务的文件上传下载操作,供大家参考,具体内容如下 1.跨服务文件上传,目前feign不支持调用文件上传接口,需要自行配置来满足feign的调用方式 ①.首先需要在pom文件里添加feign依赖 <dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form-spring</artifactId> <versio

  • spring cloud eureka微服务之间的调用详解

    微服务之间的调用如何实现 首先 你需要两个或以上的微服务模块 至于怎么创建可以参考我上一篇博客 spring cloud eureka注册中心 如果想在页面显示 那么需要先加上 compile 'org.springframework.boot:spring-boot-starter-thymeleaf' 这个thymeleaf依赖 springboot推荐使用thymeleaf模板 它的最大好处就是原型即是模板 后缀是html html文件 需要放在resources/templates文件夹

随机推荐