Commit ec8f181f2f5e970613b56eb1da6d65d43e08acba
1 parent
b6457c04
完成操作日志的记录
Showing
29 changed files
with
691 additions
and
261 deletions
pom.xml
| ... | ... | @@ -49,6 +49,8 @@ |
| 49 | 49 | <druid.version>1.2.4</druid.version> |
| 50 | 50 | <mybatis-plus.version>3.4.1</mybatis-plus.version> |
| 51 | 51 | <redisson.version>3.13.6</redisson.version> |
| 52 | + <!-- 监控相关 --> | |
| 53 | + <skywalking.version>8.3.0</skywalking.version> | |
| 52 | 54 | <!-- 工具类相关 --> |
| 53 | 55 | <lombok.version>1.16.14</lombok.version> |
| 54 | 56 | <mapstruct.version>1.4.1.Final</mapstruct.version> |
| ... | ... | @@ -88,20 +90,6 @@ |
| 88 | 90 | <!-- <version>${jna.version}</version>--> |
| 89 | 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 | 93 | <!-- <!–velocity代码生成使用模板 –>--> |
| 106 | 94 | <!-- <dependency>--> |
| 107 | 95 | <!-- <groupId>org.apache.velocity</groupId>--> |
| ... | ... | @@ -129,6 +117,11 @@ |
| 129 | 117 | <optional>true</optional> |
| 130 | 118 | </dependency> |
| 131 | 119 | |
| 120 | + <dependency> | |
| 121 | + <groupId>org.springframework.boot</groupId> | |
| 122 | + <artifactId>spring-boot-starter-aop</artifactId> | |
| 123 | + </dependency> | |
| 124 | + | |
| 132 | 125 | <!-- Web 相关 --> |
| 133 | 126 | <dependency> |
| 134 | 127 | <groupId>org.springframework.boot</groupId> |
| ... | ... | @@ -174,6 +167,13 @@ |
| 174 | 167 | <version>${redisson.version}</version> |
| 175 | 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 | 178 | <dependency> |
| 179 | 179 | <groupId>org.projectlombok</groupId> | ... | ... |
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
| ... | ... | @@ -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 | 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
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 | 5 | * 2. API 日志:包含两类 |
| 6 | 6 | * 2.1 API 访问日志:记录用户访问 API 的访问日志,定期归档历史日志。 |
| 7 | 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 | 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 | 2 | |
| 3 | 3 | import cn.iocoder.dashboard.common.enums.CommonStatusEnum; |
| 4 | 4 | import cn.iocoder.dashboard.common.pojo.CommonResult; |
| 5 | +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog; | |
| 5 | 6 | import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginReqVO; |
| 6 | 7 | import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginRespVO; |
| 7 | 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 | 29 | import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserId; |
| 29 | 30 | import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserRoleIds; |
| 30 | 31 | |
| 31 | -@Api(tags = "认证 API") | |
| 32 | +@Api("认证 API") | |
| 32 | 33 | @RestController |
| 33 | 34 | @RequestMapping("/") |
| 34 | 35 | public class SysAuthController { |
| ... | ... | @@ -44,6 +45,7 @@ public class SysAuthController { |
| 44 | 45 | |
| 45 | 46 | @ApiOperation("使用账号密码登录") |
| 46 | 47 | @PostMapping("/login") |
| 48 | + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | |
| 47 | 49 | public CommonResult<SysAuthLoginRespVO> login(@RequestBody @Valid SysAuthLoginReqVO reqVO) { |
| 48 | 50 | String token = authService.login(reqVO.getUsername(), reqVO.getPassword(), reqVO.getUuid(), reqVO.getCode()); |
| 49 | 51 | // 返回结果 |
| ... | ... | @@ -52,6 +54,7 @@ public class SysAuthController { |
| 52 | 54 | |
| 53 | 55 | @ApiOperation("获取登陆用户的权限信息") |
| 54 | 56 | @GetMapping("/get-permission-info") |
| 57 | + @OperateLog | |
| 55 | 58 | public CommonResult<SysAuthPermissionInfoRespVO> getPermissionInfo() { |
| 56 | 59 | // 获得用户信息 |
| 57 | 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 | 36 | */ |
| 37 | 37 | private String traceId; |
| 38 | 38 | /** |
| 39 | - * 操作人 | |
| 39 | + * 用户编号 | |
| 40 | 40 | * |
| 41 | 41 | * {@link SysUserDO#getId()} |
| 42 | 42 | */ |
| ... | ... | @@ -69,7 +69,7 @@ public class SysOperateLogDO extends BaseDO { |
| 69 | 69 | * TODO 预留字段 |
| 70 | 70 | */ |
| 71 | 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 | 80 | */ |
| 81 | 81 | private String requestUrl; |
| 82 | 82 | /** |
| 83 | - * 请求参数 | |
| 84 | - */ | |
| 85 | - @TableField(typeHandler = FastjsonTypeHandler.class) | |
| 86 | - private Map<String, Object> requestParams; | |
| 87 | - /** | |
| 88 | 83 | * 用户 IP |
| 89 | 84 | */ |
| 90 | 85 | private String userIp; |
| ... | ... | @@ -98,6 +93,11 @@ public class SysOperateLogDO extends BaseDO { |
| 98 | 93 | */ |
| 99 | 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 | 103 | private Date startTime; |
| ... | ... | @@ -119,8 +119,9 @@ public class SysOperateLogDO extends BaseDO { |
| 119 | 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 | 1 | package cn.iocoder.dashboard.modules.system.enums.logger; |
| 2 | 2 | |
| 3 | +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog; | |
| 3 | 4 | import lombok.AllArgsConstructor; |
| 4 | 5 | import lombok.Getter; |
| 5 | 6 | |
| ... | ... | @@ -13,25 +14,32 @@ import lombok.Getter; |
| 13 | 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 | 5 | import com.alibaba.fastjson.JSON; |
| 6 | 6 | import org.springframework.http.MediaType; |
| 7 | 7 | |
| 8 | +import javax.servlet.http.HttpServletRequest; | |
| 8 | 9 | import javax.servlet.http.HttpServletResponse; |
| 9 | 10 | import java.io.IOException; |
| 10 | 11 | import java.net.URLEncoder; |
| ... | ... | @@ -44,4 +45,13 @@ public class ServletUtils { |
| 44 | 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 | } | ... | ... |