RestTemplate OR Spring Cloud Feign 上传文件

SpringBoot,通过RestTemplate 或者 Spring Cloud Feign,上传文件(支持多文件上传),服务端接口是MultipartFile接收。

将文件的字节流,放入ByteArrayResource中,并重写getFilename方法。

然后将ByteArrayResource放入MultiValueMap中(如果是Feign调用,方法里传参就是MultiValueMap),

然后进行上传时,Spring会自动识别到Map中的文件数据,然后通过FormHttpMessageConverter,将数据转成form表单型的multipart/formdata请求。

这里有个坑!

Spring web 4里面的FormHttpMessageConverter在将文件转成formdata时,会将文件名转成Byte[],但是使用的编码却是写死 US-ASCII,该编码不支持中文,使用该编码转换后,中文变成?号,是无法转回来的。

我想到的解决方法:

1.将spring版本升到5,Spring5里面,该编码是可以传入修改的。Springboot,默认UTF8

2.客户端进行一次编码,比如URLEncoder。然后服务端进行Decoder。

贴部分代码:

Feign

 调用方,使用Spring 的MultiValueMap类,将文件File 转成 Resource,如果多个文件,则可以循环 用 add 方法,放入一个key下。

MultiValueMap是允许一key多值的。

或者,将多个Resource放入list,然后将list  put 进 map中。

 接收方

接收,可以用

(MultiValueMap map)

如果有其他的 值。

则是(MultiValueMap map,String XXX,String  AAA)

多文件,则是

(MultiValueMap[] map,String XXX,String  AAA)

或者用对象接收,也可以,不需要 @RequestBody 注解,这个注解是接收 http body里的json的。

(Bean bean),bean对象里,则是  MultiValueMap[] map,String XXX,String  AAA

======================  分割线  =================================

我在查询Feign上传文件时,还查到了另一种方式,就是专门给Feign方式提供的feign form相关Jar包,

引入Jar包后,然后进行相关配置,便可以在Feign方法中,参数直接传递MultipartFile。

该方法,或许也可以解决Spring4的编码问题。

===================     分隔线:2018-11-8补充 关于 feign form用法      ====================

先引入相关jar:

1<dependency> 2 <groupId>io.github.openfeign.form</groupId> 3 <artifactId>feign-form</artifactId> 4 <version>3.2.2</version> 5 </dependency> 6 <dependency> 7 <groupId>io.github.openfeign.form</groupId> 8 <artifactId>feign-form-spring</artifactId> 9 <version>3.2.2</version> 10 </dependency> 11 12@Bean 13 public Logger.Level feignLoggerLevel() { 14 return Logger.Level.FULL; 15 } 16 17 @Bean 18 public Encoder feignFormEncoder() { 19 return new SpringFormEncoder(); 20 }

feign 调用方法 写法 : 

1save(@RequestPart MultipartFile file,@RequestParam("khbh") String khbh)但是如果,参数多时,一个一个写较为麻烦,可以用 2 3save(Map<String,?> param)但是,经过测试,发现如果 map中value是null,会出现异常。(原因好像是因为,在将 值写入 formdata时,没有null判断)========== 上面的用的 feign form下面 有从网络上查到的,是类似于 feign form的解决方式:http://b-l-east.iteye.com/blog/2373462 4 5糞坑-SpringCloud中使用Feign的坑 6示例如下: 7@FeignClient("service-resource") 8//@RequestMapping("/api/test") 9public interface TestResourceItg { 10 11 @RequestMapping(value = "/api/test/raw", method = RequestMethod.POST, consumes = "application/x-www-form-urlencoded") 12 public String raw1(@PathVariable("subject") String subject, // 标题 13 @RequestParam("content") String content); // 内容 14 15} 16 17 18说明: 19*使用RequestMapping中的consumes指定生成的请求的Content-Type 20*RequestParam指定的参数会拼接在URL之后,如: ?name=xxx&age=18 21*PathVariable指定的参数会放到一个LinkedHashMap<String, ?>传入到feign的Encoder中进行处理,而在Spring中实现了该接口的Encoder为SpringEncoder,而该实现又会使用Spring中的HttpMessageConverter进行请求体的写入。 22 23 24坑: 25*不要在接口类名上使用RequestMapping,虽然可以使用,但同时SpringMVC会把该接口的实例当作Controller开放出去,这个可以在启动的Mapping日志中查看到 26*使用默认的SpringEncoder,在不指定consumes时,PathVariable中的参数会生成JSON字符串发送,且默认情况下不支持Form表单的生成方式,原因为:FormHttpMessageConverter只能处理MultiValueMap,而使用PathVariable参数被放在了HashMap中。默认更不支持文件上传。其实已经有支持处理各种情况的HttpMessageConverter存在。 27 28填坑: 29*支持Form表单提交:只需要编写一个支持Map的FormHttpMessageConverter即可,内部可调用FormHttpMessageConverter的方法简化操作。 30*支持文件上传:只需要把要上传的文件封装成一个Resource(该Resource一定要实现filename接口,这个是把请求参数解析成文件的标识),使用默认的ResourceHttpMessageConverter处理即可。 31*支持处理MultipartFile参数:编写一个支持MultipartFile的MultipartFileHttpMessageConverter即可,内部可调用ResourceHttpMessageConverter实现,同时注意需要将其添加至FormHttpMessageConverter的Parts中,并重写FormHttpMessageConverter的getFilename方法支持从MultipartFile中获取filename 32*所有的HttpMessageConverter直接以@Bean的方式生成即可,spring会自动识别添加 33 34完美支持表单和文件上传: 35方案一: 36使用附件中的MapFormHttpMessageConverter.java和MultipartFileHttpMessageConverter.java 37在Spring中进行如下配置即可 38@Bean 39public MapFormHttpMessageConverter mapFormHttpMessageConverter(MultipartFileHttpMessageConverter multipartFileHttpMessageConverter) { 40 MapFormHttpMessageConverter mapFormHttpMessageConverter = new MapFormHttpMessageConverter(); 41 mapFormHttpMessageConverter.addPartConverter(multipartFileHttpMessageConverter); 42 return mapFormHttpMessageConverter; 43} 44 45@Bean 46public MultipartFileHttpMessageConverter multipartFileHttpMessageConverter() { 47 return new MultipartFileHttpMessageConverter(); 48} 49方案二: 50使用FeignSpringFormEncoder.java 51在Spring中配置如下: 52@Bean 53public Encoder feignEncoder(ObjectFactory<HttpMessageConverters> messageConverters) { 54 return new FeignSpringFormEncoder(messageConverters); 55} 56 57推荐使用方案一 58方案二为参考https://github.com/pcan/feign-client-test而来,未测

上面方案中所用代码,贴在下面:

1package com.access.service.saas.cmpt.utl; 2 3import java.io.IOException; 4import java.io.InputStream; 5import java.util.ArrayList; 6import java.util.Collections; 7import java.util.List; 8 9import org.springframework.core.io.InputStreamResource; 10import org.springframework.http.HttpInputMessage; 11import org.springframework.http.HttpOutputMessage; 12import org.springframework.http.MediaType; 13import org.springframework.http.converter.HttpMessageConverter; 14import org.springframework.http.converter.HttpMessageNotReadableException; 15import org.springframework.http.converter.HttpMessageNotWritableException; 16import org.springframework.http.converter.ResourceHttpMessageConverter; 17import org.springframework.web.multipart.MultipartFile; 18 19/** 20 * @author elvis.xu 21 * @since 2017-05-09 11:17 22 */ 23public class MultipartFileHttpMessageConverter implements HttpMessageConverter<MultipartFile> { 24 protected List<MediaType> supportedMediaTypes = new ArrayList<MediaType>(); 25 protected ResourceHttpMessageConverter resourceHttpMessageConverter; 26 27 public MultipartFileHttpMessageConverter() { 28 supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); 29 resourceHttpMessageConverter = new ResourceHttpMessageConverter(); 30 } 31 32 public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) { 33 this.supportedMediaTypes = supportedMediaTypes; 34 } 35 36 @Override 37 public List<MediaType> getSupportedMediaTypes() { 38 return Collections.unmodifiableList(this.supportedMediaTypes); 39 } 40 41 @Override 42 public boolean canRead(Class<?> clazz, MediaType mediaType) { 43 return false; 44 } 45 46 @Override 47 public boolean canWrite(Class<?> clazz, MediaType mediaType) { 48 if (!MultipartFile.class.isAssignableFrom(clazz)) { 49 return false; 50 } 51 if (mediaType == null || MediaType.ALL.equals(mediaType)) { 52 return true; 53 } 54 for (MediaType supportedMT : getSupportedMediaTypes()) { 55 if (supportedMT.isCompatibleWith(mediaType)) { 56 return true; 57 } 58 } 59 return false; 60 } 61 62 @Override 63 public MultipartFile read(Class<? extends MultipartFile> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { 64 return null; 65 } 66 67 @Override 68 public void write(MultipartFile file, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { 69 MultipartFileResource multipartFileResource = new MultipartFileResource(file); 70 resourceHttpMessageConverter.write(multipartFileResource, contentType, outputMessage); 71 } 72 73 public static class MultipartFileResource extends InputStreamResource { 74 75 private final String filename; 76 private final long size; 77 78 public MultipartFileResource(MultipartFile multipartFile) throws IOException { 79 this(multipartFile.getOriginalFilename(), multipartFile.getSize(), multipartFile.getInputStream()); 80 } 81 82 public MultipartFileResource(String filename, long size, InputStream inputStream) { 83 super(inputStream); 84 this.size = size; 85 this.filename = filename; 86 } 87 88 @Override 89 public String getFilename() { 90 return this.filename; 91 } 92 93 @Override 94 public InputStream getInputStream() throws IOException, IllegalStateException { 95 return super.getInputStream(); //To change body of generated methods, choose Tools | Templates. 96 } 97 98 @Override 99 public long contentLength() throws IOException { 100 return size; 101 } 102 103 } 104} 105 106package com.access.service.saas.cmpt.utl; 107 108import java.io.ByteArrayOutputStream; 109import java.io.IOException; 110import java.io.InputStream; 111import java.io.OutputStream; 112import java.lang.reflect.Type; 113import java.nio.charset.Charset; 114import java.util.Arrays; 115import java.util.List; 116import java.util.Map; 117 118import feign.RequestTemplate; 119import feign.codec.EncodeException; 120 121import org.springframework.beans.factory.ObjectFactory; 122import org.springframework.boot.autoconfigure.web.HttpMessageConverters; 123import org.springframework.cloud.netflix.feign.support.SpringEncoder; 124import org.springframework.core.io.InputStreamResource; 125import org.springframework.core.io.Resource; 126import org.springframework.http.HttpEntity; 127import org.springframework.http.HttpHeaders; 128import org.springframework.http.HttpOutputMessage; 129import org.springframework.http.MediaType; 130import org.springframework.http.converter.HttpMessageConverter; 131import org.springframework.util.LinkedMultiValueMap; 132import org.springframework.web.multipart.MultipartFile; 133 134/** 135 * @author elvis.xu 136 * @since 2017-04-11 15:33 137 */ 138public class FeignSpringFormEncoder extends SpringEncoder { 139 140 protected ObjectFactory<HttpMessageConverters> messageConverters; 141 protected HttpHeaders multipartHeaders = new HttpHeaders(); 142 public static final Charset UTF_8 = Charset.forName("UTF-8"); 143 144 public FeignSpringFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) { 145 super(messageConverters); 146 this.messageConverters = messageConverters; 147 multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); 148 } 149 150 protected static boolean isFormRequest(Type type) { 151 return MAP_STRING_WILDCARD.equals(type); 152 } 153 154 protected static boolean isMultipart(Object body, Type bodyType) { 155 if (isFormRequest(bodyType)) { 156 Map<String, ?> map = (Map<String, ?>) body; 157 for (Map.Entry<String, ?> entry : map.entrySet()) { 158 Object value = entry.getValue(); 159 if (isMultipartFile(value) || isMultipartFileArray(value)) { 160 return true; 161 } 162 } 163 } 164 return false; 165 } 166 167 protected static boolean isMultipartFile(Object obj) { 168 return obj instanceof MultipartFile; 169 } 170 171 protected static boolean isMultipartFileArray(Object o) { 172 return o != null && o.getClass().isArray() && MultipartFile.class.isAssignableFrom(o.getClass().getComponentType()); 173 } 174 175 @Override 176 public void encode(Object requestBody, Type bodyType, RequestTemplate request) throws EncodeException { 177 if (isMultipart(requestBody, bodyType)) { 178 encodeMultipartFormRequest((Map<String, ?>) requestBody, request); 179 } else { 180 super.encode(requestBody, bodyType, request); 181 } 182 } 183 184 /** 185 * Encodes the request as a multipart form. It can detect a single {@link MultipartFile}, an 186 * array of {@link MultipartFile}s, or POJOs (that are converted to JSON). 187 * 188 * @param formMap 189 * @param template 190 * @throws EncodeException 191 */ 192 private void encodeMultipartFormRequest(Map<String, ?> formMap, RequestTemplate template) throws EncodeException { 193 if (formMap == null) { 194 throw new EncodeException("Cannot encode request with null form."); 195 } 196 LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); 197 for (Map.Entry<String, ?> entry : formMap.entrySet()) { 198 Object value = entry.getValue(); 199 if (isMultipartFile(value)) { 200 map.add(entry.getKey(), encodeMultipartFile((MultipartFile) value)); 201 } else if (isMultipartFileArray(value)) { 202 encodeMultipartFiles(map, entry.getKey(), Arrays.asList((MultipartFile[]) value)); 203 } else { 204 map.add(entry.getKey(), encodeJsonObject(value)); 205 } 206 } 207 encodeRequest(map, multipartHeaders, template); 208 } 209 210 /** 211 * Wraps a single {@link MultipartFile} into a {@link HttpEntity} and sets the 212 * {@code Content-type} header to {@code application/octet-stream} 213 * 214 * @param file 215 * @return 216 */ 217 private HttpEntity<?> encodeMultipartFile(MultipartFile file) { 218 HttpHeaders filePartHeaders = new HttpHeaders(); 219 filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); 220 try { 221 Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream()); 222 return new HttpEntity<>(multipartFileResource, filePartHeaders); 223 } catch (IOException ex) { 224 throw new EncodeException("Cannot encode request.", ex); 225 } 226 } 227 228 /** 229 * Fills the request map with {@link HttpEntity}s containing the given {@link MultipartFile}s. 230 * Sets the {@code Content-type} header to {@code application/octet-stream} for each file. 231 * 232 * @param map the current request map. 233 * @param name the name of the array field in the multipart form. 234 * @param files 235 */ 236 private void encodeMultipartFiles(LinkedMultiValueMap<String, Object> map, String name, List<? extends MultipartFile> files) { 237 HttpHeaders filePartHeaders = new HttpHeaders(); 238 filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); 239 try { 240 for (MultipartFile file : files) { 241 Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream()); 242 map.add(name, new HttpEntity<>(multipartFileResource, filePartHeaders)); 243 } 244 } catch (IOException ex) { 245 throw new EncodeException("Cannot encode request.", ex); 246 } 247 } 248 249 /** 250 * Wraps an object into a {@link HttpEntity} and sets the {@code Content-type} header to 251 * {@code application/json} 252 * 253 * @param o 254 * @return 255 */ 256 private HttpEntity<?> encodeJsonObject(Object o) { 257 HttpHeaders jsonPartHeaders = new HttpHeaders(); 258 jsonPartHeaders.setContentType(MediaType.APPLICATION_JSON); 259 return new HttpEntity<>(o, jsonPartHeaders); 260 } 261 262 /** 263 * Calls the conversion chain actually used by 264 * {@link org.springframework.web.client.RestTemplate}, filling the body of the request 265 * template. 266 * 267 * @param value 268 * @param requestHeaders 269 * @param template 270 * @throws EncodeException 271 */ 272 private void encodeRequest(Object value, HttpHeaders requestHeaders, RequestTemplate template) throws EncodeException { 273 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 274 HttpOutputMessage dummyRequest = new HttpOutputMessageImpl(outputStream, requestHeaders); 275 try { 276 Class<?> requestType = value.getClass(); 277 MediaType requestContentType = requestHeaders.getContentType(); 278 for (HttpMessageConverter<?> messageConverter : messageConverters.getObject().getConverters()) { 279 if (messageConverter.canWrite(requestType, requestContentType)) { 280 ((HttpMessageConverter<Object>) messageConverter).write(value, requestContentType, dummyRequest); 281 break; 282 } 283 } 284 } catch (IOException ex) { 285 throw new EncodeException("Cannot encode request.", ex); 286 } 287 HttpHeaders headers = dummyRequest.getHeaders(); 288 if (headers != null) { 289 for (Map.Entry<String, List<String>> entry : headers.entrySet()) { 290 template.header(entry.getKey(), entry.getValue()); 291 } 292 } 293 /* 294 we should use a template output stream... this will cause issues if files are too big, 295 since the whole request will be in memory. 296 */ 297 template.body(outputStream.toByteArray(), UTF_8); 298 } 299 300 /** 301 * Dummy resource class. Wraps file content and its original name. 302 */ 303 static class MultipartFileResource extends InputStreamResource { 304 305 private final String filename; 306 private final long size; 307 308 public MultipartFileResource(String filename, long size, InputStream inputStream) { 309 super(inputStream); 310 this.size = size; 311 this.filename = filename; 312 } 313 314 @Override 315 public String getFilename() { 316 return this.filename; 317 } 318 319 @Override 320 public InputStream getInputStream() throws IOException, IllegalStateException { 321 return super.getInputStream(); //To change body of generated methods, choose Tools | Templates. 322 } 323 324 @Override 325 public long contentLength() throws IOException { 326 return size; 327 } 328 329 } 330 331 /** 332 * Minimal implementation of {@link org.springframework.http.HttpOutputMessage}. It's needed to 333 * provide the request body output stream to 334 * {@link org.springframework.http.converter.HttpMessageConverter}s 335 */ 336 private class HttpOutputMessageImpl implements HttpOutputMessage { 337 338 private final OutputStream body; 339 private final HttpHeaders headers; 340 341 public HttpOutputMessageImpl(OutputStream body, HttpHeaders headers) { 342 this.body = body; 343 this.headers = headers; 344 } 345 346 @Override 347 public OutputStream getBody() throws IOException { 348 return body; 349 } 350 351 @Override 352 public HttpHeaders getHeaders() { 353 return headers; 354 } 355 356 } 357} 358 359package com.access.service.saas.cmpt.utl; 360 361import java.io.IOException; 362import java.util.List; 363import java.util.Map; 364 365import org.springframework.http.HttpInputMessage; 366import org.springframework.http.HttpOutputMessage; 367import org.springframework.http.MediaType; 368import org.springframework.http.converter.FormHttpMessageConverter; 369import org.springframework.http.converter.HttpMessageConverter; 370import org.springframework.http.converter.HttpMessageNotReadableException; 371import org.springframework.http.converter.HttpMessageNotWritableException; 372import org.springframework.util.LinkedMultiValueMap; 373import org.springframework.util.MultiValueMap; 374import org.springframework.web.multipart.MultipartFile; 375 376/** 377 * @author elvis.xu 378 * @since 2017-05-09 10:58 379 */ 380public class MapFormHttpMessageConverter implements HttpMessageConverter<Map<String, ?>> { 381 382 protected FormHttpMessageConverter formHttpMessageConverter; 383 384 public MapFormHttpMessageConverter() { 385 this.formHttpMessageConverter = new MultipartFormHttpMessageConverter(); 386 } 387 388 389 public void addPartConverter(HttpMessageConverter<?> partConverter) { 390 this.formHttpMessageConverter.addPartConverter(partConverter); 391 } 392 393 @Override 394 public boolean canRead(Class<?> clazz, MediaType mediaType) { 395 return formHttpMessageConverter.canRead(clazz, mediaType); 396 } 397 398 @Override 399 public List<MediaType> getSupportedMediaTypes() { 400 return formHttpMessageConverter.getSupportedMediaTypes(); 401 } 402 403 @Override 404 public boolean canWrite(Class<?> clazz, MediaType mediaType) { 405 if (!Map.class.isAssignableFrom(clazz)) { 406 return false; 407 } 408 if (mediaType == null || MediaType.ALL.equals(mediaType)) { 409 return true; 410 } 411 for (MediaType supportedMediaType : getSupportedMediaTypes()) { 412 if (supportedMediaType.isCompatibleWith(mediaType)) { 413 return true; 414 } 415 } 416 return false; 417 } 418 419 @Override 420 public Map<String, ?> read(Class<? extends Map<String, ?>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { 421 return formHttpMessageConverter.read(null, inputMessage); 422 } 423 424 public void write(Map<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { 425 MultiValueMap<String, Object> multiMap = null; 426 if (map != null) { 427 if (map instanceof MultiValueMap) { 428 multiMap = (MultiValueMap<String, Object>) map; 429 } else { 430 multiMap = new LinkedMultiValueMap<>(); 431 for (Map.Entry<String, ?> entry : map.entrySet()) { 432 multiMap.add(entry.getKey(), entry.getValue()); 433 } 434 } 435 } 436 formHttpMessageConverter.write(multiMap, contentType, outputMessage); 437 } 438 439 public static class MultipartFormHttpMessageConverter extends FormHttpMessageConverter { 440 @Override 441 protected String getFilename(Object part) { 442 String rt = super.getFilename(part); 443 if (rt == null && part instanceof MultipartFile) { 444 return ((MultipartFile) part).getOriginalFilename(); 445 } 446 return null; 447 } 448 } 449}
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

RestTemplate OR Spring Cloud Feign 上传文件 - HelloWorld