SpringCloud+Feign环境下⽂件上传与form-data同时存在的解决
办法(2)
书接上⽂。
上⽂中描述了如何在 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. 去掉依赖,因为不再需要
1<dependency>
2<groupId>io.github.openfeign.form</groupId>
3<artifactId>feign-form-spring</artifactId>
4<version>3.2.2</version>
5</dependency>
6
7<dependency>
8<groupId>io.github.openfeign.form</groupId>
9<artifactId>feign-form</artifactId>
10<version>3.2.2</version>
11</dependency>
2. 去掉类 FeignSpringFormEncoder ,不再需要
3. 注解 RequestPartParam 、类 RequestPartParamParameterProcessor 以及 bean 的定义均与上⽂保持⼀致
4. 参考类 FormHttpMessageConverter 添加类 LinkedHashMapFormHttpMessageConverter ,并注意使⽤ @Conponent注解进⾏Spring 托管。代码如下:
1import io.Resource;
2import org.springframework.http.HttpEntity;
3import org.springframework.http.HttpHeaders;
4import org.springframework.http.HttpInputMessage;
5import org.springframework.http.HttpOutputMessage;
6import org.springframework.http.MediaType;
7import org.springframework.http.StreamingHttpOutputMessage;
8import org.verter.AbstractHttpMessageConverter;
9import org.verter.ByteArrayHttpMessageConverter;
10import org.verter.HttpMessageConverter;
11import org.verter.HttpMessageNotReadableException;
12import org.verter.HttpMessageNotWritableException;
13import org.verter.ResourceHttpMessageConverter;
14import org.verter.StringHttpMessageConverter;
15import org.springframework.stereotype.Component;
16import org.springframework.util.Assert;
17import org.springframework.util.MimeTypeUtils;
18import org.springframework.web.multipart.MultipartFile;
19
20import javax.mail.internet.MimeUtility;
21
22import java.io.IOException;
23import java.io.OutputStream;
24import java.io.UnsupportedEncodingException;
25import java.nio.charset.Charset;
26import java.util.ArrayList;
27import java.util.Collections;
28import java.util.LinkedHashMap;
29import java.util.List;
30import java.util.Map;
31
32/**
33 * 参考 FormHttpMessageConverter
35 @Component
36public class LinkedHashMapFormHttpMessageConverter implements HttpMessageConverter<LinkedHashMap<String, ?>> { 37
38
39public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
40
41
42private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
43
44private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
45
46private Charset charset = DEFAULT_CHARSET;
47
48private Charset multipartCharset;
49
50
51public LinkedHashMapFormHttpMessageConverter() {
52this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
53
54 StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
55 stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
56
57this.partConverters.add(new ByteArrayHttpMessageConverter());
58this.partConverters.add(stringHttpMessageConverter);
59this.partConverters.add(new ResourceHttpMessageConverter());
60
61 MultipartFileHttpMessageConverter multipartFileHttpMessageConverter = new MultipartFileHttpMessageConverter(); 62this.partConverters.add(multipartFileHttpMessageConverter);
63
64 applyDefaultCharset();
65 }
66
67 @Override
68public List<MediaType> getSupportedMediaTypes() {
69return Collections.unmodifiableList(this.supportedMediaTypes);
70 }
71
72public void setPartConverters(List<HttpMessageConverter<?>> partConverters) {
73 Empty(partConverters, "'partConverters' must not be empty");
74this.partConverters = partConverters;
75 }
76
77public void addPartConverter(HttpMessageConverter<?> partConverter) {
78 Null(partConverter, "'partConverter' must not be null");
79this.partConverters.add(partConverter);
80 }
81
82public void setCharset(Charset charset) {
83if (charset != this.charset) {
84this.charset = (charset != null ? charset : DEFAULT_CHARSET);
85 applyDefaultCharset();
86 }
87 }
88
89private void applyDefaultCharset() {
90for (HttpMessageConverter<?> candidate : this.partConverters) {
91if (candidate instanceof AbstractHttpMessageConverter) {
92 AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;
93// Only override default charset if the converter operates with a charset to
94if (DefaultCharset() != null) {
95 converter.setDefaultCharset(this.charset);
96 }
97 }
98 }
99 }
100
101public void setMultipartCharset(Charset charset) {
102this.multipartCharset = charset;
103 }
104
105
106 @Override
107public boolean canRead(Class<?> clazz, MediaType mediaType) {
108return false;
109 }
110
111 @Override
112public boolean canWrite(Class<?> clazz, MediaType mediaType) {
113if (!LinkedHashMap.class.isAssignableFrom(clazz)) {
114return false;
115 }
116if (mediaType == null || MediaType.ALL.equals(mediaType)) {
117return false;
119for (MediaType supportedMediaType : getSupportedMediaTypes()) {
120if (supportedMediaType.isCompatibleWith(mediaType)) {
121return true;
122 }
123 }
124return false;
125 }
126
127 @Override
128public LinkedHashMap<String, String> read(Class<? extends LinkedHashMap<String, ?>> clazz,
129 HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
130throw new HttpMessageNotReadableException("Not supportted for read.");
131 }
132
133 @Override
134 @SuppressWarnings("unchecked")
135public void write(LinkedHashMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)
136throws IOException, HttpMessageNotWritableException {
137
138 writeMultipart((LinkedHashMap<String, Object>) map, outputMessage);
139 }
140
141private void writeMultipart(final LinkedHashMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException { 142final byte[] boundary = generateMultipartBoundary();
143 Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII"));
144
145 MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
146 HttpHeaders headers = Headers();
147 headers.setContentType(contentType);
148
149if (outputMessage instanceof StreamingHttpOutputMessage) {
150 StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
151 streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
152 @Override
153public void writeTo(OutputStream outputStream) throws IOException {
154 writeParts(outputStream, parts, boundary);
155 writeEnd(outputStream, boundary);
156 }
157 });
158 }
159else {
160 Body(), parts, boundary);
161 Body(), boundary);
162 }
163 }
164
165private void writeParts(OutputStream os, LinkedHashMap<String, Object> parts, byte[] boundary) throws IOException {
166for (Map.Entry<String, Object> entry : Set()) {
167 String name = Key();
168 Object part = Value();
169if (part != null) {
170 writeBoundary(os, boundary);
171 writePart(name, getHttpEntity(part), os);
172 writeNewLine(os);
173 }
174 }
175 }
176
177 @SuppressWarnings("unchecked")
178private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException {
179 Object partBody = Body();
180 Class<?> partType = Class();
181 HttpHeaders partHeaders = Headers();
182 MediaType partContentType = ContentType();
183for (HttpMessageConverter<?> messageConverter : this.partConverters) {
184if (messageConverter.canWrite(partType, partContentType)) {
185 HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os);
186 Headers().setContentDispositionFormData(name, getFilename(partBody));
187if (!partHeaders.isEmpty()) {
188 Headers().putAll(partHeaders);
189 }
190 ((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
191return;
192 }
193 }
194throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +
195 "found for request type [" + Name() + "]");
196 }
197
198protected byte[] generateMultipartBoundary() {
ateMultipartBoundary();
200 }
203return (part instanceof HttpEntity ? (HttpEntity<?>) part : new HttpEntity<Object>(part));
204 }
205
206protected String getFilename(Object part) {
207if (part instanceof Resource) {
208 Resource resource = (Resource) part;
209 String filename = Filename();
210if (filename != null && this.multipartCharset != null) {
211 filename = de(filename, this.multipartCharset.name());
212 }
213return filename;
214 } else if (part instanceof MultipartFile) {
215 MultipartFile multipartFile = (MultipartFile) part;
216 String filename = Name();
217if (filename == null || filename.isEmpty()) {
218 filename = OriginalFilename();
219 }
220return filename;
221 }
222else {
223return null;
224 }
225 }
226
227
228private void writeBoundary(OutputStream os, byte[] boundary) throws IOException {
229 os.write('-');
230 os.write('-');
231 os.write(boundary);
232 writeNewLine(os);
233 }
234
235private static void writeEnd(OutputStream os, byte[] boundary) throws IOException {
236 os.write('-');
237 os.write('-');
238 os.write(boundary);
239 os.write('-');
240 os.write('-');
241 writeNewLine(os);
242 }
243
244private static void writeNewLine(OutputStream os) throws IOException {
245 os.write('\r');
246 os.write('\n');
247 }
248
249private static class MultipartHttpOutputMessage implements HttpOutputMessage {
250
251private final OutputStream outputStream;
252
253private final HttpHeaders headers = new HttpHeaders();
254
255private boolean headersWritten = false;
256
257public MultipartHttpOutputMessage(OutputStream outputStream) {
258this.outputStream = outputStream;
259 }
260
261 @Override
262public HttpHeaders getHeaders() {
263return (this.headersWritten ? adOnlyHttpHeaders(this.headers) : this.headers); 264 }
265
266 @Override
267public OutputStream getBody() throws IOException {
268 writeHeaders();
269return this.outputStream;
270 }
271
272private void writeHeaders() throws IOException {
273if (!this.headersWritten) {
274for (Map.Entry<String, List<String>> entry : Set()) {
275byte[] headerName = Key());
276for (String headerValueString : Value()) {
277byte[] headerValue = getAsciiBytes(headerValueString);
278this.outputStream.write(headerName);
279this.outputStream.write(':');
280this.outputStream.write(' ');
281this.outputStream.write(headerValue);
282 writeNewLine(this.outputStream);
283 }
284 }
287 }
288 }
289
290private byte[] getAsciiBytes(String name) {
291try {
Bytes("US-ASCII");
293 }
294catch (UnsupportedEncodingException ex) {write的返回值
295// Should not happen - US-ASCII is always supported.
296throw new IllegalStateException(ex);
297 }
298 }
299 }
300
301private static class MimeDelegate {
302
303public static String encode(String value, String charset) {
304try {
deText(value, charset, null);
306 }
307catch (UnsupportedEncodingException ex) {
308throw new IllegalStateException(ex);
309 }
310 }
311 }
312 }
5. 参考类 ResourceHttpMessageConverter 添加类 MultipartFileHttpMessageConverter ,代码如下:
1import org.springframework.http.HttpInputMessage;
2import org.springframework.http.HttpOutputMessage;
3import org.springframework.http.MediaType;
4import org.verter.AbstractHttpMessageConverter;
5import org.verter.HttpMessageNotReadableException;
6import org.verter.HttpMessageNotWritableException;
7import org.springframework.util.StreamUtils;
8import org.springframework.web.multipart.MultipartFile;
9
10import java.io.FileNotFoundException;
11import java.io.IOException;
12import java.io.InputStream;
13
14public class MultipartFileHttpMessageConverter extends AbstractHttpMessageConverter<MultipartFile> {
15
16public MultipartFileHttpMessageConverter() {
17super(MediaType.APPLICATION_OCTET_STREAM);
18 }
19
20 @Override
21protected boolean supports(Class<?> clazz) {
22return MultipartFile.class.isAssignableFrom(clazz);
23 }
24
25 @Override
26protected MultipartFile readInternal(Class<? extends MultipartFile> clazz, HttpInputMessage inputMessage)
27throws IOException, HttpMessageNotReadableException {
28throw new HttpMessageNotReadableException("Not supportted for read.");
29 }
30
31 @Override
32protected MediaType getDefaultContentType(MultipartFile multipartFile) {
33try {
34 String contentType = ContentType();
35 MediaType mediaType = MediaType.valueOf(contentType);
36if (mediaType != null) {
37return mediaType;
38 }
39 } catch (Exception ex) {
40 }
41return MediaType.APPLICATION_OCTET_STREAM;
42 }
43
44 @Override
45protected Long getContentLength(MultipartFile multipartFile, MediaType contentType) throws IOException {
46long contentLength = Size();
47return (contentLength < 0 ? null : contentLength);
48 }
49
50 @Override
51protected void writeInternal(MultipartFile multipartFile, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
52 writeContent(multipartFile, outputMessage);
53 }
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论