通过Feign跨服务上传文件,getParts错误,头信息设置,boundary为null,multipart错误等

前提

由于本人喜欢使用feign做请求转发,故而时常会遇到一些奇奇怪怪的问题
在这里声明一下,feign不是万能的,确实有一些请求不能无缝转发

基础的使用可以参考我之前的文章,可以解决因为版本升级导致无法使用等问题
https://blog.youkuaiyun.com/yuexiahunone/article/details/132175038

可以解决的问题

  • Content type ‘’ not supported
  • Failed to parse multipart servlet request
  • Current request is not a multipart request
  • feign.codec.EncodeException: Error converting request body
  • file=org.springframework.web.multipart.support.StandardMultipartHttpServletRequest%24StandardMultipartFile%402bab8d83
  • 读取流失败
  • 转换流失败

本人遇到的问题

可以略过

在服务端B读取流信息转换失败
这里报的错 Request.parseParts()
流程大概就是处理表单
获取参数 StandardMultipartHttpServletRequest.getParameterMap
表单参数不是空,初始化并转换表单信息 StandardMultipartHttpServletRequest.initializeMultipart()/parseRequest(HttpServletRequest request)
发现是文件处理文件 Request.parseParts(2562行)
初始化文件(FileItemIteratorImpl.init)
获取流长度
读取流信息
获取编码格式
获取content-type
获取boundary值 FileUploadBase.getBoundary(我这里报错了)
把流信息读取到文件中

需要断点的文件
	StandardMultipartHttpServletRequest
	HttpServletRequestWrapper
	Request
	FileUploadBase
	FileUpload
	FileItemIteratorImpl
	MultipartStream

开发环境

  • 前端为vue,使用axios插件,表单方式提交
  • 后台为springboot
  • 局域网(这个没影响)
  • 上传的是excel文件

注意事项

  • 后台feign接口参数一定要对应上
  • 头信息要设置对
  • 高版本的spring一定要修改代码,参考链接

代码排查

  • 主要是排查后台代码
  • 服务端A,feign的接参,可以使用@RequestPart和@RequestParam,我使用的是@RequestPart
  • 服务端A,feign的content-type是否设置正确,consumes = “multipart/form-data”
  • 服务端B,接口的参数要和服务端A的feign接口一致,但是不需要设置content-type
  • 服务端B在拦截器中要判断一下content-type,如果是multipart/form-data,则略过,不要在拦截器中读取流信息
  • 服务端A,不要在feign的拦截器中设置content-type,会覆盖接口上的content-type,
    例如template.header(“Content-Type”, “multipart/form-data”);,此举会导致服务端B无法读取content-lenght以及boundary值(我就是栽倒在这里了)

上代码

以下代码为伪代码
//前端
//post方式,使用data作为数据载体
export function apiUploadFile(data) {
  return request({
    url: '/backend/oak/cardInfo/uploadCardInfo',
    method: 'post',
    data: data
  })
}

function uploadFile(file){
	//这里使用表单方式提交
	let forms = new FormData()
    forms.append('file', file)
    this.apiUploadFile(forms).then(res => {
		///......
    });
}
//服务端A
@RestController
@RequestMapping(value = "/api")
@FeignClient(name = "UploadFileProvider", url = "${apiUrl}", path = "/api", configuration = {FeignUploadFileInterceptor.class})
public interface UploadFileApi {

	//consumes就是content-type,因为是文件(流信息)所以设置为multipart/form-data
	//然后feign会自动设置content-length以及boundary值,这里的boundary值会被修改
    @Operation(summary = "上传文件")
    @PostMapping(value = "/uploadFile", consumes = "multipart/form-data")
    FeignResult<Object> uploadCardInfo(@RequestPart("file") MultipartFile file);

}
//服务端B
@RestController
@RequestMapping(value = "/api")
@RequiredArgsConstructor
public class UploadFileController {

    private final OssService ossService;

    @Operation(summary = "上传卡信息")
    @PostMapping("/uploadFile")
    public Result<Void> uploadFile(@RequestPart("file") MultipartFile file) {
        return Result.data(() -> ossService.uploadFile(file));
    }

}

//拦截器代码 .....
//这里不写了,没啥东西,记住不要在这里设置content-type
跨服务异步回调中,通过 Feign 调用无法获取请求头,可采用添加请求拦截器的方式来解决。在远程调用时,由于创建了新的 request,导致请求头数据丢失,可在调用方添加请求拦截器以补上请求头数据。以下是具体代码示例: ```java import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @Configuration public class GuliFeignConfig { @Bean("requestInterceptor") public RequestInterceptor requestInterceptor() { return new RequestInterceptor() { @Override public void apply(RequestTemplate requestTemplate) { // 1. RequestContextHolder拿到刚进来的这个请求 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest();// 老请求 // 同步请求头, 例如 Cookie String cookie = request.getHeader("Cookie"); // 给新请求同步老请求的cookie requestTemplate.header("Cookie", cookie); // System.out.println("feign远程之前先进行RequestInterceptor.apply"); } }; } } ``` 上述代码定义了一个配置类 `GuliFeignConfig`,并创建了一个 `RequestInterceptor` 类型的 Bean。在 `apply` 方法中,首先通过 `RequestContextHolder` 获取当前请求信息,然后从老请求中获取请求头(如 `Cookie`),最后将这些请求头同步到新请求中 [^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值