【重构】V2 操作日志转正,基于注解的可使用变量、可以自定义函数的通用操作日志组件

This commit is contained in:
YunaiV 2024-04-04 01:21:17 +08:00
parent f88c07af17
commit e67f16e5bb
31 changed files with 177 additions and 1157 deletions

View File

@ -11,7 +11,7 @@
Target Server Version : 80200 (8.2.0)
File Encoding : 65001
Date: 03/04/2024 19:07:31
Date: 04/04/2024 01:17:25
*/
SET NAMES utf8mb4;
@ -347,7 +347,7 @@ CREATE TABLE `infra_api_access_log` (
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_create_time`(`create_time` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 35920 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
) ENGINE = InnoDB AUTO_INCREMENT = 35925 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
-- ----------------------------
-- Records of infra_api_access_log
@ -726,7 +726,7 @@ CREATE TABLE `infra_file_config` (
-- ----------------------------
BEGIN;
INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库', 1, '我是数据库', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2024-02-28 22:54:07', b'0');
INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (22, '七牛存储器', 20, '', b'1', '{\"@class\":\"cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\"}', '1', '2024-01-13 22:11:12', '1', '2024-01-13 22:24:06', b'0');
INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (22, '七牛存储器', 20, '', b'1', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\"}', '1', '2024-01-13 22:11:12', '1', '2024-04-03 19:38:34', b'0');
COMMIT;
-- ----------------------------
@ -1420,7 +1420,7 @@ CREATE TABLE `system_login_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3066 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
) ENGINE = InnoDB AUTO_INCREMENT = 3067 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
-- ----------------------------
-- Records of system_login_log
@ -2457,7 +2457,7 @@ CREATE TABLE `system_oauth2_access_token` (
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6366 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
) ENGINE = InnoDB AUTO_INCREMENT = 6372 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
-- ----------------------------
-- Records of system_oauth2_access_token
@ -2579,7 +2579,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1441 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
) ENGINE = InnoDB AUTO_INCREMENT = 1442 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
-- ----------------------------
-- Records of system_oauth2_refresh_token
@ -2592,46 +2592,6 @@ COMMIT;
-- ----------------------------
DROP TABLE IF EXISTS `system_operate_log`;
CREATE TABLE `system_operate_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '链路追踪编号',
`user_id` bigint NOT NULL COMMENT '用户编号',
`user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型',
`module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模块标题',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作名',
`type` bigint NOT NULL DEFAULT 0 COMMENT '操作分类',
`content` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '操作内容',
`exts` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '拓展字段',
`request_method` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '请求方法名',
`request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '请求地址',
`user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户 IP',
`user_agent` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '浏览器 UA',
`java_method` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Java 方法名',
`java_method_args` varchar(8000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT 'Java 方法的参数',
`start_time` datetime NOT NULL COMMENT '操作时间',
`duration` int NOT NULL COMMENT '执行时长',
`result_code` int NOT NULL DEFAULT 0 COMMENT '结果码',
`result_msg` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '结果提示',
`result_data` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '结果数据',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12000 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
-- ----------------------------
-- Records of system_operate_log
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for system_operate_log_v2
-- ----------------------------
DROP TABLE IF EXISTS `system_operate_log_v2`;
CREATE TABLE `system_operate_log_v2` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '链路追踪编号',
`user_id` bigint NOT NULL COMMENT '用户编号',
@ -2655,7 +2615,7 @@ CREATE TABLE `system_operate_log_v2` (
) ENGINE = InnoDB AUTO_INCREMENT = 9019 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
-- ----------------------------
-- Records of system_operate_log_v2
-- Records of system_operate_log
-- ----------------------------
BEGIN;
COMMIT;
@ -5623,7 +5583,7 @@ CREATE TABLE `system_users` (
-- Records of system_users
-- ----------------------------
BEGIN;
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png', 0, '127.0.0.1', '2024-04-03 17:31:00', 'admin', '2021-01-05 17:03:47', NULL, '2024-04-03 17:31:00', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png', 0, '127.0.0.1', '2024-04-03 20:01:18', 'admin', '2021-01-05 17:03:47', NULL, '2024-04-03 20:01:18', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-03-18 21:09:04', '', '2021-01-13 23:50:35', NULL, '2024-03-18 21:09:04', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$KhExCYl7lx6eWWZYKsibKOZ8IBJRyuNuCcEOLQ11RYhJKgHmlSwK.', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-03-26 07:11:35', '', '2021-01-21 02:13:53', NULL, '2024-03-26 07:11:35', b'0', 1);

View File

@ -1,23 +0,0 @@
package cn.iocoder.yudao.framework.operatelog.config;
import cn.iocoder.yudao.framework.operatelog.core.aop.OperateLogAspect;
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkServiceImpl;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
public class YudaoOperateLogAutoConfiguration {
@Bean
public OperateLogAspect operateLogAspect() {
return new OperateLogAspect();
}
@Bean
public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
return new OperateLogFrameworkServiceImpl(operateLogApi);
}
}

View File

@ -1,16 +0,0 @@
package cn.iocoder.yudao.framework.operatelog.config;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 操作日志使用到 Feign 的配置项
*
* @author 芋道源码
*/
@AutoConfiguration
@EnableFeignClients(clients = OperateLogApi.class) // 主要是引入相关的 API 服务
public class YudaoOperateLogRpcAutoConfiguration {
}

View File

@ -1,380 +0,0 @@
package cn.iocoder.yudao.framework.operatelog.core.aop;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLog;
import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
/**
* 拦截使用 @OperateLog 注解如果满足条件则生成操作日志
* 满足如下任一条件则会进行记录
* 1. 使用 @ApiOperation + @GetMapping
* 2. 使用 @OperateLog 注解
* <p>
* 但是如果声明 @OperateLog 注解时 enable 属性设置为 false 强制不记录
*
* @author 芋道源码
*/
@Aspect
@Slf4j
public class OperateLogAspect {
/**
* 用于记录操作内容的上下文
*
* @see OperateLog#getContent()
*/
private static final ThreadLocal<String> CONTENT = new ThreadLocal<>();
/**
* 用于记录拓展字段的上下文
*
* @see OperateLog#getExts()
*/
private static final ThreadLocal<Map<String, Object>> EXTS = new ThreadLocal<>();
@Resource
private OperateLogFrameworkService operateLogFrameworkService;
@Around("@annotation(operation)")
public Object around(ProceedingJoinPoint joinPoint, Operation operation) throws Throwable {
// 可能也添加了 @ApiOperation 注解
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog = getMethodAnnotation(joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog.class);
return around0(joinPoint, operateLog, operation);
}
@Around("!@annotation(io.swagger.v3.oas.annotations.Operation) && @annotation(operateLog)")
// 兼容处理只添加 @OperateLog 注解的情况
public Object around(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog) throws Throwable {
return around0(joinPoint, operateLog, null);
}
private Object around0(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
Operation operation) throws Throwable {
// 目前只有管理员才记录操作日志所以非管理员直接调用不进行记录
Integer userType = WebFrameworkUtils.getLoginUserType();
if (!Objects.equals(userType, UserTypeEnum.ADMIN.getValue())) {
return joinPoint.proceed();
}
// 记录开始时间
LocalDateTime startTime = LocalDateTime.now();
try {
// 执行原有方法
Object result = joinPoint.proceed();
// 记录正常执行时的操作日志
this.log(joinPoint, operateLog, operation, startTime, result, null);
return result;
} catch (Throwable exception) {
this.log(joinPoint, operateLog, operation, startTime, null, exception);
throw exception;
} finally {
clearThreadLocal();
}
}
public static void setContent(String content) {
CONTENT.set(content);
}
public static void addExt(String key, Object value) {
if (EXTS.get() == null) {
EXTS.set(new HashMap<>());
}
EXTS.get().put(key, value);
}
private static void clearThreadLocal() {
CONTENT.remove();
EXTS.remove();
}
private void log(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
Operation operation,
LocalDateTime startTime, Object result, Throwable exception) {
try {
// 判断不记录的情况
if (!isLogEnable(joinPoint, operateLog)) {
return;
}
// 真正记录操作日志
this.log0(joinPoint, operateLog, operation, startTime, result, exception);
} catch (Throwable ex) {
log.error("[log][记录操作日志时,发生异常,其中参数是 joinPoint({}) operateLog({}) apiOperation({}) result({}) exception({}) ]",
joinPoint, operateLog, operation, result, exception, ex);
}
}
private void log0(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
Operation operation,
LocalDateTime startTime, Object result, Throwable exception) {
OperateLog operateLogObj = new OperateLog();
// 补全通用字段
operateLogObj.setTraceId(TracerUtils.getTraceId());
operateLogObj.setStartTime(startTime);
// 补充用户信息
fillUserFields(operateLogObj);
// 补全模块信息
fillModuleFields(operateLogObj, joinPoint, operateLog, operation);
// 补全请求信息
fillRequestFields(operateLogObj);
// 补全方法信息
fillMethodFields(operateLogObj, joinPoint, operateLog, startTime, result, exception);
// 异步记录日志
operateLogFrameworkService.createOperateLog(operateLogObj);
}
private static void fillUserFields(OperateLog operateLogObj) {
operateLogObj.setUserId(WebFrameworkUtils.getLoginUserId());
operateLogObj.setUserType(WebFrameworkUtils.getLoginUserType());
}
private static void fillModuleFields(OperateLog operateLogObj,
ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
Operation operation) {
// module 属性
if (operateLog != null) {
operateLogObj.setModule(operateLog.module());
}
if (StrUtil.isEmpty(operateLogObj.getModule())) {
Tag tag = getClassAnnotation(joinPoint, Tag.class);
if (tag != null) {
// 优先读取 @Tag name 属性
if (StrUtil.isNotEmpty(tag.name())) {
operateLogObj.setModule(tag.name());
}
// 没有的话读取 @API description 属性
if (StrUtil.isEmpty(operateLogObj.getModule()) && ArrayUtil.isNotEmpty(tag.description())) {
operateLogObj.setModule(tag.description());
}
}
}
// name 属性
if (operateLog != null) {
operateLogObj.setName(operateLog.name());
}
if (StrUtil.isEmpty(operateLogObj.getName()) && operation != null) {
operateLogObj.setName(operation.summary());
}
// type 属性
if (operateLog != null && ArrayUtil.isNotEmpty(operateLog.type())) {
operateLogObj.setType(operateLog.type()[0].getType());
}
if (operateLogObj.getType() == null) {
RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
OperateTypeEnum operateLogType = convertOperateLogType(requestMethod);
operateLogObj.setType(operateLogType != null ? operateLogType.getType() : null);
}
// content exts 属性
operateLogObj.setContent(CONTENT.get());
operateLogObj.setExts(EXTS.get());
}
private static void fillRequestFields(OperateLog operateLogObj) {
// 获得 Request 对象
HttpServletRequest request = ServletUtils.getRequest();
if (request == null) {
return;
}
// 补全请求信息
operateLogObj.setRequestMethod(request.getMethod());
operateLogObj.setRequestUrl(request.getRequestURI());
operateLogObj.setUserIp(ServletUtils.getClientIP(request));
operateLogObj.setUserAgent(ServletUtils.getUserAgent(request));
}
private static void fillMethodFields(OperateLog operateLogObj,
ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog,
LocalDateTime startTime, Object result, Throwable exception) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
operateLogObj.setJavaMethod(methodSignature.toString());
if (operateLog == null || operateLog.logArgs()) {
operateLogObj.setJavaMethodArgs(obtainMethodArgs(joinPoint));
}
if (operateLog == null || operateLog.logResultData()) {
operateLogObj.setResultData(obtainResultData(result));
}
operateLogObj.setDuration((int) (LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis()));
// 正常处理 resultCode resultMsg 字段
if (result instanceof CommonResult) {
CommonResult<?> commonResult = (CommonResult<?>) result;
operateLogObj.setResultCode(commonResult.getCode());
operateLogObj.setResultMsg(commonResult.getMsg());
} else {
operateLogObj.setResultCode(SUCCESS.getCode());
}
// 异常处理 resultCode resultMsg 字段
if (exception != null) {
operateLogObj.setResultCode(INTERNAL_SERVER_ERROR.getCode());
operateLogObj.setResultMsg(ExceptionUtil.getRootCauseMessage(exception));
}
}
private static boolean isLogEnable(ProceedingJoinPoint joinPoint,
cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog operateLog) {
// @OperateLog 注解的情况下
if (operateLog != null) {
return operateLog.enable();
}
// Cloud 专属逻辑如果是 RPC 请求则必须 @OperateLog 注解才会记录操作日志
String className = joinPoint.getSignature().getDeclaringType().getName();
if (WebFrameworkUtils.isRpcRequest(className)) {
return false;
}
// 没有 @ApiOperation 注解的情况下只记录 POSTPUTDELETE 的情况
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;
}
// 优先匹配最优的 POSTPUTDELETE
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 OperateTypeEnum convertOperateLogType(RequestMethod requestMethod) {
if (requestMethod == null) {
return null;
}
switch (requestMethod) {
case GET:
return OperateTypeEnum.GET;
case POST:
return OperateTypeEnum.CREATE;
case PUT:
return OperateTypeEnum.UPDATE;
case DELETE:
return OperateTypeEnum.DELETE;
default:
return OperateTypeEnum.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 extends Annotation> T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
return ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(annotationClass);
}
@SuppressWarnings("SameParameterValue")
private static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
}
private static String obtainMethodArgs(ProceedingJoinPoint joinPoint) {
// TODO 提升参数脱敏和忽略
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] argNames = methodSignature.getParameterNames();
Object[] argValues = joinPoint.getArgs();
// 拼接参数
Map<String, Object> 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 JsonUtils.toJsonString(args);
}
private static String obtainResultData(Object result) {
// TODO 提升结果脱敏和忽略
if (result instanceof CommonResult) {
result = ((CommonResult<?>) result).getData();
}
return JsonUtils.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)));
}
// 递归处理数组CollectionMap 的情况
if (Collection.class.isAssignableFrom(clazz)) {
return ((Collection<?>) object).stream()
.anyMatch((Predicate<Object>) OperateLogAspect::isIgnoreArgs);
}
if (Map.class.isAssignableFrom(clazz)) {
return isIgnoreArgs(((Map<?, ?>) object).values());
}
// obj
return object instanceof MultipartFile
|| object instanceof HttpServletRequest
|| object instanceof HttpServletResponse
|| object instanceof BindingResult;
}
}

View File

@ -1,110 +0,0 @@
package cn.iocoder.yudao.framework.operatelog.core.service;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 操作日志
*
* @author 芋道源码
*/
@Data
public class OperateLog {
/**
* 链路追踪编号
*/
private String traceId;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 操作模块
*/
private String module;
/**
* 操作名
*/
private String name;
/**
* 操作分类
*/
private Integer type;
/**
* 操作明细
*/
private String content;
/**
* 拓展字段
*/
private Map<String, Object> exts;
/**
* 请求方法名
*/
private String requestMethod;
/**
* 请求地址
*/
private String requestUrl;
/**
* 用户 IP
*/
private String userIp;
/**
* 浏览器 UserAgent
*/
private String userAgent;
/**
* Java 方法名
*/
private String javaMethod;
/**
* Java 方法的参数
*/
private String javaMethodArgs;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 执行时长单位毫秒
*/
private Integer duration;
/**
* 结果码
*/
private Integer resultCode;
/**
* 结果提示
*/
private String resultMsg;
/**
* 结果数据
*/
private String resultData;
}

View File

@ -1,17 +0,0 @@
package cn.iocoder.yudao.framework.operatelog.core.service;
/**
* 操作日志 Framework Service 接口
*
* @author 芋道源码
*/
public interface OperateLogFrameworkService {
/**
* 记录操作日志
*
* @param operateLog 操作日志请求
*/
void createOperateLog(OperateLog operateLog);
}

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.framework.operatelog.core.service;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Async;
/**
* 操作日志 Framework Service 实现类
*
* 基于 {@link OperateLogApi} 实现记录操作日志
*
* @author 芋道源码
*/
@RequiredArgsConstructor
public class OperateLogFrameworkServiceImpl implements OperateLogFrameworkService {
private final OperateLogApi operateLogApi;
@Override
@Async
public void createOperateLog(OperateLog operateLog) {
OperateLogCreateReqDTO reqDTO = BeanUtil.toBean(operateLog, OperateLogCreateReqDTO.class);
operateLogApi.createOperateLog(reqDTO);
}
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.framework.operatelog.core.util;
import cn.iocoder.yudao.framework.operatelog.core.aop.OperateLogAspect;
/**
* 操作日志工具类
* 目前主要的作用是提供给业务代码记录操作明细和拓展字段
*
* @author 芋道源码
*/
public class OperateLogUtils {
public static void setContent(String content) {
OperateLogAspect.setContent(content);
}
public static void addExt(String key, Object value) {
OperateLogAspect.addExt(key, value);
}
}

View File

@ -1,6 +0,0 @@
/**
* 用户操作日志记录用户的操作用于对用户的操作的审计与追溯永久保存
*
* @author 芋道源码
*/
package cn.iocoder.yudao.framework.operatelog;

View File

@ -1,2 +0,0 @@
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogRpcAutoConfiguration
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogAutoConfiguration

View File

@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
import com.mzt.logapi.beans.LogRecord;
import com.mzt.logapi.service.ILogRecordService;
import jakarta.annotation.Resource;
@ -30,7 +30,7 @@ public class LogRecordServiceImpl implements ILogRecordService {
@Override
public void record(LogRecord logRecord) {
// 1. 补全通用字段
OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO();
OperateLogCreateReqDTO reqDTO = new OperateLogCreateReqDTO();
reqDTO.setTraceId(TracerUtils.getTraceId());
// 补充用户信息
fillUserFields(reqDTO);
@ -40,12 +40,10 @@ public class LogRecordServiceImpl implements ILogRecordService {
fillRequestFields(reqDTO);
// 2. 异步记录日志
operateLogApi.createOperateLogV2(reqDTO);
// TODO 测试结束删除或搞个开关
log.info("操作日志 ===> {}", reqDTO);
operateLogApi.createOperateLog(reqDTO);
}
private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) {
private static void fillUserFields(OperateLogCreateReqDTO reqDTO) {
// 使用 SecurityFrameworkUtils因为要考虑rpcmqjob它其实不是 web
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
if (loginUser == null) {
@ -55,7 +53,7 @@ public class LogRecordServiceImpl implements ILogRecordService {
reqDTO.setUserType(loginUser.getUserType());
}
public static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, LogRecord logRecord) {
public static void fillModuleFields(OperateLogCreateReqDTO reqDTO, LogRecord logRecord) {
reqDTO.setType(logRecord.getType()); // 大模块类型例如CRM 客户
reqDTO.setSubType(logRecord.getSubType());// 操作名称例如转移客户
reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 业务编号例如客户编号
@ -63,7 +61,7 @@ public class LogRecordServiceImpl implements ILogRecordService {
reqDTO.setExtra(logRecord.getExtra()); // 拓展字段有些复杂的业务需要记录一些字段 ( JSON 格式 )例如说记录订单编号{ orderId: "1"}
}
private static void fillRequestFields(OperateLogV2CreateReqDTO reqDTO) {
private static void fillRequestFields(OperateLogCreateReqDTO reqDTO) {
// 获得 Request 对象
HttpServletRequest request = ServletUtils.getRequest();
if (request == null) {

View File

@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogR
import cn.iocoder.yudao.module.crm.enums.LogRecordConstants;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
@ -55,9 +55,9 @@ public class CrmOperateLogController {
@Operation(summary = "获得操作日志")
@PreAuthorize("@ss.hasPermission('crm:operate-log:query')")
public CommonResult<PageResult<CrmOperateLogRespVO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO();
OperateLogPageReqDTO reqDTO = new OperateLogPageReqDTO();
reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页需要分页需注释
reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId());
reqDTO.setType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId());
return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO).getCheckedData(), CrmOperateLogRespVO.class));
}

View File

@ -8,6 +8,8 @@ import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.engine.velocity.VelocityEngine;
import cn.hutool.system.SystemUtil;
import cn.iocoder.yudao.framework.apilog.core.annotations.ApiAccessLog;
import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
@ -24,8 +26,6 @@ import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
@ -211,7 +211,7 @@ public class CodegenEngine {
globalBindingMap.put("LocalDateTimeUtilsClassName", LocalDateTimeUtils.class.getName());
globalBindingMap.put("ObjectUtilsClassName", ObjectUtils.class.getName());
globalBindingMap.put("DictConvertClassName", DictConvert.class.getName());
globalBindingMap.put("OperateLogClassName", OperateLog.class.getName());
globalBindingMap.put("ApiAccessLogClassName", ApiAccessLog.class.getName());
globalBindingMap.put("OperateTypeEnumClassName", OperateTypeEnum.class.getName());
globalBindingMap.put("BeanUtils", BeanUtils.class.getName());
}

View File

@ -23,7 +23,7 @@ import static ${CommonResultClassName}.success;
import ${ExcelUtilsClassName};
import ${OperateLogClassName};
import ${ApiAccessLogClassName};
import static ${OperateTypeEnumClassName}.*;
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
@ -114,7 +114,7 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
#if ($sceneEnum.scene == 1)
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')")
#end
@OperateLog(type = EXPORT)
@ApiAccessLog(operateType = EXPORT)
#if ( $table.templateType != 2 )
public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO,
HttpServletResponse response) throws IOException {

View File

@ -3,13 +3,11 @@ package cn.iocoder.yudao.module.system.api.logger;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogRespDTO;
import cn.iocoder.yudao.module.system.enums.ApiConstants;
import feign.QueryMap;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
@ -27,12 +25,8 @@ public interface OperateLogApi {
@Operation(summary = "创建操作日志")
CommonResult<Boolean> createOperateLog(@Valid @RequestBody OperateLogCreateReqDTO createReqDTO);
@PostMapping(PREFIX + "/create-v2")
@Operation(summary = "创建操作日志")
CommonResult<Boolean> createOperateLogV2(@Valid @RequestBody OperateLogV2CreateReqDTO createReqDTO);
@PostMapping(PREFIX + "/page")
@Operation(summary = "获取指定模块的指定数据的操作日志分页")
CommonResult<PageResult<OperateLogV2RespDTO>> getOperateLogPage(@QueryMap OperateLogV2PageReqDTO pageReqVO);
CommonResult<PageResult<OperateLogRespDTO>> getOperateLogPage(@QueryMap OperateLogPageReqDTO pageReqVO);
}

View File

@ -1,84 +1,50 @@
package cn.iocoder.yudao.module.system.api.logger.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Map;
import lombok.Data;
@Schema(description = "RPC 服务 - 操作日志创建 Request DTO")
@Schema(name = "RPC 服务 - 系统操作日志 Create Request DTO")
@Data
public class OperateLogCreateReqDTO {
@Schema(description = "链路追踪编号", example = "89aca178-a370-411c-ae02-3f0d672be4ab")
@Schema(description = "链路追踪编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "89aca178-a370-411c-ae02-3f0d672be4ab")
private String traceId;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "2" )
@NotNull(message = "用户类型不能为空")
private Integer userType;
@Schema(description = "操作模块", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
@NotEmpty(message = "操作模块不能为空")
private String module;
@Schema(description = "操作模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
@NotEmpty(message = "操作模块类型不能为空")
private String type;
@Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建订单")
@NotEmpty(message = "操作名")
private String name;
@Schema(description = "操作分类,参见 SysOperateLogTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "操作分类不能为空")
private Integer type;
@Schema(description = "操作明细", example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。")
private String content;
@Schema(description = "拓展字段", example = "{'orderId': 1}")
private Map<String, Object> exts;
@NotEmpty(message = "操作名不能为空")
private String subType;
@Schema(description = "操作模块业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "188")
@NotNull(message = "操作模块业务编号不能为空")
private Long bizId;
@Schema(description = "操作内容", requiredMode = Schema.RequiredMode.REQUIRED,
example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码")
@NotEmpty(message = "操作内容不能为空")
private String action;
@Schema(description = "拓展字段", example = "{\"orderId\": \"1\"}")
private String extra;
@Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET")
@NotEmpty(message = "请求方法名不能为空")
private String requestMethod;
@Schema(description = "请求地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "/xxx/yyy")
@Schema(description = "请求地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "/order/get")
@NotEmpty(message = "请求地址不能为空")
private String requestUrl;
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
@Schema(description = "浏览器 UserAgent", requiredMode = Schema.RequiredMode.REQUIRED, example = "Mozilla/5.0")
@NotEmpty(message = "浏览器 UserAgent 不能为空")
@NotEmpty(message = "浏览器 UA 不能为空")
private String userAgent;
@Schema(description = "Java 方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "cn.iocoder.yudao.UserController.save(...)")
@NotEmpty(message = "Java 方法名不能为空")
private String javaMethod;
@Schema(description = "Java 方法的参数")
private String javaMethodArgs;
@Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "开始时间不能为空")
private LocalDateTime startTime;
@Schema(description = "执行时长,单位:毫秒", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "执行时长不能为空")
private Integer duration;
@Schema(description = "结果码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "结果码不能为空")
private Integer resultCode;
@Schema(description = "结果提示")
private String resultMsg;
@Schema(description = "结果数据")
private String resultData;
}

View File

@ -6,11 +6,10 @@ import lombok.Data;
@Schema(name = "RPC 服务 - 操作日志分页 Request DTO")
@Data
public class OperateLogV2PageReqDTO extends PageParam {
public class OperateLogPageReqDTO extends PageParam {
// TODO @puhui999应该用 type
@Schema(description = "模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
private String bizType;
private String type;
@Schema(description = "模块数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "188")
private Long bizId;

View File

@ -10,7 +10,7 @@ import java.time.LocalDateTime;
@Schema(name = "RPC 服务 - 系统操作日志 Response DTO")
@Data
public class OperateLogV2RespDTO implements VO {
public class OperateLogRespDTO implements VO {
@Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;

View File

@ -1,50 +0,0 @@
package cn.iocoder.yudao.module.system.api.logger.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(name = "RPC 服务 - 系统操作日志 Create Request DTO")
@Data
public class OperateLogV2CreateReqDTO {
@Schema(description = "链路追踪编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "89aca178-a370-411c-ae02-3f0d672be4ab")
private String traceId;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "2" )
@NotNull(message = "用户类型不能为空")
private Integer userType;
@Schema(description = "操作模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
@NotEmpty(message = "操作模块类型不能为空")
private String type;
@Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建订单")
@NotEmpty(message = "操作名不能为空")
private String subType;
@Schema(description = "操作模块业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "188")
@NotNull(message = "操作模块业务编号不能为空")
private Long bizId;
@Schema(description = "操作内容", requiredMode = Schema.RequiredMode.REQUIRED,
example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码")
@NotEmpty(message = "操作内容不能为空")
private String action;
@Schema(description = "拓展字段", example = "{\"orderId\": \"1\"}")
private String extra;
@Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET")
@NotEmpty(message = "请求方法名不能为空")
private String requestMethod;
@Schema(description = "请求地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "/order/get")
@NotEmpty(message = "请求地址不能为空")
private String requestUrl;
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
@Schema(description = "浏览器 UserAgent", requiredMode = Schema.RequiredMode.REQUIRED, example = "Mozilla/5.0")
@NotEmpty(message = "浏览器 UA 不能为空")
private String userAgent;
}

View File

@ -4,10 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
@ -29,15 +28,9 @@ public class OperateLogApiImpl implements OperateLogApi {
}
@Override
public CommonResult<Boolean> createOperateLogV2(OperateLogV2CreateReqDTO createReqDTO) {
operateLogService.createOperateLogV2(createReqDTO);
return success(true);
}
@Override
public CommonResult<PageResult<OperateLogV2RespDTO>> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO) {
PageResult<OperateLogV2DO> operateLogPage = operateLogService.getOperateLogPage(pageReqVO);
return success(BeanUtils.toBean(operateLogPage, OperateLogV2RespDTO.class));
public CommonResult<PageResult<OperateLogRespDTO>> getOperateLogPage(OperateLogPageReqDTO pageReqVO) {
PageResult<OperateLogDO> operateLogPage = operateLogService.getOperateLogPage(pageReqVO);
return success(BeanUtils.toBean(operateLogPage, OperateLogRespDTO.class));
}
}

View File

@ -13,20 +13,23 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@Data
public class OperateLogPageReqVO extends PageParam {
@Schema(description = "用户编号", example = "芋道")
private Long userId;
@Schema(description = "操作模块业务编号", example = "1")
private Long bizId;
@Schema(description = "操作模块,模拟匹配", example = "订单")
private String module;
private String type;
@Schema(description = "用户昵称,模拟匹配", example = "芋道")
private String userNickname;
@Schema(description = "操作名,模拟匹配", example = "创建订单")
private String subType;
@Schema(description = "操作分类,参见 OperateLogTypeEnum 枚举类", example = "1")
private Integer type;
@Schema(description = "操作状态", example = "true")
private Boolean success;
@Schema(description = "操作明细,模拟匹配", example = "修改编号为 1 的用户信息")
private String action;
@Schema(description = "开始时间", example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] startTime;
private LocalDateTime[] createTime;
}

View File

@ -1,9 +1,6 @@
package cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fhs.core.trans.anno.Trans;
@ -14,7 +11,6 @@ import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Map;
@Schema(description = "管理后台 - 操作日志 Response VO")
@Data
@ -29,31 +25,29 @@ public class OperateLogRespVO implements VO {
private String traceId;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Trans(type = TransType.SIMPLE, target = AdminUserDO.class, fields = "nickname", ref = "userNickname")
@Trans(type = TransType.SIMPLE, target = AdminUserDO.class, fields = "nickname", ref = "userName")
private Long userId;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@ExcelProperty("操作人")
private String userNickname;
private String userName;
@Schema(description = "操作模块", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
@ExcelProperty("操作模块")
private String module;
@Schema(description = "操作模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
@ExcelProperty("操作模块类型")
private String type;
@Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建订单")
@ExcelProperty("操作名")
private String name;
private String subType;
@Schema(description = "操作分类,参见 OperateLogTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "操作类型", converter = DictConvert.class)
@DictFormat(DictTypeConstants.OPERATE_TYPE)
private Integer type;
@Schema(description = "操作模块业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("操作模块业务编号")
private Long bizId;
@Schema(description = "操作明细", example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。")
private String content;
private String action;
@Schema(description = "拓展字段", example = "{'orderId': 1}")
private Map<String, Object> exts;
private String extra;
@Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET")
@NotEmpty(message = "请求方法名不能为空")
@ -68,28 +62,7 @@ public class OperateLogRespVO implements VO {
@Schema(description = "浏览器 UserAgent", requiredMode = Schema.RequiredMode.REQUIRED, example = "Mozilla/5.0")
private String userAgent;
@Schema(description = "Java 方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "cn.iocoder.yudao.adminserver.UserController.save(...)")
private String javaMethod;
@Schema(description = "Java 方法的参数")
private String javaMethodArgs;
@Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("操作日志")
private LocalDateTime startTime;
@Schema(description = "执行时长,单位:毫秒", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("执行时长")
private Integer duration;
@Schema(description = "结果码", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty(value = "结果码")
private Integer resultCode;
@Schema(description = "结果提示")
private String resultMsg;
@Schema(description = "结果数据")
private String resultData;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -1,19 +1,11 @@
package cn.iocoder.yudao.module.system.dal.dataobject.logger;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 操作日志表
@ -23,19 +15,8 @@ import java.util.Map;
@TableName(value = "system_operate_log", autoResultMap = true)
@KeySequence("system_operate_log_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
public class OperateLogDO extends BaseDO {
/**
* {@link #javaMethodArgs} 的最大长度
*/
public static final Integer JAVA_METHOD_ARGS_MAX_LENGTH = 8000;
/**
* {@link #resultData} 的最大长度
*/
public static final Integer RESULT_MAX_LENGTH = 4000;
/**
* 日志主键
*/
@ -60,30 +41,29 @@ public class OperateLogDO extends BaseDO {
*/
private Integer userType;
/**
* 操作模块
* 操作模块类型
*/
private String module;
private String type;
/**
* 操作名
*/
private String name;
private String subType;
/**
* 操作分类
*
* 枚举 {@link OperateTypeEnum}
* 操作模块业务编号
*/
private Integer type;
private Long bizId;
/**
* 操作内容记录整个操作的明细
* 日志内容记录整个操作的明细
*
* 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
*/
private String content;
private String action;
/**
* 拓展字段有些复杂的业务需要记录一些字段
* 例如说记录订单编号则可以添加 key "orderId"value 为订单编号
* 拓展字段有些复杂的业务需要记录一些字段 ( JSON 格式 )
*
* 例如说记录订单编号{ orderId: "1"}
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> exts;
private String extra;
/**
* 请求方法名
@ -102,43 +82,4 @@ public class OperateLogDO extends BaseDO {
*/
private String userAgent;
/**
* Java 方法名
*/
private String javaMethod;
/**
* Java 方法的参数
*
* 实际格式为 Map<String, Object>
* 不使用 @TableField(typeHandler = FastjsonTypeHandler.class) 注解的原因是数据库存储有长度限制会进行裁剪会导致 JSON 反序列化失败
* 其中key 为参数名value 为参数值
*/
private String javaMethodArgs;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 执行时长单位毫秒
*/
private Integer duration;
/**
* 结果码
*
* 目前使用的 {@link CommonResult#getCode()} 属性
*/
private Integer resultCode;
/**
* 结果提示
*
* 目前使用的 {@link CommonResult#getMsg()} 属性
*/
private String resultMsg;
/**
* 结果数据
*
* 如果是对象则使用 JSON 格式化
*/
private String resultData;
}

View File

@ -1,87 +0,0 @@
package cn.iocoder.yudao.module.system.dal.dataobject.logger;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 操作日志表 V2
*
* @author 芋道源码
*/
@TableName(value = "system_operate_log_v2", autoResultMap = true)
@KeySequence("system_operate_log_seq_v2") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
public class OperateLogV2DO extends BaseDO {
/**
* 日志主键
*/
@TableId
private Long id;
/**
* 链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等结合在一起从而进行排错
*/
private String traceId;
/**
* 用户编号
*
* 关联 MemberUserDO id 属性或者 AdminUserDO id 属性
*/
private Long userId;
/**
* 用户类型
*
* 关联 {@link UserTypeEnum}
*/
private Integer userType;
/**
* 操作模块类型
*/
private String type;
/**
* 操作名
*/
private String subType;
/**
* 操作模块业务编号
*/
private Long bizId;
/**
* 日志内容记录整个操作的明细
*
* 例如说修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
*/
private String action;
/**
* 拓展字段有些复杂的业务需要记录一些字段 ( JSON 格式 )
*
* 例如说记录订单编号{ orderId: "1"}
*/
private String extra;
/**
* 请求方法名
*/
private String requestMethod;
/**
* 请求地址
*/
private String requestUrl;
/**
* 用户 IP
*/
private String userIp;
/**
* 浏览器 UA
*/
private String userAgent;
}

View File

@ -1,31 +1,33 @@
package cn.iocoder.yudao.module.system.dal.mysql.logger;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
@Mapper
public interface OperateLogMapper extends BaseMapperX<OperateLogDO> {
default PageResult<OperateLogDO> selectPage(OperateLogPageReqVO reqVO, Collection<Long> userIds) {
LambdaQueryWrapperX<OperateLogDO> query = new LambdaQueryWrapperX<OperateLogDO>()
.likeIfPresent(OperateLogDO::getModule, reqVO.getModule())
.inIfPresent(OperateLogDO::getUserId, userIds)
.eqIfPresent(OperateLogDO::getType, reqVO.getType())
.betweenIfPresent(OperateLogDO::getStartTime, reqVO.getStartTime());
if (Boolean.TRUE.equals(reqVO.getSuccess())) {
query.eq(OperateLogDO::getResultCode, GlobalErrorCodeConstants.SUCCESS.getCode());
} else if (Boolean.FALSE.equals(reqVO.getSuccess())) {
query.gt(OperateLogDO::getResultCode, GlobalErrorCodeConstants.SUCCESS.getCode());
}
query.orderByDesc(OperateLogDO::getId); // 降序
return selectPage(reqVO, query);
default PageResult<OperateLogDO> selectPage(OperateLogPageReqVO pageReqDTO) {
return selectPage(pageReqDTO, new LambdaQueryWrapperX<OperateLogDO>()
.eqIfPresent(OperateLogDO::getUserId, pageReqDTO.getUserId())
.eqIfPresent(OperateLogDO::getBizId, pageReqDTO.getBizId())
.likeIfPresent(OperateLogDO::getType, pageReqDTO.getType())
.likeIfPresent(OperateLogDO::getSubType, pageReqDTO.getSubType())
.likeIfPresent(OperateLogDO::getAction, pageReqDTO.getAction())
.betweenIfPresent(OperateLogDO::getCreateTime, pageReqDTO.getCreateTime())
.orderByDesc(OperateLogDO::getId));
}
default PageResult<OperateLogDO> selectPage(OperateLogPageReqDTO pageReqDTO) {
return selectPage(pageReqDTO, new LambdaQueryWrapperX<OperateLogDO>()
.eqIfPresent(OperateLogDO::getType, pageReqDTO.getType())
.eqIfPresent(OperateLogDO::getBizId, pageReqDTO.getBizId())
.eqIfPresent(OperateLogDO::getUserId, pageReqDTO.getUserId())
.orderByDesc(OperateLogDO::getId));
}
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.system.dal.mysql.logger;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OperateLogV2Mapper extends BaseMapperX<OperateLogV2DO> {
default PageResult<OperateLogV2DO> selectPage(OperateLogV2PageReqDTO pageReqDTO) {
return selectPage(pageReqDTO, new LambdaQueryWrapperX<OperateLogV2DO>()
.eqIfPresent(OperateLogV2DO::getType, pageReqDTO.getBizType())
.eqIfPresent(OperateLogV2DO::getBizId, pageReqDTO.getBizId())
.eqIfPresent(OperateLogV2DO::getUserId, pageReqDTO.getUserId())
.orderByDesc(OperateLogV2DO::getId));
}
}

View File

@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.system.service.logger;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
/**
* 操作日志 Service 接口
@ -18,7 +16,7 @@ public interface OperateLogService {
/**
* 记录操作日志
*
* @param createReqDTO 操作日志请求
* @param createReqDTO 创建请求
*/
void createOperateLog(OperateLogCreateReqDTO createReqDTO);
@ -30,21 +28,12 @@ public interface OperateLogService {
*/
PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqVO pageReqVO);
// ======================= LOG V2 =======================
/**
* 记录操作日志 V2
*
* @param createReqDTO 创建请求
*/
void createOperateLogV2(OperateLogV2CreateReqDTO createReqDTO);
/**
* 获得操作日志分页列表
*
* @param pageReqVO 分页条件
* @return 操作日志分页列表
*/
PageResult<OperateLogV2DO> getOperateLogPage(OperateLogV2PageReqDTO pageReqVO);
PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqDTO pageReqVO);
}

View File

@ -1,31 +1,17 @@
package cn.iocoder.yudao.module.system.service.logger;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogMapper;
import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogV2Mapper;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO.JAVA_METHOD_ARGS_MAX_LENGTH;
import static cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO.RESULT_MAX_LENGTH;
/**
* 操作日志 Service 实现类
*
@ -38,45 +24,21 @@ public class OperateLogServiceImpl implements OperateLogService {
@Resource
private OperateLogMapper operateLogMapper;
@Resource
private OperateLogV2Mapper operateLogV2Mapper;
@Resource
private AdminUserService userService;
@Override
public void createOperateLog(OperateLogCreateReqDTO createReqDTO) {
OperateLogDO log = BeanUtils.toBean(createReqDTO, OperateLogDO.class);
log.setJavaMethodArgs(StrUtils.maxLength(log.getJavaMethodArgs(), JAVA_METHOD_ARGS_MAX_LENGTH));
log.setResultData(StrUtils.maxLength(log.getResultData(), RESULT_MAX_LENGTH));
operateLogMapper.insert(log);
}
@Override
public PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqVO pageReqVO) {
// 处理基于用户昵称的查询
Collection<Long> userIds = null;
if (StrUtil.isNotEmpty(pageReqVO.getUserNickname())) {
userIds = convertSet(userService.getUserListByNickname(pageReqVO.getUserNickname()), AdminUserDO::getId);
if (CollUtil.isEmpty(userIds)) {
return PageResult.empty();
}
}
// 查询分页
return operateLogMapper.selectPage(pageReqVO, userIds);
}
// ======================= LOG V2 =======================
@Override
public void createOperateLogV2(OperateLogV2CreateReqDTO createReqDTO) {
OperateLogV2DO log = BeanUtils.toBean(createReqDTO, OperateLogV2DO.class);
operateLogV2Mapper.insert(log);
return operateLogMapper.selectPage(pageReqVO);
}
@Override
public PageResult<OperateLogV2DO> getOperateLogPage(OperateLogV2PageReqDTO pageReqDTO) {
return operateLogV2Mapper.selectPage(pageReqDTO);
public PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqDTO pageReqDTO) {
return operateLogMapper.selectPage(pageReqDTO);
}
}

View File

@ -1,35 +1,24 @@
package cn.iocoder.yudao.module.system.service.logger;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.logger.OperateLogMapper;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource;
import java.util.Collections;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@Import({OperateLogServiceImpl.class})
public class OperateLogServiceImplTest extends BaseDbUnitTest {
@ -40,13 +29,9 @@ public class OperateLogServiceImplTest extends BaseDbUnitTest {
@Resource
private OperateLogMapper operateLogMapper;
@MockBean
private AdminUserService userService;
@Test
public void testCreateOperateLog() {
OperateLogCreateReqDTO reqVO = RandomUtils.randomPojo(OperateLogCreateReqDTO.class,
o -> o.setExts(MapUtil.<String, Object>builder("orderId", randomLongId()).build()));
OperateLogCreateReqDTO reqVO = RandomUtils.randomPojo(OperateLogCreateReqDTO.class);
// 调研
operateLogServiceImpl.createOperateLog(reqVO);
@ -56,44 +41,38 @@ public class OperateLogServiceImplTest extends BaseDbUnitTest {
}
@Test
public void testGetOperateLogPage() {
// mock用户信息
AdminUserDO user = RandomUtils.randomPojo(AdminUserDO.class, o -> {
o.setNickname("wang");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
});
when(userService.getUserListByNickname("wang")).thenReturn(Collections.singletonList(user));
Long userId = user.getId();
public void testGetOperateLogPage_vo() {
// 构造操作日志
OperateLogDO operateLogDO = RandomUtils.randomPojo(OperateLogDO.class, o -> {
o.setUserId(userId);
o.setUserType(randomEle(UserTypeEnum.values()).getValue());
o.setModule("order");
o.setType(OperateTypeEnum.CREATE.getType());
o.setStartTime(buildTime(2021, 3, 6));
o.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode());
o.setExts(MapUtil.<String, Object>builder("orderId", randomLongId()).build());
o.setUserId(2048L);
o.setBizId(999L);
o.setType("订单");
o.setSubType("创建订单");
o.setAction("修改编号为 1 的用户信息");
o.setCreateTime(buildTime(2021, 3, 6));
});
operateLogMapper.insert(operateLogDO);
// 测试 userId 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setUserId(userId + 1)));
// 测试 module 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setModule("user")));
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setUserId(1024L)));
// 测试 bizId 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setBizId(888L)));
// 测试 type 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setType(OperateTypeEnum.IMPORT.getType())));
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setType("退款")));
// 测试 subType 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setSubType("创建退款")));
// 测试 action 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setAction("修改编号为 1 退款信息")));
// 测试 createTime 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setStartTime(buildTime(2021, 2, 6))));
// 测试 resultCode 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setResultCode(BAD_REQUEST.getCode())));
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setCreateTime(buildTime(2021, 2, 6))));
// 构造调用参数
OperateLogPageReqVO reqVO = new OperateLogPageReqVO();
reqVO.setUserNickname("wang");
reqVO.setModule("order");
reqVO.setType(OperateTypeEnum.CREATE.getType());
reqVO.setStartTime(buildBetweenTime(2021, 3, 5, 2021, 3, 7));
reqVO.setSuccess(true);
reqVO.setUserId(2048L);
reqVO.setBizId(999L);
reqVO.setType("");
reqVO.setSubType("订单");
reqVO.setAction("用户信息");
reqVO.setCreateTime(buildBetweenTime(2021, 3, 5, 2021, 3, 7));
// 调用
PageResult<OperateLogDO> pageResult = operateLogServiceImpl.getOperateLogPage(reqVO);
@ -103,4 +82,34 @@ public class OperateLogServiceImplTest extends BaseDbUnitTest {
assertPojoEquals(operateLogDO, pageResult.getList().get(0));
}
@Test
public void testGetOperateLogPage_dto() {
// 构造操作日志
OperateLogDO operateLogDO = RandomUtils.randomPojo(OperateLogDO.class, o -> {
o.setUserId(2048L);
o.setBizId(999L);
o.setType("订单");
});
operateLogMapper.insert(operateLogDO);
// 测试 userId 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setUserId(1024L)));
// 测试 bizId 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setBizId(888L)));
// 测试 type 不匹配
operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setType("退款")));
// 构造调用参数
OperateLogPageReqDTO reqDTO = new OperateLogPageReqDTO();
reqDTO.setUserId(2048L);
reqDTO.setBizId(999L);
reqDTO.setType("订单");
// 调用
PageResult<OperateLogDO> pageResult = operateLogServiceImpl.getOperateLogPage(reqDTO);
// 断言只查到了一条符合条件的
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(operateLogDO, pageResult.getList().get(0));
}
}

View File

@ -202,22 +202,15 @@ CREATE TABLE IF NOT EXISTS `system_operate_log` (
`trace_id` varchar(64) NOT NULL DEFAULT '',
`user_id` bigint(20) NOT NULL,
"user_type" tinyint not null default '0',
`module` varchar(50) NOT NULL,
`name` varchar(50) NOT NULL,
`type` bigint(4) NOT NULL DEFAULT '0',
`content` varchar(2000) NOT NULL DEFAULT '',
`exts` varchar(512) NOT NULL DEFAULT '',
`type` varchar(50) NOT NULL,
`sub_type` varchar(50) NOT NULL,
`biz_id` bigint(20) NOT NULL,
`action` varchar(2000) NOT NULL DEFAULT '',
`extra` varchar(512) NOT NULL DEFAULT '',
`request_method` varchar(16) DEFAULT '',
`request_url` varchar(255) DEFAULT '',
`user_ip` varchar(50) DEFAULT NULL,
`user_agent` varchar(200) DEFAULT NULL,
`java_method` varchar(512) NOT NULL DEFAULT '',
`java_method_args` varchar(8000) DEFAULT '',
`start_time` datetime NOT NULL,
`duration` int(11) NOT NULL,
`result_code` int(11) NOT NULL DEFAULT '0',
`result_msg` varchar(512) DEFAULT '',
`result_data` varchar(4000) DEFAULT '',
`creator` varchar(64) DEFAULT '',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) DEFAULT '',