论一次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 {


    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    @Scope("prototype")
    @Primary
    public 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());
    }

    @Override

    public 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

    @Override
    public 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);
    }

    @Bean
    public 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);

    @Override
    public 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:含泪哭诉,如果正好解决你的问题,请点个赞吧~~~~

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boomLJIE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值