Przeglądaj źródła

1. 修复 request body 缓存的 bug
2. 进一步完善 api 访问日志的实现

YunaiV 4 lat temu
rodzic
commit
7a87fdbd79
19 zmienionych plików z 306 dodań i 41 usunięć
  1. 4 2
      src/main/java/cn/iocoder/dashboard/framework/logger/apilog/config/ApiLogConfiguration.java
  2. 49 5
      src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java
  3. 21 0
      src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiAccessLogFrameworkService.java
  4. 4 1
      src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiAccessLogCreateDTO.java
  5. 2 2
      src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java
  6. 3 7
      src/main/java/cn/iocoder/dashboard/framework/security/core/filter/JwtAuthenticationTokenFilter.java
  7. 2 2
      src/main/java/cn/iocoder/dashboard/framework/security/core/handler/AccessDeniedHandlerImpl.java
  8. 2 2
      src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java
  9. 13 4
      src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityFrameworkUtils.java
  10. 3 3
      src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java
  11. 2 5
      src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyFilter.java
  12. 63 0
      src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyWrapper.java
  13. 5 3
      src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java
  14. 45 0
      src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalResponseBodyHandler.java
  15. 46 0
      src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java
  16. 2 2
      src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java
  17. 11 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysApiAccessLogService.java
  18. 26 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysApiAccessLogServiceImpl.java
  19. 3 3
      src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java

+ 4 - 2
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> apiAccessLogFilter(WebProperties webProperties) {
-        ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties);
+    public FilterRegistrationBean<ApiAccessLogFilter> apiAccessLogFilter(WebProperties webProperties,
+                                                                         ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
+        ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, apiAccessLogFrameworkService);
         return createFilterBean(filter, FilterOrderEnum.API_ACCESS_LOG_FILTER);
     }
 

+ 49 - 5
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<String, String> 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<String, String> 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<String, Object> requestParams = MapUtil.<String, Object>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;
     }
 
 }

+ 21 - 0
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);
+
+}

+ 4 - 1
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;
     /**
      * 用户类型
      */

+ 2 - 2
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,

+ 3 - 7
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);

+ 2 - 2
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));
     }

+ 2 - 2
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);
         }

+ 13 - 4
src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityUtils.java → 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<Long> 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());
     }
 
 }

+ 3 - 3
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> requestBodyCacheFilter() {
-        return createFilterBean(new RequestBodyCacheFilter(), FilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
+    public FilterRegistrationBean<CacheRequestBodyFilter> requestBodyCacheFilter() {
+        return createFilterBean(new CacheRequestBodyFilter(), FilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
     }
 
     /**

+ 2 - 5
src/main/java/cn/iocoder/dashboard/framework/web/core/filter/RequestBodyCacheFilter.java → 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

+ 63 - 0
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) {}
+
+        };
+    }
+
+}

+ 5 - 3
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))

+ 45 - 0
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;
+    }
+
+}

+ 46 - 0
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);
+    }
+
+}

+ 2 - 2
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;
 

+ 11 - 0
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 {
+}

+ 26 - 0
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) {
+
+    }
+
+}

+ 3 - 3
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<Long> roleIds = SecurityUtils.getLoginUserRoleIds();
+        Set<Long> roleIds = SecurityFrameworkUtils.getLoginUserRoleIds();
         if (CollUtil.isEmpty(roleIds)) {
             return false;
         }
@@ -297,7 +297,7 @@ public class SysPermissionServiceImpl implements SysPermissionService {
         }
 
         // 获得当前登陆的角色。如果为空,说明没有权限
-        Set<Long> roleIds = SecurityUtils.getLoginUserRoleIds();
+        Set<Long> roleIds = SecurityFrameworkUtils.getLoginUserRoleIds();
         if (CollUtil.isEmpty(roleIds)) {
             return false;
         }