From 7a87fdbd799e7ac2cddcf6d471870f95972c971a Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 26 Feb 2021 20:42:14 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=20request=20body=20?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=9A=84=20bug=202.=20=E8=BF=9B=E4=B8=80?= =?UTF-8?q?=E6=AD=A5=E5=AE=8C=E5=96=84=20api=20=E8=AE=BF=E9=97=AE=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apilog/config/ApiLogConfiguration.java | 6 +- .../core/filter/ApiAccessLogFilter.java | 54 ++++++++++++++-- .../service/ApiAccessLogFrameworkService.java | 21 +++++++ .../service/dto/ApiAccessLogCreateDTO.java | 5 +- .../operatelog/core/aop/OperateLogAspect.java | 4 +- .../filter/JwtAuthenticationTokenFilter.java | 10 +-- .../core/handler/AccessDeniedHandlerImpl.java | 4 +- .../handler/LogoutSuccessHandlerImpl.java | 4 +- ...Utils.java => SecurityFrameworkUtils.java} | 17 +++-- .../web/config/WebConfiguration.java | 6 +- ...ilter.java => CacheRequestBodyFilter.java} | 7 +-- .../core/filter/CacheRequestBodyWrapper.java | 63 +++++++++++++++++++ .../core/handler/GlobalExceptionHandler.java | 8 ++- .../handler/GlobalResponseBodyHandler.java | 45 +++++++++++++ .../web/core/util/WebFrameworkUtils.java | 46 ++++++++++++++ .../controller/auth/SysAuthController.java | 4 +- .../logger/SysApiAccessLogService.java | 11 ++++ .../impl/SysApiAccessLogServiceImpl.java | 26 ++++++++ .../impl/SysPermissionServiceImpl.java | 6 +- 19 files changed, 306 insertions(+), 41 deletions(-) create mode 100644 src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiAccessLogFrameworkService.java rename src/main/java/cn/iocoder/dashboard/framework/security/core/util/{SecurityUtils.java => SecurityFrameworkUtils.java} (76%) rename src/main/java/cn/iocoder/dashboard/framework/web/core/filter/{RequestBodyCacheFilter.java => CacheRequestBodyFilter.java} (72%) create mode 100644 src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyWrapper.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalResponseBodyHandler.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysApiAccessLogService.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysApiAccessLogServiceImpl.java diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/config/ApiLogConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/config/ApiLogConfiguration.java index b36a1df5d..7d8341fbe 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/config/ApiLogConfiguration.java +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/config/ApiLogConfiguration.java @@ -1,6 +1,7 @@ package cn.iocoder.dashboard.framework.logger.apilog.config; import cn.iocoder.dashboard.framework.logger.apilog.core.filter.ApiAccessLogFilter; +import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiAccessLogFrameworkService; import cn.iocoder.dashboard.framework.web.config.WebProperties; import cn.iocoder.dashboard.framework.web.core.enums.FilterOrderEnum; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -16,8 +17,9 @@ public class ApiLogConfiguration { * 创建 ApiAccessLogFilter Bean,记录 API 请求日志 */ @Bean - public FilterRegistrationBean apiAccessLogFilter(WebProperties webProperties) { - ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties); + public FilterRegistrationBean apiAccessLogFilter(WebProperties webProperties, + ApiAccessLogFrameworkService apiAccessLogFrameworkService) { + ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, apiAccessLogFrameworkService); return createFilterBean(filter, FilterOrderEnum.API_ACCESS_LOG_FILTER); } diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java index bd839610f..02a90530a 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java @@ -1,11 +1,21 @@ package cn.iocoder.dashboard.framework.logger.apilog.core.filter; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.extra.servlet.ServletUtil; +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants; +import cn.iocoder.dashboard.common.pojo.CommonResult; +import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiAccessLogFrameworkService; import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiAccessLogCreateDTO; +import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils; import cn.iocoder.dashboard.framework.web.config.WebProperties; +import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils; +import cn.iocoder.dashboard.util.date.DateUtils; +import cn.iocoder.dashboard.util.json.JsonUtils; import cn.iocoder.dashboard.util.servlet.ServletUtils; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; @@ -21,14 +31,19 @@ import java.util.Map; * * @author 芋道源码 */ -@AllArgsConstructor +@RequiredArgsConstructor @Slf4j public class ApiAccessLogFilter extends OncePerRequestFilter { private final WebProperties webProperties; + private final ApiAccessLogFrameworkService apiAccessLogFrameworkService; + + @Value("${spring.application.name}") + private String applicationName; @Override protected boolean shouldNotFilter(HttpServletRequest request) { + // 只过滤 API 请求的地址 return !request.getRequestURI().startsWith(webProperties.getApiPrefix()); } @@ -56,8 +71,8 @@ public class ApiAccessLogFilter extends OncePerRequestFilter { private void createApiAccessLog(HttpServletRequest request, Date startTime, Map queryString, String requestBody, Exception ex) { try { - ApiAccessLogCreateDTO createDTO = this.buildApiAccessLogDTO(request, startTime, queryString, requestBody, ex); - + ApiAccessLogCreateDTO accessLog = this.buildApiAccessLogDTO(request, startTime, queryString, requestBody, ex); + apiAccessLogFrameworkService.createApiAccessLogAsync(accessLog); } catch (Exception e) { log.error("[createApiAccessLog][url({}) 发生异常]", request.getRequestURI(), ex); } @@ -65,7 +80,36 @@ public class ApiAccessLogFilter extends OncePerRequestFilter { private ApiAccessLogCreateDTO buildApiAccessLogDTO(HttpServletRequest request, Date startTime, Map queryString, String requestBody, Exception ex) { - return null; + ApiAccessLogCreateDTO accessLog = new ApiAccessLogCreateDTO(); + // 处理用户信息 + accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request)); + accessLog.setUserType(WebFrameworkUtils.getUsrType(request)); + // 设置访问结果 + CommonResult result = WebFrameworkUtils.getCommonResult(request); + if (result != null) { + accessLog.setResultCode(result.getCode()); + accessLog.setResultMsg(result.getMsg()); + } else if (ex != null) { + accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode()); + accessLog.setResultMsg(ExceptionUtil.getRootCauseMessage(ex)); + } else { + accessLog.setResultCode(0); + accessLog.setResultMsg(""); + } + // 设置其它字段 + accessLog.setTraceId(TracerUtils.getTraceId()); + accessLog.setApplicationName(applicationName); + accessLog.setRequestUrl(request.getRequestURI()); + Map requestParams = MapUtil.builder().put("query", queryString).put("body", requestBody).build(); + accessLog.setRequestParams(JsonUtils.toJsonString(requestParams)); + accessLog.setRequestMethod(request.getMethod()); + accessLog.setUserAgent(ServletUtils.getUserAgent(request)); + accessLog.setUserIp(ServletUtil.getClientIP(request)); + // 持续时间 + accessLog.setStartTime(startTime); + accessLog.setEndTime(new Date()); + accessLog.setDuration((int) DateUtils.diff(accessLog.getEndTime(), accessLog.getStartTime())); + return accessLog; } } diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiAccessLogFrameworkService.java b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiAccessLogFrameworkService.java new file mode 100644 index 000000000..eda202ac3 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiAccessLogFrameworkService.java @@ -0,0 +1,21 @@ +package cn.iocoder.dashboard.framework.logger.apilog.core.service; + +import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiAccessLogCreateDTO; + +import javax.validation.Valid; + +/** + * API 访问日志 Framework Service 接口 + * + * @author 芋道源码 + */ +public interface ApiAccessLogFrameworkService { + + /** + * 创建 API 访问日志 + * + * @param createDTO 创建信息 + */ + void createApiAccessLogAsync(@Valid ApiAccessLogCreateDTO createDTO); + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiAccessLogCreateDTO.java b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiAccessLogCreateDTO.java index be4176de8..ec82e8e4d 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiAccessLogCreateDTO.java +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiAccessLogCreateDTO.java @@ -1,5 +1,7 @@ package cn.iocoder.dashboard.framework.logger.apilog.core.service.dto; +import lombok.Data; + import javax.validation.constraints.NotNull; import java.util.Date; @@ -8,6 +10,7 @@ import java.util.Date; * * @author 芋道源码 */ +@Data public class ApiAccessLogCreateDTO { /** @@ -17,7 +20,7 @@ public class ApiAccessLogCreateDTO { /** * 用户编号 */ - private Integer userId; + private Long userId; /** * 用户类型 */ diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java index 0855b2b92..d7d3e5975 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java @@ -8,7 +8,7 @@ import cn.iocoder.dashboard.common.pojo.CommonResult; import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog; import cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum; import cn.iocoder.dashboard.framework.logger.operatelog.core.service.OperateLogFrameworkService; -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils; import cn.iocoder.dashboard.modules.system.controller.logger.vo.operatelog.SysOperateLogCreateReqVO; import cn.iocoder.dashboard.util.json.JsonUtils; @@ -148,7 +148,7 @@ public class OperateLogAspect { } private static void fillUserFields(SysOperateLogCreateReqVO operateLogVO) { - operateLogVO.setUserId(SecurityUtils.getLoginUserId()); + operateLogVO.setUserId(SecurityFrameworkUtils.getLoginUserId()); } private static void fillModuleFields(SysOperateLogCreateReqVO operateLogVO, diff --git a/src/main/java/cn/iocoder/dashboard/framework/security/core/filter/JwtAuthenticationTokenFilter.java b/src/main/java/cn/iocoder/dashboard/framework/security/core/filter/JwtAuthenticationTokenFilter.java index 506c7caea..b4dcf6720 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/security/core/filter/JwtAuthenticationTokenFilter.java +++ b/src/main/java/cn/iocoder/dashboard/framework/security/core/filter/JwtAuthenticationTokenFilter.java @@ -1,17 +1,13 @@ package cn.iocoder.dashboard.framework.security.core.filter; import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; import cn.iocoder.dashboard.common.pojo.CommonResult; import cn.iocoder.dashboard.framework.security.config.SecurityProperties; import cn.iocoder.dashboard.framework.security.core.LoginUser; -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.dashboard.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService; import cn.iocoder.dashboard.util.servlet.ServletUtils; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -42,7 +38,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @SuppressWarnings("NullableProblems") protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); + String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); if (StrUtil.isNotEmpty(token)) { try { // 验证 token 有效性 @@ -53,7 +49,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { } // 设置当前用户 if (loginUser != null) { - SecurityUtils.setLoginUser(loginUser, request); + SecurityFrameworkUtils.setLoginUser(loginUser, request); } } catch (Throwable ex) { CommonResult result = globalExceptionHandler.allExceptionHandler(request, ex); diff --git a/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/AccessDeniedHandlerImpl.java b/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/AccessDeniedHandlerImpl.java index cd9c7a39e..2e813d659 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/AccessDeniedHandlerImpl.java +++ b/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/AccessDeniedHandlerImpl.java @@ -2,7 +2,7 @@ package cn.iocoder.dashboard.framework.security.core.handler; import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants; 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 cn.iocoder.dashboard.util.servlet.ServletUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.AccessDeniedException; @@ -35,7 +35,7 @@ public class AccessDeniedHandlerImpl implements AccessDeniedHandler { throws IOException, ServletException { // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏 log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(), - SecurityUtils.getLoginUser().getId(), e); + SecurityFrameworkUtils.getLoginUser().getId(), e); // 返回 403 ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED)); } diff --git a/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java b/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java index 759970631..07826b9ab 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java +++ b/src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java @@ -3,7 +3,7 @@ package cn.iocoder.dashboard.framework.security.core.handler; import cn.hutool.core.util.StrUtil; import cn.iocoder.dashboard.framework.security.config.SecurityProperties; import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService; -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.dashboard.util.servlet.ServletUtils; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; @@ -31,7 +31,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { // 执行退出 - String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); + String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader()); if (StrUtil.isNotBlank(token)) { securityFrameworkService.logout(token); } diff --git a/src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityUtils.java b/src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityFrameworkUtils.java similarity index 76% rename from src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityUtils.java rename to src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityFrameworkUtils.java index 4107b8724..eb67479d4 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityUtils.java +++ b/src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityFrameworkUtils.java @@ -1,6 +1,7 @@ package cn.iocoder.dashboard.framework.security.core.util; import cn.iocoder.dashboard.framework.security.core.LoginUser; +import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; @@ -12,11 +13,11 @@ import java.util.Set; /** * 安全服务工具类 * - * @author ruoyi + * @author 芋道源码 */ -public class SecurityUtils { +public class SecurityFrameworkUtils { - private SecurityUtils() {} + private SecurityFrameworkUtils() {} /** * 从请求中,获得认证 Token @@ -45,7 +46,7 @@ public class SecurityUtils { } /** - * 获得当前用户的编号 + * 获得当前用户的编号,从上下文中 * * @return 用户编号 */ @@ -53,6 +54,11 @@ public class SecurityUtils { return getLoginUser().getId(); } + /** + * 获得当前用户的角色编号数组 + * + * @return 角色编号数组 + */ public static Set getLoginUserRoleIds() { return getLoginUser().getRoleIds(); } @@ -70,6 +76,9 @@ public class SecurityUtils { authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 设置到上下文 SecurityContextHolder.getContext().setAuthentication(authenticationToken); + // 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号; + // 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息 + WebFrameworkUtils.setLoginUserId(request, loginUser.getId()); } } diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java index 38a841229..9fbe6237c 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java +++ b/src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java @@ -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() { - return createFilterBean(new RequestBodyCacheFilter(), FilterOrderEnum.REQUEST_BODY_CACHE_FILTER); + public FilterRegistrationBean requestBodyCacheFilter() { + return createFilterBean(new CacheRequestBodyFilter(), FilterOrderEnum.REQUEST_BODY_CACHE_FILTER); } /** diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/core/filter/RequestBodyCacheFilter.java b/src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyFilter.java similarity index 72% rename from src/main/java/cn/iocoder/dashboard/framework/web/core/filter/RequestBodyCacheFilter.java rename to src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyFilter.java index 6bd560299..9aad216f7 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/web/core/filter/RequestBodyCacheFilter.java +++ b/src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyFilter.java @@ -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 diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyWrapper.java b/src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyWrapper.java new file mode 100644 index 000000000..6d1e9919f --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyWrapper.java @@ -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) {} + + }; + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java b/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java index 5f2748dfd..95e343f51 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java +++ b/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java @@ -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)) diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalResponseBodyHandler.java b/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalResponseBodyHandler.java new file mode 100644 index 000000000..53b8e84d3 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalResponseBodyHandler.java @@ -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; + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java b/src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java new file mode 100644 index 000000000..1a98335e2 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java @@ -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); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java index d046f726b..ea7dbfd76 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java @@ -26,8 +26,8 @@ import javax.validation.Valid; import java.util.List; import static cn.iocoder.dashboard.common.pojo.CommonResult.success; -import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserId; -import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserRoleIds; +import static cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils.getLoginUserRoleIds; import static cn.iocoder.dashboard.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.dashboard.util.servlet.ServletUtils.getUserAgent; diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysApiAccessLogService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysApiAccessLogService.java new file mode 100644 index 000000000..6a3da8897 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysApiAccessLogService.java @@ -0,0 +1,11 @@ +package cn.iocoder.dashboard.modules.system.service.logger; + +import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiAccessLogFrameworkService; + +/** + * API 访问日志 Service 接口 + * + * @author 芋道源码 + */ +public interface SysApiAccessLogService extends ApiAccessLogFrameworkService { +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysApiAccessLogServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysApiAccessLogServiceImpl.java new file mode 100644 index 000000000..2346c49f3 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysApiAccessLogServiceImpl.java @@ -0,0 +1,26 @@ +package cn.iocoder.dashboard.modules.system.service.logger.impl; + +import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiAccessLogCreateDTO; +import cn.iocoder.dashboard.modules.system.service.logger.SysApiAccessLogService; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; + +/** + * API 访问日志 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class SysApiAccessLogServiceImpl implements SysApiAccessLogService { + + + + @Override + public void createApiAccessLogAsync(@Valid ApiAccessLogCreateDTO createDTO) { + + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java index a53134837..9f4c87d7b 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java @@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysRoleMenuMapper; import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysUserRoleMapper; import cn.iocoder.dashboard.modules.system.dal.dataobject.permission.SysMenuDO; @@ -262,7 +262,7 @@ public class SysPermissionServiceImpl implements SysPermissionService { } // 获得当前登陆的角色。如果为空,说明没有权限 - Set roleIds = SecurityUtils.getLoginUserRoleIds(); + Set roleIds = SecurityFrameworkUtils.getLoginUserRoleIds(); if (CollUtil.isEmpty(roleIds)) { return false; } @@ -297,7 +297,7 @@ public class SysPermissionServiceImpl implements SysPermissionService { } // 获得当前登陆的角色。如果为空,说明没有权限 - Set roleIds = SecurityUtils.getLoginUserRoleIds(); + Set roleIds = SecurityFrameworkUtils.getLoginUserRoleIds(); if (CollUtil.isEmpty(roleIds)) { return false; }