controller添加aop记录日志

controller添加aop记录日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.example.demo.demos.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.util.Set;

@Aspect
@Component
public class ControllerLogAspect {
private static final Logger logger = LoggerFactory.getLogger(ControllerLogAspect.class);
private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();

private static final Set<Class<?>> SIMPLE_TYPES = Set.of(
String.class, Integer.class, Long.class, Double.class, Float.class,
Boolean.class, Short.class, Byte.class, Character.class
);

private final ObjectMapper objectMapper;

public ControllerLogAspect(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

@Pointcut("execution(* com.example.demo.controller..*.*(..)) || " +
"execution(* com.example.demo..*Controller.*(..))")
public void controllerPointcut() {}

@Before("controllerPointcut()")
public void logBefore(JoinPoint joinPoint) {
if (!logger.isInfoEnabled()) return;

START_TIME.set(System.currentTimeMillis());

try {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();

logger.info("🚀 进入方法: {}.{}()", className, methodName);

if (args.length > 0) {
logger.info("📋 参数列表:");
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
String typeName = arg != null ? arg.getClass().getSimpleName() : "null";
String argDisplay = convertArgToString(arg);
logger.info(" 参数[{}]: 类型={}, 值={}", i, typeName, argDisplay);
}
} else {
logger.info("📋 无参数");
}
} catch (Exception e) {
logger.warn("记录入参日志时发生异常", e);
}
}

@AfterReturning(pointcut = "controllerPointcut()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
if (!logger.isInfoEnabled()) return;

try {
Long startTime = START_TIME.get();
long executionTime = startTime != null ? System.currentTimeMillis() - startTime : -1;

String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();

logger.info("✅ 方法返回: {}.{}()", className, methodName);
logger.info("⏱️ 执行时间: {}ms", executionTime);

if (result != null) {
String resultJson = convertArgToString(result);
logger.info("📤 返回类型: {}, 值: {}",
result.getClass().getSimpleName(), resultJson);
} else {
logger.info("📤 返回值为null");
}
} catch (Exception e) {
logger.warn("记录出参日志时发生异常", e);
} finally {
START_TIME.remove();
}
}

private String convertArgToString(Object arg) {
if (arg == null) {
return "null";
}

Class<?> clazz = arg.getClass();

// 文件类型特殊处理 - 保留文件名!
if (arg instanceof MultipartFile file) {
return String.format("MultipartFile{filename=%s, size=%d, contentType=%s}",
file.getOriginalFilename(), file.getSize(), file.getContentType());
}

// 文件数组处理
if (arg instanceof MultipartFile[] files) {
StringBuilder sb = new StringBuilder("MultipartFile[");
for (int i = 0; i < files.length; i++) {
if (i > 0) sb.append(", ");
MultipartFile file = files[i];
sb.append(String.format("{filename=%s, size=%d}",
file.getOriginalFilename(), file.getSize()));
}
sb.append("]");
return sb.toString();
}

// 简单类型直接toString
if (clazz.isPrimitive() || SIMPLE_TYPES.contains(clazz)) {
return arg.toString();
}

// 其他复杂对象使用JSON序列化
try {
return objectMapper.writeValueAsString(arg);
} catch (Exception e) {
return String.format("[序列化失败: %s - 原始值: %s]", e.getMessage(), arg.toString());
}
}
}