论一次feign微服务直接多文件上传由版本引发的血案
由于项目架构微服务SpringCloud的方式部署,独立了文件系统微服务,其他服务需要调到此服务。但是发现调用不是报错就是文件系统接收端接收到的file为空。
先抛下我的pom引用的版本支持:
1.spring-cloud-starter-openfeign 版本为 :2.1.1.RELEASE
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2.原有依赖的 feign-form-spring 等包,用来支持跨服务间文件传输
<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>
同时为了支持多服务:
3.服务端的文件上传代码如下:
/*** 上传文件 单个** @param file* @param sorId* @param fileType* @return* @throws Exception*/ @RequestMapping(value = {"/addfilesor","/pturl/addfilesor"}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE},consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R addFileSor(@RequestPart( name ="file", value = "file", required = false) MultipartFile file,@RequestParam("sorId") String sorId,@RequestParam("fileType") String fileType) throws Exception {return sorFileService.addFileSor(file, sorId, fileType, getUser()); }
/*** 上传文件 批量** @return* @throws Exception*/ @RequestMapping(value = {"/addfilesorbath","/pturl/addfilesorbath"} , produces = {MediaType.APPLICATION_JSON_UTF8_VALUE},consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R addfilesorBath(@RequestPart("file") MultipartFile[] file,@RequestParam("sorId") String sorId,@RequestParam("fileType") String fileType) throws Exception {return sorFileService.addfilesorBath(file, sorId, fileType, getUser()); }
其中MultipartFile 需要用@RequestPart 来注解,@RequestPart和@RequestParam的区别请自行百度。
3.消费端代码如下:
3.1 PubAppClient 消费端接口 如下FeignMultipartSupportConfig类是为了支持跨服务文件传输做的配置。下面会讲到
@FeignClient(value = "qboa-pubapp", configuration = FeignMultipartSupportConfig.class) public interface PubAppClient {
/*** 单个文件上传** @param file* @param sorId* @param fileType* @return* @throws Exception*/ @RequestMapping(value = "/filesor/pturl/addfilesor",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R addFileSor(@RequestPart(name = "file", value = "file", required = false) MultipartFile file, @RequestParam("sorId") String sorId, @RequestParam("fileType") String fileType) throws Exception;/*** 批量文件上传** @param file* @param sorId* @param fileType* @return* @throws Exception*/ @RequestMapping(value = "/filesor/pturl/addfilesorbath",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R addfilesorBath(@RequestPart("file") MultipartFile[] file, @RequestParam("sorId") String sorId, @RequestParam("fileType") String fileType) throws Exception;
}
3.2 FeignMultipartSupportConfig 中 最重要的一行代码:return new FeignSpringFormEncoder(new SpringEncoder(messageConverters)) 中重写,原生SpringFormEncoder 有个bug没有判断MultipartFile数组类型。
package com.qboa.oa.config.ftp;/**/***说明:*@auther Luojie*@date 2020/03/24 10:36*/import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope;@Configuration public class FeignMultipartSupportConfig {@Autowiredprivate ObjectFactory<HttpMessageConverters> messageConverters;@Bean@Scope("prototype")@Primarypublic Encoder feignEncoder() {// return new SpringFormEncoder(new SpringEncoder(messageConverters));//支持多文件传输return new FeignSpringFormEncoder(new SpringEncoder(messageConverters));}}
3.3 FeignSpringFormEncoder 类中重写 支持MultipartFile数组,网上抄的至于抄谁的我已经忘记了。。。原博主不要打死我
package com.qboa.oa.config.ftp;import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; import feign.form.FormEncoder; import feign.form.MultipartFormContentProcessor; import feign.form.spring.SpringManyMultipartFilesWriter; import feign.form.spring.SpringSingleMultipartFileWriter; import lombok.val; import org.springframework.web.multipart.MultipartFile;import java.lang.reflect.Type; import java.util.Collections; import java.util.Map;import static feign.form.ContentType.MULTIPART;/*** fegin文件解析*/ public class FeignSpringFormEncoder extends FormEncoder {/*** Constructor with the default Feign's encoder as a delegate.*/public FeignSpringFormEncoder() {this(new Encoder.Default());}/*** Constructor with specified delegate encoder.** @param delegate delegate encoder, if this encoder couldn't encode object.*/public FeignSpringFormEncoder(Encoder delegate) {super(delegate);val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);processor.addFirstWriter(new SpringSingleMultipartFileWriter());processor.addFirstWriter(new SpringManyMultipartFilesWriter());}@Overridepublic void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {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;} else if (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);}private boolean isMultipartFileCollection(Object object) {if (!(object instanceof Iterable)) {return false;}val iterable = (Iterable<?>) object;val iterator = iterable.iterator();return iterator.hasNext() && iterator.next() instanceof MultipartFile;} }
3.5 写接收界面传递的controller了
@RequestMapping("/saveUpload") public R saveUpload(@RequestPart(value = "file") MultipartFile[] files,@RequestParam("patNo") String patNo,@RequestParam(value = "docId", required = false) String docId,@RequestParam("fileType") String fileType) throws Exception {Long userId = getUserId();String ret = reqAndTransMangeService.saveUpload(files, patNo, docId, fileType, userId);if (!RCodestants.SUCCESS.equals(ret)) {return R.error(ret);}return R.ok(); }
service
@Overridepublic String saveUpload(MultipartFile[] files, String patNo, String docId, String fileType, Long userId) throws Exception {//业务代码略略略//调用文件公共方法 单个测试pubAppClient.addFileSor(files[0], patNo, fileType);//调用文件公共方法 多个测试pubAppClient.addfilesorBath(files, patNo, fileType);return RCodestants.SUCCESS;}
按照上面配置我们抛下起来出现了以下血案:
血案一:
调用的时候 the request was rejected because no multipart boundary was found
案情分析:该异常的源码抛出地方及原因可以看看
破案: 可以看到是request中boundary的为空,但是我在接受前台的时候有,跨服务的时候丢了(这些可以打印出来分析)
引发1 的Content-Type 中 multipart/form-data 可能在feign调用的时候没传递或传递的时候被更改了
相关链接https://blog.csdn.net/qq_20076823/article/details/100103358?utm_source=app
2.未使用@RequestPart (我的排除了,你们的可以检查检查)
3. 就是引入的eign-form-spring 等包 和spring-cloud-starter-openfeign 版本
相关链接https://blog.csdn.net/weixin_32885733/article/details/100993405
验证1:启动类中加了FeignClientInterceptor feign拦截器
@SpringBootApplication @EnableFeignClients @EnableScheduling public class QboaOaApplication {public static void main(String[] args) {SpringApplication.run(QboaOaApplication.class, args);}@Beanpublic FeignClientInterceptor getFeignClientInterceptor() {return new FeignClientInterceptor();} }
FeignClientInterceptor 拦截器我突发奇想,想把request都传递下去包括原有cont-type
package com.qboa.common.interceptor;import com.alibaba.fastjson.JSONObject; import feign.RequestInterceptor; import feign.RequestTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Part; import java.util.Collection; import java.util.Enumeration;/*** Feign拦截器** @author Administrator* @version 1.0**/ public class FeignClientInterceptor implements RequestInterceptor {private static final Logger logger = LoggerFactory.getLogger(FeignClientInterceptor.class);@Overridepublic void apply(RequestTemplate requestTemplate) {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (requestAttributes != null) {HttpServletRequest request = requestAttributes.getRequest();//取出当前请求的header,找到jwt令牌Enumeration<String> headerNames = request.getHeaderNames();if (headerNames != null) {while (headerNames.hasMoreElements()) {String headerName = headerNames.nextElement();String headerValue = request.getHeader(headerName);//临时解决支付表单改为json // if("/startpay/patTransNotifyUrl" .equals(request.getServletPath()) // && "content-type".equals(headerName)){ // headerValue="application/json;charset=UTF-8"; // }// 将header向下传递requestTemplate.header(headerName, headerValue);}}}} }
由此从血案1引发了血案2,导致我服务端接收的file 文件一直是null 如果接收端一直file一直是null的可以看看是否是因为这原因引起的。还有接收端的 参数名称也可以检查是否一直,检验方法就是把接收端的request和消费端传递的request中的参数打印出来对比下。
查找了下原有找到了解决方案:
在FeignClientInterceptor 的 requestTemplate.header(headerName, headerValue);之前过滤掉content-type原因可以看引发1中的链接。博主讲得简洁明了!!!
//解决跨服务文件上传 防止请求头Content-Type的boundary被更改 if ("content-type".equals(headerName)) {continue; }
由此血案2 解决,回到血案1,时间节点一天。抱着司马当活马医的心态替换了feign form版本
<!-- Feign进行跨服务传递文件依赖 --> <!--spring feign form 表单提交相关 请不要更换版本否则批量会报错 --> <dependency><groupId>io.github.openfeign.form</groupId><artifactId>feign-form</artifactId><version>3.8.0</version> </dependency><dependency><groupId>io.github.openfeign.form</groupId><artifactId>feign-form-spring</artifactId><version>3.8.0</version> </dependency> <!-- Feign进行跨服务传递文件依赖结束 -->
然后发现问题居然好了, 唉版本问题重于泰山,不重视的话出来的问题真的难以觉察。本着人人为我,我为人人的心,不忍心看到像我这样的菜鸟们不重复趟这趟雷,含泪写下了这篇总结,以及感谢各位无私博主,特别感谢两位大牛分享。
ps:含泪哭诉,如果正好解决你的问题,请点个赞吧~~~~
论一次feign微服务直接多文件上传由版本引发的血案相关推荐
- input file文件上传_微服务间的文件上传与下载-Feign
一.文件上传 (1)服务提供者 使用一个MultipartFile接收上传的文件 /** (2)Feign的Client接口 服务门面-feign的接口 pom.xml <dependencie ...
- 【6】使用dockerfile打包/运行微服务项目,并上传到私服harbor
使用dockerfile打包/运行微服务项目,并上传到私服harbor 关键词 dockerfile自定义demodocker 镜像 把自定义镜像上传到私服harbor 一.环境准备 主机地址 部署 ...
- 谷粒学院-云存储微服务环境搭建、上传讲师图像后端接口的实现
在service模块下创建子模块service_oss 配置pom.xml service_oss上级模块service已经引入service的公共依赖,所以service_oss模块只需引入阿里云 ...
- springcloud feign前后端分离实现文件上传下载
文件上传 一.服务消费者Controller package com.biddingportal.controller;import com.alibaba.fastjson.JSON; import ...
- U-Mail邮件服务系统任意文件上传+执行漏洞(runtime缺陷与验证绕过)
http://www.wooyun.org/bugs/wooyun-2010-061859 转载于:https://www.cnblogs.com/hookjoy/p/4068326.html
- 文件上传结合SpringCloud的Feign进行服务调用
工作中一直使用的是SpringCloud,其中的多个组件的使用也已经有一段时间了,包括对组件的配置文件的编写,今天要讲的是SpringCloud中的一个远程服务调用的组件,使用Feign之后,我们调用 ...
- 前后端分离微服务管理系统项目实战SaaS-HRM项目(九)——文件上传与PDF报表入门
文章目录 九.文件上传与PDF报表入门 1.图片上传 <1>.Data URL (1).概述 (2).入门 (3).基本原理 (4).优缺点分析 <2>.实现用户头像上传 2. ...
- 泛微任意文件上传(CNVD-2021-49104)
CNVD-2021-49104 goby exp 声明 代码 poc集合 声明 本程序仅供于学习交流,请使用者遵守<中华人民共和国网络安全法>,勿将此脚本用于非授权的测试,脚本开发者不负任 ...
- 微服务引擎的线上流量治理最佳实践
简介:本实践将重点介绍如何快速集成主流开源微服务框架,实现业务零改造,解决开源框架在生产落地过程中的痛点,例如无损上下线.标签路由等,并通过托管微服务开源组件(API网关.注册中心.配置中心等)的服务 ...
最新文章
- 大学可以学前端开发_所有开发人员在大学中应该学习的东西
- m行n列最大值和最小值C语言,找数组最值 按如下函数原型编程从键盘输入一个m行n列的二维数...
- 非常棒的 「Sublime Text 配色/主题」与「编程字体」
- android 8.0 三星,这些三星手机竟到2019年才能升级安卓8.0:等到头发都白了
- 以WinGrub 引导安装Fedora 4.0 为例,详述用WinGrub来引导Linux的安装
- 说说低功耗的那些事儿
- 【HDU - 3038】How Many Answers Are Wrong 【带权并查集 - 向量偏移】
- matlab2018a制图,MatLab 2018a 官方教程
- 2018中国企业云计算应用现状及需求调研报告
- java调用js模板引擎_JavaScript模板引擎用法实例
- Typescript基础知识--学习笔记
- 专注要事、把手弄脏、高效优雅是对抗规模化焦虑的好办法--读Getting Real(达成现实)和 Rework(重塑工作)
- 【杂烩】Tesla M40 训练机组装与散热改造
- grpc双向流 python_gRPC Golang/Python使用
- 【深度学习】使用tensorflow实现AlexNet
- Pacemaker-学习总结(概念、结构)
- [CF1153F]Serval and Bonus Problem(dp/积分+OGF)
- unity 游戏存档
- 中英保险业务常用词汇--寿险
- 车联网发展的昨天和明天