现象就是后台操作日志记录接口大量报错,但又不是每个都报错,错误日志如下
09:45:51.850 [http-nio-9600-exec-5] ERROR c.u.c.s.h.GlobalExceptionHandler - [handleRuntimeException,85] - 请求地址'/inner/operateLog',发生未知异常.
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:406)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:354)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:184)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:161)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:135)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:224)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:178)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:547)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)
at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)
at
结合前台的请求记录,发现只有请求体或请求头比较大的时候会报错
上面提到的操作日志记录接口会将请求体和响应体全部记录,所以大概可以猜测是在调用某些业务接口的时候记录操作日志的接口报错了
操作日志保存接口
@PostMapping(value = "/inner/operateLog", headers = SecurityConstants.FROM_SOURCE_INNER)
R<Boolean> saveOperateLog(@RequestBody SysOperateLogDto operateLog, @RequestHeader(SecurityConstants.SOURCE_NAME) String sourceName) throws Exception;
操作日志Bean
@Data
@EqualsAndHashCode(callSuper = true)
@TableName(value = "sys_operate_log",excludeProperty = {"name","sort","createBy","createTime","updateBy","updateTime","remark"})
public class SysOperateLogPo extends TBaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/** 操作模块 */
protected String title;
/** 业务类型(0其它 1新增 2修改 3删除) */
protected String businessType;
/** 请求方法 */
protected String method;
/** 请求方式 */
protected String requestMethod;
/** 操作类别(0其它 1后台用户 2手机端用户) */
protected String operateType;
/** 操作Id */
protected Long userId;
/** 操作人员账号 */
protected String userName;
/** 操作人员名称 */
protected String userNick;
/** 请求url */
protected String url;
/** 操作地址 */
protected String ip;
/** 请求参数 */
protected String param;
/** 请求参数 */
protected String location;
/** 返回参数 */
protected String jsonResult;
/** 操作状态(0正常 1异常) */
protected String status;
/** 错误消息 */
protected String errorMsg;
@TableField(exist = false)
protected String menuTitle;
@TableField(exist = false)
protected String menuName;
@TableField(exist = false)
protected String counter;
/** 操作时间 */
@OrderBy(sort = 10)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
protected LocalDateTime operateTime;
/** 消耗时间 */
@Excel(name = "消耗时间", suffix = "毫秒")
private Long costTime;
}
接下来我们直接给报错的地方打断点
然后在前端重放报错的接口,进入断点后直接看InputStream中内容
上面从流中获取到的是一个字节数组,右键结果查看方式选择String,得到如下结果,很明显这个请求体乱码了
为了做对比,我们再调用一个不报错的接口来观测InputStream中内容,可以看到这里是可以正常查看到请求体内容的

这个时候可以想到大请求体的报文肯定被处理,经过排查发现openfeign的配置文件中配置了对request压缩,关闭这个配置后也确实不报错了
spring:
# feign 配置
cloud:
openfeign:
okhttp:
enabled: true
httpclient:
enabled: false
client:
config:
default:
connectTimeout: 10000
readTimeout: 10000
compression:
request:
enabled: true
response:
enabled: false
但是这个也仅限于猜测,还是要眼见为实,继续调试,讲读取出来的字节数组以gz格式保存下来
打开压缩包,我们可以正常读取里面的内容

明确了问题,剩下解决问题的办法就比较灵活了,可以选择关闭压缩,也可以选择自己添加拦截器来处理压缩的情况,但是得和其他正常的请求做区分。