1. 修复 request body 缓存的 bug
2. 进一步完善 api 访问日志的实现
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -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) {}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user