1. 修复 request body 缓存的 bug

2. 进一步完善 api 访问日志的实现
This commit is contained in:
YunaiV
2021-02-26 20:42:14 +08:00
parent 70af2bc78c
commit 7a87fdbd79
19 changed files with 306 additions and 41 deletions

View File

@@ -1,7 +1,7 @@
package cn.iocoder.dashboard.framework.web.config;
import cn.iocoder.dashboard.framework.web.core.enums.FilterOrderEnum;
import cn.iocoder.dashboard.framework.web.core.filter.RequestBodyCacheFilter;
import cn.iocoder.dashboard.framework.web.core.filter.CacheRequestBodyFilter;
import cn.iocoder.dashboard.framework.web.core.filter.XssFilter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -55,8 +55,8 @@ public class WebConfiguration implements WebMvcConfigurer {
* 创建 RequestBodyCacheFilter Bean可重复读取请求内容
*/
@Bean
public FilterRegistrationBean<RequestBodyCacheFilter> requestBodyCacheFilter() {
return createFilterBean(new RequestBodyCacheFilter(), FilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
public FilterRegistrationBean<CacheRequestBodyFilter> requestBodyCacheFilter() {
return createFilterBean(new CacheRequestBodyFilter(), FilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
}
/**

View File

@@ -2,7 +2,6 @@ package cn.iocoder.dashboard.framework.web.core.filter;
import cn.iocoder.dashboard.util.servlet.ServletUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@@ -13,16 +12,14 @@ import java.io.IOException;
/**
* Request Body 缓存 Filter实现它的可重复读取
*
* 基于 Spring 提供的 {@link org.springframework.web.util.ContentCachingRequestWrapper} 实现
*
* @author 芋道源码
*/
public class RequestBodyCacheFilter extends OncePerRequestFilter {
public class CacheRequestBodyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
filterChain.doFilter(new ContentCachingRequestWrapper(request), response);
filterChain.doFilter(new CacheRequestBodyWrapper(request), response);
}
@Override

View File

@@ -0,0 +1,63 @@
package cn.iocoder.dashboard.framework.web.core.filter;
import cn.hutool.extra.servlet.ServletUtil;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Request Body 缓存 Wrapper
*
* @author 芋道源码
*/
public class CacheRequestBodyWrapper extends HttpServletRequestWrapper {
/**
* 缓存的内容
*/
private final byte[] body;
public CacheRequestBodyWrapper(HttpServletRequest request) {
super(request);
body = ServletUtil.getBodyBytes(request);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
// 返回 ServletInputStream
return new ServletInputStream() {
@Override
public int read() {
return inputStream.read();
}
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {}
};
}
}

View File

@@ -3,7 +3,7 @@ package cn.iocoder.dashboard.framework.web.core.handler;
import cn.iocoder.dashboard.common.exception.GlobalException;
import cn.iocoder.dashboard.common.exception.ServiceException;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
@@ -26,6 +26,8 @@ import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstan
/**
* 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号
*
* @author 芋道源码
*/
@RestControllerAdvice
@Slf4j
@@ -183,7 +185,7 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(value = AccessDeniedException.class)
public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", SecurityUtils.getLoginUserId(),
log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", SecurityFrameworkUtils.getLoginUserId(),
req.getRequestURL(), ex);
return CommonResult.error(FORBIDDEN);
}
@@ -275,7 +277,7 @@ public class GlobalExceptionHandler {
// // 设置其它字段
// exceptionLog.setTraceId(MallUtils.getTraceId())
// .setApplicationName(applicationName)
// .setUri(request.getRequestURI()) // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
// .setUri(request.getRequestURI())
// .setQueryString(HttpUtil.buildQueryString(request))
// .setMethod(request.getMethod())
// .setUserAgent(HttpUtil.getUserAgent(request))

View File

@@ -0,0 +1,45 @@
package cn.iocoder.dashboard.framework.web.core.handler;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* 全局响应结果ResponseBody处理器
*
* 不同于在网上看到的很多文章,会选择自动将 Controller 返回结果包上 {@link CommonResult}
* 在 onemall 中,是 Controller 在返回时,主动自己包上 {@link CommonResult}。
* 原因是GlobalResponseBodyHandler 本质上是 AOP它不应该改变 Controller 返回的数据结构
*
* 目前GlobalResponseBodyHandler 的主要作用是,记录 Controller 的返回结果,
* 方便 {@link cn.iocoder.dashboard.framework.logger.apilog.core.filter.ApiAccessLogFilter} 记录访问日志
*/
@ControllerAdvice
public class GlobalResponseBodyHandler implements ResponseBodyAdvice {
@Override
@SuppressWarnings("NullableProblems") // 避免 IDEA 警告
public boolean supports(MethodParameter returnType, Class converterType) {
if (returnType.getMethod() == null) {
return false;
}
// 只拦截返回结果为 CommonResult 类型
return returnType.getMethod().getReturnType() == CommonResult.class;
}
@Override
@SuppressWarnings("NullableProblems") // 避免 IDEA 警告
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// 记录 Controller 结果
WebFrameworkUtils.setCommonResult(((ServletServerHttpRequest) request).getServletRequest(), (CommonResult<?>) body);
return body;
}
}

View File

@@ -0,0 +1,46 @@
package cn.iocoder.dashboard.framework.web.core.util;
import cn.iocoder.dashboard.common.enums.UserTypeEnum;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
/**
* 专属于 web 包的工具类
*
* @author 芋道源码
*/
public class WebFrameworkUtils {
private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = "login_user_id";
private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
public static void setLoginUserId(ServletRequest request, Long userId) {
request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
}
/**
* 获得当前用户的编号,从请求中
*
* @param request 请求
* @return 用户编号
*/
public static Long getLoginUserId(HttpServletRequest request) {
return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID);
}
public static Integer getUsrType(HttpServletRequest request) {
return UserTypeEnum.ADMIN.getValue(); // TODO 芋艿:等后续优化
}
public static void setCommonResult(ServletRequest request, CommonResult<?> result) {
request.setAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT, result);
}
public static CommonResult<?> getCommonResult(ServletRequest request) {
return (CommonResult<?>) request.getAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT);
}
}