Current request is not a multipart request问题排查

概述

在应用工程里看到如下被标记为@deprecated的代码,这对有代码洁癖的我而言是无法忍受的:
在这里插入图片描述

row.getCell(10).setCellType(Cell.CELL_TYPE_STRING);
String hospital = row.getCell(0).getStringCellValue();

对应的poi版本号?是的,你没猜错,使用次数最多的版本3.17!!

<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version>
</dependency>

洁癖

Google一番,终于找到一个靠谱的解决方案-StackOverflow-setcelltypecelltype-string-is-deprecated:

You can just call row.setCellValue(String) you don’t have to set the cell type beforehand.

删除此行代码即可。

验证

这就完了吗?当然不!作为一个有追求的资深程序员,发现不clean的code,修改只是第一步,接下来还需要验证,此番修改是否会引发新的问题。

一步步找到Controller层接口,好在就一个接口,改动也只会影响这一个接口。具体来说,这是一个Excel文件上传接口,解析Excel内容,然后把Excel数据insert or update到MySQL数据库。本地启动应用,Postman模拟请求:
在这里插入图片描述
然后去数据库验证一下,一切完美?

问题爆出

等等,这个接口前端在哪里用到呢?通过IDEA强大的全局代码搜索能力(是的,作为一个小型公司的资深开发&技术经理,需要了解公司的全部业务,当然也需要知道前后端开发概况,如代码库),当然需要提前把前后端代码都导入到一个目录下,然后IDEA打开此目录,则会自动把此目录下的全部子目录下的全部工程导入到IDEA的工作空间:
在这里插入图片描述
总之,找到接口调用处,知道具体的业务场景,结果不管是测试环境还是生产环境都有问题,报错如下:
在这里插入图片描述

排查

借助于ELK,可以快速找到错误堆栈日志:

Current request is not a multipart request
org.springframework.web.multipart.MultipartException: Current request is not a multipart request
at org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver.resolveArgument(RequestPartMethodArgumentResolver.java:157)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:126)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)

对应的后端接口代码:

@PostMapping(value = "/uploadExcel")public Response<String> uploadExcel(@RequestPart("excelFile") MultipartFile file, @RequestParam("flag") String flag, @RequestParam("channel") String channel) {
}

上面这段代码将近3年没人改过。并且Postman模拟接口请求,文件上传功能是正常的!!!

看了下前端代码,发现有Axios组件库升级之类的修改,于是想着让前端去排查。扔过来一张截图,并坚持声称前端代码没有问题,把我噎得够呛:
在这里插入图片描述

初次尝试

Google搜了一圈没有找到靠谱的解决方法。其中包括stackoverflow这篇multipartexception-current-request-is-not-a-multipart-request,在controller层增加配置,改成下面这样:

@PostMapping(value = "/uploadExcel", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})

前文提到过,因为Postman模拟文件上传功能是好的,也就是说本地无法复现问题,当然也就不能验证修改,只能提交代码发布到测试环境:提交->打版本号->编译构建->滚动发布->测试环境验证。虽然有CI流水线这一套,但是也需要5分钟左右。

测试环境报了另一个错误:

Content type 'application/json;charset=UTF-8' not supported
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:214)

无解。。搁置一个周末。。

本地调试环境

既然Postman模拟接口请求没有问题,那咱就启动前端工程(虽然已经将近一年不写前端代码),前端工程直接调用本地后端工程,尝试本地调试复现问题。

前端工程使用React,看到熟悉的package.json文件:
在这里插入图片描述
直接在IDEA的Terminal里执行命令:npm install,会在项目根目录下生成node_modules文件夹。
在这里插入图片描述
有4种启动方式,如上截图所示,有Run模式和Debug模式。是的,你没看错,前端代码使用IDEA工程也可以Debug。就在上一家公司的上一份工作里,一年里我还提交过四百多次前端代码。

现在这份工作9个月多,第一次启动前端工程。这里我也习惯性使用Debug模式,结果并不行。Anyway最终以Run模式执行dev启动成功。

再来看看上面截图dev命令提到的server.js文件:

const devProxy = {'/api': {target: 'http://10.18.65.51:8848',// pathRewrite: { '^/api': '' },// target: 'https://stg-open.aaaaa.com',pathRewrite: { '^/api': '/api' },changeOrigin: true,},
}

主要有两个配置:

  • target:指向的后端服务。如果指向的是域名,则需要在nginx里配置域名对应的后端服务,如果指向的是IP,则是联调时的后端服务所在的机器IP。此处前后端工程是同一个机器启动的。IP当然需要修改为本机IP,使用命令查看:ifconfig en0即可。
  • pathRewrite:发送请求时,请求路径重写。就是多少一个/api的差别。这也太眼熟了吧,和Spring Cloud Gateway网关路由配置差不多意思。

浏览器打开http://localhost:3001,看到熟悉的界面,后端服务接口设置断点,界面操作。ok,本地联调环境已具备。

复现成功

一开始后端只启动一个merchant工程,也就是上面Postman截图里的merchant/open/uploadExcel接口,修改server.js为:

target: 'http://10.18.65.51:8849', // merchant服务占用端口
pathRewrite: { '^/api': '' }, // 直接请求merchant服务

在这里插入图片描述
上传成功,本地没有复现。

那测试环境为啥有问题呢?

看到pathRewrite,前面也提到Spring Cloud Gateway网关路由配置。

对了,测试和生产环境里所有的服务请求都是走Gateway网关。

ok,再启动一个Gateway服务,同时需要修改server.js为:

target: 'http://10.18.65.51:8848', // gateway服务占用端口
pathRewrite: { '^/api': '/api' }, // 直接请求gateway服务,后端gateway服务再负责转发请求到merchant服务

问题复现!!所以,问题出现在Gateway网关服务。

看到曙光

我们再来看看Gateway服务的Apollo配置:
在这里插入图片描述
这里有个RequestLogFilter!!

打断点,再来一次。前端页面点击文件上传,请求进入到gateway服务,断点进入RequestLogFilter!!

来看看源码:

import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import java.util.List;/*** 所有请求的日志过滤器**/
@Slf4j
@Component
public class RequestLogFilter extends AbstractGatewayFilterFactory<Config> {private final List<HttpMessageReader<?>> messageReaders;public RequestLogFilter() {super(Config.class);this.messageReaders = HandlerStrategies.withDefaults().messageReaders();}private RewriteFunction<String, String> rewriteFunction() {return (exchange, body) -> {String url = exchange.getRequest().getURI().getPath();log.info("*****请求信息日志拦截*****,请求的路径:{},请求的入参数据:{}", url, body);return Mono.just(body);};}@Overridepublic GatewayFilter apply(Config config) {config.setRewriteFunction(String.class, String.class, rewriteFunction());return (exchange, chain) -> {Class inClass = config.getInClass();ServerRequest serverRequest = ServerRequest.create(exchange, this.messageReaders);Mono<?> responseBody = serverRequest.bodyToMono(inClass).flatMap((o) -> config.getRewriteFunction().apply(exchange, o));BodyInserter bodyInserter = BodyInserters.fromPublisher(responseBody, config.getOutClass());HttpHeaders headers = new HttpHeaders();headers.putAll(exchange.getRequest().getHeaders());headers.remove("Content-Length");if (config.getContentType() != null) {headers.set("Content-Type", config.getContentType());}CachedBody outputMessage = new CachedBody(exchange, headers);return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage);return chain.filter(exchange.mutate().request(decorator).build());}));};}ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBody outputMessage) {return new ServerHttpRequestDecorator(exchange.getRequest()) {@NotNull@Overridepublic HttpHeaders getHeaders() {long contentLength = headers.getContentLength();HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.putAll(super.getHeaders());httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);if (contentLength > 0L) {httpHeaders.setContentLength(contentLength);} else {httpHeaders.set(OpenConstants.TRANSFER_ENCODING, OpenConstants.CHUNKED);}return httpHeaders;}@NotNull@Overridepublic Flux<DataBuffer> getBody() {return outputMessage.getBody();}};}
}

大致瞟一眼就看到这里有对request进行解析的代码,也有设置HTTP headers的代码。问题大概率就出现在这里。

好在测试环境也有问题,那就删除spring.cloud.gateway.routes[23].filters[0] = RequestLogFilter这条配置项。再来一次。

!!!问题消失!!!

分析原因

RequestLogFilter是一个Filter(废话),用于在请求转发到对应的后端其他服务前,解析requestBody,并打印出来,类似于AOP日志记录。

去掉httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);这行代码,莫名其妙报了另一个错误:

ERROR | com.aba.open.merchant.service.impl.MerchantOpenServiceImpl | uploadExcel | 93 | - 
java.io.IOException: ZIP entry size is too large or invalidat org.apache.poi.openxml4j.util.ZipArchiveFakeEntry.<init>(ZipArchiveFakeEntry.java:43)at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource.<init>(ZipInputStreamZipEntrySource.java:53)at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:106)at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:307)at org.apache.poi.ooxml.util.PackageHelper.open(PackageHelper.java:47)at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:309)at com.aba.open.merchant.service.impl.MerchantOpenServiceImpl.uploadExcel(MerchantOpenServiceImpl.java:68)

难搞。

附录

AOP

@Aspect
@Component
@Slf4j
public class ControllerLogAop {@Pointcut("execution(public * com.aaaaa.dialog.controller..*.*(..))")public void webLog() {}/*** 在切点之前织入*/@Before("webLog()")public void doBefore(JoinPoint joinPoint) {try {log.info("======= Start ===========");// 打印请求入参Object[] args = joinPoint.getArgs();Object[] arguments = new Object[args.length];for (int i = 0; i < args.length; i++) {if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {continue;}arguments[i] = args[i];}log.info("类{}方法{},请求参数= {}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), JSON.toJSONString(arguments));} catch (Exception e) {log.error("日志切面异常", e);}}/*** 在切点之后织入*/@After("webLog()")public void doAfter() {log.info("=========== End =========");}/*** 环绕*/@Around("webLog()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {Object result = null;try {long startTime = System.currentTimeMillis();result = proceedingJoinPoint.proceed();// 打印出参log.info("返回参数= {}", JSON.toJSONString(result));// 执行耗时log.info("耗时{} ms", System.currentTimeMillis() - startTime);return result;} catch (Exception e) {log.error("日志切面异常", e);}return result;}
}

ControllerLogAop这种配置类由于涉及到controller包路径的切入,即@Pointcut,可能需要在每个服务里都写一份(事实上我们目前也是这样做的,功能定位和RequestLogFilter有交集甚至冗余嫌疑)。

参考

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/177152.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL安装与配置教程

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

初刷leetcode题目(9)——数据结构与算法

&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️…

Feign调用的两种方式

一、 先看一下项目结构 ply模块是主要的业务模块&#xff0c;upms是用户管理模块 他们都分为api和biz 其中api就是一些实体类&#xff0c;工具类&#xff0c;biz就是业务逻辑代码。 首先在upms-api中建立feign的文件夹&#xff0c;然后新建一个接口CustomApi CustomApi 然…

(Linux2.6内核)进程调度队列与切换

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 我们首先来了解几个概念 1. 进程在CPU上运行的时候&#xff0c;一定要运行完才行吗&#xff1f;答案是否定的&#xff0c;我们大部分的操作系统&#xff0c;主流就是分时操作系统&#xff0c;即基于时间片进程轮转执行的。 …

Nginx Openresty通过Lua+Redis 实现动态封禁IP

需求 为了封禁某些爬虫或者恶意用户对服务器的请求&#xff0c;我们需要建立一个动态的 IP 黑名单。对于黑名单中的 IP &#xff0c;我们将拒绝提供服务。并且可以设置封禁失效时间 环境准备 linux version: centos7 / ubuntu 等 redis version: 5.0.5 nginx version: nginx…

智能优化算法应用:基于正余弦算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于正余弦算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于正余弦算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.正余弦算法4.实验参数设定5.算法结果6.参考文献7.…

学习知识回顾随笔

文章目录 如何远程连接MySQL数据库1.创建用户来运行&#xff0c;此用户从任何主机连接到mysql数据库2.使用IP地址来访问MySQL数据库 如何远程访问Django项目Web应用什么是Web应用应用程序的两种模式Web应用程序的优缺点 HTTP协议&#xff08;超文本传输协议&#xff09;简介HTT…

FLASK博客系列4——再谈路由

最近好像拖更有点久了。抱歉抱歉~ 今天我们继续来聊聊路由&#xff08;其实就是我上次偷懒剩下一点没讲完&#xff09;。 通过上次的文章&#xff0c;我们基本了解了Flask中的路由&#xff0c;是不是比较简单呢&#xff1f;别急&#xff0c;今天来点猛料。 一、路由之HTTP方法绑…

C++之STL库:string类(用法列举和总结)

前言 大家在学习STL库的时候一定要学会看英文文档&#xff0c;俗话说熟能生巧&#xff0c;所以还得多练&#xff01; 在使用string类之前&#xff0c;要包含头文件#include <string>和using namespace std; 文档链接&#xff1a;string - C Reference 一、string——构造…

Springboot日志-logback

logback-spring.xml的配置项 共有一个父标签、两种属性、三个节点: 一个父标签&#xff1a;configuration 两种属性&#xff1a;contextName和property 三个节点&#xff1a;appender、root、logger 日志级别 日志级别从低到高分为TRACE < DEBUG < INFO < WARN &…

filebeat日志收集工具

elk:filebeat日志收集工具和logstash相同 filebeat是一个轻量级的日志收集工具&#xff0c;所使用的系统资源比logstash部署和启动时使用的资源要小得多 filebeat可以运行在非Java环境&#xff0c;它可以代理logstash在非Java环境上收集日志 filebeat无法实现数据的过滤&…

Programming Abstractions in C阅读笔记:p197-p201

《Programming Abstractions in C》学习第64天&#xff0c;p196-p201总结。 一、技术总结 很难&#xff0c;唯有继续往下看才能让其变容易。 二、英语总结 1.psychologically是什么意思&#xff1f; 答&#xff1a; (1))psychology > psychological > psychologica…

名词解释之EID和SR

大家在聊辅助驾驶时&#xff0c;经常会发现有名词叫SR&#xff0c;或者EID&#xff0c;理想的环境感知界面叫EID&#xff0c;而其他很多车型里大家管那个界面叫SR。我们下面具体看下这两个词具体指什么。 SR是“Situational Awareness”的缩写,意思是环境感知或场景认知。 SR系…

【JMeter】配置元件

1. 元件的分类 HTTP Request Default 作用&#xff1a; 可以配置成通用的信息&#xff0c;可复用 ​​​​​​​ JDBC Connection Configuration 作用&#xff1a;连接数据库 前提&#xff1a; 下载好对应数据类型的jar包 ​​​​​​​ HTTP Header Manager信息头管理…

微信小程序推送服务号消息(一)【Go+微信小程序+微信服务号+微信开放平台】

一、需求场景 业务需要给微信小程序用户在某些场景推送微信服务号消息&#xff0c;例如&#xff1a;订单即将超时&#xff0c;电子合同签约超时等&#xff1b; 二、开发准备 1、开通微信服务号 入口&#xff1a;微信公众平台 1.1 在服务号中获取推送消息所需的配置信息&#…

免费部署开源大模型 ChatGLM-6B

参考&#xff1a;【大模型-第一篇】在阿里云上部署ChatGLM3-CSDN博客 ChatGLM 是一个开源的、支持中英双语的对话语言模型&#xff0c;由智谱 AI 和清华大学 KEG 实验室联合发布&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。ChatGLM3-6B 更…

基于JavaWeb+SSM+Vue校园综合服务小程序系统的设计和实现

基于JavaWebSSMVue校园综合服务小程序系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 摘 要 I Abstract II 第一章 绪 论 1 1.1选题背景 2 1.2研究现状 3 1.3研究内容 …

易基因: MeRIP-seq等从m6A RNA甲基化角度揭示NFATc1对破骨细胞的调控机制|研究速递

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 双膦酸盐类药物是强效骨吸收抑制剂&#xff0c;是治疗骨质疏松症、多发性骨髓瘤、骨转移等疾病的首选药物。这些药物通过抑制甲羟戊酸通路和促进破骨细胞凋亡来促进骨吸收。双膦酸盐类药…

使用jenkins和tomcat创建并部署maven项目

准备三台服务器&#xff1a; 192.168.58.139 部署tomcat 详细参照&#xff1a;http://t.csdnimg.cn/Yp2z2 192.168.58.140 部署gitlab 详细参照&#xff1a;http://t.csdnimg.cn/Sb1uz 192.168.58.153 部署Jenkins 详细参照…

P8A005-A008系统加固

系统账户数据库安全 预备知识】 数据库研究跨越于计算机应用、系统软件和理论三个领域&#xff0c;其中应用促进新系统的研制开发&#xff0c;新系统带来新的理论研究&#xff0c;而理论研究又对前两个领域起着指导作用。 【实验步骤】 网络拓扑&#xff1a;server2008-bas…