From ec8f181f2f5e970613b56eb1da6d65d43e08acba Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 17 Jan 2021 00:28:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=93=8D=E4=BD=9C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E7=9A=84=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 28 +- .../ruoyi/framework/aspectj/LogAspect.java | 52 --- .../ruoyi/framework/manager/AsyncManager.java | 53 --- .../framework/manager/ShutdownManager.java | 34 -- .../manager/factory/AsyncFactory.java | 92 ----- .../async/config/AsyncConfiguration.java | 9 + .../framework/async/package-info.java | 4 + .../《芋道 Spring Boot 异步任务入门》.md | 1 + .../config/OperateLogConfiguration.java | 15 + .../core/annotations/OperateLog.java | 52 +++ .../operatelog/core/aop/OperateLogAspect.java | 320 ++++++++++++++++++ .../logger/operatelog/core/package-info.java | 1 + .../service/OperateLogFrameworkService.java | 14 + .../framework/logger/package-info.java | 2 +- .../tracer/core/util/TracerUtils.java | 36 ++ .../framework/tracer/package-info.java | 6 + .../controller/auth/SysAuthController.java | 5 +- .../controller/logger/package-info.java | 1 + .../logger/vo/SysOperateLogBaseVO.java | 79 +++++ .../logger/vo/SysOperateLogCreateReqVO.java | 13 + .../controller/logger/vo/package-info.java | 1 + .../convert/logger/SysOperateLogConvert.java | 15 + .../mysql/dao/logger/SysOperateLogMapper.java | 9 + .../dataobject/logger/SysOperateLogDO.java | 19 +- .../enums/logger/SysOperateLogTypeEnum.java | 18 +- .../service/logger/SysOperateLogService.java | 9 + .../logger/impl/SysOperateLogServiceImpl.java | 33 ++ .../dashboard/util/json/JSONUtils.java | 21 ++ .../dashboard/util/servlet/ServletUtils.java | 10 + 29 files changed, 691 insertions(+), 261 deletions(-) delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java delete mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/async/config/AsyncConfiguration.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/async/package-info.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/async/《芋道 Spring Boot 异步任务入门》.md create mode 100644 src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/config/OperateLogConfiguration.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/annotations/OperateLog.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/package-info.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/service/OperateLogFrameworkService.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java create mode 100644 src/main/java/cn/iocoder/dashboard/framework/tracer/package-info.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/package-info.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogBaseVO.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogCreateReqVO.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/package-info.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/convert/logger/SysOperateLogConvert.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/logger/SysOperateLogMapper.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogService.java create mode 100644 src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysOperateLogServiceImpl.java create mode 100644 src/main/java/cn/iocoder/dashboard/util/json/JSONUtils.java diff --git a/pom.xml b/pom.xml index 01c8d42be..20ede4f7e 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,8 @@ 1.2.4 3.4.1 3.13.6 + + 8.3.0 1.16.14 1.4.1.Final @@ -88,20 +90,6 @@ - - - - - - - - - - - - - - @@ -129,6 +117,11 @@ true + + org.springframework.boot + spring-boot-starter-aop + + org.springframework.boot @@ -174,6 +167,13 @@ ${redisson.version} + + + org.apache.skywalking + apm-toolkit-trace + ${skywalking.version} + + org.projectlombok diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java index 27d2ecf17..2c58eb9be 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java @@ -116,58 +116,6 @@ public class LogAspect { } } - /** - * 获取注解中对方法的描述信息 用于Controller层注解 - * - * @param log 日志 - * @param operLog 操作日志 - * @throws Exception - */ - public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception { - // 设置action动作 - operLog.setBusinessType(log.businessType().ordinal()); - // 设置标题 - operLog.setTitle(log.title()); - // 设置操作人类别 - operLog.setOperatorType(log.operatorType().ordinal()); - // 是否需要保存request,参数和值 - if (log.isSaveRequestData()) { - // 获取参数的信息,传入到数据库中。 - setRequestValue(joinPoint, operLog); - } - } - - /** - * 获取请求的参数,放到log中 - * - * @param operLog 操作日志 - * @throws Exception 异常 - */ - private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception { - String requestMethod = operLog.getRequestMethod(); - if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) { - String params = argsArrayToString(joinPoint.getArgs()); - operLog.setOperParam(StringUtils.substring(params, 0, 2000)); - } else { - Map paramsMap = (Map) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); - operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000)); - } - } - - /** - * 是否存在注解,如果存在就获取 - */ - private Log getAnnotationLog(JoinPoint joinPoint) throws Exception { - Signature signature = joinPoint.getSignature(); - MethodSignature methodSignature = (MethodSignature) signature; - Method method = methodSignature.getMethod(); - - if (method != null) { - return method.getAnnotation(Log.class); - } - return null; - } - /** * 参数拼装 */ diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java deleted file mode 100644 index c74d629eb..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.ruoyi.framework.manager; - -import java.util.TimerTask; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import com.ruoyi.common.utils.Threads; -import com.ruoyi.common.utils.spring.SpringUtils; - -/** - * 异步任务管理器 - * - * @author ruoyi - */ -public class AsyncManager { - /** - * 操作延迟10毫秒 - */ - private final int OPERATE_DELAY_TIME = 10; - - /** - * 异步操作任务调度线程池 - */ - private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); - - /** - * 单例模式 - */ - private AsyncManager() { - } - - private static AsyncManager me = new AsyncManager(); - - public static AsyncManager me() { - return me; - } - - /** - * 执行任务 - * - * @param task 任务 - */ - public void execute(TimerTask task) { - executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); - } - - /** - * 停止任务线程池 - */ - public void shutdown() { - Threads.shutdownAndAwaitTermination(executor); - } -} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java deleted file mode 100644 index 7e5901ece..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ruoyi.framework.manager; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import javax.annotation.PreDestroy; - -/** - * 确保应用退出时能关闭后台线程 - * - * @author ruoyi - */ -@Component -public class ShutdownManager { - private static final Logger logger = LoggerFactory.getLogger("sys-user"); - - @PreDestroy - public void destroy() { - shutdownAsyncManager(); - } - - /** - * 停止异步执行任务 - */ - private void shutdownAsyncManager() { - try { - logger.info("====关闭后台任务任务线程池===="); - AsyncManager.me().shutdown(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } -} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java deleted file mode 100644 index f13b50d5f..000000000 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.ruoyi.framework.manager.factory; - -import java.util.TimerTask; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.ruoyi.common.constant.Constants; -import com.ruoyi.common.utils.LogUtils; -import com.ruoyi.common.utils.ServletUtils; -import com.ruoyi.common.utils.ip.AddressUtils; -import com.ruoyi.common.utils.ip.IpUtils; -import com.ruoyi.common.utils.spring.SpringUtils; -import com.ruoyi.system.domain.SysLogininfor; -import com.ruoyi.system.domain.SysOperLog; -import com.ruoyi.system.service.ISysLogininforService; -import com.ruoyi.system.service.ISysOperLogService; -import eu.bitwalker.useragentutils.UserAgent; - -/** - * 异步工厂(产生任务用) - * - * @author ruoyi - */ -public class AsyncFactory { - private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); - - /** - * 记录登录信息 - * - * @param username 用户名 - * @param status 状态 - * @param message 消息 - * @param args 列表 - * @return 任务task - */ - public static TimerTask recordLogininfor(final String username, final String status, final String message, - final Object... args) { - final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); - final String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); - return new TimerTask() { - @Override - public void run() { - String address = AddressUtils.getRealAddressByIP(ip); - StringBuilder s = new StringBuilder(); - s.append(LogUtils.getBlock(ip)); - s.append(address); - s.append(LogUtils.getBlock(username)); - s.append(LogUtils.getBlock(status)); - s.append(LogUtils.getBlock(message)); - // 打印信息到日志 - sys_user_logger.info(s.toString(), args); - // 获取客户端操作系统 - String os = userAgent.getOperatingSystem().getName(); - // 获取客户端浏览器 - String browser = userAgent.getBrowser().getName(); - // 封装对象 - SysLogininfor logininfor = new SysLogininfor(); - logininfor.setUserName(username); - logininfor.setIpaddr(ip); - logininfor.setLoginLocation(address); - logininfor.setBrowser(browser); - logininfor.setOs(os); - logininfor.setMsg(message); - // 日志状态 - if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status)) { - logininfor.setStatus(Constants.SUCCESS); - } else if (Constants.LOGIN_FAIL.equals(status)) { - logininfor.setStatus(Constants.FAIL); - } - // 插入数据 - SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor); - } - }; - } - - /** - * 操作日志记录 - * - * @param operLog 操作日志信息 - * @return 任务task - */ - public static TimerTask recordOper(final SysOperLog operLog) { - return new TimerTask() { - @Override - public void run() { - // 远程查询操作地点 - operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); - SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); - } - }; - } -} diff --git a/src/main/java/cn/iocoder/dashboard/framework/async/config/AsyncConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/async/config/AsyncConfiguration.java new file mode 100644 index 000000000..b2c126c28 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/async/config/AsyncConfiguration.java @@ -0,0 +1,9 @@ +package cn.iocoder.dashboard.framework.async.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; + +@Configuration +@EnableAsync(proxyTargetClass = true) +public class AsyncConfiguration { +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/async/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/async/package-info.java new file mode 100644 index 000000000..375358995 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/async/package-info.java @@ -0,0 +1,4 @@ +/** + * 异步执行,基于 Spring @Async 实现 + */ +package cn.iocoder.dashboard.framework.async; diff --git a/src/main/java/cn/iocoder/dashboard/framework/async/《芋道 Spring Boot 异步任务入门》.md b/src/main/java/cn/iocoder/dashboard/framework/async/《芋道 Spring Boot 异步任务入门》.md new file mode 100644 index 000000000..b2ef210bd --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/async/《芋道 Spring Boot 异步任务入门》.md @@ -0,0 +1 @@ + diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/config/OperateLogConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/config/OperateLogConfiguration.java new file mode 100644 index 000000000..ff9a35a7a --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/config/OperateLogConfiguration.java @@ -0,0 +1,15 @@ +package cn.iocoder.dashboard.framework.logger.operatelog.config; + +import cn.iocoder.dashboard.framework.logger.operatelog.core.aop.OperateLogAspect; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OperateLogConfiguration { + + @Bean + public OperateLogAspect operateLogAspect() { + return new OperateLogAspect(); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/annotations/OperateLog.java b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/annotations/OperateLog.java new file mode 100644 index 000000000..ce61c77a0 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/annotations/OperateLog.java @@ -0,0 +1,52 @@ +package cn.iocoder.dashboard.framework.logger.operatelog.core.annotations; + +import cn.iocoder.dashboard.modules.system.enums.logger.SysOperateLogTypeEnum; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperateLog { + + // ========== 模块字段 ========== + + /** + * 操作模块 + * + * 为空时,会尝试读取 {@link Api#value()} 属性 + */ + String module() default ""; + /** + * 操作名 + * + * 为空时,会尝试读取 {@link ApiOperation#value()} 属性 + */ + String name() default ""; + /** + * 操作分类 + * + * 实际并不是数组,因为枚举不能设置 null 作为默认值 + */ + SysOperateLogTypeEnum[] type() default {}; + + // ========== 开关字段 ========== + + /** + * 是否记录操作日志 + */ + boolean enable() default true; + /** + * 是否记录方法参数 + */ + boolean logArgs() default true; + /** + * 是否记录方法结果的数据 + */ + boolean logResultData() default true; + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java new file mode 100644 index 000000000..97a2d68a4 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java @@ -0,0 +1,320 @@ +package cn.iocoder.dashboard.framework.logger.operatelog.core.aop; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.iocoder.dashboard.common.pojo.CommonResult; +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog; +import cn.iocoder.dashboard.framework.logger.operatelog.core.service.OperateLogFrameworkService; +import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; +import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils; +import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO; +import cn.iocoder.dashboard.modules.system.enums.logger.SysOperateLogTypeEnum; +import cn.iocoder.dashboard.util.servlet.ServletUtils; +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Maps; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.IntStream; + +import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; +import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.SUCCESS; + +/** + * 拦截使用 @ApiOperation 注解,如果满足条件,则生成操作日志。 + * 满足如下任一条件,则会进行记录: + * 1. 使用 @ApiOperation + 非 @GetMapping + * 2. 使用 @OperateLog 注解 + * + * 但是,如果声明 @OperateLog 注解时,将 enable 属性设置为 false 时,强制不记录。 + * + * 为什么考虑使用 @ApiOperation 记录呢?避免有小伙伴忘记添加 @OperateLog 注解 + * + * @author 芋道源码 + */ +@Aspect +@Slf4j +public class OperateLogAspect { + + @Resource + private OperateLogFrameworkService operateLogFrameworkService; + + @Around("@annotation(apiOperation)") + public Object around(ProceedingJoinPoint joinPoint, ApiOperation apiOperation) throws Throwable { + // 可能也添加了 @ApiOperation 注解 + OperateLog operateLog = getMethodAnnotation(joinPoint, OperateLog.class); + return around0(joinPoint, operateLog, apiOperation); + } + + @Around("!@annotation(io.swagger.annotations.ApiOperation) && @annotation(operateLog)") // 兼容处理,只添加 @OperateLog 注解的情况 + public Object around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable { + return around0(joinPoint, operateLog, null); + } + + private Object around0(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation) throws Throwable { + // 记录开始时间 + Date startTime = new Date(); + try { + // 执行原有方法 + Object result = joinPoint.proceed(); + // 记录正常执行时的操作日志 + this.log(joinPoint, operateLog, apiOperation, startTime, result, null); + return result; + } catch (Throwable exception) { + this.log(joinPoint, operateLog, apiOperation, startTime, null, exception); + throw exception; + } + } + + private void log(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation, + Date startTime, Object result, Throwable exception) { + try { + // 判断不记录的情况 + if (!isLogEnable(joinPoint, operateLog)) { + return; + } + // 真正记录操作日志 + this.log0(joinPoint, operateLog, apiOperation, startTime, result, exception); + } catch (Throwable ex) { + log.error("[log][记录操作日志时,发生异常,其中参数是 joinPoint({}) operateLog({}) apiOperation({}) result({}) exception({}) ]", + joinPoint, operateLog, apiOperation, result, exception, ex); + } + } + + private void log0(ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation, + Date startTime, Object result, Throwable exception) { + SysOperateLogCreateReqVO operateLogVO = new SysOperateLogCreateReqVO(); + // 补全通用字段 + operateLogVO.setTraceId(TracerUtils.getTraceId()); + operateLogVO.setStartTime(startTime); + // 补充用户信息 + fillUserFields(operateLogVO); + // 补全模块信息 + fillModuleFields(operateLogVO, joinPoint, operateLog, apiOperation); + // 补全请求信息 + fillRequestFields(operateLogVO); + // 补全方法信息 + fillMethodFields(operateLogVO, joinPoint, operateLog, startTime, result, exception); + + // 异步记录日志 + operateLogFrameworkService.createOperateLogAsync(operateLogVO); + } + + private static void fillUserFields(SysOperateLogCreateReqVO operateLogVO) { + operateLogVO.setUserId(SecurityUtils.getLoginUserId()); + } + + private static void fillModuleFields(SysOperateLogCreateReqVO operateLogVO, + ProceedingJoinPoint joinPoint, OperateLog operateLog, ApiOperation apiOperation) { + // module 属性 + if (operateLog != null) { + operateLogVO.setModule(operateLog.module()); + } + if (StrUtil.isEmpty(operateLogVO.getModule())) { + Api api = getClassAnnotation(joinPoint, Api.class); + if (api != null) { + operateLogVO.setModule(Optional.of(api.value()) + .orElse(ArrayUtil.isEmpty(api.tags()) ? api.tags()[0] : null)); + } + } + // name 属性 + if (operateLog != null) { + operateLogVO.setName(operateLog.name()); + } + if (StrUtil.isEmpty(operateLogVO.getName()) && apiOperation != null) { + operateLogVO.setName(apiOperation.value()); + } + // type 属性 + if (operateLog != null && ArrayUtil.isNotEmpty(operateLog.type())) { + operateLogVO.setType(operateLog.type()[0].getType()); + } + if (operateLogVO.getType() == null) { + RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint)); + SysOperateLogTypeEnum operateLogType = convertOperateLogType(requestMethod); + operateLogVO.setType(operateLogType != null ? operateLogType.getType() : null); + } + } + + private static void fillRequestFields(SysOperateLogCreateReqVO operateLogVO) { + // 获得 Request 对象 + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (!(requestAttributes instanceof ServletRequestAttributes)) { + return; + } + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + // 补全请求信息 + operateLogVO.setRequestMethod(request.getMethod()); + operateLogVO.setRequestUrl(request.getRequestURI()); + operateLogVO.setUserIp(ServletUtil.getClientIP(request)); + operateLogVO.setUserAgent(ServletUtils.getUserAgent(request)); + } + + private static void fillMethodFields(SysOperateLogCreateReqVO operateLogVO, + ProceedingJoinPoint joinPoint, OperateLog operateLog, + Date startTime, Object result, Throwable exception) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + operateLogVO.setJavaMethod(methodSignature.toString()); + if (operateLog == null || operateLog.logArgs()) { + operateLogVO.setJavaMethodArgs(obtainMethodArgs(joinPoint)); + } + if (operateLog == null || operateLog.logResultData()) { + operateLogVO.setResultData(obtainResultData(result)); + } + operateLogVO.setDuration((int) (System.currentTimeMillis() - startTime.getTime())); + // (正常)处理 resultCode 和 resultMsg 字段 + if (result != null) { + if (result instanceof CommonResult) { + CommonResult commonResult = (CommonResult) result; + operateLogVO.setResultCode(commonResult.getCode()); + operateLogVO.setResultMsg(commonResult.getMsg()); + } else { + operateLogVO.setResultCode(SUCCESS.getCode()); + } + } + // (异常)处理 resultCode 和 resultMsg 字段 + if (exception != null) { + operateLogVO.setResultCode(INTERNAL_SERVER_ERROR.getCode()); + operateLogVO.setResultMsg(ExceptionUtil.getRootCauseMessage(exception)); + } + } + + private static boolean isLogEnable(ProceedingJoinPoint joinPoint, OperateLog operateLog) { + // 有 @OperateLog 注解的情况下 + if (operateLog != null) { + return operateLog.enable(); + } + // 没有 @ApiOperation 注解的情况下,只记录 POST、PUT、DELETE 的情况 + return obtainFirstLogRequestMethod(obtainRequestMethod(joinPoint)) != null; + } + + private static RequestMethod obtainFirstLogRequestMethod(RequestMethod[] requestMethods) { + if (ArrayUtil.isEmpty(requestMethods)) { + return null; + } + return Arrays.stream(requestMethods).filter(requestMethod -> + requestMethod == RequestMethod.POST + || requestMethod == RequestMethod.PUT + || requestMethod == RequestMethod.DELETE) + .findFirst().orElse(null); + } + + private static RequestMethod obtainFirstMatchRequestMethod(RequestMethod[] requestMethods) { + if (ArrayUtil.isEmpty(requestMethods)) { + return null; + } + // 优先,匹配最优的 POST、PUT、DELETE + RequestMethod result = obtainFirstLogRequestMethod(requestMethods); + if (result != null) { + return result; + } + // 然后,匹配次优的 GET + result = Arrays.stream(requestMethods).filter(requestMethod -> requestMethod == RequestMethod.GET) + .findFirst().orElse(null); + if (result != null) { + return result; + } + // 兜底,获得第一个 + return requestMethods[0]; + } + + private static SysOperateLogTypeEnum convertOperateLogType(RequestMethod requestMethod) { + if (requestMethod == null) { + return null; + } + switch (requestMethod) { + case GET: + return SysOperateLogTypeEnum.GET; + case POST: + return SysOperateLogTypeEnum.CREATE; + case PUT: + return SysOperateLogTypeEnum.UPDATE; + case DELETE: + return SysOperateLogTypeEnum.DELETE; + default: + return SysOperateLogTypeEnum.OTHER; + } + } + + private static RequestMethod[] obtainRequestMethod(ProceedingJoinPoint joinPoint) { + RequestMapping requestMapping = AnnotationUtils.getAnnotation( // 使用 Spring 的工具类,可以处理 @RequestMapping 别名注解 + ((MethodSignature) joinPoint.getSignature()).getMethod(), RequestMapping.class); + return requestMapping != null ? requestMapping.method() : new RequestMethod[]{}; + } + + @SuppressWarnings("SameParameterValue") + private static T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class annotationClass) { + return ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(annotationClass); + } + + @SuppressWarnings("SameParameterValue") + private static T getClassAnnotation(ProceedingJoinPoint joinPoint, Class annotationClass) { + return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass); + } + + private static Map obtainMethodArgs(ProceedingJoinPoint joinPoint) { + // TODO 提升:参数脱敏和忽略 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + String[] argNames = methodSignature.getParameterNames(); + Object[] argValues = joinPoint.getArgs(); + // 拼接参数 + Map args = Maps.newHashMapWithExpectedSize(argValues.length); + for (int i = 0; i < argNames.length; i++) { + String argName = argNames[i]; + Object argValue = argValues[i]; + // 被忽略时,标记为 ignore 字符串,避免和 null 混在一起 + args.put(argName, !isIgnoreArgs(argValue) ? argValue : "[ignore]"); + } + return args; + } + + private static String obtainResultData(Object result) { + // TODO 提升:结果脱敏和忽略 + if (result instanceof CommonResult) { + result = ((CommonResult) result).getData(); + } + return JSON.toJSONString(result); + } + + private static boolean isIgnoreArgs(Object object) { + Class clazz = object.getClass(); + // 处理数组的情况 + if (clazz.isArray()) { + return IntStream.range(0, Array.getLength(object)) + .anyMatch(index -> isIgnoreArgs(Array.get(object, index))); + } + // 递归,处理数组、Collection、Map 的情况 + if (Collection.class.isAssignableFrom(clazz)) { + return ((Collection) object).stream() + .anyMatch((Predicate) o -> isIgnoreArgs(object)); + } + if (Map.class.isAssignableFrom(clazz)) { + return isIgnoreArgs(((Map) object).values()); + } + // obj + return object instanceof MultipartFile + || object instanceof HttpServletRequest + || object instanceof HttpServletResponse; + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/package-info.java new file mode 100644 index 000000000..88d69c2a8 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.dashboard.framework.logger.operatelog.core; diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/service/OperateLogFrameworkService.java b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/service/OperateLogFrameworkService.java new file mode 100644 index 000000000..9077cc152 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/service/OperateLogFrameworkService.java @@ -0,0 +1,14 @@ +package cn.iocoder.dashboard.framework.logger.operatelog.core.service; + +import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO; + +public interface OperateLogFrameworkService { + + /** + * 要不记录操作日志 + * + * @param reqVO 操作日志请求 + */ + void createOperateLogAsync(SysOperateLogCreateReqVO reqVO); + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/logger/package-info.java index 2cba832b8..258db8aa7 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/logger/package-info.java +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/package-info.java @@ -5,6 +5,6 @@ * 2. API 日志:包含两类 * 2.1 API 访问日志:记录用户访问 API 的访问日志,定期归档历史日志。 * 2.2 API 异常日志:记录用户访问 API 的系统异常,方便日常排查问题与告警。 - * 3. 通用 Logger 日志:将 {@link org.slf4j.Logger} 打印的日志,只满足大于等于 {@link org.slf4j.event.Level} 进行持久化,可以理解成简易的“日志中心”。 + * 3. 通用 Logger 日志:将 {@link org.slf4j.Logger} 打印的日志,只满足大于等于配置的 {@link org.slf4j.event.Level} 进行持久化,可以理解成简易的“日志中心”。 */ package cn.iocoder.dashboard.framework.logger; diff --git a/src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java b/src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java new file mode 100644 index 000000000..1a6b29cb1 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/tracer/core/util/TracerUtils.java @@ -0,0 +1,36 @@ +package cn.iocoder.dashboard.framework.tracer.core.util; + +import cn.hutool.core.util.StrUtil; +import org.apache.skywalking.apm.toolkit.trace.TraceContext; + +import java.util.UUID; + +/** + * 链路追踪工具类 + * + * @author 芋道源码 + */ +public class TracerUtils { + + /** + * 获得链路追踪编号 + * + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。 + * + * 默认情况下,我们使用 Apache SkyWalking 的 traceId 作为链路追踪编号。当然,可能会存在并未引入 Skywalking 的情况,此时使用 UUID 。 + * + * @return 链路追踪编号 + */ + public static String getTraceId() { + // 通过 SkyWalking 获取链路编号 + try { + String traceId = TraceContext.traceId(); + if (StrUtil.isNotBlank(traceId)) { + return traceId; + } + } catch (Throwable ignore) {} + // TODO 芋艿 多次调用会问题 + return UUID.randomUUID().toString(); + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/tracer/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/tracer/package-info.java new file mode 100644 index 000000000..54d505760 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/tracer/package-info.java @@ -0,0 +1,6 @@ +/** + * 链路追踪 + * + * 主要目的,是生成全局的链路追踪编号 + */ +package cn.iocoder.dashboard.framework.tracer; diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java index 008482420..3db8ed8ef 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java @@ -2,6 +2,7 @@ package cn.iocoder.dashboard.modules.system.controller.auth; import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.pojo.CommonResult; +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog; import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginReqVO; import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthLoginRespVO; import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthMenuRespVO; @@ -28,7 +29,7 @@ import static cn.iocoder.dashboard.common.pojo.CommonResult.success; import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserId; import static cn.iocoder.dashboard.framework.security.core.util.SecurityUtils.getLoginUserRoleIds; -@Api(tags = "认证 API") +@Api("认证 API") @RestController @RequestMapping("/") public class SysAuthController { @@ -44,6 +45,7 @@ public class SysAuthController { @ApiOperation("使用账号密码登录") @PostMapping("/login") + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult login(@RequestBody @Valid SysAuthLoginReqVO reqVO) { String token = authService.login(reqVO.getUsername(), reqVO.getPassword(), reqVO.getUuid(), reqVO.getCode()); // 返回结果 @@ -52,6 +54,7 @@ public class SysAuthController { @ApiOperation("获取登陆用户的权限信息") @GetMapping("/get-permission-info") + @OperateLog public CommonResult getPermissionInfo() { // 获得用户信息 SysUserDO user = userService.getUser(getLoginUserId()); diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/package-info.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/package-info.java new file mode 100644 index 000000000..d682a7365 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.dashboard.modules.system.controller.logger; diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogBaseVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogBaseVO.java new file mode 100644 index 000000000..0fbf1ee05 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogBaseVO.java @@ -0,0 +1,79 @@ +package cn.iocoder.dashboard.modules.system.controller.logger.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.Map; + +/** + * 操作日志 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class SysOperateLogBaseVO { + + @ApiModelProperty(value = "链路追踪编号", required = true, example = "89aca178-a370-411c-ae02-3f0d672be4ab") + @NotEmpty(message = "链路追踪编号不能为空") + private String traceId; + + @ApiModelProperty(value = "用户编号", required = true, example = "1024") + @NotNull(message = "用户编号不能为空") + private Long userId; + + @ApiModelProperty(value = "操作模块", required = true, example = "订单") + @NotEmpty(message = "操作模块不能为空") + private String module; + + @ApiModelProperty(value = "操作名", required = true, example = "创建订单") + @NotEmpty(message = "操作名") + private String name; + + @ApiModelProperty(value = "操作分类", required = true, example = "操作分类", notes = "参见 SysOperateLogTypeEnum 枚举类") + @NotNull(message = "操作分类不能为空") + private Integer type; + + @ApiModelProperty(value = "请求方法名", required = true, example = "GET") + @NotEmpty(message = "请求方法名不能为空") + private String requestMethod; + + @ApiModelProperty(value = "请求地址", required = true, example = "/xxx/yyy") + @NotEmpty(message = "请求地址不能为空") + private String requestUrl; + + @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1") + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + @ApiModelProperty(value = "浏览器 UserAgent", required = true, example = "Mozilla/5.0") + @NotEmpty(message = "浏览器 UserAgent 不能为空") + private String userAgent; + + @ApiModelProperty(value = "Java 方法名", required = true, example = "cn.iocoder.dashboard.UserController.save(...)") + @NotEmpty(message = "Java 方法名不能为空") + private String javaMethod; + + @ApiModelProperty(value = "Java 方法的参数") + private Map javaMethodArgs; + + @ApiModelProperty(value = "开始时间", required = true) + @NotNull(message = "开始时间不能为空") + private Date startTime; + + @ApiModelProperty(value = "执行时长,单位:毫秒", required = true) + @NotNull(message = "执行时长不能为空") + private Integer duration; + + @ApiModelProperty(value = "结果码", required = true) + @NotNull(message = "结果码不能为空") + private Integer resultCode; + + @ApiModelProperty(value = "结果提示") + private String resultMsg; + + @ApiModelProperty(value = "结果数据") + private String resultData; + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogCreateReqVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogCreateReqVO.java new file mode 100644 index 000000000..bf2e79419 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/SysOperateLogCreateReqVO.java @@ -0,0 +1,13 @@ +package cn.iocoder.dashboard.modules.system.controller.logger.vo; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@ApiModel(value = "操作日志创建 Request VO", description = "暂时提供给前端,仅仅后端切面记录操作日志时,进行使用") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SysOperateLogCreateReqVO extends SysOperateLogBaseVO { +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/package-info.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/package-info.java new file mode 100644 index 000000000..80d384946 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.dashboard.modules.system.controller.logger.vo; diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/convert/logger/SysOperateLogConvert.java b/src/main/java/cn/iocoder/dashboard/modules/system/convert/logger/SysOperateLogConvert.java new file mode 100644 index 000000000..734c82c31 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/convert/logger/SysOperateLogConvert.java @@ -0,0 +1,15 @@ +package cn.iocoder.dashboard.modules.system.convert.logger; + +import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO; +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SysOperateLogConvert { + + SysOperateLogConvert INSTANCE = Mappers.getMapper(SysOperateLogConvert.class); + + SysOperateLogDO convert(SysOperateLogCreateReqVO bean); + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/logger/SysOperateLogMapper.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/logger/SysOperateLogMapper.java new file mode 100644 index 000000000..b15bd6d0c --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/logger/SysOperateLogMapper.java @@ -0,0 +1,9 @@ +package cn.iocoder.dashboard.modules.system.dal.mysql.dao.logger; + +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SysOperateLogMapper extends BaseMapper { +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/logger/SysOperateLogDO.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/logger/SysOperateLogDO.java index f3af32faf..fc807ce01 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/logger/SysOperateLogDO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/logger/SysOperateLogDO.java @@ -36,7 +36,7 @@ public class SysOperateLogDO extends BaseDO { */ private String traceId; /** - * 操作人 + * 用户编号 * * {@link SysUserDO#getId()} */ @@ -69,7 +69,7 @@ public class SysOperateLogDO extends BaseDO { * TODO 预留字段 */ @TableField(typeHandler = FastjsonTypeHandler.class) - private Map ext; + private Map exts; /** * 请求方法名 @@ -79,11 +79,6 @@ public class SysOperateLogDO extends BaseDO { * 请求地址 */ private String requestUrl; - /** - * 请求参数 - */ - @TableField(typeHandler = FastjsonTypeHandler.class) - private Map requestParams; /** * 用户 IP */ @@ -97,6 +92,11 @@ public class SysOperateLogDO extends BaseDO { * Java 方法名 */ private String javaMethod; + /** + * Java 方法的参数 + */ + @TableField(typeHandler = FastjsonTypeHandler.class) + private Map javaMethodArgs; /** * 开始时间 */ @@ -119,8 +119,9 @@ public class SysOperateLogDO extends BaseDO { private String resultMsg; /** * 结果数据 + * + * 如果是对象,则使用 JSON 格式化 */ - @TableField(typeHandler = FastjsonTypeHandler.class) - private Map resultData; + private String resultData; } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysOperateLogTypeEnum.java b/src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysOperateLogTypeEnum.java index a60d14d18..ab1f8546d 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysOperateLogTypeEnum.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysOperateLogTypeEnum.java @@ -1,5 +1,6 @@ package cn.iocoder.dashboard.modules.system.enums.logger; +import cn.iocoder.dashboard.framework.logger.operatelog.core.annotations.OperateLog; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,26 +13,33 @@ import lombok.Getter; @AllArgsConstructor public enum SysOperateLogTypeEnum { + /** + * 查询 + * + * 绝大多数情况下,不会记录查询动作,因为过于大量显得没有意义。 + * 在有需要的时候,通过声明 {@link OperateLog} 注解来记录 + */ + GET(1), /** * 新增 */ - CREATE(1), + CREATE(2), /** * 修改 */ - UPDATE(2), + UPDATE(3), /** * 删除 */ - DELETE(3), + DELETE(4), /** * 导出 */ - EXPORT(4), + EXPORT(5), /** * 导入 */ - IMPORT(5), + IMPORT(6), /** * 其它 * diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogService.java new file mode 100644 index 000000000..8cf65d209 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogService.java @@ -0,0 +1,9 @@ +package cn.iocoder.dashboard.modules.system.service.logger; + +import cn.iocoder.dashboard.framework.logger.operatelog.core.service.OperateLogFrameworkService; + +/** + * 操作日志 Service 接口 + */ +public interface SysOperateLogService extends OperateLogFrameworkService { +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysOperateLogServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysOperateLogServiceImpl.java new file mode 100644 index 000000000..9c5debfd3 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysOperateLogServiceImpl.java @@ -0,0 +1,33 @@ +package cn.iocoder.dashboard.modules.system.service.logger.impl; + +import cn.iocoder.dashboard.modules.system.controller.logger.vo.SysOperateLogCreateReqVO; +import cn.iocoder.dashboard.modules.system.convert.logger.SysOperateLogConvert; +import cn.iocoder.dashboard.modules.system.dal.mysql.dao.logger.SysOperateLogMapper; +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysOperateLogDO; +import cn.iocoder.dashboard.modules.system.service.logger.SysOperateLogService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Service +@Slf4j +public class SysOperateLogServiceImpl implements SysOperateLogService { + + @Resource + private SysOperateLogMapper operateLogMapper; + + @Override + @Async + public void createOperateLogAsync(SysOperateLogCreateReqVO reqVO) { + SysOperateLogDO logDO = SysOperateLogConvert.INSTANCE.convert(reqVO); + try { + operateLogMapper.insert(logDO); + } catch (Throwable throwable) { + // 仅仅打印日志,不对外抛出。原因是,还是要保留现场数据。 + log.error("[createOperateLogAsync][记录操作日志异常,日志为 ({})]", reqVO, throwable); + } + } + +} diff --git a/src/main/java/cn/iocoder/dashboard/util/json/JSONUtils.java b/src/main/java/cn/iocoder/dashboard/util/json/JSONUtils.java new file mode 100644 index 000000000..07ad0e9a0 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/util/json/JSONUtils.java @@ -0,0 +1,21 @@ +package cn.iocoder.dashboard.util.json; + +/** + * JSON 工具类 + * + * @author 芋道源码 + */ +public class JSONUtils { + +// public static Map toJSONMap(Object javaObject) { +// return (Map) JSON.toJSON(javaObject); +// } +// +// public static void main(String[] args) { +// SysDictTypeCreateReqVO createReqVO = new SysDictTypeCreateReqVO(); +// createReqVO.setType("1"); +// createReqVO.setRemark("2"); +// System.out.println(toJSONMap(createReqVO)); +// } + +} diff --git a/src/main/java/cn/iocoder/dashboard/util/servlet/ServletUtils.java b/src/main/java/cn/iocoder/dashboard/util/servlet/ServletUtils.java index fa680e44d..ef4a72371 100644 --- a/src/main/java/cn/iocoder/dashboard/util/servlet/ServletUtils.java +++ b/src/main/java/cn/iocoder/dashboard/util/servlet/ServletUtils.java @@ -5,6 +5,7 @@ import cn.hutool.extra.servlet.ServletUtil; import com.alibaba.fastjson.JSON; import org.springframework.http.MediaType; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; @@ -44,4 +45,13 @@ public class ServletUtils { IoUtil.write(response.getOutputStream(), false, content); } + /** + * @param request 请求 + * @return ua + */ + public static String getUserAgent(HttpServletRequest request) { + String ua = request.getHeader("User-Agent"); + return ua != null ? ua : ""; + } + }