Commit ec8f181f2f5e970613b56eb1da6d65d43e08acba
1 parent
b6457c04
完成操作日志的记录
Showing
29 changed files
with
691 additions
and
261 deletions
pom.xml
| @@ -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 | -<!-- <!–io常用工具类 –>--> | ||
| 92 | -<!-- <dependency>--> | ||
| 93 | -<!-- <groupId>commons-io</groupId>--> | ||
| 94 | -<!-- <artifactId>commons-io</artifactId>--> | ||
| 95 | -<!-- <version>${commons.io.version}</version>--> | ||
| 96 | -<!-- </dependency>--> | ||
| 97 | - | ||
| 98 | -<!-- <!–文件上传工具类 –>--> | ||
| 99 | -<!-- <dependency>--> | ||
| 100 | -<!-- <groupId>commons-fileupload</groupId>--> | ||
| 101 | -<!-- <artifactId>commons-fileupload</artifactId>--> | ||
| 102 | -<!-- <version>${commons.fileupload.version}</version>--> | ||
| 103 | -<!-- </dependency>--> | ||
| 104 | - | ||
| 105 | <!-- <!–velocity代码生成使用模板 –>--> | 93 | <!-- <!–velocity代码生成使用模板 –>--> |
| 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
src/main/java/cn/iocoder/dashboard/framework/async/package-info.java
0 → 100644
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
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
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 | } |