书接上文。

上文中描述了如何在 SpringCloud+Feign环境下上传文件与form-data同时存在的解决办法,实践证明基本可行,但却会引入其他问题。

主要导致的后果是:

1. 无法与普通Feign方法并存

2. 几率性(不确定条件下)导致其他form-data类型参数无法识别,无法正常工作,错误信息大致如下:

org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present

分析原因发现是Feign的Encoder体系中缺乏对应的配置从而无法工作;但将这些Encoder一一补上却过于困难,因此,决定换一个思路,使用Spring技术解决该问题。

该问题的本质是controller层传参时参数的编解码问题,因此,应该属于HttpMessageConverter范畴的事情,这参考SpringEncoder的代码即可得知。

最终,解决方案如下:

1. 去掉依赖,因为不再需要

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

2. 去掉类  FeignSpringFormEncoder ,不再需要

3. 注解  RequestPartParam 、类  RequestPartParamParameterProcessor 以及 bean 的定义均与上文保持一致

4. 参考类  FormHttpMessageConverter  添加类  LinkedHashMapFormHttpMessageConverter  ,并注意使用 @Conponent注解进行Spring托管。代码如下:

 import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.multipart.MultipartFile; import javax.mail.internet.MimeUtility; import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; /**
* 参考 FormHttpMessageConverter
*/
@Component
public class LinkedHashMapFormHttpMessageConverter implements HttpMessageConverter<LinkedHashMap<String, ?>> { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>(); private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>(); private Charset charset = DEFAULT_CHARSET; private Charset multipartCharset; public LinkedHashMapFormHttpMessageConverter() {
this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316 this.partConverters.add(new ByteArrayHttpMessageConverter());
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter()); MultipartFileHttpMessageConverter multipartFileHttpMessageConverter = new MultipartFileHttpMessageConverter();
this.partConverters.add(multipartFileHttpMessageConverter); applyDefaultCharset();
} @Override
public List<MediaType> getSupportedMediaTypes() {
return Collections.unmodifiableList(this.supportedMediaTypes);
} public void setPartConverters(List<HttpMessageConverter<?>> partConverters) {
Assert.notEmpty(partConverters, "'partConverters' must not be empty");
this.partConverters = partConverters;
} public void addPartConverter(HttpMessageConverter<?> partConverter) {
Assert.notNull(partConverter, "'partConverter' must not be null");
this.partConverters.add(partConverter);
} public void setCharset(Charset charset) {
if (charset != this.charset) {
this.charset = (charset != null ? charset : DEFAULT_CHARSET);
applyDefaultCharset();
}
} private void applyDefaultCharset() {
for (HttpMessageConverter<?> candidate : this.partConverters) {
if (candidate instanceof AbstractHttpMessageConverter) {
AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;
// Only override default charset if the converter operates with a charset to begin with...
if (converter.getDefaultCharset() != null) {
converter.setDefaultCharset(this.charset);
}
}
}
} public void setMultipartCharset(Charset charset) {
this.multipartCharset = charset;
} @Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
} @Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if (!LinkedHashMap.class.isAssignableFrom(clazz)) {
return false;
}
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
return false;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
} @Override
public LinkedHashMap<String, String> read(Class<? extends LinkedHashMap<String, ?>> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
throw new HttpMessageNotReadableException("Not supportted for read.");
} @Override
@SuppressWarnings("unchecked")
public void write(LinkedHashMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException { writeMultipart((LinkedHashMap<String, Object>) map, outputMessage);
} private void writeMultipart(final LinkedHashMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException {
final byte[] boundary = generateMultipartBoundary();
Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII")); MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
HttpHeaders headers = outputMessage.getHeaders();
headers.setContentType(contentType); if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
writeParts(outputStream, parts, boundary);
writeEnd(outputStream, boundary);
}
});
}
else {
writeParts(outputMessage.getBody(), parts, boundary);
writeEnd(outputMessage.getBody(), boundary);
}
} private void writeParts(OutputStream os, LinkedHashMap<String, Object> parts, byte[] boundary) throws IOException {
for (Map.Entry<String, Object> entry : parts.entrySet()) {
String name = entry.getKey();
Object part = entry.getValue();
if (part != null) {
writeBoundary(os, boundary);
writePart(name, getHttpEntity(part), os);
writeNewLine(os);
}
}
} @SuppressWarnings("unchecked")
private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException {
Object partBody = partEntity.getBody();
Class<?> partType = partBody.getClass();
HttpHeaders partHeaders = partEntity.getHeaders();
MediaType partContentType = partHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : this.partConverters) {
if (messageConverter.canWrite(partType, partContentType)) {
HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os);
multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
if (!partHeaders.isEmpty()) {
multipartMessage.getHeaders().putAll(partHeaders);
}
((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
return;
}
}
throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +
"found for request type [" + partType.getName() + "]");
} protected byte[] generateMultipartBoundary() {
return MimeTypeUtils.generateMultipartBoundary();
} protected HttpEntity<?> getHttpEntity(Object part) {
return (part instanceof HttpEntity ? (HttpEntity<?>) part : new HttpEntity<Object>(part));
} protected String getFilename(Object part) {
if (part instanceof Resource) {
Resource resource = (Resource) part;
String filename = resource.getFilename();
if (filename != null && this.multipartCharset != null) {
filename = MimeDelegate.encode(filename, this.multipartCharset.name());
}
return filename;
} else if (part instanceof MultipartFile) {
MultipartFile multipartFile = (MultipartFile) part;
String filename = multipartFile.getName();
if (filename == null || filename.isEmpty()) {
filename = multipartFile.getOriginalFilename();
}
return filename;
}
else {
return null;
}
} private void writeBoundary(OutputStream os, byte[] boundary) throws IOException {
os.write('-');
os.write('-');
os.write(boundary);
writeNewLine(os);
} private static void writeEnd(OutputStream os, byte[] boundary) throws IOException {
os.write('-');
os.write('-');
os.write(boundary);
os.write('-');
os.write('-');
writeNewLine(os);
} private static void writeNewLine(OutputStream os) throws IOException {
os.write('\r');
os.write('\n');
} private static class MultipartHttpOutputMessage implements HttpOutputMessage { private final OutputStream outputStream; private final HttpHeaders headers = new HttpHeaders(); private boolean headersWritten = false; public MultipartHttpOutputMessage(OutputStream outputStream) {
this.outputStream = outputStream;
} @Override
public HttpHeaders getHeaders() {
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
} @Override
public OutputStream getBody() throws IOException {
writeHeaders();
return this.outputStream;
} private void writeHeaders() throws IOException {
if (!this.headersWritten) {
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
byte[] headerName = getAsciiBytes(entry.getKey());
for (String headerValueString : entry.getValue()) {
byte[] headerValue = getAsciiBytes(headerValueString);
this.outputStream.write(headerName);
this.outputStream.write(':');
this.outputStream.write(' ');
this.outputStream.write(headerValue);
writeNewLine(this.outputStream);
}
}
writeNewLine(this.outputStream);
this.headersWritten = true;
}
} private byte[] getAsciiBytes(String name) {
try {
return name.getBytes("US-ASCII");
}
catch (UnsupportedEncodingException ex) {
// Should not happen - US-ASCII is always supported.
throw new IllegalStateException(ex);
}
}
} private static class MimeDelegate { public static String encode(String value, String charset) {
try {
return MimeUtility.encodeText(value, charset, null);
}
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
}
}

5. 参考类  ResourceHttpMessageConverter  添加类  MultipartFileHttpMessageConverter ,代码如下:

 import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile; import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; public class MultipartFileHttpMessageConverter extends AbstractHttpMessageConverter<MultipartFile> { public MultipartFileHttpMessageConverter() {
super(MediaType.APPLICATION_OCTET_STREAM);
} @Override
protected boolean supports(Class<?> clazz) {
return MultipartFile.class.isAssignableFrom(clazz);
} @Override
protected MultipartFile readInternal(Class<? extends MultipartFile> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new HttpMessageNotReadableException("Not supportted for read.");
} @Override
protected MediaType getDefaultContentType(MultipartFile multipartFile) {
try {
String contentType = multipartFile.getContentType();
MediaType mediaType = MediaType.valueOf(contentType);
if (mediaType != null) {
return mediaType;
}
} catch (Exception ex) {
}
return MediaType.APPLICATION_OCTET_STREAM;
} @Override
protected Long getContentLength(MultipartFile multipartFile, MediaType contentType) throws IOException {
long contentLength = multipartFile.getSize();
return (contentLength < 0 ? null : contentLength);
} @Override
protected void writeInternal(MultipartFile multipartFile, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
writeContent(multipartFile, outputMessage);
} protected void writeContent(MultipartFile multipartFile, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
try {
InputStream in = multipartFile.getInputStream();
try {
StreamUtils.copy(in, outputMessage.getBody());
}
catch (NullPointerException ex) {
// ignore, see SPR-13620
}
finally {
try {
in.close();
}
catch (Throwable ex) {
// ignore, see SPR-12999
}
}
}
catch (FileNotFoundException ex) {
// ignore, see SPR-12999
}
} }

按照上述配置即可工作,但需要严格注意:

由于使用 LinkedHashMapFormHttpMessageConverter 的原因,会导致:

当某次http请求的参数或者返回值中包含 LinkedHashMap 且其请求的MediaType兼容 MULTIPART_FORM_DATA 时,该次参数或者返回值会错误的被 LinkedHashMapFormHttpMessageConverter 处理,但此时很显然是不正确的处理方式。

对应的办法:避开该情况,或者有更好的办法请务必分享给我,不胜感激。

SpringCloud+Feign环境下文件上传与form-data同时存在的解决办法(2)的更多相关文章

  1. SpringCloud+Feign环境下文件上传与form-data同时存在的解决办法

    最近项目转型使用SpringCloud框架下的微服务架构,各微服务之间使用Feign进行调用.期间,发现若被调用方法涉及到文件上传且仅存在单个文件时,一切正常,代码片段如下: @RequestMapp ...

  2. 不同环境下文件上传Uncaught SyntaxError: Unexpected end of input

    很奇怪的问题,相同的代码和相同的数据,在两台linux服务器上执行文件上传,一台正常上传,一台在ftl页面 报:Uncaught SyntaxError: Unexpected end of inpu ...

  3. ie下文件上传无权访问的问题

    最近项目遇到个问题,ie下文件上传无权访问,在网上找了很久才找到答案,原来是因为ie下不能用js触发input=file的点击事件,必须手动点击才可以.

  4. [原创]Struts2奇葩环境任意文件上传工具(解决菜刀无法传文件或上传乱码等问题)

    上面这问题问得好  1 不知道大家有没碰到有些Strus2站点  上传JSP后访问404 或者503    注意我说的是404或503不是403(要是403换个css/img等目录或许可以)    但 ...

  5. Web下文件上传下载的路径问题

    工程结构

  6. linux 下文件上传的两种工具(XFTP5和Putty之pscp)方式

    一.使用XFTP(,需要先在LINUX上安装启用FTP服务) 然后,在WINDOWS上启动XFPT6客户端,将下载的文件上传至LINUX 指定目录: 二.使用PUTTY软件安装目录下的PSCP命令 1 ...

  7. asp.net mvc下文件上传

    典型的文件上传表单 <form action="/File" enctype="multipart/form-data" method="pos ...

  8. SpringBoot下文件上传与下载的实现

    原文:http://blog.csdn.net/colton_null/article/details/76696674 SpringBoot后台如何实现文件上传下载? 最近做的一个项目涉及到文件上传 ...

  9. IE下文件上传, SCRIPT5: 拒绝访问 问题

    最近遇到一个比较奇葩的问题,某些ie浏览器在页面中上传文件时,无法上传.查看控制台报错: SCRIPT5: 拒绝访问. jquery-3.2.1.min.js, 行4 字符5725 .并且我的最新版I ...

随机推荐

  1. 认识不一定熟悉的opencv

    对很多人来说,opencv就像在旅行路上遇到的某个人,很有可能,这个只是你生命中的匆匆过客.可是,对于一个立志要做熟悉图像处理的人来说,你不能绕过他. 他是什么? OpenCV是一个基于BSD许可(开 ...

  2. 使用autogen工具生成Makefile遇到问题解决思路

    使用autogen工具生成Makefile,最新的应用程序很多都使用autogen,本着知行合一的精神 最近有空也研究了一下该工具的使用,详细步骤请参考文档: http://blog.csdn.net ...

  3. BOM 浏览器对象模型

    1.window对象模型:(操作浏览器) 它既是ECMAScript规定的global对象,又是javascript访问浏览器窗口的一个接口 系统对话框:这些对话框有操作系统/浏览器设置决定,css不 ...

  4. 转载:https原理:证书传递、验证和数据加密、解密过程解析

    写的太好了,就是我一直想找的内容,看了这个对https立马明白多了 http://www.cnblogs.com/zhuqil/archive/2012/07/23/2604572.html 我们都知 ...

  5. Hadoop社区版搭建

    1.环境准备 1.1 硬件配置 设备名 参数 数量 6台 系统 centos6.5 内存 64G 硬盘 32T/台 CPU 16核/台 1.2 软件版本 Hadoop-2.x 下载地址 JDK1.7  ...

  6. 【详解】换一个角度看Socket的数据读写

    前言 以前对IO.NIO还算了解,也写过Netty的项目.但是对底层的数据传递不是很了解,一直存有这方面的疑惑.但是由于有其他事情就被打断了.前阵子因为想要了解volatile关键字的原理,学习了下J ...

  7. 如何在线显示php源代码

    通过php提供的函数highlight_file和highlight_string实现

  8. #2 安装Python

    上一篇文章主要记录 了Python简介,相信你已经爱上了小P,俗话说的好:公欲善其事,必先利其器,所以本文将带领你安装Python3! Windows平台 1.确认Windows位数: 鼠标右击此电脑 ...

  9. 搭建前端监控系统(二)JS错误监控篇

    ===================================================================== 前端性能监控系统: DEMO地址    GIT代码仓库地址 ...

  10. C# 元数据描述

    元数据概述:元数据是一种二进制信息,用以对存储在公共语言运行库可移植可执行文件 (PE) 文件或存储在内存中的程序进行描述.将您的代码编译为 PE 文件时,便会将元数据插入到该文件的一部分中,而将代码 ...