SpringCloud项目利用FeignClient和MultipartFile[ ]进行文件传输

2022-07-29 10:58:50

首先Feign是不支持利用MultipartFile进行项目之间的文件传输,需要引入额外的依赖才可以,而且配置起来也是比较麻烦,多文件MultipartFile[ ]更麻烦,坑点也比较多。要么是各种报错,就是文件丢失。

一:如何配置文件上传
1)第一步:先准备两个spring cloud 的服务

端口:8800 的服务,文件上传
端口:8899 的服务,文件接收

2)第二步:8800的服务引入支持通过feign进行文件上传的相关依赖

<dependency><groupId>io.github.openfeign.form</groupId><artifactId>feign-form</artifactId><version>3.0.3</version></dependency><dependency><groupId>io.github.openfeign.form</groupId><artifactId>feign-form-spring</artifactId><version>3.0.3</version></dependency><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.3</version></dependency>

3)第三步:获取文件方法编写,将文件转换为MultipartFile对象

@AutowiredprivateIUploadClientServiceIUploadClientService;@RequestMapping(value="/test")publicvoiddownloadFile(){File f=newFile("C:/Users/tao/Desktop/新建文本文档.txt");try{DiskFileItem fileItem=(DiskFileItem)newDiskFileItemFactory().createItem("files","multipart/form-data",true, f.getName());try(InputStream input=newFileInputStream(f);OutputStream os= fileItem.getOutputStream()){IOUtils.copy(input, os);}catch(Exception e){thrownewIllegalArgumentException("Invalid file: "+ e, e);}MultipartFile multi=newCommonsMultipartFile(fileItem);MultipartFile[] multis=newMultipartFile[]{multi};IUploadClientService.handleFileUpload(multis);}catch(Exception e){
            e.printStackTrace();}}}

注意①上面的代码中有个files的参数,这个需要与第四步的注解中,以及第五步8899服务文件接受方的注解保持一致,否则会取不到文件 ; ②指定类型为multipart/form-data
4)第四步:编写通过feign调用8899服务的接口

@FeignClient(value="ZT-FRANK-ENCRYPTION-PROVIDER-SERVICE-8899",configuration=IUploadClientService.FeignMultipartSupportConfig.class)publicinterfaceIUploadClientService{@PostMapping(value="/uploadFile", consumes=MediaType.MULTIPART_FORM_DATA_VALUE)StringhandleFileUpload(@RequestPart(value="files")MultipartFile[] files);/**
     * 引用配置类FeignMultipartSupportConfig 并且实例化,这里你可以单独写成一个配置文件
     */@ConfigurationclassFeignMultipartSupportConfig{@Bean@Primary@Scope("prototype")publicEncodermultipartFormEncoder(){//TODO return new SpringFormEncoder(new SpringEncoder(messageConverters));//源码不支持文件数组上传returnnewFeignSpringFormEncoder();}@AutowiredprivateObjectFactory<HttpMessageConverters> messageConverters;@Beanpublicfeign.Logger.LevelmultipartLoggerLevel(){returnfeign.Logger.Level.FULL;}}}

注意①接口的注解是@RequestPart 而且里面的files参数要第三步保持一致; ②上面的代码也就是第三步中,引入了自定义的 FeignSpringFormEncoder 其目的是为了支持多文件上传,如果你不需要支持MultipartFile[ ] 数组,只是要单个的文件上传,可以不必配置,第三步TODO部分注释打开,new FeignSpringFormEncoder 注释,但是你的接口定义不能是MultipartFile[ ] 数组了,当然了你配置支持数组而不使用数组的形式传单个文件也是可以的

支持多文件上传FeignSpringFormEncoder 的配置

importfeign.RequestTemplate;importfeign.codec.EncodeException;importfeign.codec.Encoder;importfeign.form.ContentType;importfeign.form.FormEncoder;importfeign.form.MultipartFormContentProcessor;importfeign.form.spring.SpringManyMultipartFilesWriter;importfeign.form.spring.SpringSingleMultipartFileWriter;importorg.springframework.web.multipart.MultipartFile;importjava.lang.reflect.Type;importjava.util.Collections;importjava.util.Map;/**
 * @ClassName: FeignSpringFormEncoder
 * @Author: 
 * @Description: SpringFormEncoder 不支持 MultipartFile[] 多文件上传,下面配置让其支持上传数组
 * @Date: 2021/04/27
 * @Version: 1.0
 */publicclassFeignSpringFormEncoderextendsFormEncoder{publicFeignSpringFormEncoder(){this(newDefault());}publicFeignSpringFormEncoder(Encoder delegate){super(delegate);MultipartFormContentProcessor processor=(MultipartFormContentProcessor)this.getContentProcessor(ContentType.MULTIPART);
        processor.addWriter(newSpringSingleMultipartFileWriter());
        processor.addWriter(newSpringManyMultipartFilesWriter());}publicvoidencode(Object object,Type bodyType,RequestTemplate template)throwsEncodeException{if(bodyType.equals(MultipartFile.class)){MultipartFile file=(MultipartFile) object;Map<String,Object> data=Collections.singletonMap(file.getName(), object);super.encode(data, MAP_STRING_WILDCARD, template);return;}elseif(bodyType.equals(MultipartFile[].class)){MultipartFile[] file=(MultipartFile[]) object;if(file!=null){Map<String,Object> data=Collections.singletonMap(file.length==0?"": file[0].getName(), object);super.encode(data, MAP_STRING_WILDCARD, template);return;}}super.encode(object, bodyType, template);}}

到这里8800服务的配置就结束了。。。。。。

5)第五步:文件接受方8899服务 ,接口编写

@PostMapping(value="/uploadFile", consumes=MediaType.MULTIPART_FORM_DATA_VALUE)publicStringhandleFileUpload(@RequestParam("files")MultipartFile[] files){System.out.println("========================文件数量:"+files.length);System.out.println("========================文件名称:"+files[0].getName());return"ok";}

注意接受参数files要与第三四步保持一致,注解是@RequestParam

二:测试,8899服务得到文件了
在这里插入图片描述
三,FeignClient的信息透传导致上传失败

feign默认是不支持信息透传的,但通过配置可以让其支持,什么意思呢?就比如上面的例子,我用postmen掉8800文件上传接口,8800可以获取到postmen带来的header 或者 body里面的信息,8800处理完业务逻辑后再调用8899服务,8899服务也可以获取到postmen里header 或者body里面的信息,这就是feign的信息透传

设置feign的信息透传

importcom.zt.frank.zt.frank.consumer.controller.ConsumerTestController;importfeign.RequestInterceptor;importfeign.RequestTemplate;importorg.apache.log4j.Logger;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjava.util.Enumeration;/**
 * 这个文件放在项目的扫描目录下,所有的feign调用都会使用此配置。如果只有某个feign调用则可以这样设置(但配置类不能在扫描目录下):
 * eg : @FeignClient(name = "organ",path = "/organ/OrganInfo",configuration = FeignBasicAuthRequestInterceptor.class)
 */@ConfigurationpublicclassFeignBasicAuthRequestInterceptorimplementsRequestInterceptor{privatestaticLogger logger=Logger.getLogger(ConsumerTestController.class);@Overridepublicvoidapply(RequestTemplate requestTemplate){ServletRequestAttributes attributes=(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request= attributes.getRequest();//设置请求头,header透传Enumeration<String> headerNames= request.getHeaderNames();if(headerNames!=null){while(headerNames.hasMoreElements()){String name= headerNames.nextElement();String values= request.getHeader(name);
                requestTemplate.header(name, values);}}//设置请求体,body透传Enumeration<String> bodyNames= request.getParameterNames();StringBuffer body=newStringBuffer();if(bodyNames!=null){while(bodyNames.hasMoreElements()){String name= bodyNames.nextElement();String values= request.getParameter(name);
                body.append(name).append("=").append(values).append("&");}}if(body.length()!=0){
            body.deleteCharAt(body.length()-1);
            requestTemplate.body(body.toString());
            logger.info("Feign透传 body:"+body.toString());}}}

配置完成后我们用postmen设置header,请求8800然后8800会通过feign调用8899服务进行文件上传
在这里插入图片描述
8899服务获取到了postmen 设置的header 信息,而且文件也上传成功了
在这里插入图片描述
注意,我不光设置的header 透传也设置了body透传,下面我在postman多设个参数就会导致文件上传失败
在这里插入图片描述
8899文件接受服务,文件获取失败了
在这里插入图片描述
可以断定就是因为设置feign支持信息透传导致的,其实这是我在公司做项目时遇到的问题,弄了一两天才定位到问题,不过这里我没有完全的复现出来,公司的项目只设置了透传header,而且是设置body的参数为application/json,导致的,但是具体的原因还没有找到,这里先记录下问题;

如果你使用的springboot的版本较低也可能导致文件上传失败,可以尝试更换2.0以上的版本

  • 作者:乐百寿
  • 原文链接:https://blog.csdn.net/qq_47200599/article/details/116244362
    更新时间:2022-07-29 10:58:50