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小时内删除。