Commit ec8f181f2f5e970613b56eb1da6d65d43e08acba

Authored by YunaiV
1 parent b6457c04

完成操作日志的记录

Showing 29 changed files with 691 additions and 261 deletions
@@ -49,6 +49,8 @@ @@ -49,6 +49,8 @@
49 <druid.version>1.2.4</druid.version> 49 <druid.version>1.2.4</druid.version>
50 <mybatis-plus.version>3.4.1</mybatis-plus.version> 50 <mybatis-plus.version>3.4.1</mybatis-plus.version>
51 <redisson.version>3.13.6</redisson.version> 51 <redisson.version>3.13.6</redisson.version>
  52 + <!-- 监控相关 -->
  53 + <skywalking.version>8.3.0</skywalking.version>
52 <!-- 工具类相关 --> 54 <!-- 工具类相关 -->
53 <lombok.version>1.16.14</lombok.version> 55 <lombok.version>1.16.14</lombok.version>
54 <mapstruct.version>1.4.1.Final</mapstruct.version> 56 <mapstruct.version>1.4.1.Final</mapstruct.version>
@@ -88,20 +90,6 @@ @@ -88,20 +90,6 @@
88 <!-- <version>${jna.version}</version>--> 90 <!-- <version>${jna.version}</version>-->
89 <!-- </dependency>--> 91 <!-- </dependency>-->
90 92
91 -<!-- &lt;!&ndash;io常用工具类 &ndash;&gt;-->  
92 -<!-- <dependency>-->  
93 -<!-- <groupId>commons-io</groupId>-->  
94 -<!-- <artifactId>commons-io</artifactId>-->  
95 -<!-- <version>${commons.io.version}</version>-->  
96 -<!-- </dependency>-->  
97 -  
98 -<!-- &lt;!&ndash;文件上传工具类 &ndash;&gt;-->  
99 -<!-- <dependency>-->  
100 -<!-- <groupId>commons-fileupload</groupId>-->  
101 -<!-- <artifactId>commons-fileupload</artifactId>-->  
102 -<!-- <version>${commons.fileupload.version}</version>-->  
103 -<!-- </dependency>-->  
104 -  
105 <!-- &lt;!&ndash;velocity代码生成使用模板 &ndash;&gt;--> 93 <!-- &lt;!&ndash;velocity代码生成使用模板 &ndash;&gt;-->
106 <!-- <dependency>--> 94 <!-- <dependency>-->
107 <!-- <groupId>org.apache.velocity</groupId>--> 95 <!-- <groupId>org.apache.velocity</groupId>-->
@@ -129,6 +117,11 @@ @@ -129,6 +117,11 @@
129 <optional>true</optional> 117 <optional>true</optional>
130 </dependency> 118 </dependency>
131 119
  120 + <dependency>
  121 + <groupId>org.springframework.boot</groupId>
  122 + <artifactId>spring-boot-starter-aop</artifactId>
  123 + </dependency>
  124 +
132 <!-- Web 相关 --> 125 <!-- Web 相关 -->
133 <dependency> 126 <dependency>
134 <groupId>org.springframework.boot</groupId> 127 <groupId>org.springframework.boot</groupId>
@@ -174,6 +167,13 @@ @@ -174,6 +167,13 @@
174 <version>${redisson.version}</version> 167 <version>${redisson.version}</version>
175 </dependency> 168 </dependency>
176 169
  170 + <!-- 监控相关 -->
  171 + <dependency>
  172 + <groupId>org.apache.skywalking</groupId>
  173 + <artifactId>apm-toolkit-trace</artifactId>
  174 + <version>${skywalking.version}</version>
  175 + </dependency>
  176 +
177 <!-- 工具类相关 --> 177 <!-- 工具类相关 -->
178 <dependency> 178 <dependency>
179 <groupId>org.projectlombok</groupId> 179 <groupId>org.projectlombok</groupId>
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
@@ -117,58 +117,6 @@ public class LogAspect { @@ -117,58 +117,6 @@ public class LogAspect {
117 } 117 }
118 118
119 /** 119 /**
120 - * 获取注解中对方法的描述信息 用于Controller层注解  
121 - *  
122 - * @param log 日志  
123 - * @param operLog 操作日志  
124 - * @throws Exception  
125 - */  
126 - public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception {  
127 - // 设置action动作  
128 - operLog.setBusinessType(log.businessType().ordinal());  
129 - // 设置标题  
130 - operLog.setTitle(log.title());  
131 - // 设置操作人类别  
132 - operLog.setOperatorType(log.operatorType().ordinal());  
133 - // 是否需要保存request,参数和值  
134 - if (log.isSaveRequestData()) {  
135 - // 获取参数的信息,传入到数据库中。  
136 - setRequestValue(joinPoint, operLog);  
137 - }  
138 - }  
139 -  
140 - /**  
141 - * 获取请求的参数,放到log中  
142 - *  
143 - * @param operLog 操作日志  
144 - * @throws Exception 异常  
145 - */  
146 - private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception {  
147 - String requestMethod = operLog.getRequestMethod();  
148 - if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {  
149 - String params = argsArrayToString(joinPoint.getArgs());  
150 - operLog.setOperParam(StringUtils.substring(params, 0, 2000));  
151 - } else {  
152 - Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);  
153 - operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));  
154 - }  
155 - }  
156 -  
157 - /**  
158 - * 是否存在注解,如果存在就获取  
159 - */  
160 - private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {  
161 - Signature signature = joinPoint.getSignature();  
162 - MethodSignature methodSignature = (MethodSignature) signature;  
163 - Method method = methodSignature.getMethod();  
164 -  
165 - if (method != null) {  
166 - return method.getAnnotation(Log.class);  
167 - }  
168 - return null;  
169 - }  
170 -  
171 - /**  
172 * 参数拼装 120 * 参数拼装
173 */ 121 */
174 private String argsArrayToString(Object[] paramsArray) { 122 private String argsArrayToString(Object[] paramsArray) {
ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java deleted
1 -package com.ruoyi.framework.manager;  
2 -  
3 -import java.util.TimerTask;  
4 -import java.util.concurrent.ScheduledExecutorService;  
5 -import java.util.concurrent.TimeUnit;  
6 -  
7 -import com.ruoyi.common.utils.Threads;  
8 -import com.ruoyi.common.utils.spring.SpringUtils;  
9 -  
10 -/**  
11 - * 异步任务管理器  
12 - *  
13 - * @author ruoyi  
14 - */  
15 -public class AsyncManager {  
16 - /**  
17 - * 操作延迟10毫秒  
18 - */  
19 - private final int OPERATE_DELAY_TIME = 10;  
20 -  
21 - /**  
22 - * 异步操作任务调度线程池  
23 - */  
24 - private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");  
25 -  
26 - /**  
27 - * 单例模式  
28 - */  
29 - private AsyncManager() {  
30 - }  
31 -  
32 - private static AsyncManager me = new AsyncManager();  
33 -  
34 - public static AsyncManager me() {  
35 - return me;  
36 - }  
37 -  
38 - /**  
39 - * 执行任务  
40 - *  
41 - * @param task 任务  
42 - */  
43 - public void execute(TimerTask task) {  
44 - executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);  
45 - }  
46 -  
47 - /**  
48 - * 停止任务线程池  
49 - */  
50 - public void shutdown() {  
51 - Threads.shutdownAndAwaitTermination(executor);  
52 - }  
53 -}  
ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java deleted
1 -package com.ruoyi.framework.manager;  
2 -  
3 -import org.slf4j.Logger;  
4 -import org.slf4j.LoggerFactory;  
5 -import org.springframework.stereotype.Component;  
6 -  
7 -import javax.annotation.PreDestroy;  
8 -  
9 -/**  
10 - * 确保应用退出时能关闭后台线程  
11 - *  
12 - * @author ruoyi  
13 - */  
14 -@Component  
15 -public class ShutdownManager {  
16 - private static final Logger logger = LoggerFactory.getLogger("sys-user");  
17 -  
18 - @PreDestroy  
19 - public void destroy() {  
20 - shutdownAsyncManager();  
21 - }  
22 -  
23 - /**  
24 - * 停止异步执行任务  
25 - */  
26 - private void shutdownAsyncManager() {  
27 - try {  
28 - logger.info("====关闭后台任务任务线程池====");  
29 - AsyncManager.me().shutdown();  
30 - } catch (Exception e) {  
31 - logger.error(e.getMessage(), e);  
32 - }  
33 - }  
34 -}  
ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java deleted
1 -package com.ruoyi.framework.manager.factory;  
2 -  
3 -import java.util.TimerTask;  
4 -  
5 -import org.slf4j.Logger;  
6 -import org.slf4j.LoggerFactory;  
7 -import com.ruoyi.common.constant.Constants;  
8 -import com.ruoyi.common.utils.LogUtils;  
9 -import com.ruoyi.common.utils.ServletUtils;  
10 -import com.ruoyi.common.utils.ip.AddressUtils;  
11 -import com.ruoyi.common.utils.ip.IpUtils;  
12 -import com.ruoyi.common.utils.spring.SpringUtils;  
13 -import com.ruoyi.system.domain.SysLogininfor;  
14 -import com.ruoyi.system.domain.SysOperLog;  
15 -import com.ruoyi.system.service.ISysLogininforService;  
16 -import com.ruoyi.system.service.ISysOperLogService;  
17 -import eu.bitwalker.useragentutils.UserAgent;  
18 -  
19 -/**  
20 - * 异步工厂(产生任务用)  
21 - *  
22 - * @author ruoyi  
23 - */  
24 -public class AsyncFactory {  
25 - private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");  
26 -  
27 - /**  
28 - * 记录登录信息  
29 - *  
30 - * @param username 用户名  
31 - * @param status 状态  
32 - * @param message 消息  
33 - * @param args 列表  
34 - * @return 任务task  
35 - */  
36 - public static TimerTask recordLogininfor(final String username, final String status, final String message,  
37 - final Object... args) {  
38 - final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));  
39 - final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());  
40 - return new TimerTask() {  
41 - @Override  
42 - public void run() {  
43 - String address = AddressUtils.getRealAddressByIP(ip);  
44 - StringBuilder s = new StringBuilder();  
45 - s.append(LogUtils.getBlock(ip));  
46 - s.append(address);  
47 - s.append(LogUtils.getBlock(username));  
48 - s.append(LogUtils.getBlock(status));  
49 - s.append(LogUtils.getBlock(message));  
50 - // 打印信息到日志  
51 - sys_user_logger.info(s.toString(), args);  
52 - // 获取客户端操作系统  
53 - String os = userAgent.getOperatingSystem().getName();  
54 - // 获取客户端浏览器  
55 - String browser = userAgent.getBrowser().getName();  
56 - // 封装对象  
57 - SysLogininfor logininfor = new SysLogininfor();  
58 - logininfor.setUserName(username);  
59 - logininfor.setIpaddr(ip);  
60 - logininfor.setLoginLocation(address);  
61 - logininfor.setBrowser(browser);  
62 - logininfor.setOs(os);  
63 - logininfor.setMsg(message);  
64 - // 日志状态  
65 - if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status)) {  
66 - logininfor.setStatus(Constants.SUCCESS);  
67 - } else if (Constants.LOGIN_FAIL.equals(status)) {  
68 - logininfor.setStatus(Constants.FAIL);  
69 - }  
70 - // 插入数据  
71 - SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);  
72 - }  
73 - };  
74 - }  
75 -  
76 - /**  
77 - * 操作日志记录  
78 - *  
79 - * @param operLog 操作日志信息  
80 - * @return 任务task  
81 - */  
82 - public static TimerTask recordOper(final SysOperLog operLog) {  
83 - return new TimerTask() {  
84 - @Override  
85 - public void run() {  
86 - // 远程查询操作地点  
87 - operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));  
88 - SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);  
89 - }  
90 - };  
91 - }  
92 -}  
src/main/java/cn/iocoder/dashboard/framework/async/config/AsyncConfiguration.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.async.config;
  2 +
  3 +import org.springframework.context.annotation.Configuration;
  4 +import org.springframework.scheduling.annotation.EnableAsync;
  5 +
  6 +@Configuration
  7 +@EnableAsync(proxyTargetClass = true)
  8 +public class AsyncConfiguration {
  9 +}
src/main/java/cn/iocoder/dashboard/framework/async/package-info.java 0 → 100644
  1 +/**
  2 + * 异步执行,基于 Spring @Async 实现
  3 + */
  4 +package cn.iocoder.dashboard.framework.async;
src/main/java/cn/iocoder/dashboard/framework/async/《芋道 Spring Boot 异步任务入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/Async-Job/?dashboard>
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/config/OperateLogConfiguration.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.logger.operatelog.config;
  2 +
  3 +import cn.iocoder.dashboard.framework.logger.operatelog.core.aop.OperateLogAspect;
  4 +import org.springframework.context.annotation.Bean;
  5 +import org.springframework.context.annotation.Configuration;
  6 +
  7 +@Configuration
  8 +public class OperateLogConfiguration {
  9 +
  10 + @Bean
  11 + public OperateLogAspect operateLogAspect() {
  12 + return new OperateLogAspect();
  13 + }
  14 +
  15 +}
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/annotations/OperateLog.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.logger.operatelog.core.annotations;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.enums.logger.SysOperateLogTypeEnum;
  4 +import io.swagger.annotations.Api;
  5 +import io.swagger.annotations.ApiOperation;
  6 +
  7 +import java.lang.annotation.ElementType;
  8 +import java.lang.annotation.Retention;
  9 +import java.lang.annotation.RetentionPolicy;
  10 +import java.lang.annotation.Target;
  11 +
  12 +@Target({ElementType.METHOD})
  13 +@Retention(RetentionPolicy.RUNTIME)
  14 +public @interface OperateLog {
  15 +
  16 + // ========== 模块字段 ==========
  17 +
  18 + /**
  19 + * 操作模块
  20 + *
  21 + * 为空时,会尝试读取 {@link Api#value()} 属性
  22 + */
  23 + String module() default "";
  24 + /**
  25 + * 操作名
  26 + *
  27 + * 为空时,会尝试读取 {@link ApiOperation#value()} 属性
  28 + */
  29 + String name() default "";
  30 + /**
  31 + * 操作分类
  32 + *
  33 + * 实际并不是数组,因为枚举不能设置 null 作为默认值
  34 + */
  35 + SysOperateLogTypeEnum[] type() default {};
  36 +
  37 + // ========== 开关字段 ==========
  38 +
  39 + /**
  40 + * 是否记录操作日志
  41 + */
  42 + boolean enable() default true;
  43 + /**
  44 + * 是否记录方法参数
  45 + */
  46 + boolean logArgs() default true;
  47 + /**
  48 + * 是否记录方法结果的数据
  49 + */
  50 + boolean logResultData() default true;
  51 +
  52 +}
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.logger.operatelog.core.aop;
  2 +
  3 +import cn.hutool.core.exceptions.ExceptionUtil;
  4 +import cn.hutool.core.util.ArrayUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.extra.servlet.ServletUtil;
  7 +import cn.iocoder.dashboard.common.pojo.CommonResult;
  8 +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
  9 +import cn.iocoder.dashboard.framework.logger.operatelog.core.service.OperateLogFrameworkService;
  10 +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils;
  11 +import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils;
  12 +import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO;
  13 +import cn.iocoder.dashboard.modules.system.enums.logger.SysOperateLogTypeEnum;
  14 +import cn.iocoder.dashboard.util.servlet.ServletUtils;
  15 +import com.alibaba.fastjson.JSON;
  16 +import com.google.common.collect.Maps;
  17 +import io.swagger.annotations.Api;
  18 +import io.swagger.annotations.ApiOperation;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.aspectj.lang.ProceedingJoinPoint;
  21 +import org.aspectj.lang.annotation.Around;
  22 +import org.aspectj.lang.annotation.Aspect;
  23 +import org.aspectj.lang.reflect.MethodSignature;
  24 +import org.springframework.core.annotation.AnnotationUtils;
  25 +import org.springframework.web.bind.annotation.RequestMapping;
  26 +import org.springframework.web.bind.annotation.RequestMethod;
  27 +import org.springframework.web.context.request.RequestAttributes;
  28 +import org.springframework.web.context.request.RequestContextHolder;
  29 +import org.springframework.web.context.request.ServletRequestAttributes;
  30 +import org.springframework.web.multipart.MultipartFile;
  31 +
  32 +import javax.annotation.Resource;
  33 +import javax.servlet.http.HttpServletRequest;
  34 +import javax.servlet.http.HttpServletResponse;
  35 +import java.lang.annotation.Annotation;
  36 +import java.lang.reflect.Array;
  37 +import java.util.*;
  38 +import java.util.function.Predicate;
  39 +import java.util.stream.IntStream;
  40 +
  41 +import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  42 +import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
  43 +
  44 +/**
  45 + * 拦截使用 @ApiOperation 注解,如果满足条件,则生成操作日志。
  46 + * 满足如下任一条件,则会进行记录:
  47 + * 1. 使用 @ApiOperation + 非 @GetMapping
  48 + * 2. 使用 @OperateLog 注解
  49 + *
  50 + * 但是,如果声明 @OperateLog 注解时,将 enable 属性设置为 false 时,强制不记录。
  51 + *
  52 + * 为什么考虑使用 @ApiOperation 记录呢?避免有小伙伴忘记添加 @OperateLog 注解
  53 + *
  54 + * @author 芋道源码
  55 + */
  56 +@Aspect
  57 +@Slf4j
  58 +public class OperateLogAspect {
  59 +
  60 + @Resource
  61 + private OperateLogFrameworkService operateLogFrameworkService;
  62 +
  63 + @Around("@annotation(apiOperation)")
  64 + public Object around(ProceedingJoinPoint joinPoint, ApiOperation apiOperation) throws Throwable {
  65 + // 可能也添加了 @ApiOperation 注解
  66 + OperateLog operateLog = getMethodAnnotation(joinPoint, OperateLog.class);
  67 + return around0(joinPoint, operateLog, apiOperation);
  68 + }
  69 +
  70 + @Around("!@annotation(io.swagger.annotations.ApiOperation) && @annotation(operateLog)") // 兼容处理,只添加 @OperateLog 注解的情况
  71 + public Object around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable {
  72 + return around0(joinPoint, operateLog, null);
  73 + }
  74 +
  75 + private Object around0(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation) throws Throwable {
  76 + // 记录开始时间
  77 + Date startTime = new Date();
  78 + try {
  79 + // 执行原有方法
  80 + Object result = joinPoint.proceed();
  81 + // 记录正常执行时的操作日志
  82 + this.log(joinPoint, operateLog, apiOperation, startTime, result, null);
  83 + return result;
  84 + } catch (Throwable exception) {
  85 + this.log(joinPoint, operateLog, apiOperation, startTime, null, exception);
  86 + throw exception;
  87 + }
  88 + }
  89 +
  90 + private void log(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation,
  91 + Date startTime, Object result, Throwable exception) {
  92 + try {
  93 + // 判断不记录的情况
  94 + if (!isLogEnable(joinPoint, operateLog)) {
  95 + return;
  96 + }
  97 + // 真正记录操作日志
  98 + this.log0(joinPoint, operateLog, apiOperation, startTime, result, exception);
  99 + } catch (Throwable ex) {
  100 + log.error("[log][记录操作日志时,发生异常,其中参数是 joinPoint({}) operateLog({}) apiOperation({}) result({}) exception({}) ]",
  101 + joinPoint, operateLog, apiOperation, result, exception, ex);
  102 + }
  103 + }
  104 +
  105 + private void log0(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation,
  106 + Date startTime, Object result, Throwable exception) {
  107 + SysOperateLogCreateReqVO operateLogVO = new SysOperateLogCreateReqVO();
  108 + // 补全通用字段
  109 + operateLogVO.setTraceId(TracerUtils.getTraceId());
  110 + operateLogVO.setStartTime(startTime);
  111 + // 补充用户信息
  112 + fillUserFields(operateLogVO);
  113 + // 补全模块信息
  114 + fillModuleFields(operateLogVO, joinPoint, operateLog, apiOperation);
  115 + // 补全请求信息
  116 + fillRequestFields(operateLogVO);
  117 + // 补全方法信息
  118 + fillMethodFields(operateLogVO, joinPoint, operateLog, startTime, result, exception);
  119 +
  120 + // 异步记录日志
  121 + operateLogFrameworkService.createOperateLogAsync(operateLogVO);
  122 + }
  123 +
  124 + private static void fillUserFields(SysOperateLogCreateReqVO operateLogVO) {
  125 + operateLogVO.setUserId(SecurityUtils.getLoginUserId());
  126 + }
  127 +
  128 + private static void fillModuleFields(SysOperateLogCreateReqVO operateLogVO,
  129 + ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation) {
  130 + // module 属性
  131 + if (operateLog != null) {
  132 + operateLogVO.setModule(operateLog.module());
  133 + }
  134 + if (StrUtil.isEmpty(operateLogVO.getModule())) {
  135 + Api api = getClassAnnotation(joinPoint, Api.class);
  136 + if (api != null) {
  137 + operateLogVO.setModule(Optional.of(api.value())
  138 + .orElse(ArrayUtil.isEmpty(api.tags()) ? api.tags()[0] : null));
  139 + }
  140 + }
  141 + // name 属性
  142 + if (operateLog != null) {
  143 + operateLogVO.setName(operateLog.name());
  144 + }
  145 + if (StrUtil.isEmpty(operateLogVO.getName()) && apiOperation != null) {
  146 + operateLogVO.setName(apiOperation.value());
  147 + }
  148 + // type 属性
  149 + if (operateLog != null && ArrayUtil.isNotEmpty(operateLog.type())) {
  150 + operateLogVO.setType(operateLog.type()[0].getType());
  151 + }
  152 + if (operateLogVO.getType() == null) {
  153 + RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
  154 + SysOperateLogTypeEnum operateLogType = convertOperateLogType(requestMethod);
  155 + operateLogVO.setType(operateLogType != null ? operateLogType.getType() : null);
  156 + }
  157 + }
  158 +
  159 + private static void fillRequestFields(SysOperateLogCreateReqVO operateLogVO) {
  160 + // 获得 Request 对象
  161 + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  162 + if (!(requestAttributes instanceof ServletRequestAttributes)) {
  163 + return;
  164 + }
  165 + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
  166 + // 补全请求信息
  167 + operateLogVO.setRequestMethod(request.getMethod());
  168 + operateLogVO.setRequestUrl(request.getRequestURI());
  169 + operateLogVO.setUserIp(ServletUtil.getClientIP(request));
  170 + operateLogVO.setUserAgent(ServletUtils.getUserAgent(request));
  171 + }
  172 +
  173 + private static void fillMethodFields(SysOperateLogCreateReqVO operateLogVO,
  174 + ProceedingJoinPoint joinPoint, OperateLog operateLog,
  175 + Date startTime, Object result, Throwable exception) {
  176 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  177 + operateLogVO.setJavaMethod(methodSignature.toString());
  178 + if (operateLog == null || operateLog.logArgs()) {
  179 + operateLogVO.setJavaMethodArgs(obtainMethodArgs(joinPoint));
  180 + }
  181 + if (operateLog == null || operateLog.logResultData()) {
  182 + operateLogVO.setResultData(obtainResultData(result));
  183 + }
  184 + operateLogVO.setDuration((int) (System.currentTimeMillis() - startTime.getTime()));
  185 + // (正常)处理 resultCode 和 resultMsg 字段
  186 + if (result != null) {
  187 + if (result instanceof CommonResult) {
  188 + CommonResult<?> commonResult = (CommonResult<?>) result;
  189 + operateLogVO.setResultCode(commonResult.getCode());
  190 + operateLogVO.setResultMsg(commonResult.getMsg());
  191 + } else {
  192 + operateLogVO.setResultCode(SUCCESS.getCode());
  193 + }
  194 + }
  195 + // (异常)处理 resultCode 和 resultMsg 字段
  196 + if (exception != null) {
  197 + operateLogVO.setResultCode(INTERNAL_SERVER_ERROR.getCode());
  198 + operateLogVO.setResultMsg(ExceptionUtil.getRootCauseMessage(exception));
  199 + }
  200 + }
  201 +
  202 + private static boolean isLogEnable(ProceedingJoinPoint joinPoint, OperateLog operateLog) {
  203 + // 有 @OperateLog 注解的情况下
  204 + if (operateLog != null) {
  205 + return operateLog.enable();
  206 + }
  207 + // 没有 @ApiOperation 注解的情况下,只记录 POST、PUT、DELETE 的情况
  208 + return obtainFirstLogRequestMethod(obtainRequestMethod(joinPoint)) != null;
  209 + }
  210 +
  211 + private static RequestMethod obtainFirstLogRequestMethod(RequestMethod[] requestMethods) {
  212 + if (ArrayUtil.isEmpty(requestMethods)) {
  213 + return null;
  214 + }
  215 + return Arrays.stream(requestMethods).filter(requestMethod ->
  216 + requestMethod == RequestMethod.POST
  217 + || requestMethod == RequestMethod.PUT
  218 + || requestMethod == RequestMethod.DELETE)
  219 + .findFirst().orElse(null);
  220 + }
  221 +
  222 + private static RequestMethod obtainFirstMatchRequestMethod(RequestMethod[] requestMethods) {
  223 + if (ArrayUtil.isEmpty(requestMethods)) {
  224 + return null;
  225 + }
  226 + // 优先,匹配最优的 POST、PUT、DELETE
  227 + RequestMethod result = obtainFirstLogRequestMethod(requestMethods);
  228 + if (result != null) {
  229 + return result;
  230 + }
  231 + // 然后,匹配次优的 GET
  232 + result = Arrays.stream(requestMethods).filter(requestMethod -> requestMethod == RequestMethod.GET)
  233 + .findFirst().orElse(null);
  234 + if (result != null) {
  235 + return result;
  236 + }
  237 + // 兜底,获得第一个
  238 + return requestMethods[0];
  239 + }
  240 +
  241 + private static SysOperateLogTypeEnum convertOperateLogType(RequestMethod requestMethod) {
  242 + if (requestMethod == null) {
  243 + return null;
  244 + }
  245 + switch (requestMethod) {
  246 + case GET:
  247 + return SysOperateLogTypeEnum.GET;
  248 + case POST:
  249 + return SysOperateLogTypeEnum.CREATE;
  250 + case PUT:
  251 + return SysOperateLogTypeEnum.UPDATE;
  252 + case DELETE:
  253 + return SysOperateLogTypeEnum.DELETE;
  254 + default:
  255 + return SysOperateLogTypeEnum.OTHER;
  256 + }
  257 + }
  258 +
  259 + private static RequestMethod[] obtainRequestMethod(ProceedingJoinPoint joinPoint) {
  260 + RequestMapping requestMapping = AnnotationUtils.getAnnotation( // 使用 Spring 的工具类,可以处理 @RequestMapping 别名注解
  261 + ((MethodSignature) joinPoint.getSignature()).getMethod(), RequestMapping.class);
  262 + return requestMapping != null ? requestMapping.method() : new RequestMethod[]{};
  263 + }
  264 +
  265 + @SuppressWarnings("SameParameterValue")
  266 + private static <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
  267 + return ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(annotationClass);
  268 + }
  269 +
  270 + @SuppressWarnings("SameParameterValue")
  271 + private static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
  272 + return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
  273 + }
  274 +
  275 + private static Map<String, Object> obtainMethodArgs(ProceedingJoinPoint joinPoint) {
  276 + // TODO 提升:参数脱敏和忽略
  277 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  278 + String[] argNames = methodSignature.getParameterNames();
  279 + Object[] argValues = joinPoint.getArgs();
  280 + // 拼接参数
  281 + Map<String, Object> args = Maps.newHashMapWithExpectedSize(argValues.length);
  282 + for (int i = 0; i < argNames.length; i++) {
  283 + String argName = argNames[i];
  284 + Object argValue = argValues[i];
  285 + // 被忽略时,标记为 ignore 字符串,避免和 null 混在一起
  286 + args.put(argName, !isIgnoreArgs(argValue) ? argValue : "[ignore]");
  287 + }
  288 + return args;
  289 + }
  290 +
  291 + private static String obtainResultData(Object result) {
  292 + // TODO 提升:结果脱敏和忽略
  293 + if (result instanceof CommonResult) {
  294 + result = ((CommonResult<?>) result).getData();
  295 + }
  296 + return JSON.toJSONString(result);
  297 + }
  298 +
  299 + private static boolean isIgnoreArgs(Object object) {
  300 + Class<?> clazz = object.getClass();
  301 + // 处理数组的情况
  302 + if (clazz.isArray()) {
  303 + return IntStream.range(0, Array.getLength(object))
  304 + .anyMatch(index -> isIgnoreArgs(Array.get(object, index)));
  305 + }
  306 + // 递归,处理数组、Collection、Map 的情况
  307 + if (Collection.class.isAssignableFrom(clazz)) {
  308 + return ((Collection<?>) object).stream()
  309 + .anyMatch((Predicate<Object>) o -> isIgnoreArgs(object));
  310 + }
  311 + if (Map.class.isAssignableFrom(clazz)) {
  312 + return isIgnoreArgs(((Map<?, ?>) object).values());
  313 + }
  314 + // obj
  315 + return object instanceof MultipartFile
  316 + || object instanceof HttpServletRequest
  317 + || object instanceof HttpServletResponse;
  318 + }
  319 +
  320 +}
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/package-info.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.logger.operatelog.core;
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/service/OperateLogFrameworkService.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.logger.operatelog.core.service;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO;
  4 +
  5 +public interface OperateLogFrameworkService {
  6 +
  7 + /**
  8 + * 要不记录操作日志
  9 + *
  10 + * @param reqVO 操作日志请求
  11 + */
  12 + void createOperateLogAsync(SysOperateLogCreateReqVO reqVO);
  13 +
  14 +}
src/main/java/cn/iocoder/dashboard/framework/logger/package-info.java
@@ -5,6 +5,6 @@ @@ -5,6 +5,6 @@
5 * 2. API 日志:包含两类 5 * 2. API 日志:包含两类
6 * 2.1 API 访问日志:记录用户访问 API 的访问日志,定期归档历史日志。 6 * 2.1 API 访问日志:记录用户访问 API 的访问日志,定期归档历史日志。
7 * 2.2 API 异常日志:记录用户访问 API 的系统异常,方便日常排查问题与告警。 7 * 2.2 API 异常日志:记录用户访问 API 的系统异常,方便日常排查问题与告警。
8 - * 3. 通用 Logger 日志:将 {@link org.slf4j.Logger} 打印的日志,只满足大于等于 {@link org.slf4j.event.Level} 进行持久化,可以理解成简易的“日志中心”。 8 + * 3. 通用 Logger 日志:将 {@link org.slf4j.Logger} 打印的日志,只满足大于等于配置的 {@link org.slf4j.event.Level} 进行持久化,可以理解成简易的“日志中心”。
9 */ 9 */
10 package cn.iocoder.dashboard.framework.logger; 10 package cn.iocoder.dashboard.framework.logger;
src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.framework.tracer.core.util;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import org.apache.skywalking.apm.toolkit.trace.TraceContext;
  5 +
  6 +import java.util.UUID;
  7 +
  8 +/**
  9 + * 链路追踪工具类
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public class TracerUtils {
  14 +
  15 + /**
  16 + * 获得链路追踪编号
  17 + *
  18 + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
  19 + *
  20 + * 默认情况下,我们使用 Apache SkyWalking 的 traceId 作为链路追踪编号。当然,可能会存在并未引入 Skywalking 的情况,此时使用 UUID 。
  21 + *
  22 + * @return 链路追踪编号
  23 + */
  24 + public static String getTraceId() {
  25 + // 通过 SkyWalking 获取链路编号
  26 + try {
  27 + String traceId = TraceContext.traceId();
  28 + if (StrUtil.isNotBlank(traceId)) {
  29 + return traceId;
  30 + }
  31 + } catch (Throwable ignore) {}
  32 + // TODO 芋艿 多次调用会问题
  33 + return UUID.randomUUID().toString();
  34 + }
  35 +
  36 +}
src/main/java/cn/iocoder/dashboard/framework/tracer/package-info.java 0 → 100644
  1 +/**
  2 + * 链路追踪
  3 + *
  4 + * 主要目的,是生成全局的链路追踪编号
  5 + */
  6 +package cn.iocoder.dashboard.framework.tracer;
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java
@@ -2,6 +2,7 @@ package cn.iocoder.dashboard.modules.system.controller.auth; @@ -2,6 +2,7 @@ package cn.iocoder.dashboard.modules.system.controller.auth;
2 2
3 import cn.iocoder.dashboard.common.enums.CommonStatusEnum; 3 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
4 import cn.iocoder.dashboard.common.pojo.CommonResult; 4 import cn.iocoder.dashboard.common.pojo.CommonResult;
  5 +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
5 import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginReqVO; 6 import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginReqVO;
6 import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginRespVO; 7 import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginRespVO;
7 import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthMenuRespVO; 8 import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthMenuRespVO;
@@ -28,7 +29,7 @@ import static cn.iocoder.dashboard.common.pojo.CommonResult.success; @@ -28,7 +29,7 @@ import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
28 import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserId; 29 import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserId;
29 import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserRoleIds; 30 import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserRoleIds;
30 31
31 -@Api(tags = "认证 API") 32 +@Api("认证 API")
32 @RestController 33 @RestController
33 @RequestMapping("/") 34 @RequestMapping("/")
34 public class SysAuthController { 35 public class SysAuthController {
@@ -44,6 +45,7 @@ public class SysAuthController { @@ -44,6 +45,7 @@ public class SysAuthController {
44 45
45 @ApiOperation("使用账号密码登录") 46 @ApiOperation("使用账号密码登录")
46 @PostMapping("/login") 47 @PostMapping("/login")
  48 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
47 public CommonResult<SysAuthLoginRespVO> login(@RequestBody @Valid SysAuthLoginReqVO reqVO) { 49 public CommonResult<SysAuthLoginRespVO> login(@RequestBody @Valid SysAuthLoginReqVO reqVO) {
48 String token = authService.login(reqVO.getUsername(), reqVO.getPassword(), reqVO.getUuid(), reqVO.getCode()); 50 String token = authService.login(reqVO.getUsername(), reqVO.getPassword(), reqVO.getUuid(), reqVO.getCode());
49 // 返回结果 51 // 返回结果
@@ -52,6 +54,7 @@ public class SysAuthController { @@ -52,6 +54,7 @@ public class SysAuthController {
52 54
53 @ApiOperation("获取登陆用户的权限信息") 55 @ApiOperation("获取登陆用户的权限信息")
54 @GetMapping("/get-permission-info") 56 @GetMapping("/get-permission-info")
  57 + @OperateLog
55 public CommonResult<SysAuthPermissionInfoRespVO> getPermissionInfo() { 58 public CommonResult<SysAuthPermissionInfoRespVO> getPermissionInfo() {
56 // 获得用户信息 59 // 获得用户信息
57 SysUserDO user = userService.getUser(getLoginUserId()); 60 SysUserDO user = userService.getUser(getLoginUserId());
src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/package-info.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.logger;
src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogBaseVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.logger.vo;
  2 +
  3 +import io.swagger.annotations.ApiModelProperty;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.constraints.NotEmpty;
  7 +import javax.validation.constraints.NotNull;
  8 +import java.util.Date;
  9 +import java.util.Map;
  10 +
  11 +/**
  12 + * 操作日志 Base VO,提供给添加、修改、详细的子 VO 使用
  13 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  14 + */
  15 +@Data
  16 +public class SysOperateLogBaseVO {
  17 +
  18 + @ApiModelProperty(value = "链路追踪编号", required = true, example = "89aca178-a370-411c-ae02-3f0d672be4ab")
  19 + @NotEmpty(message = "链路追踪编号不能为空")
  20 + private String traceId;
  21 +
  22 + @ApiModelProperty(value = "用户编号", required = true, example = "1024")
  23 + @NotNull(message = "用户编号不能为空")
  24 + private Long userId;
  25 +
  26 + @ApiModelProperty(value = "操作模块", required = true, example = "订单")
  27 + @NotEmpty(message = "操作模块不能为空")
  28 + private String module;
  29 +
  30 + @ApiModelProperty(value = "操作名", required = true, example = "创建订单")
  31 + @NotEmpty(message = "操作名")
  32 + private String name;
  33 +
  34 + @ApiModelProperty(value = "操作分类", required = true, example = "操作分类", notes = "参见 SysOperateLogTypeEnum 枚举类")
  35 + @NotNull(message = "操作分类不能为空")
  36 + private Integer type;
  37 +
  38 + @ApiModelProperty(value = "请求方法名", required = true, example = "GET")
  39 + @NotEmpty(message = "请求方法名不能为空")
  40 + private String requestMethod;
  41 +
  42 + @ApiModelProperty(value = "请求地址", required = true, example = "/xxx/yyy")
  43 + @NotEmpty(message = "请求地址不能为空")
  44 + private String requestUrl;
  45 +
  46 + @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1")
  47 + @NotEmpty(message = "用户 IP 不能为空")
  48 + private String userIp;
  49 +
  50 + @ApiModelProperty(value = "浏览器 UserAgent", required = true, example = "Mozilla/5.0")
  51 + @NotEmpty(message = "浏览器 UserAgent 不能为空")
  52 + private String userAgent;
  53 +
  54 + @ApiModelProperty(value = "Java 方法名", required = true, example = "cn.iocoder.dashboard.UserController.save(...)")
  55 + @NotEmpty(message = "Java 方法名不能为空")
  56 + private String javaMethod;
  57 +
  58 + @ApiModelProperty(value = "Java 方法的参数")
  59 + private Map<String, Object> javaMethodArgs;
  60 +
  61 + @ApiModelProperty(value = "开始时间", required = true)
  62 + @NotNull(message = "开始时间不能为空")
  63 + private Date startTime;
  64 +
  65 + @ApiModelProperty(value = "执行时长,单位:毫秒", required = true)
  66 + @NotNull(message = "执行时长不能为空")
  67 + private Integer duration;
  68 +
  69 + @ApiModelProperty(value = "结果码", required = true)
  70 + @NotNull(message = "结果码不能为空")
  71 + private Integer resultCode;
  72 +
  73 + @ApiModelProperty(value = "结果提示")
  74 + private String resultMsg;
  75 +
  76 + @ApiModelProperty(value = "结果数据")
  77 + private String resultData;
  78 +
  79 +}
src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.logger.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +
  8 +@ApiModel(value = "操作日志创建 Request VO", description = "暂时提供给前端,仅仅后端切面记录操作日志时,进行使用")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +@ToString(callSuper = true)
  12 +public class SysOperateLogCreateReqVO extends SysOperateLogBaseVO {
  13 +}
src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/package-info.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.controller.logger.vo;
src/main/java/cn/iocoder/dashboard/modules/system/convert/logger/SysOperateLogConvert.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.convert.logger;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO;
  4 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO;
  5 +import org.mapstruct.Mapper;
  6 +import org.mapstruct.factory.Mappers;
  7 +
  8 +@Mapper
  9 +public interface SysOperateLogConvert {
  10 +
  11 + SysOperateLogConvert INSTANCE = Mappers.getMapper(SysOperateLogConvert.class);
  12 +
  13 + SysOperateLogDO convert(SysOperateLogCreateReqVO bean);
  14 +
  15 +}
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/logger/SysOperateLogMapper.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.dal.mysql.dao.logger;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO;
  4 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +@Mapper
  8 +public interface SysOperateLogMapper extends BaseMapper<SysOperateLogDO> {
  9 +}
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/logger/SysOperateLogDO.java
@@ -36,7 +36,7 @@ public class SysOperateLogDO extends BaseDO { @@ -36,7 +36,7 @@ public class SysOperateLogDO extends BaseDO {
36 */ 36 */
37 private String traceId; 37 private String traceId;
38 /** 38 /**
39 - * 操作人 39 + * 用户编号
40 * 40 *
41 * {@link SysUserDO#getId()} 41 * {@link SysUserDO#getId()}
42 */ 42 */
@@ -69,7 +69,7 @@ public class SysOperateLogDO extends BaseDO { @@ -69,7 +69,7 @@ public class SysOperateLogDO extends BaseDO {
69 * TODO 预留字段 69 * TODO 预留字段
70 */ 70 */
71 @TableField(typeHandler = FastjsonTypeHandler.class) 71 @TableField(typeHandler = FastjsonTypeHandler.class)
72 - private Map<String, Object> ext; 72 + private Map<String, Object> exts;
73 73
74 /** 74 /**
75 * 请求方法名 75 * 请求方法名
@@ -80,11 +80,6 @@ public class SysOperateLogDO extends BaseDO { @@ -80,11 +80,6 @@ public class SysOperateLogDO extends BaseDO {
80 */ 80 */
81 private String requestUrl; 81 private String requestUrl;
82 /** 82 /**
83 - * 请求参数  
84 - */  
85 - @TableField(typeHandler = FastjsonTypeHandler.class)  
86 - private Map<String, Object> requestParams;  
87 - /**  
88 * 用户 IP 83 * 用户 IP
89 */ 84 */
90 private String userIp; 85 private String userIp;
@@ -98,6 +93,11 @@ public class SysOperateLogDO extends BaseDO { @@ -98,6 +93,11 @@ public class SysOperateLogDO extends BaseDO {
98 */ 93 */
99 private String javaMethod; 94 private String javaMethod;
100 /** 95 /**
  96 + * Java 方法的参数
  97 + */
  98 + @TableField(typeHandler = FastjsonTypeHandler.class)
  99 + private Map<String, Object> javaMethodArgs;
  100 + /**
101 * 开始时间 101 * 开始时间
102 */ 102 */
103 private Date startTime; 103 private Date startTime;
@@ -119,8 +119,9 @@ public class SysOperateLogDO extends BaseDO { @@ -119,8 +119,9 @@ public class SysOperateLogDO extends BaseDO {
119 private String resultMsg; 119 private String resultMsg;
120 /** 120 /**
121 * 结果数据 121 * 结果数据
  122 + *
  123 + * 如果是对象,则使用 JSON 格式化
122 */ 124 */
123 - @TableField(typeHandler = FastjsonTypeHandler.class)  
124 - private Map<String, Object> resultData; 125 + private String resultData;
125 126
126 } 127 }
src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysOperateLogTypeEnum.java
1 package cn.iocoder.dashboard.modules.system.enums.logger; 1 package cn.iocoder.dashboard.modules.system.enums.logger;
2 2
  3 +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog;
3 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
4 import lombok.Getter; 5 import lombok.Getter;
5 6
@@ -13,25 +14,32 @@ import lombok.Getter; @@ -13,25 +14,32 @@ import lombok.Getter;
13 public enum SysOperateLogTypeEnum { 14 public enum SysOperateLogTypeEnum {
14 15
15 /** 16 /**
  17 + * 查询
  18 + *
  19 + * 绝大多数情况下,不会记录查询动作,因为过于大量显得没有意义。
  20 + * 在有需要的时候,通过声明 {@link OperateLog} 注解来记录
  21 + */
  22 + GET(1),
  23 + /**
16 * 新增 24 * 新增
17 */ 25 */
18 - CREATE(1), 26 + CREATE(2),
19 /** 27 /**
20 * 修改 28 * 修改
21 */ 29 */
22 - UPDATE(2), 30 + UPDATE(3),
23 /** 31 /**
24 * 删除 32 * 删除
25 */ 33 */
26 - DELETE(3), 34 + DELETE(4),
27 /** 35 /**
28 * 导出 36 * 导出
29 */ 37 */
30 - EXPORT(4), 38 + EXPORT(5),
31 /** 39 /**
32 * 导入 40 * 导入
33 */ 41 */
34 - IMPORT(5), 42 + IMPORT(6),
35 /** 43 /**
36 * 其它 44 * 其它
37 * 45 *
src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogService.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.logger;
  2 +
  3 +import cn.iocoder.dashboard.framework.logger.operatelog.core.service.OperateLogFrameworkService;
  4 +
  5 +/**
  6 + * 操作日志 Service 接口
  7 + */
  8 +public interface SysOperateLogService extends OperateLogFrameworkService {
  9 +}
src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysOperateLogServiceImpl.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.logger.impl;
  2 +
  3 +import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO;
  4 +import cn.iocoder.dashboard.modules.system.convert.logger.SysOperateLogConvert;
  5 +import cn.iocoder.dashboard.modules.system.dal.mysql.dao.logger.SysOperateLogMapper;
  6 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO;
  7 +import cn.iocoder.dashboard.modules.system.service.logger.SysOperateLogService;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.scheduling.annotation.Async;
  10 +import org.springframework.stereotype.Service;
  11 +
  12 +import javax.annotation.Resource;
  13 +
  14 +@Service
  15 +@Slf4j
  16 +public class SysOperateLogServiceImpl implements SysOperateLogService {
  17 +
  18 + @Resource
  19 + private SysOperateLogMapper operateLogMapper;
  20 +
  21 + @Override
  22 + @Async
  23 + public void createOperateLogAsync(SysOperateLogCreateReqVO reqVO) {
  24 + SysOperateLogDO logDO = SysOperateLogConvert.INSTANCE.convert(reqVO);
  25 + try {
  26 + operateLogMapper.insert(logDO);
  27 + } catch (Throwable throwable) {
  28 + // 仅仅打印日志,不对外抛出。原因是,还是要保留现场数据。
  29 + log.error("[createOperateLogAsync][记录操作日志异常,日志为 ({})]", reqVO, throwable);
  30 + }
  31 + }
  32 +
  33 +}
src/main/java/cn/iocoder/dashboard/util/json/JSONUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.util.json;
  2 +
  3 +/**
  4 + * JSON 工具类
  5 + *
  6 + * @author 芋道源码
  7 + */
  8 +public class JSONUtils {
  9 +
  10 +// public static Map<String, Object> toJSONMap(Object javaObject) {
  11 +// return (Map<String, Object>) JSON.toJSON(javaObject);
  12 +// }
  13 +//
  14 +// public static void main(String[] args) {
  15 +// SysDictTypeCreateReqVO createReqVO = new SysDictTypeCreateReqVO();
  16 +// createReqVO.setType("1");
  17 +// createReqVO.setRemark("2");
  18 +// System.out.println(toJSONMap(createReqVO));
  19 +// }
  20 +
  21 +}
src/main/java/cn/iocoder/dashboard/util/servlet/ServletUtils.java
@@ -5,6 +5,7 @@ import cn.hutool.extra.servlet.ServletUtil; @@ -5,6 +5,7 @@ import cn.hutool.extra.servlet.ServletUtil;
5 import com.alibaba.fastjson.JSON; 5 import com.alibaba.fastjson.JSON;
6 import org.springframework.http.MediaType; 6 import org.springframework.http.MediaType;
7 7
  8 +import javax.servlet.http.HttpServletRequest;
8 import javax.servlet.http.HttpServletResponse; 9 import javax.servlet.http.HttpServletResponse;
9 import java.io.IOException; 10 import java.io.IOException;
10 import java.net.URLEncoder; 11 import java.net.URLEncoder;
@@ -44,4 +45,13 @@ public class ServletUtils { @@ -44,4 +45,13 @@ public class ServletUtils {
44 IoUtil.write(response.getOutputStream(), false, content); 45 IoUtil.write(response.getOutputStream(), false, content);
45 } 46 }
46 47
  48 + /**
  49 + * @param request 请求
  50 + * @return ua
  51 + */
  52 + public static String getUserAgent(HttpServletRequest request) {
  53 + String ua = request.getHeader("User-Agent");
  54 + return ua != null ? ua : "";
  55 + }
  56 +
47 } 57 }