Commit 7a87fdbd799e7ac2cddcf6d471870f95972c971a

Authored by YunaiV
1 parent 70af2bc7

1. 修复 request body 缓存的 bug

2. 进一步完善 api 访问日志的实现
Showing 19 changed files with 306 additions and 41 deletions
src/main/java/cn/iocoder/dashboard/framework/logger/apilog/config/ApiLogConfiguration.java
1 1 package cn.iocoder.dashboard.framework.logger.apilog.config;
2 2  
3 3 import cn.iocoder.dashboard.framework.logger.apilog.core.filter.ApiAccessLogFilter;
  4 +import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiAccessLogFrameworkService;
4 5 import cn.iocoder.dashboard.framework.web.config.WebProperties;
5 6 import cn.iocoder.dashboard.framework.web.core.enums.FilterOrderEnum;
6 7 import org.springframework.boot.web.servlet.FilterRegistrationBean;
... ... @@ -16,8 +17,9 @@ public class ApiLogConfiguration {
16 17 * 创建 ApiAccessLogFilter Bean,记录 API 请求日志
17 18 */
18 19 @Bean
19   - public FilterRegistrationBean<ApiAccessLogFilter> apiAccessLogFilter(WebProperties webProperties) {
20   - ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties);
  20 + public FilterRegistrationBean<ApiAccessLogFilter> apiAccessLogFilter(WebProperties webProperties,
  21 + ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
  22 + ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, apiAccessLogFrameworkService);
21 23 return createFilterBean(filter, FilterOrderEnum.API_ACCESS_LOG_FILTER);
22 24 }
23 25  
... ...
src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java
1 1 package cn.iocoder.dashboard.framework.logger.apilog.core.filter;
2 2  
  3 +import cn.hutool.core.exceptions.ExceptionUtil;
  4 +import cn.hutool.core.map.MapUtil;
3 5 import cn.hutool.extra.servlet.ServletUtil;
  6 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  7 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  8 +import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiAccessLogFrameworkService;
4 9 import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiAccessLogCreateDTO;
  10 +import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils;
5 11 import cn.iocoder.dashboard.framework.web.config.WebProperties;
  12 +import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils;
  13 +import cn.iocoder.dashboard.util.date.DateUtils;
  14 +import cn.iocoder.dashboard.util.json.JsonUtils;
6 15 import cn.iocoder.dashboard.util.servlet.ServletUtils;
7   -import lombok.AllArgsConstructor;
  16 +import lombok.RequiredArgsConstructor;
8 17 import lombok.extern.slf4j.Slf4j;
  18 +import org.springframework.beans.factory.annotation.Value;
9 19 import org.springframework.web.filter.OncePerRequestFilter;
10 20  
11 21 import javax.servlet.FilterChain;
... ... @@ -21,14 +31,19 @@ import java.util.Map;
21 31 *
22 32 * @author 芋道源码
23 33 */
24   -@AllArgsConstructor
  34 +@RequiredArgsConstructor
25 35 @Slf4j
26 36 public class ApiAccessLogFilter extends OncePerRequestFilter {
27 37  
28 38 private final WebProperties webProperties;
  39 + private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;
  40 +
  41 + @Value("${spring.application.name}")
  42 + private String applicationName;
29 43  
30 44 @Override
31 45 protected boolean shouldNotFilter(HttpServletRequest request) {
  46 + // 只过滤 API 请求的地址
32 47 return !request.getRequestURI().startsWith(webProperties.getApiPrefix());
33 48 }
34 49  
... ... @@ -56,8 +71,8 @@ public class ApiAccessLogFilter extends OncePerRequestFilter {
56 71 private void createApiAccessLog(HttpServletRequest request, Date startTime,
57 72 Map<String, String> queryString, String requestBody, Exception ex) {
58 73 try {
59   - ApiAccessLogCreateDTO createDTO = this.buildApiAccessLogDTO(request, startTime, queryString, requestBody, ex);
60   -
  74 + ApiAccessLogCreateDTO accessLog = this.buildApiAccessLogDTO(request, startTime, queryString, requestBody, ex);
  75 + apiAccessLogFrameworkService.createApiAccessLogAsync(accessLog);
61 76 } catch (Exception e) {
62 77 log.error("[createApiAccessLog][url({}) 发生异常]", request.getRequestURI(), ex);
63 78 }
... ... @@ -65,7 +80,36 @@ public class ApiAccessLogFilter extends OncePerRequestFilter {
65 80  
66 81 private ApiAccessLogCreateDTO buildApiAccessLogDTO(HttpServletRequest request, Date startTime,
67 82 Map<String, String> queryString, String requestBody, Exception ex) {
68   - return null;
  83 + ApiAccessLogCreateDTO accessLog = new ApiAccessLogCreateDTO();
  84 + // 处理用户信息
  85 + accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
  86 + accessLog.setUserType(WebFrameworkUtils.getUsrType(request));
  87 + // 设置访问结果
  88 + CommonResult<?> result = WebFrameworkUtils.getCommonResult(request);
  89 + if (result != null) {
  90 + accessLog.setResultCode(result.getCode());
  91 + accessLog.setResultMsg(result.getMsg());
  92 + } else if (ex != null) {
  93 + accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode());
  94 + accessLog.setResultMsg(ExceptionUtil.getRootCauseMessage(ex));
  95 + } else {
  96 + accessLog.setResultCode(0);
  97 + accessLog.setResultMsg("");
  98 + }
  99 + // 设置其它字段
  100 + accessLog.setTraceId(TracerUtils.getTraceId());
  101 + accessLog.setApplicationName(applicationName);
  102 + accessLog.setRequestUrl(request.getRequestURI());
  103 + Map<String, Object> requestParams = MapUtil.<String, Object>builder().put("query", queryString).put("body", requestBody).build();
  104 + accessLog.setRequestParams(JsonUtils.toJsonString(requestParams));
  105 + accessLog.setRequestMethod(request.getMethod());
  106 + accessLog.setUserAgent(ServletUtils.getUserAgent(request));
  107 + accessLog.setUserIp(ServletUtil.getClientIP(request));
  108 + // 持续时间
  109 + accessLog.setStartTime(startTime);
  110 + accessLog.setEndTime(new Date());
  111 + accessLog.setDuration((int) DateUtils.diff(accessLog.getEndTime(), accessLog.getStartTime()));
  112 + return accessLog;
69 113 }
70 114  
71 115 }
... ...
src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiAccessLogFrameworkService.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.logger.apilog.core.service;
  2 +
  3 +import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiAccessLogCreateDTO;
  4 +
  5 +import javax.validation.Valid;
  6 +
  7 +/**
  8 + * API 访问日志 Framework Service 接口
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface ApiAccessLogFrameworkService {
  13 +
  14 + /**
  15 + * 创建 API 访问日志
  16 + *
  17 + * @param createDTO 创建信息
  18 + */
  19 + void createApiAccessLogAsync(@Valid ApiAccessLogCreateDTO createDTO);
  20 +
  21 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiAccessLogCreateDTO.java
1 1 package cn.iocoder.dashboard.framework.logger.apilog.core.service.dto;
2 2  
  3 +import lombok.Data;
  4 +
3 5 import javax.validation.constraints.NotNull;
4 6 import java.util.Date;
5 7  
... ... @@ -8,6 +10,7 @@ import java.util.Date;
8 10 *
9 11 * @author 芋道源码
10 12 */
  13 +@Data
11 14 public class ApiAccessLogCreateDTO {
12 15  
13 16 /**
... ... @@ -17,7 +20,7 @@ public class ApiAccessLogCreateDTO {
17 20 /**
18 21 * 用户编号
19 22 */
20   - private Integer userId;
  23 + private Long userId;
21 24 /**
22 25 * 用户类型
23 26 */
... ...
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java
... ... @@ -8,7 +8,7 @@ import cn.iocoder.dashboard.common.pojo.CommonResult;
8 8 import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
9 9 import cn.iocoder.dashboard.framework.logger.operatelog.core.enums.OperateTypeEnum;
10 10 import cn.iocoder.dashboard.framework.logger.operatelog.core.service.OperateLogFrameworkService;
11   -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  11 +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
12 12 import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils;
13 13 import cn.iocoder.dashboard.modules.system.controller.logger.vo.operatelog.SysOperateLogCreateReqVO;
14 14 import cn.iocoder.dashboard.util.json.JsonUtils;
... ... @@ -148,7 +148,7 @@ public class OperateLogAspect {
148 148 }
149 149  
150 150 private static void fillUserFields(SysOperateLogCreateReqVO operateLogVO) {
151   - operateLogVO.setUserId(SecurityUtils.getLoginUserId());
  151 + operateLogVO.setUserId(SecurityFrameworkUtils.getLoginUserId());
152 152 }
153 153  
154 154 private static void fillModuleFields(SysOperateLogCreateReqVO operateLogVO,
... ...
src/main/java/cn/iocoder/dashboard/framework/security/core/filter/JwtAuthenticationTokenFilter.java
1 1 package cn.iocoder.dashboard.framework.security.core.filter;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4   -import cn.hutool.extra.servlet.ServletUtil;
5 4 import cn.iocoder.dashboard.common.pojo.CommonResult;
6 5 import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
7 6 import cn.iocoder.dashboard.framework.security.core.LoginUser;
8   -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  7 +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
9 8 import cn.iocoder.dashboard.framework.web.core.handler.GlobalExceptionHandler;
10 9 import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService;
11 10 import cn.iocoder.dashboard.util.servlet.ServletUtils;
12   -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13   -import org.springframework.security.core.context.SecurityContextHolder;
14   -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
15 11 import org.springframework.stereotype.Component;
16 12 import org.springframework.web.filter.OncePerRequestFilter;
17 13  
... ... @@ -42,7 +38,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
42 38 @SuppressWarnings("NullableProblems")
43 39 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
44 40 throws ServletException, IOException {
45   - String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
  41 + String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
46 42 if (StrUtil.isNotEmpty(token)) {
47 43 try {
48 44 // 验证 token 有效性
... ... @@ -53,7 +49,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
53 49 }
54 50 // 设置当前用户
55 51 if (loginUser != null) {
56   - SecurityUtils.setLoginUser(loginUser, request);
  52 + SecurityFrameworkUtils.setLoginUser(loginUser, request);
57 53 }
58 54 } catch (Throwable ex) {
59 55 CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
... ...
src/main/java/cn/iocoder/dashboard/framework/security/core/handler/AccessDeniedHandlerImpl.java
... ... @@ -2,7 +2,7 @@ package cn.iocoder.dashboard.framework.security.core.handler;
2 2  
3 3 import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
4 4 import cn.iocoder.dashboard.common.pojo.CommonResult;
5   -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  5 +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
6 6 import cn.iocoder.dashboard.util.servlet.ServletUtils;
7 7 import lombok.extern.slf4j.Slf4j;
8 8 import org.springframework.security.access.AccessDeniedException;
... ... @@ -35,7 +35,7 @@ public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
35 35 throws IOException, ServletException {
36 36 // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
37 37 log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
38   - SecurityUtils.getLoginUser().getId(), e);
  38 + SecurityFrameworkUtils.getLoginUser().getId(), e);
39 39 // 返回 403
40 40 ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED));
41 41 }
... ...
src/main/java/cn/iocoder/dashboard/framework/security/core/handler/LogoutSuccessHandlerImpl.java
... ... @@ -3,7 +3,7 @@ package cn.iocoder.dashboard.framework.security.core.handler;
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
5 5 import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService;
6   -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  6 +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
7 7 import cn.iocoder.dashboard.util.servlet.ServletUtils;
8 8 import org.springframework.security.core.Authentication;
9 9 import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
... ... @@ -31,7 +31,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
31 31 @Override
32 32 public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
33 33 // 执行退出
34   - String token = SecurityUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
  34 + String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
35 35 if (StrUtil.isNotBlank(token)) {
36 36 securityFrameworkService.logout(token);
37 37 }
... ...
src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityUtils.java renamed to src/main/java/cn/iocoder/dashboard/framework/security/core/util/SecurityFrameworkUtils.java
1 1 package cn.iocoder.dashboard.framework.security.core.util;
2 2  
3 3 import cn.iocoder.dashboard.framework.security.core.LoginUser;
  4 +import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils;
4 5 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
5 6 import org.springframework.security.core.context.SecurityContextHolder;
6 7 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
... ... @@ -12,11 +13,11 @@ import java.util.Set;
12 13 /**
13 14 * 安全服务工具类
14 15 *
15   - * @author ruoyi
  16 + * @author 芋道源码
16 17 */
17   -public class SecurityUtils {
  18 +public class SecurityFrameworkUtils {
18 19  
19   - private SecurityUtils() {}
  20 + private SecurityFrameworkUtils() {}
20 21  
21 22 /**
22 23 * 从请求中,获得认证 Token
... ... @@ -45,7 +46,7 @@ public class SecurityUtils {
45 46 }
46 47  
47 48 /**
48   - * 获得当前用户的编号
  49 + * 获得当前用户的编号,从上下文中
49 50 *
50 51 * @return 用户编号
51 52 */
... ... @@ -53,6 +54,11 @@ public class SecurityUtils {
53 54 return getLoginUser().getId();
54 55 }
55 56  
  57 + /**
  58 + * 获得当前用户的角色编号数组
  59 + *
  60 + * @return 角色编号数组
  61 + */
56 62 public static Set<Long> getLoginUserRoleIds() {
57 63 return getLoginUser().getRoleIds();
58 64 }
... ... @@ -70,6 +76,9 @@ public class SecurityUtils {
70 76 authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
71 77 // 设置到上下文
72 78 SecurityContextHolder.getContext().setAuthentication(authenticationToken);
  79 + // 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号;
  80 + // 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息
  81 + WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
73 82 }
74 83  
75 84 }
... ...
src/main/java/cn/iocoder/dashboard/framework/web/config/WebConfiguration.java
1 1 package cn.iocoder.dashboard.framework.web.config;
2 2  
3 3 import cn.iocoder.dashboard.framework.web.core.enums.FilterOrderEnum;
4   -import cn.iocoder.dashboard.framework.web.core.filter.RequestBodyCacheFilter;
  4 +import cn.iocoder.dashboard.framework.web.core.filter.CacheRequestBodyFilter;
5 5 import cn.iocoder.dashboard.framework.web.core.filter.XssFilter;
6 6 import org.springframework.boot.context.properties.EnableConfigurationProperties;
7 7 import org.springframework.boot.web.servlet.FilterRegistrationBean;
... ... @@ -55,8 +55,8 @@ public class WebConfiguration implements WebMvcConfigurer {
55 55 * 创建 RequestBodyCacheFilter Bean,可重复读取请求内容
56 56 */
57 57 @Bean
58   - public FilterRegistrationBean<RequestBodyCacheFilter> requestBodyCacheFilter() {
59   - return createFilterBean(new RequestBodyCacheFilter(), FilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
  58 + public FilterRegistrationBean<CacheRequestBodyFilter> requestBodyCacheFilter() {
  59 + return createFilterBean(new CacheRequestBodyFilter(), FilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
60 60 }
61 61  
62 62 /**
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/filter/RequestBodyCacheFilter.java renamed to src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyFilter.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.dashboard.framework.web.core.filter;
2 2  
3 3 import cn.iocoder.dashboard.util.servlet.ServletUtils;
4 4 import org.springframework.web.filter.OncePerRequestFilter;
5   -import org.springframework.web.util.ContentCachingRequestWrapper;
6 5  
7 6 import javax.servlet.FilterChain;
8 7 import javax.servlet.ServletException;
... ... @@ -13,16 +12,14 @@ import java.io.IOException;
13 12 /**
14 13 * Request Body 缓存 Filter,实现它的可重复读取
15 14 *
16   - * 基于 Spring 提供的 {@link org.springframework.web.util.ContentCachingRequestWrapper} 实现
17   - *
18 15 * @author 芋道源码
19 16 */
20   -public class RequestBodyCacheFilter extends OncePerRequestFilter {
  17 +public class CacheRequestBodyFilter extends OncePerRequestFilter {
21 18  
22 19 @Override
23 20 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
24 21 throws IOException, ServletException {
25   - filterChain.doFilter(new ContentCachingRequestWrapper(request), response);
  22 + filterChain.doFilter(new CacheRequestBodyWrapper(request), response);
26 23 }
27 24  
28 25 @Override
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/filter/CacheRequestBodyWrapper.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.web.core.filter;
  2 +
  3 +import cn.hutool.extra.servlet.ServletUtil;
  4 +
  5 +import javax.servlet.ReadListener;
  6 +import javax.servlet.ServletInputStream;
  7 +import javax.servlet.http.HttpServletRequest;
  8 +import javax.servlet.http.HttpServletRequestWrapper;
  9 +import java.io.BufferedReader;
  10 +import java.io.ByteArrayInputStream;
  11 +import java.io.IOException;
  12 +import java.io.InputStreamReader;
  13 +
  14 +/**
  15 + * Request Body 缓存 Wrapper
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +public class CacheRequestBodyWrapper extends HttpServletRequestWrapper {
  20 +
  21 + /**
  22 + * 缓存的内容
  23 + */
  24 + private final byte[] body;
  25 +
  26 + public CacheRequestBodyWrapper(HttpServletRequest request) {
  27 + super(request);
  28 + body = ServletUtil.getBodyBytes(request);
  29 + }
  30 +
  31 + @Override
  32 + public BufferedReader getReader() throws IOException {
  33 + return new BufferedReader(new InputStreamReader(this.getInputStream()));
  34 + }
  35 +
  36 + @Override
  37 + public ServletInputStream getInputStream() throws IOException {
  38 + final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
  39 + // 返回 ServletInputStream
  40 + return new ServletInputStream() {
  41 +
  42 + @Override
  43 + public int read() {
  44 + return inputStream.read();
  45 + }
  46 +
  47 + @Override
  48 + public boolean isFinished() {
  49 + return true;
  50 + }
  51 +
  52 + @Override
  53 + public boolean isReady() {
  54 + return true;
  55 + }
  56 +
  57 + @Override
  58 + public void setReadListener(ReadListener readListener) {}
  59 +
  60 + };
  61 + }
  62 +
  63 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java
... ... @@ -3,7 +3,7 @@ package cn.iocoder.dashboard.framework.web.core.handler;
3 3 import cn.iocoder.dashboard.common.exception.GlobalException;
4 4 import cn.iocoder.dashboard.common.exception.ServiceException;
5 5 import cn.iocoder.dashboard.common.pojo.CommonResult;
6   -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  6 +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
7 7 import io.github.resilience4j.ratelimiter.RequestNotPermitted;
8 8 import lombok.extern.slf4j.Slf4j;
9 9 import org.springframework.security.access.AccessDeniedException;
... ... @@ -26,6 +26,8 @@ import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstan
26 26  
27 27 /**
28 28 * 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号
  29 + *
  30 + * @author 芋道源码
29 31 */
30 32 @RestControllerAdvice
31 33 @Slf4j
... ... @@ -183,7 +185,7 @@ public class GlobalExceptionHandler {
183 185 */
184 186 @ExceptionHandler(value = AccessDeniedException.class)
185 187 public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
186   - log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", SecurityUtils.getLoginUserId(),
  188 + log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", SecurityFrameworkUtils.getLoginUserId(),
187 189 req.getRequestURL(), ex);
188 190 return CommonResult.error(FORBIDDEN);
189 191 }
... ... @@ -275,7 +277,7 @@ public class GlobalExceptionHandler {
275 277 // // 设置其它字段
276 278 // exceptionLog.setTraceId(MallUtils.getTraceId())
277 279 // .setApplicationName(applicationName)
278   -// .setUri(request.getRequestURI()) // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
  280 +// .setUri(request.getRequestURI())
279 281 // .setQueryString(HttpUtil.buildQueryString(request))
280 282 // .setMethod(request.getMethod())
281 283 // .setUserAgent(HttpUtil.getUserAgent(request))
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalResponseBodyHandler.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.web.core.handler;
  2 +
  3 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  4 +import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils;
  5 +import org.springframework.core.MethodParameter;
  6 +import org.springframework.http.MediaType;
  7 +import org.springframework.http.server.ServerHttpRequest;
  8 +import org.springframework.http.server.ServerHttpResponse;
  9 +import org.springframework.http.server.ServletServerHttpRequest;
  10 +import org.springframework.web.bind.annotation.ControllerAdvice;
  11 +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
  12 +
  13 +/**
  14 + * 全局响应结果(ResponseBody)处理器
  15 + *
  16 + * 不同于在网上看到的很多文章,会选择自动将 Controller 返回结果包上 {@link CommonResult},
  17 + * 在 onemall 中,是 Controller 在返回时,主动自己包上 {@link CommonResult}。
  18 + * 原因是,GlobalResponseBodyHandler 本质上是 AOP,它不应该改变 Controller 返回的数据结构
  19 + *
  20 + * 目前,GlobalResponseBodyHandler 的主要作用是,记录 Controller 的返回结果,
  21 + * 方便 {@link cn.iocoder.dashboard.framework.logger.apilog.core.filter.ApiAccessLogFilter} 记录访问日志
  22 + */
  23 +@ControllerAdvice
  24 +public class GlobalResponseBodyHandler implements ResponseBodyAdvice {
  25 +
  26 + @Override
  27 + @SuppressWarnings("NullableProblems") // 避免 IDEA 警告
  28 + public boolean supports(MethodParameter returnType, Class converterType) {
  29 + if (returnType.getMethod() == null) {
  30 + return false;
  31 + }
  32 + // 只拦截返回结果为 CommonResult 类型
  33 + return returnType.getMethod().getReturnType() == CommonResult.class;
  34 + }
  35 +
  36 + @Override
  37 + @SuppressWarnings("NullableProblems") // 避免 IDEA 警告
  38 + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
  39 + ServerHttpRequest request, ServerHttpResponse response) {
  40 + // 记录 Controller 结果
  41 + WebFrameworkUtils.setCommonResult(((ServletServerHttpRequest) request).getServletRequest(), (CommonResult<?>) body);
  42 + return body;
  43 + }
  44 +
  45 +}
... ...
src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.web.core.util;
  2 +
  3 +import cn.iocoder.dashboard.common.enums.UserTypeEnum;
  4 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  5 +
  6 +import javax.servlet.ServletRequest;
  7 +import javax.servlet.http.HttpServletRequest;
  8 +
  9 +/**
  10 + * 专属于 web 包的工具类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class WebFrameworkUtils {
  15 +
  16 + private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = "login_user_id";
  17 +
  18 + private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
  19 +
  20 + public static void setLoginUserId(ServletRequest request, Long userId) {
  21 + request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
  22 + }
  23 +
  24 + /**
  25 + * 获得当前用户的编号,从请求中
  26 + *
  27 + * @param request 请求
  28 + * @return 用户编号
  29 + */
  30 + public static Long getLoginUserId(HttpServletRequest request) {
  31 + return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID);
  32 + }
  33 +
  34 + public static Integer getUsrType(HttpServletRequest request) {
  35 + return UserTypeEnum.ADMIN.getValue(); // TODO 芋艿:等后续优化
  36 + }
  37 +
  38 + public static void setCommonResult(ServletRequest request, CommonResult<?> result) {
  39 + request.setAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT, result);
  40 + }
  41 +
  42 + public static CommonResult<?> getCommonResult(ServletRequest request) {
  43 + return (CommonResult<?>) request.getAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT);
  44 + }
  45 +
  46 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java
... ... @@ -26,8 +26,8 @@ import javax.validation.Valid;
26 26 import java.util.List;
27 27  
28 28 import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
29   -import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserId;
30   -import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserRoleIds;
  29 +import static cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  30 +import static cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils.getLoginUserRoleIds;
31 31 import static cn.iocoder.dashboard.util.servlet.ServletUtils.getClientIP;
32 32 import static cn.iocoder.dashboard.util.servlet.ServletUtils.getUserAgent;
33 33  
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysApiAccessLogService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.logger;
  2 +
  3 +import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiAccessLogFrameworkService;
  4 +
  5 +/**
  6 + * API 访问日志 Service 接口
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +public interface SysApiAccessLogService extends ApiAccessLogFrameworkService {
  11 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysApiAccessLogServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.logger.impl;
  2 +
  3 +import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiAccessLogCreateDTO;
  4 +import cn.iocoder.dashboard.modules.system.service.logger.SysApiAccessLogService;
  5 +import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
  7 +
  8 +import javax.validation.Valid;
  9 +
  10 +/**
  11 + * API 访问日志 Service 实现类
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Service
  16 +@Validated
  17 +public class SysApiAccessLogServiceImpl implements SysApiAccessLogService {
  18 +
  19 +
  20 +
  21 + @Override
  22 + public void createApiAccessLogAsync(@Valid ApiAccessLogCreateDTO createDTO) {
  23 +
  24 + }
  25 +
  26 +}
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java
... ... @@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
5 5 import cn.hutool.core.util.ArrayUtil;
6 6 import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
7   -import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  7 +import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
8 8 import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysRoleMenuMapper;
9 9 import cn.iocoder.dashboard.modules.system.dal.mysql.permission.SysUserRoleMapper;
10 10 import cn.iocoder.dashboard.modules.system.dal.dataobject.permission.SysMenuDO;
... ... @@ -262,7 +262,7 @@ public class SysPermissionServiceImpl implements SysPermissionService {
262 262 }
263 263  
264 264 // 获得当前登陆的角色。如果为空,说明没有权限
265   - Set<Long> roleIds = SecurityUtils.getLoginUserRoleIds();
  265 + Set<Long> roleIds = SecurityFrameworkUtils.getLoginUserRoleIds();
266 266 if (CollUtil.isEmpty(roleIds)) {
267 267 return false;
268 268 }
... ... @@ -297,7 +297,7 @@ public class SysPermissionServiceImpl implements SysPermissionService {
297 297 }
298 298  
299 299 // 获得当前登陆的角色。如果为空,说明没有权限
300   - Set<Long> roleIds = SecurityUtils.getLoginUserRoleIds();
  300 + Set<Long> roleIds = SecurityFrameworkUtils.getLoginUserRoleIds();
301 301 if (CollUtil.isEmpty(roleIds)) {
302 302 return false;
303 303 }
... ...