在 web 项目中,日志记录是必不可少的功能。数据分析与问题排查都少不了日志,其重要性不言而喻。在本项目中已经介绍过一种通过自行写 AOP 切点的方式实现日志记录。
10.springBoot2.0使用AOP打印controller层出入参日志.md
现在将再介绍一种通过实现 HandlerInterceptor
接口以及 @ControllerAdvice
注解来实现日志记录。
本文基于上一篇介绍拦截器的文章进行实现
30 Spring Boot 2(Spring 5.0+) HandlerInterceptor 拦截器简单示例
./pom.xml
<!-- web,mvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
./demo-common/pom.xml
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- Apache commons lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- web-mvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
其中 ${springboot.version}
版本为 2.0.6.RELEASE
./demo-common/src/main/java/com/ljq/demo/springboot/common/log/LogService.java
package com.ljq.demo.springboot.common.log;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* @Description: 日志记录工具类
* @Author: junqiang.lu
* @Date: 2020/1/8
*/
@Component
@Slf4j
public class LogService {
/**
* 记录请求参数日志
*
* @param request
* @param body
*/
public void logRequest(HttpServletRequest request, Object body) {
log.info("[LOG-REQUEST]\n\trequestIP: {}\n\tcontentType:{}\n\trequestUrl: {}\n\t" +
"requestMethod: {}\n\trequestParams: {}\n\trequestBody: {}",
getIpAddress(request),request.getHeader("Content-Type"),
request.getRequestURL(), request.getMethod(), getRequestParamsMap(request), body);
}
/**
* 记录返回参数日志
*
* @param request
* @param response
* @param body
*/
public void logResponse(HttpServletRequest request, HttpServletResponse response, Object body) {
log.info("[LOG-RESPONSE]\n\trequestIp: {}\n\trequestUrl: {}\n\tresponse: {}",
getIpAddress(request), request.getRequestURL(), body);
}
/**
* 获取请求参数
*
* @param httpServletRequest
* @return
*/
private Map<String, String> getRequestParamsMap(HttpServletRequest httpServletRequest) {
Map<String, String> resultMap = new HashMap<>(16);
if (httpServletRequest.getParameterMap() != null && !httpServletRequest.getParameterMap().isEmpty()) {
for (Map.Entry<String, String[]> entry : httpServletRequest.getParameterMap().entrySet()) {
resultMap.put(entry.getKey(),Arrays.toString(entry.getValue()));
}
}
return resultMap;
}
/**
* 获取客户端请求 ip
*
* @param request
* @return
*/
private static String getIpAddress(HttpServletRequest request) {
String xip = request.getHeader("X-Real-IP");
String xFor = request.getHeader("X-Forwarded-For");
if(StringUtils.isNotEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)){
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = xFor.indexOf(",");
if(index != -1){
return xFor.substring(0,index);
}else{
return xFor;
}
}
xFor = xip;
if(StringUtils.isNotEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)){
return xFor;
}
if (StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) {
xFor = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) {
xFor = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) {
xFor = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) {
xFor = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) {
xFor = request.getRemoteAddr();
}
return xFor;
}
}
./demo-web/src/main/java/com/ljq/demo/springboot/web/acpect/SimpleInterceptor.java
package com.ljq.demo.springboot.web.acpect;
import com.ljq.demo.springboot.common.log.LogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
/**
* @Description: 简易拦截器应用
* @Author: junqiang.lu
* @Date: 2019/12/10
*/
@Slf4j
@Component
public class SimpleInterceptor implements HandlerInterceptor {
@Autowired
private LogService logService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle");
if (Objects.nonNull(request.getQueryString())) {
logService.logRequest(request, null);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion");
}
}
./demo-web/src/main/java/com/ljq/demo/springboot/web/acpect/CustomRequestBodyAdviceAdapter.java
package com.ljq.demo.springboot.web.acpect;
import com.ljq.demo.springboot.common.log.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Type;
/**
* @Description: 请求参数处理类
* @Author: junqiang.lu
* @Date: 2020/1/8
*/
@ControllerAdvice
public class CustomRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {
@Autowired
private LogService logService;
@Autowired
HttpServletRequest request;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
logService.logRequest(request, body);
return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
}
./demo-web/src/main/java/com/ljq/demo/springboot/web/acpect/CustomResponseBodyAdviceAdapter.java
package com.ljq.demo.springboot.web.acpect;
import com.ljq.demo.springboot.common.log.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @Description: 返回参数处理类
* @Author: junqiang.lu
* @Date: 2020/1/8
*/
@ControllerAdvice
public class CustomResponseBodyAdviceAdapter implements ResponseBodyAdvice<Object> {
@Autowired
private LogService logService;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (request instanceof ServletServerHttpRequest && response instanceof ServletServerHttpResponse) {
logService.logResponse(((ServletServerHttpRequest) request).getServletRequest(),
((ServletServerHttpResponse) response).getServletResponse(), body);
}
return body;
}
}
从上边的代码中可以看出,日志的记录维度包括:
请求参数:
用户源 ip(requestIP)、请求数据类型(content-Type)、请求接口地址(requestUrl)、请求方式(requestMethod)、请求参数(requestParasm/requestBody)
返回参数:
用户源 ip(requestIP)、请求接口地址(requestUrl)、返回数据
说明:返回参数的日志记录也包括 requestIP 和 requestUrl 是为了方便在并发情况下能够区分出对应的请求参数和返回参数
为避免干扰,测试前先将原来的 AOP 日志记录关闭
./demo-web/src/main/java/com/ljq/demo/springboot/web/acpect/LogAspect.java
//@Aspect
//@Component
public class LogAspect {
请求参数在 requestParams 中
请求链接:
http://127.0.0.1:8088/api/user/list?demoKey=demoValue
日志:
2020-01-10 14:44:45 | INFO | http-nio-8088-exec-4 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 29| preHandle
2020-01-10 14:44:45 | INFO | http-nio-8088-exec-4 | com.ljq.demo.springboot.common.log.LogService 28| [LOG-REQUEST]
requestIP: 127.0.0.1
contentType:application/x-www-form-urlencoded
requestUrl: http://127.0.0.1:8088/api/user/list
requestMethod: GET
requestParams: {demoKey=[demoValue]}
requestBody: null
2020-01-10 14:44:45 | DEBUG | http-nio-8088-exec-4 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| ==> Preparing: select u.id, u.user_name, u.user_passcode, u.user_email, u.user_insert_time, u.user_update_time, u.user_status, u.user_version, u.user_del from `user` u where u.user_del = 0 ORDER BY id DESC LIMIT 0, 5
2020-01-10 14:44:45 | DEBUG | http-nio-8088-exec-4 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| ==> Parameters:
2020-01-10 14:44:45 | DEBUG | http-nio-8088-exec-4 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| <== Total: 5
2020-01-10 14:44:45 | INFO | http-nio-8088-exec-4 | com.ljq.demo.springboot.common.log.LogService 42| [LOG-RESPONSE]
requestIp: 127.0.0.1
requestUrl: http://127.0.0.1:8088/api/user/list
response: ApiResult(code=200, msg=成功, data=[UserDO(id=5, userName=liming, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=liming@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=4, userName=lily, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=lily@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=3, userName=jack, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=jack@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=2, userName=bob, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=bob@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=1, userName=tom, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=tom@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0)], extraData=null, timestamp=1578638685118)
2020-01-10 14:44:45 | INFO | http-nio-8088-exec-4 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 38| postHandle
2020-01-10 14:44:45 | INFO | http-nio-8088-exec-4 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 44| afterCompletion
请求参数在 requestParams 中
请求链接:
http://127.0.0.1:8088/api/user/lists?demoKey=demoValue
日志:
2020-01-10 14:44:45 | INFO | http-nio-8088-exec-4 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 38| postHandle
2020-01-10 14:44:45 | INFO | http-nio-8088-exec-4 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 44| afterCompletion
2020-01-10 14:46:58 | DEBUG | Eureka-PeerNodesUpdater | com.netflix.discovery.endpoint.EndpointUtils 198| The availability zone for the given region us-east-1 are [defaultZone]
2020-01-10 14:46:58 | WARN | Eureka-PeerNodesUpdater | com.netflix.eureka.cluster.PeerEurekaNodes 156| The replica size seems to be empty. Check the route 53 DNS Registry
2020-01-10 14:50:49 | INFO | http-nio-8088-exec-6 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 29| preHandle
2020-01-10 14:50:49 | INFO | http-nio-8088-exec-6 | com.ljq.demo.springboot.common.log.LogService 28| [LOG-REQUEST]
requestIP: 127.0.0.1
contentType:null
requestUrl: http://127.0.0.1:8088/api/user/lists
requestMethod: POST
requestParams: {demoKey=[demoValue]}
requestBody: null
2020-01-10 14:50:49 | DEBUG | http-nio-8088-exec-6 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| ==> Preparing: select u.id, u.user_name, u.user_passcode, u.user_email, u.user_insert_time, u.user_update_time, u.user_status, u.user_version, u.user_del from `user` u where u.user_del = 0 ORDER BY id DESC LIMIT 0, 5
2020-01-10 14:50:49 | DEBUG | http-nio-8088-exec-6 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| ==> Parameters:
2020-01-10 14:50:49 | DEBUG | http-nio-8088-exec-6 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| <== Total: 5
2020-01-10 14:50:49 | INFO | http-nio-8088-exec-6 | com.ljq.demo.springboot.common.log.LogService 42| [LOG-RESPONSE]
requestIp: 127.0.0.1
requestUrl: http://127.0.0.1:8088/api/user/lists
response: ApiResult(code=200, msg=成功, data=[UserDO(id=5, userName=liming, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=liming@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=4, userName=lily, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=lily@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=3, userName=jack, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=jack@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=2, userName=bob, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=bob@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=1, userName=tom, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=tom@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0)], extraData=null, timestamp=1578639049810)
2020-01-10 14:50:49 | INFO | http-nio-8088-exec-6 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 38| postHandle
2020-01-10 14:50:49 | INFO | http-nio-8088-exec-6 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 44| afterCompletion
请求参数在 Body 中
请求链接:
http://127.0.0.1:8088/api/user/list
request Body:
{
"page" : 1,
"offset" : 1,
"limit" : 2
}
日志:
2020-01-10 14:52:39 | INFO | http-nio-8088-exec-8 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 29| preHandle
2020-01-10 14:52:39 | INFO | http-nio-8088-exec-8 | com.ljq.demo.springboot.common.log.LogService 28| [LOG-REQUEST]
requestIP: 127.0.0.1
contentType:application/json
requestUrl: http://127.0.0.1:8088/api/user/list
requestMethod: POST
requestParams: {}
requestBody: {page=1, offset=1, limit=2}
2020-01-10 14:52:39 | DEBUG | http-nio-8088-exec-8 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| ==> Preparing: select u.id, u.user_name, u.user_passcode, u.user_email, u.user_insert_time, u.user_update_time, u.user_status, u.user_version, u.user_del from `user` u where u.user_del = 0 ORDER BY id DESC LIMIT 0, 5
2020-01-10 14:52:39 | DEBUG | http-nio-8088-exec-8 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| ==> Parameters:
2020-01-10 14:52:39 | DEBUG | http-nio-8088-exec-8 | c.l.d.springboot.dao.user.UserDao.queryListComplex 159| <== Total: 5
2020-01-10 14:52:39 | INFO | http-nio-8088-exec-8 | com.ljq.demo.springboot.common.log.LogService 42| [LOG-RESPONSE]
requestIp: 127.0.0.1
requestUrl: http://127.0.0.1:8088/api/user/list
response: ApiResult(code=200, msg=成功, data=[UserDO(id=5, userName=liming, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=liming@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=4, userName=lily, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=lily@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=3, userName=jack, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=jack@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=2, userName=bob, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=bob@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0), UserDO(id=1, userName=tom, userPasscode=ed0de7252acf2980e677bacab01bde25, userEmail=tom@example.com, userInsertTime=2019-06-12 17:42:40, userUpdateTime=2019-06-12 17:42:40, userStatus=1, userVersion=1, userDel=0)], extraData=null, timestamp=1578639159441)
2020-01-10 14:52:39 | INFO | http-nio-8088-exec-8 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 38| postHandle
2020-01-10 14:52:39 | INFO | http-nio-8088-exec-8 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 44| afterCompletion
POST 请求,上传文件
请求链接:
http://127.0.0.1:8088/api/demo/common/upload
file: {…}
_events: {}
_eventsCount: 3
_readableState: {…}
autoClose: true
bytesRead: 1420182
closed: true
domain: null
fd: null
flags: "r"
mode: 438
path: "F:\download\阿里巴巴Java开发手册(华山版).pdf"
readable: false
uploadKey: "uploadValue"
一份文件,一个 uploadKey
参数
日志:
2020-01-10 14:55:49 | INFO | http-nio-8088-exec-1 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 29| preHandle
2020-01-10 14:55:49 | INFO | http-nio-8088-exec-1 | com.ljq.demo.springboot.common.log.LogService 42| [LOG-RESPONSE]
requestIp: 127.0.0.1
requestUrl: http://127.0.0.1:8088/api/demo/common/upload
response: ApiResult(code=200, msg=成功, data=null, extraData=null, timestamp=1578639349715)
2020-01-10 14:55:49 | INFO | http-nio-8088-exec-1 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 38| postHandle
2020-01-10 14:55:49 | INFO | http-nio-8088-exec-1 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 44| afterCompletion
没有请求参数相关的日志,只有返回参数日志
请求连接
http://127.0.0.1:8088/api/demo/common/download?fileName=url
日志:
2020-01-10 15:06:14 | INFO | http-nio-8088-exec-9 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 29| preHandle
2020-01-10 15:06:14 | INFO | http-nio-8088-exec-9 | com.ljq.demo.springboot.common.log.LogService 28| [LOG-REQUEST]
requestIP: 127.0.0.1
contentType:application/x-www-form-urlencoded
requestUrl: http://127.0.0.1:8088/api/demo/common/download
requestMethod: GET
requestParams: {fileName=[url]}
requestBody: null
2020-01-10 15:06:14 | INFO | http-nio-8088-exec-9 | com.ljq.demo.springboot.common.log.LogService 42| [LOG-RESPONSE]
requestIp: 127.0.0.1
requestUrl: http://127.0.0.1:8088/api/demo/common/download
response: [-1, -40, -1, -32]
2020-01-10 15:06:14 | INFO | http-nio-8088-exec-9 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 38| postHandle
2020-01-10 15:06:14 | INFO | http-nio-8088-exec-9 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 44| afterCompletion
由于返回的日志将整个文件转化为 byte 打印出来,为了演示,作者将 response
进行了截取
优点:
- 使用
HandlerInterceptor
和@ControllerAdvice
便于开发者理解程序 - 很好解决了读取 POST 请求 Body 中数据的问题
- 能够实现较多维度的参数日志记录,便于日志分析与问题排查
- 满足基本的 REST 接口出入参日志记录
- 能够很好处理 Params 和 Body 的参数日志记录
- 日志格式友好
缺点:
- 不能完全保证请求参数日志与返回参数日志一一对应,在高并发请求下,多用户同一 ip 请求同一接口的情况是存在的
- 暂未对文件相关操作做适配,上传文件与下载文件日志记录不完善
- 使用了多个类才实现出入参的日志记录,较为繁琐
Logging Requests and Responses in Spring (including body)
Spring Boot - How to log all requests and responses with exceptions in single place?
Get RequestBody and ResponseBody at HandlerInterceptor
Spring – Log Incoming Requests
java在filter中获取POST请求中request参数以及解决ServletInputStream重复读取的问题
filter过滤器,解决request获取前端参数中,参数以json流的形式传输的问题
commit 39518644867d5e969569a825ff42a9ef2b2e2c32 (HEAD -> dev, origin/master, origin/dev, origin/HEAD, master)
Author: flying9001 <flying9001@gmail.com>
Date: Wed Jan 8 18:22:59 2020 +0800
代码-新增一种 Spring Boot 日志记录解决方案
版本回退命令:
git reset --soft 39518644867d5e969569a825ff42a9ef2b2e2c32