Using Feign to realize file transfer between micro services

  • 2021-07-24 10:53:27
  • OfStack

In many cases, we will encounter file transfer between microservices, and in many cases we can solve it by serialization (such as pictures, etc.).

Recently, there is an excel upload in the project, as well as multimedia file upload, which directly reports errors.

Two solutions have been tried, but none of them are feasible.

1. Write a file Encoder parser, and other rest requests will have encoder errors

2. springcloud feign has a specification, which can not transmit two objects, but can be one object with several parameters.

Then we now need a way to manage uploaded files through Feign Builder instead of configuring a global parser, which is also more convenient to manage.

Reference package


<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>

Calling mode


@ApiOperation(value = " Upload Excel", notes = " Upload 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," File and operation users ID Neither can be empty ");
  String fileName = file.getOriginalFilename();
  if (!fileName.matches("^.+\\.(?i)(xls)$") && !fileName.matches("^.+\\.(?i)(xlsx)$")) {
    return new ActionResult<>(ResultType.BAD_REQUEST," The uploaded file is in the wrong format. Please upload the suffix .xls Or .xlsx File of ");
  }
  Feign.Builder encoder = Feign.builder()
      .decoder(new JacksonDecoder())
      .encoder(new FeignEncoder());
  FileUpload complainFileUpload = encoder.target(FileUpload.class,LABEL_URL);
  return complainFileUpload.imComplainExcel(file,operatorId);
 
}

File 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(" Parameter cannot be empty .");
    }
    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());
  }
 
  /**
   *  Set head 
   * @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);
    }
  }
 
  /**
   *  Mapping 
   * @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());
      }
    }
    /*
     Use bytearray Mode transmission 
     */
    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 call interface


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

Related articles: