Commit 1fae341cae3ab3667757c2c766b3606fc78bf9e6

Authored by puhui999
1 parent 55a215da

重构操作日志记录实现

Showing 22 changed files with 491 additions and 106 deletions
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.operatelog.config;
2 2  
3   -import cn.iocoder.yudao.framework.operatelog.core.aop.OperateLogAspect;
4   -import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkService;
5   -import cn.iocoder.yudao.framework.operatelog.core.service.OperateLogFrameworkServiceImpl;
6   -import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
7 3 import org.springframework.boot.autoconfigure.AutoConfiguration;
8   -import org.springframework.context.annotation.Bean;
9 4  
10 5 @AutoConfiguration
11 6 public class YudaoOperateLogAutoConfiguration {
12 7  
13   - @Bean
14   - public OperateLogAspect operateLogAspect() {
15   - return new OperateLogAspect();
16   - }
17   -
18   - @Bean
19   - public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
20   - return new OperateLogFrameworkServiceImpl(operateLogApi);
21   - }
  8 + //@Bean
  9 + //public OperateLogAspect operateLogAspect() {
  10 + // return new OperateLogAspect();
  11 + //}
  12 + //
  13 + //@Bean
  14 + //public OperateLogFrameworkService operateLogFrameworkService(OperateLogApi operateLogApi) {
  15 + // return new OperateLogFrameworkServiceImpl(operateLogApi);
  16 + //}
22 17  
23 18 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/config/YudaoOperateLogV2Configuration.java
1 1 package cn.iocoder.yudao.framework.operatelogv2.config;
2 2  
  3 +import cn.iocoder.yudao.framework.operatelogv2.core.aop.OperateLogV2Aspect;
3 4 import cn.iocoder.yudao.framework.operatelogv2.core.service.ILogRecordServiceImpl;
4   -import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
5 5 import com.mzt.logapi.service.ILogRecordService;
6 6 import com.mzt.logapi.starter.annotation.EnableLogRecord;
7 7 import lombok.extern.slf4j.Slf4j;
... ... @@ -21,8 +21,13 @@ public class YudaoOperateLogV2Configuration {
21 21  
22 22 @Bean
23 23 @Primary
24   - public ILogRecordService iLogRecordServiceImpl(OperateLogApi operateLogApi) {
25   - return new ILogRecordServiceImpl(operateLogApi);
  24 + public ILogRecordService iLogRecordServiceImpl() {
  25 + return new ILogRecordServiceImpl();
  26 + }
  27 +
  28 + @Bean
  29 + public OperateLogV2Aspect operateLogV2Aspect() {
  30 + return new OperateLogV2Aspect();
26 31 }
27 32  
28 33 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/aop/OperateLogV2Aspect.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2.core.aop;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.exceptions.ExceptionUtil;
  5 +import cn.hutool.core.util.ArrayUtil;
  6 +import cn.hutool.core.util.ObjUtil;
  7 +import cn.hutool.core.util.StrUtil;
  8 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  9 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  10 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  11 +import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
  12 +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
  13 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  14 +import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
  15 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
  16 +import com.google.common.collect.Maps;
  17 +import com.mzt.logapi.beans.LogRecord;
  18 +import io.swagger.v3.oas.annotations.Operation;
  19 +import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
  21 +import jakarta.servlet.http.HttpServletRequest;
  22 +import jakarta.servlet.http.HttpServletResponse;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.aspectj.lang.ProceedingJoinPoint;
  25 +import org.aspectj.lang.annotation.Around;
  26 +import org.aspectj.lang.annotation.Aspect;
  27 +import org.aspectj.lang.reflect.MethodSignature;
  28 +import org.springframework.core.annotation.AnnotationUtils;
  29 +import org.springframework.validation.BindingResult;
  30 +import org.springframework.web.bind.annotation.RequestMapping;
  31 +import org.springframework.web.bind.annotation.RequestMethod;
  32 +import org.springframework.web.multipart.MultipartFile;
  33 +
  34 +import java.lang.annotation.Annotation;
  35 +import java.lang.reflect.Array;
  36 +import java.time.LocalDateTime;
  37 +import java.util.Arrays;
  38 +import java.util.Collection;
  39 +import java.util.HashMap;
  40 +import java.util.Map;
  41 +import java.util.function.Predicate;
  42 +import java.util.stream.IntStream;
  43 +
  44 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  45 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS;
  46 +import static cn.iocoder.yudao.framework.operatelogv2.core.enums.OperateLogV2Constants.*;
  47 +
  48 +/**
  49 + * 拦截使用 @Operation 注解
  50 + *
  51 + * @author HUIHUI
  52 + */
  53 +@Aspect
  54 +@Slf4j
  55 +public class OperateLogV2Aspect {
  56 +
  57 + /**
  58 + * 用于记录操作内容的上下文
  59 + *
  60 + * @see OperateLogV2CreateReqDTO#getContent()
  61 + */
  62 + private static final ThreadLocal<LogRecord> CONTENT = new ThreadLocal<>();
  63 + /**
  64 + * 用于记录拓展字段的上下文
  65 + *
  66 + * @see OperateLogV2CreateReqDTO#getExtra()
  67 + */
  68 + private static final ThreadLocal<Map<String, Object>> EXTRA = new ThreadLocal<>();
  69 +
  70 + @Resource
  71 + private OperateLogApi operateLogApi;
  72 +
  73 + @Around("@annotation(operation)")
  74 + public Object around(ProceedingJoinPoint joinPoint, Operation operation) throws Throwable {
  75 + RequestMethod requestMethod = obtainFirstMatchRequestMethod(obtainRequestMethod(joinPoint));
  76 + if (requestMethod == RequestMethod.GET) { // 跳过 get 方法
  77 + return joinPoint.proceed();
  78 + }
  79 +
  80 + // 目前,只有管理员,才记录操作日志!所以非管理员,直接调用,不进行记录
  81 + Integer userType = WebFrameworkUtils.getLoginUserType();
  82 + if (ObjUtil.notEqual(userType, UserTypeEnum.ADMIN.getValue())) {
  83 + return joinPoint.proceed();
  84 + }
  85 +
  86 + // 记录开始时间
  87 + LocalDateTime startTime = LocalDateTime.now();
  88 + try {
  89 + // 执行原有方法
  90 + Object result = joinPoint.proceed();
  91 + // 记录正常执行时的操作日志
  92 + this.log(joinPoint, operation, startTime, result, null);
  93 + return result;
  94 + } catch (Throwable exception) {
  95 + this.log(joinPoint, operation, startTime, null, exception);
  96 + throw exception;
  97 + } finally {
  98 + clearThreadLocal();
  99 + }
  100 + }
  101 +
  102 + public static void setContent(LogRecord content) {
  103 + CONTENT.set(content);
  104 + }
  105 +
  106 + public static void addExtra(String key, Object value) {
  107 + if (EXTRA.get() == null) {
  108 + EXTRA.set(new HashMap<>());
  109 + }
  110 + EXTRA.get().put(key, value);
  111 + }
  112 +
  113 + public static void addExtra(Map<String, Object> extra) {
  114 + if (EXTRA.get() == null) {
  115 + EXTRA.set(new HashMap<>());
  116 + }
  117 + EXTRA.get().putAll(extra);
  118 + }
  119 +
  120 + private static void clearThreadLocal() {
  121 + CONTENT.remove();
  122 + EXTRA.remove();
  123 + }
  124 +
  125 + private void log(ProceedingJoinPoint joinPoint, Operation operation,
  126 + LocalDateTime startTime, Object result, Throwable exception) {
  127 + try {
  128 + // 判断不记录的情况(默认没有值就是记录)
  129 + if (EXTRA.get() != null && EXTRA.get().get(ENABLE) != null) {
  130 + return;
  131 + }
  132 + // 真正记录操作日志
  133 + this.log0(joinPoint, operation, startTime, result, exception);
  134 + } catch (Throwable ex) {
  135 + log.error("[log][记录操作日志时,发生异常,其中参数是 joinPoint({}) apiOperation({}) result({}) exception({}) ]",
  136 + joinPoint, operation, result, exception, ex);
  137 + }
  138 + }
  139 +
  140 + private void log0(ProceedingJoinPoint joinPoint, Operation operation,
  141 + LocalDateTime startTime, Object result, Throwable exception) {
  142 + OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO();
  143 + // 补全通用字段
  144 + reqDTO.setTraceId(TracerUtils.getTraceId());
  145 + reqDTO.setStartTime(startTime);
  146 + // 补充用户信息
  147 + fillUserFields(reqDTO);
  148 + // 补全模块信息
  149 + fillModuleFields(reqDTO, joinPoint, operation);
  150 + // 补全请求信息
  151 + fillRequestFields(reqDTO);
  152 + // 补全方法信息
  153 + fillMethodFields(reqDTO, joinPoint, startTime, result, exception);
  154 +
  155 + // 异步记录日志
  156 + operateLogApi.createOperateLogV2(reqDTO);
  157 + }
  158 +
  159 + private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) {
  160 + reqDTO.setUserId(WebFrameworkUtils.getLoginUserId());
  161 + reqDTO.setUserType(WebFrameworkUtils.getLoginUserType());
  162 + }
  163 +
  164 + private static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, ProceedingJoinPoint joinPoint, Operation operation) {
  165 + LogRecord logRecord = CONTENT.get();
  166 + reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
  167 + reqDTO.setContent(logRecord.getAction());// 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
  168 + if (EXTRA.get() != null) {
  169 + reqDTO.setExtra((Map<String, Object>) EXTRA.get().get(EXTRA_KEY)); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
  170 + }
  171 +
  172 + // type 属性
  173 + if (logRecord.getType() != null) {
  174 + reqDTO.setType(logRecord.getType()); // 大模块类型如 crm 客户
  175 + }
  176 + if (StrUtil.isEmpty(reqDTO.getType())) {
  177 + Tag tag = getClassAnnotation(joinPoint, Tag.class);
  178 + if (tag != null) {
  179 + // 优先读取 @Tag 的 name 属性
  180 + if (StrUtil.isNotEmpty(tag.name())) {
  181 + reqDTO.setType(tag.name());
  182 + }
  183 + // 没有的话,读取 @API 的 description 属性
  184 + if (StrUtil.isEmpty(reqDTO.getType()) && ArrayUtil.isNotEmpty(tag.description())) {
  185 + reqDTO.setType(tag.description());
  186 + }
  187 + }
  188 + }
  189 + // subType 属性
  190 + if (logRecord.getSubType() != null) {
  191 + reqDTO.setSubType(logRecord.getSubType());// 操作名称如 转移客户
  192 + }
  193 + if (StrUtil.isEmpty(reqDTO.getSubType()) && operation != null) {
  194 + reqDTO.setSubType(operation.summary());
  195 + }
  196 +
  197 + }
  198 +
  199 + private static void fillRequestFields(OperateLogV2CreateReqDTO reqDTO) {
  200 + // 获得 Request 对象
  201 + HttpServletRequest request = ServletUtils.getRequest();
  202 + if (request == null) {
  203 + return;
  204 + }
  205 + // 补全请求信息
  206 + reqDTO.setRequestMethod(request.getMethod());
  207 + reqDTO.setRequestUrl(request.getRequestURI());
  208 + reqDTO.setUserIp(ServletUtils.getClientIP(request));
  209 + reqDTO.setUserAgent(ServletUtils.getUserAgent(request));
  210 + }
  211 +
  212 + private static void fillMethodFields(OperateLogV2CreateReqDTO reqDTO, ProceedingJoinPoint joinPoint, LocalDateTime startTime,
  213 + Object result, Throwable exception) {
  214 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  215 + reqDTO.setJavaMethod(methodSignature.toString());
  216 + if (EXTRA.get().get(IS_LOG_ARGS) == null) {
  217 + reqDTO.setJavaMethodArgs(obtainMethodArgs(joinPoint));
  218 + }
  219 + if (EXTRA.get().get(IS_LOG_RESULT_DATA) == null) {
  220 + reqDTO.setResultData(obtainResultData(result));
  221 + }
  222 + reqDTO.setDuration((int) (LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis()));
  223 + // (正常)处理 resultCode 和 resultMsg 字段
  224 + if (result instanceof CommonResult) {
  225 + CommonResult<?> commonResult = (CommonResult<?>) result;
  226 + reqDTO.setResultCode(commonResult.getCode());
  227 + reqDTO.setResultMsg(commonResult.getMsg());
  228 + } else {
  229 + reqDTO.setResultCode(SUCCESS.getCode());
  230 + }
  231 + // (异常)处理 resultCode 和 resultMsg 字段
  232 + if (exception != null) {
  233 + reqDTO.setResultCode(INTERNAL_SERVER_ERROR.getCode());
  234 + reqDTO.setResultMsg(ExceptionUtil.getRootCauseMessage(exception));
  235 + }
  236 + }
  237 +
  238 + @SuppressWarnings("SameParameterValue")
  239 + private static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
  240 + return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
  241 + }
  242 +
  243 + private static String obtainMethodArgs(ProceedingJoinPoint joinPoint) {
  244 + // TODO 提升:参数脱敏和忽略
  245 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  246 + String[] argNames = methodSignature.getParameterNames();
  247 + Object[] argValues = joinPoint.getArgs();
  248 + // 拼接参数
  249 + Map<String, Object> args = Maps.newHashMapWithExpectedSize(argValues.length);
  250 + for (int i = 0; i < argNames.length; i++) {
  251 + String argName = argNames[i];
  252 + Object argValue = argValues[i];
  253 + // 被忽略时,标记为 ignore 字符串,避免和 null 混在一起
  254 + args.put(argName, !isIgnoreArgs(argValue) ? argValue : "[ignore]");
  255 + }
  256 + return JsonUtils.toJsonString(args);
  257 + }
  258 +
  259 + private static String obtainResultData(Object result) {
  260 + // TODO 提升:结果脱敏和忽略
  261 + if (result instanceof CommonResult) {
  262 + result = ((CommonResult<?>) result).getData();
  263 + }
  264 + return JsonUtils.toJsonString(result);
  265 + }
  266 +
  267 + private static boolean isIgnoreArgs(Object object) {
  268 + Class<?> clazz = object.getClass();
  269 + // 处理数组的情况
  270 + if (clazz.isArray()) {
  271 + return IntStream.range(0, Array.getLength(object))
  272 + .anyMatch(index -> isIgnoreArgs(Array.get(object, index)));
  273 + }
  274 + // 递归,处理数组、Collection、Map 的情况
  275 + if (Collection.class.isAssignableFrom(clazz)) {
  276 + return ((Collection<?>) object).stream()
  277 + .anyMatch((Predicate<Object>) OperateLogV2Aspect::isIgnoreArgs);
  278 + }
  279 + if (Map.class.isAssignableFrom(clazz)) {
  280 + return isIgnoreArgs(((Map<?, ?>) object).values());
  281 + }
  282 + // obj
  283 + return object instanceof MultipartFile
  284 + || object instanceof HttpServletRequest
  285 + || object instanceof HttpServletResponse
  286 + || object instanceof BindingResult;
  287 + }
  288 +
  289 + private static RequestMethod obtainFirstMatchRequestMethod(RequestMethod[] requestMethods) {
  290 + if (ArrayUtil.isEmpty(requestMethods)) {
  291 + return null;
  292 + }
  293 + // 优先,匹配最优的 POST、PUT、DELETE
  294 + RequestMethod result = obtainFirstLogRequestMethod(requestMethods);
  295 + if (result != null) {
  296 + return result;
  297 + }
  298 + // 然后,匹配次优的 GET
  299 + result = Arrays.stream(requestMethods).filter(requestMethod -> requestMethod == RequestMethod.GET)
  300 + .findFirst().orElse(null);
  301 + if (result != null) {
  302 + return result;
  303 + }
  304 + // 兜底,获得第一个
  305 + return requestMethods[0];
  306 + }
  307 +
  308 + private static RequestMethod[] obtainRequestMethod(ProceedingJoinPoint joinPoint) {
  309 + RequestMapping requestMapping = AnnotationUtils.getAnnotation( // 使用 Spring 的工具类,可以处理 @RequestMapping 别名注解
  310 + ((MethodSignature) joinPoint.getSignature()).getMethod(), RequestMapping.class);
  311 + return requestMapping != null ? requestMapping.method() : new RequestMethod[]{};
  312 + }
  313 +
  314 + private static RequestMethod obtainFirstLogRequestMethod(RequestMethod[] requestMethods) {
  315 + if (ArrayUtil.isEmpty(requestMethods)) {
  316 + return null;
  317 + }
  318 + return Arrays.stream(requestMethods).filter(requestMethod ->
  319 + requestMethod == RequestMethod.POST
  320 + || requestMethod == RequestMethod.PUT
  321 + || requestMethod == RequestMethod.DELETE)
  322 + .findFirst().orElse(null);
  323 + }
  324 +
  325 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/enums/OperateLogV2Constants.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelogv2.core.enums;
  2 +
  3 +/**
  4 + * 操作日志常量接口
  5 + *
  6 + * @author HUIHUI
  7 + */
  8 +public interface OperateLogV2Constants {
  9 +
  10 + // ========== 开关字段-如果没有值默认就是记录 ==========
  11 +
  12 + /**
  13 + * 是否记录日志
  14 + */
  15 + String ENABLE = "enable";
  16 +
  17 + /**
  18 + * 是否记录方法参数
  19 + */
  20 + String IS_LOG_ARGS = "isLogArgs";
  21 +
  22 + /**
  23 + * 是否记录方法结果的数据
  24 + */
  25 + String IS_LOG_RESULT_DATA = "isLogResultData";
  26 +
  27 + // ========== 扩展 ==========
  28 +
  29 + /**
  30 + * 扩展信息
  31 + */
  32 + String EXTRA_KEY = "extra";
  33 +
  34 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/src/main/java/cn/iocoder/yudao/framework/operatelogv2/core/service/ILogRecordServiceImpl.java
1 1 package cn.iocoder.yudao.framework.operatelogv2.core.service;
2 2  
3   -import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
4   -import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
5   -import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
6   -import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
7   -import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
  3 +import cn.iocoder.yudao.framework.operatelogv2.core.aop.OperateLogV2Aspect;
8 4 import com.mzt.logapi.beans.LogRecord;
  5 +import com.mzt.logapi.context.LogRecordContext;
9 6 import com.mzt.logapi.service.ILogRecordService;
10   -import jakarta.servlet.http.HttpServletRequest;
11   -import lombok.RequiredArgsConstructor;
12 7 import lombok.extern.slf4j.Slf4j;
13 8  
14 9 import java.util.Collections;
... ... @@ -17,56 +12,17 @@ import java.util.List;
17 12 /**
18 13 * 操作日志 ILogRecordService 实现类
19 14 *
20   - * 基于 {@link OperateLogApi} 实现,记录操作日志
  15 + * 基于 {@link OperateLogV2Aspect} 实现,记录操作日志
21 16 *
22 17 * @author HUIHUI
23 18 */
24 19 @Slf4j
25   -@RequiredArgsConstructor
26 20 public class ILogRecordServiceImpl implements ILogRecordService {
27 21  
28   - private final OperateLogApi operateLogApi;
29   -
30 22 @Override
31 23 public void record(LogRecord logRecord) {
32   - OperateLogV2CreateReqDTO reqBO = new OperateLogV2CreateReqDTO();
33   - // 补全通用字段
34   - reqBO.setTraceId(TracerUtils.getTraceId());
35   - // 补充用户信息
36   - fillUserFields(reqBO);
37   - // 补全模块信息
38   - fillModuleFields(reqBO, logRecord);
39   - // 补全请求信息
40   - fillRequestFields(reqBO);
41   - // 异步记录日志
42   - operateLogApi.createOperateLogV2(reqBO);
43   - log.info("操作日志 ===> {}", reqBO);
44   - }
45   -
46   - private static void fillUserFields(OperateLogV2CreateReqDTO reqBO) {
47   - reqBO.setUserId(WebFrameworkUtils.getLoginUserId());
48   - reqBO.setUserType(WebFrameworkUtils.getLoginUserType());
49   - }
50   -
51   - public static void fillModuleFields(OperateLogV2CreateReqDTO reqBO, LogRecord logRecord) {
52   - reqBO.setType(logRecord.getType()); // 大模块类型如 crm-客户
53   - reqBO.setSubType(logRecord.getSubType());// 操作名称如 转移客户
54   - reqBO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
55   - reqBO.setContent(logRecord.getAction());// 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
56   - reqBO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
57   - }
58   -
59   - private static void fillRequestFields(OperateLogV2CreateReqDTO reqBO) {
60   - // 获得 Request 对象
61   - HttpServletRequest request = ServletUtils.getRequest();
62   - if (request == null) {
63   - return;
64   - }
65   - // 补全请求信息
66   - reqBO.setRequestMethod(request.getMethod());
67   - reqBO.setRequestUrl(request.getRequestURI());
68   - reqBO.setUserIp(ServletUtils.getClientIP(request));
69   - reqBO.setUserAgent(ServletUtils.getUserAgent(request));
  24 + OperateLogV2Aspect.setContent(logRecord); // 操作日志
  25 + OperateLogV2Aspect.addExtra(LogRecordContext.getVariables()); // 扩展信息
70 26 }
71 27  
72 28 @Override
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
... ... @@ -67,7 +67,7 @@ public class CrmCustomerController {
67 67 }
68 68  
69 69 @PutMapping("/update")
70   - //@Operation(summary = "更新客户")
  70 + @Operation(summary = "更新客户")
71 71 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
72 72 public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
73 73 customerService.updateCustomer(updateReqVO);
... ... @@ -131,7 +131,7 @@ public class CrmCustomerController {
131 131 }
132 132  
133 133 @PutMapping("/transfer")
134   - //@Operation(summary = "客户转移")
  134 + @Operation(summary = "客户转移")
135 135 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
136 136 public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
137 137 customerService.transferCustomer(reqVO, getLoginUserId());
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/package-info.java deleted
1   -package cn.iocoder.yudao.module.crm.framework.bizlog;
2 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmIndustryParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/function/CrmIndustryParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.function;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ... @@ -8,7 +8,6 @@ import org.springframework.stereotype.Component;
8 8  
9 9 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
10 10  
11   -// TODO @puhui999:包名使用 operatelog 更合适哈;
12 11 /**
13 12 * 自定义函数-通过行业编号获取行业信息
14 13 *
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmLevelParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/function/CrmLevelParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.function;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmSourceParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/function/CrmSourceParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.function;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog;
0 2 \ No newline at end of file
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2CreateReqDTO.java
... ... @@ -5,6 +5,9 @@ import jakarta.validation.constraints.NotEmpty;
5 5 import jakarta.validation.constraints.NotNull;
6 6 import lombok.Data;
7 7  
  8 +import java.time.LocalDateTime;
  9 +import java.util.Map;
  10 +
8 11 /**
9 12 * 系统操作日志 Create Req BO
10 13 *
... ... @@ -58,7 +61,7 @@ public class OperateLogV2CreateReqDTO {
58 61 * 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 )
59 62 * 例如说,记录订单编号,{ orderId: "1"}
60 63 */
61   - private String extra;
  64 + private Map<String, Object> extra;
62 65  
63 66 /**
64 67 * 请求方法名
... ... @@ -81,4 +84,38 @@ public class OperateLogV2CreateReqDTO {
81 84 @NotEmpty(message = "浏览器 UA 不能为空")
82 85 private String userAgent;
83 86  
  87 + /**
  88 + * Java 方法名
  89 + */
  90 + private String javaMethod;
  91 + /**
  92 + * Java 方法的参数
  93 + */
  94 + private String javaMethodArgs;
  95 +
  96 + /**
  97 + * 开始时间
  98 + */
  99 + private LocalDateTime startTime;
  100 +
  101 + /**
  102 + * 执行时长,单位:毫秒
  103 + */
  104 + private Integer duration;
  105 +
  106 + /**
  107 + * 结果码
  108 + */
  109 + private Integer resultCode;
  110 +
  111 + /**
  112 + * 结果提示
  113 + */
  114 + private String resultMsg;
  115 +
  116 + /**
  117 + * 结果数据
  118 + */
  119 + private String resultData;
  120 +
84 121 }
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogV2RespDTO.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.system.api.logger.dto;
2 2  
3 3 import lombok.Data;
4 4  
  5 +import java.time.LocalDateTime;
  6 +
5 7 /**
6 8 * 系统操作日志 Resp DTO
7 9 *
... ... @@ -65,8 +67,8 @@ public class OperateLogV2RespDTO {
65 67 private String userAgent;
66 68  
67 69 /**
68   - * 创建时间, 直接返回字符串
  70 + * 创建时间
69 71 */
70   - private String createTime;
  72 + private LocalDateTime createTime;
71 73  
72 74 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java
... ... @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
7 7 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
8 8 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
9 9 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
10   -import cn.iocoder.yudao.module.system.convert.logger.OperateLogConvert;
11 10 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
12 11 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
13 12 import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
... ... @@ -63,7 +62,7 @@ public class OperateLogApiImpl implements OperateLogApi {
63 62 private static List<OperateLogV2RespDTO> setUserInfo(List<OperateLogV2DO> logList, List<AdminUserDO> userList) {
64 63 Map<Long, AdminUserDO> userMap = convertMap(userList, AdminUserDO::getId);
65 64 return convertList(logList, item -> {
66   - OperateLogV2RespDTO respDTO = OperateLogConvert.INSTANCE.convert(item);
  65 + OperateLogV2RespDTO respDTO = BeanUtils.toBean(item, OperateLogV2RespDTO.class);
67 66 findAndThen(userMap, item.getUserId(), user -> {
68 67 respDTO.setUserName(user.getNickname());
69 68 });
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/logger/OperateLogConvert.java
... ... @@ -3,20 +3,15 @@ package cn.iocoder.yudao.module.system.convert.logger;
3 3 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 4 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
7 6 import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogRespVO;
8 7 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
9   -import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
10 8 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
11 9 import org.mapstruct.Mapper;
12   -import org.mapstruct.Mapping;
13 10 import org.mapstruct.factory.Mappers;
14 11  
15 12 import java.util.List;
16 13 import java.util.Map;
17 14  
18   -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
19   -
20 15 @Mapper
21 16 public interface OperateLogConvert {
22 17  
... ... @@ -30,7 +25,4 @@ public interface OperateLogConvert {
30 25 });
31 26 }
32 27  
33   - @Mapping(target = "createTime", source = "logV2DO.createTime", dateFormat = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
34   - OperateLogV2RespDTO convert(OperateLogV2DO logV2DO);
35   -
36 28 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/logger/OperateLogV2DO.java
1 1 package cn.iocoder.yudao.module.system.dal.dataobject.logger;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 6 import com.baomidou.mybatisplus.annotation.KeySequence;
  7 +import com.baomidou.mybatisplus.annotation.TableField;
6 8 import com.baomidou.mybatisplus.annotation.TableId;
7 9 import com.baomidou.mybatisplus.annotation.TableName;
  10 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
8 11 import lombok.Data;
9 12 import lombok.EqualsAndHashCode;
10 13  
  14 +import java.time.LocalDateTime;
  15 +import java.util.Map;
  16 +
11 17 /**
12 18 * 操作日志表 V2
13 19 *
... ... @@ -20,6 +26,16 @@ import lombok.EqualsAndHashCode;
20 26 public class OperateLogV2DO extends BaseDO {
21 27  
22 28 /**
  29 + * {@link #javaMethodArgs} 的最大长度
  30 + */
  31 + public static final Integer JAVA_METHOD_ARGS_MAX_LENGTH = 8000;
  32 +
  33 + /**
  34 + * {@link #resultData} 的最大长度
  35 + */
  36 + public static final Integer RESULT_MAX_LENGTH = 4000;
  37 +
  38 + /**
23 39 * 日志主键
24 40 */
25 41 @TableId
... ... @@ -65,8 +81,8 @@ public class OperateLogV2DO extends BaseDO {
65 81 *
66 82 * 例如说,记录订单编号,{ orderId: "1"}
67 83 */
68   - // TODO @puhui999:看看能不能类似 exts 搞 json 格式;
69   - private String extra;
  84 + @TableField(typeHandler = JacksonTypeHandler.class)
  85 + private Map<String, Object> extra;
70 86 /**
71 87 * 请求方法名
72 88 */
... ... @@ -84,9 +100,43 @@ public class OperateLogV2DO extends BaseDO {
84 100 */
85 101 private String userAgent;
86 102  
87   - // TODO @芋艿:requestUrl、requestMethod
88   - // TODO @芋艿:javaMethod、javaMethodArgs
89   - // TODO @芋艿:startTime、duration
90   - // TODO @芋艿:resultMsg、resultData
  103 + /**
  104 + * Java 方法名
  105 + */
  106 + private String javaMethod;
  107 + /**
  108 + * Java 方法的参数
  109 + *
  110 + * 实际格式为 Map<String, Object>
  111 + * 不使用 @TableField(typeHandler = FastjsonTypeHandler.class) 注解的原因是,数据库存储有长度限制,会进行裁剪,会导致 JSON 反序列化失败
  112 + * 其中,key 为参数名,value 为参数值
  113 + */
  114 + private String javaMethodArgs;
  115 + /**
  116 + * 开始时间
  117 + */
  118 + private LocalDateTime startTime;
  119 + /**
  120 + * 执行时长,单位:毫秒
  121 + */
  122 + private Integer duration;
  123 + /**
  124 + * 结果码
  125 + *
  126 + * 目前使用的 {@link CommonResult#getCode()} 属性
  127 + */
  128 + private Integer resultCode;
  129 + /**
  130 + * 结果提示
  131 + *
  132 + * 目前使用的 {@link CommonResult#getMsg()} 属性
  133 + */
  134 + private String resultMsg;
  135 + /**
  136 + * 结果数据
  137 + *
  138 + * 如果是对象,则使用 JSON 格式化
  139 + */
  140 + private String resultData;
91 141  
92 142 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/logger/OperateLogV2Mapper.java
... ... @@ -4,23 +4,12 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
7   -import cn.iocoder.yudao.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
8 7 import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogV2DO;
9 8 import org.apache.ibatis.annotations.Mapper;
10 9  
11   -import java.util.Collection;
12   -
13 10 @Mapper
14 11 public interface OperateLogV2Mapper extends BaseMapperX<OperateLogV2DO> {
15 12  
16   - default PageResult<OperateLogV2DO> selectPage(OperateLogPageReqVO reqVO, Collection<Long> userIds) {
17   - LambdaQueryWrapperX<OperateLogV2DO> query = new LambdaQueryWrapperX<OperateLogV2DO>()
18   - .likeIfPresent(OperateLogV2DO::getType, reqVO.getModule())
19   - .inIfPresent(OperateLogV2DO::getUserId, userIds);
20   - query.orderByDesc(OperateLogV2DO::getId); // 降序
21   - return selectPage(reqVO, query);
22   - }
23   -
24 13 default PageResult<OperateLogV2DO> selectPage(OperateLogV2PageReqDTO pageReqVO) {
25 14 return selectPage(pageReqVO, new LambdaQueryWrapperX<OperateLogV2DO>()
26 15 .eqIfPresent(OperateLogV2DO::getType, pageReqVO.getBizType())
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/package-info.java deleted
1   -package cn.iocoder.yudao.module.system.framework.bizlog;
2 0 \ No newline at end of file
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/function/AdminUserParseFunction.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/function/AdminUserParseFunction.java
1   -package cn.iocoder.yudao.module.system.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.system.framework.operatelog.function;
2 2  
3 3 import cn.hutool.core.util.ObjUtil;
4 4 import cn.hutool.core.util.StrUtil;
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/function/AreaParseFunction.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/function/AreaParseFunction.java
1   -package cn.iocoder.yudao.module.system.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.system.framework.operatelog.function;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.operatelog;
0 2 \ No newline at end of file
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/logger/OperateLogServiceImpl.java
... ... @@ -71,6 +71,8 @@ public class OperateLogServiceImpl implements OperateLogService {
71 71 @Override
72 72 public void createOperateLogV2(OperateLogV2CreateReqDTO createReqBO) {
73 73 OperateLogV2DO log = BeanUtils.toBean(createReqBO, OperateLogV2DO.class);
  74 + log.setJavaMethodArgs(StrUtils.maxLength(log.getJavaMethodArgs(), JAVA_METHOD_ARGS_MAX_LENGTH));
  75 + log.setResultData(StrUtils.maxLength(log.getResultData(), RESULT_MAX_LENGTH));
74 76 operateLogV2Mapper.insert(log);
75 77 }
76 78  
... ...