From 531d99b3df4a86204f34885d33b5b481e00dc28e Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Wed, 26 Nov 2025 20:08:48 +0800 Subject: [PATCH] feat(common): add API operation logging with AOP and trace ID tracking --- .idea/compiler.xml | 6 +- .idea/modules.xml | 2 - build.gradle.kts | 25 ++-- weblog-module-common/build.gradle.kts | 6 +- .../common/aspect/ApiOperationLog.java | 15 +++ .../common/aspect/ApiOperationLogAspect.java | 109 ++++++++++++++++++ .../com/hanserwei/common/utils/JsonUtils.java | 18 +++ .../hanserwei/web/WeblogWebApplication.java | 2 + .../web/controller/TestController.java | 21 ++++ .../java/com/hanserwei/web/model/User.java | 11 ++ .../src/main/resources/logback-weblog.xml | 5 +- 11 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/aspect/ApiOperationLog.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/aspect/ApiOperationLogAspect.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/utils/JsonUtils.java create mode 100644 weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java create mode 100644 weblog-web/src/main/java/com/hanserwei/web/model/User.java diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 9cdbbc2..dc0d1fa 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -8,8 +8,12 @@ - + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml index 850799d..dea5dca 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,8 +3,6 @@ - - \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 9a39965..17ac4cc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,14 @@ plugins { java + // Spring Boot 插件:声明,但不应用到父工程 id("org.springframework.boot") version "3.5.8" apply false - id("io.spring.dependency-management") version "1.1.7" apply false + + // Spring 的 dependency-management 插件:父工程和子工程都需要 + id("io.spring.dependency-management") version "1.1.7" } group = "com.hanserwei" version = "0.0.1-SNAPSHOT" -description = "weblog-springboot" java { toolchain { @@ -14,12 +16,6 @@ java { } } -buildscript { - repositories { - mavenCentral() - } -} - allprojects { group = "com.hanserwei" version = "0.0.1-SNAPSHOT" @@ -30,15 +26,28 @@ allprojects { } subprojects { + apply(plugin = "java") apply(plugin = "io.spring.dependency-management") java.sourceCompatibility = JavaVersion.VERSION_21 java.targetCompatibility = JavaVersion.VERSION_21 + // ========= 统一版本管理(类似 Maven dependencyManagement)========= + dependencyManagement { + imports { + // Spring Boot 官方 BOM(最重要,统一管理绝大部分依赖版本) + mavenBom("org.springframework.boot:spring-boot-dependencies:3.5.8") + } + } + dependencies { + + // Lombok (compileOnly + annotationProcessor) compileOnly("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") + + // 如果你的测试也使用 Lombok testCompileOnly("org.projectlombok:lombok") testAnnotationProcessor("org.projectlombok:lombok") } diff --git a/weblog-module-common/build.gradle.kts b/weblog-module-common/build.gradle.kts index 14e8e4a..3a9f1ed 100644 --- a/weblog-module-common/build.gradle.kts +++ b/weblog-module-common/build.gradle.kts @@ -8,7 +8,11 @@ dependencies { implementation("com.google.guava:guava:33.5.0-jre") // commons-lang3 implementation("org.apache.commons:commons-lang3:3.20.0") - // test testImplementation("org.springframework.boot:spring-boot-starter-test") + // jackson + implementation("com.fasterxml.jackson.core:jackson-databind") + implementation("com.fasterxml.jackson.core:jackson-core") + // aop + implementation("org.springframework.boot:spring-boot-starter-aop") } diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/aspect/ApiOperationLog.java b/weblog-module-common/src/main/java/com/hanserwei/common/aspect/ApiOperationLog.java new file mode 100644 index 0000000..4b02d8c --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/aspect/ApiOperationLog.java @@ -0,0 +1,15 @@ +package com.hanserwei.common.aspect; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface ApiOperationLog { + /** + * API 功能描述 + * + * @return API 功能描述 + */ + String description() default ""; +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/aspect/ApiOperationLogAspect.java b/weblog-module-common/src/main/java/com/hanserwei/common/aspect/ApiOperationLogAspect.java new file mode 100644 index 0000000..7646d31 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/aspect/ApiOperationLogAspect.java @@ -0,0 +1,109 @@ +package com.hanserwei.common.aspect; + +import com.hanserwei.common.utils.JsonUtils; +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.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Aspect +@Component +@Slf4j +public class ApiOperationLogAspect { + + /** + * 以自定义 @ApiOperationLog 注解为切点,凡是添加 @ApiOperationLog 的方法,都会执行环绕中的代码 + */ + @Pointcut("@annotation(com.hanserwei.common.aspect.ApiOperationLog)") + public void apiOperationLog() { + } + + /** + * 环绕通知处理方法 + * + * @param joinPoint 连接点对象,包含目标方法的信息 + * @return 目标方法执行结果 + * @throws Throwable 目标方法可能抛出的异常 + */ + @Around("apiOperationLog()") + public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { + try { + // 记录请求开始时间 + long startTime = System.currentTimeMillis(); + + // 设置追踪ID到MDC中,用于日志追踪 + MDC.put("traceId", UUID.randomUUID().toString()); + + // 获取被请求的类名和方法名 + String className = joinPoint.getTarget().getClass().getSimpleName(); + String methodName = joinPoint.getSignature().getName(); + + // 获取请求参数 + Object[] args = joinPoint.getArgs(); + // 将请求参数转换为JSON字符串格式 + String argsJsonStr = Arrays.stream(args).map(toJsonStr()).collect(Collectors.joining(", ")); + + // 获取API操作日志注解中的描述信息 + String description = getApiOperationLogDescription(joinPoint); + + // 记录请求开始日志,包括功能描述、请求参数、类名和方法名 + log.info("====== 请求开始: [{}], 入参: {}, 请求类: {}, 请求方法: {} =================================== ", + description, argsJsonStr, className, methodName); + + // 执行目标方法 + Object result = joinPoint.proceed(); + + // 计算方法执行耗时 + long executionTime = System.currentTimeMillis() - startTime; + + // 记录请求结束日志,包括功能描述、执行耗时和返回结果 + log.info("====== 请求结束: [{}], 耗时: {}ms, 出参: {} =================================== ", + description, executionTime, JsonUtils.toJsonString(result)); + + return result; + } finally { + // 清理MDC中的追踪ID + MDC.clear(); + } + } + + /** + * 获取API操作日志注解的描述信息 + * + * @param joinPoint 连接点对象 + * @return 注解中的描述信息 + */ + private String getApiOperationLogDescription(ProceedingJoinPoint joinPoint) { + // 1. 从 ProceedingJoinPoint 获取 MethodSignature + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + + // 2. 使用 MethodSignature 获取当前被注解的 Method + Method method = signature.getMethod(); + + // 3. 从 Method 中提取 ApiOperationLog 注解 + ApiOperationLog apiOperationLog = method.getAnnotation(ApiOperationLog.class); + + // 4. 从 ApiOperationLog 注解中获取 description 属性 + return apiOperationLog.description(); + } + + /** + * 转换对象为JSON字符串的函数 + * + * @return 转换函数 + */ + private Function toJsonStr() { + return JsonUtils::toJsonString; + } + +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/utils/JsonUtils.java b/weblog-module-common/src/main/java/com/hanserwei/common/utils/JsonUtils.java new file mode 100644 index 0000000..bf1cb46 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/utils/JsonUtils.java @@ -0,0 +1,18 @@ +package com.hanserwei.common.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class JsonUtils { + private static final ObjectMapper INSTANCE = new ObjectMapper(); + + public static String toJsonString(Object obj) { + try { + return INSTANCE.writeValueAsString(obj); + } catch (JsonProcessingException e) { + return obj.toString(); + } + } +} diff --git a/weblog-web/src/main/java/com/hanserwei/web/WeblogWebApplication.java b/weblog-web/src/main/java/com/hanserwei/web/WeblogWebApplication.java index 2a93678..e10d4e7 100644 --- a/weblog-web/src/main/java/com/hanserwei/web/WeblogWebApplication.java +++ b/weblog-web/src/main/java/com/hanserwei/web/WeblogWebApplication.java @@ -2,8 +2,10 @@ package com.hanserwei.web; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication +@ComponentScan({"com.hanserwei.*"}) public class WeblogWebApplication { public static void main(String[] args) { SpringApplication.run(WeblogWebApplication.class, args); diff --git a/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java b/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java new file mode 100644 index 0000000..3d5a892 --- /dev/null +++ b/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java @@ -0,0 +1,21 @@ +package com.hanserwei.web.controller; + +import com.hanserwei.common.aspect.ApiOperationLog; +import com.hanserwei.web.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Slf4j +public class TestController { + + @PostMapping("/test") + @ApiOperationLog(description = "测试接口") + public User test(@RequestBody User user) { + // 返参 + return user; + } + +} \ No newline at end of file diff --git a/weblog-web/src/main/java/com/hanserwei/web/model/User.java b/weblog-web/src/main/java/com/hanserwei/web/model/User.java new file mode 100644 index 0000000..f3d64d4 --- /dev/null +++ b/weblog-web/src/main/java/com/hanserwei/web/model/User.java @@ -0,0 +1,11 @@ +package com.hanserwei.web.model; + +import lombok.Data; + +@Data +public class User { + // 用户名 + private String username; + // 性别 + private Integer sex; +} \ No newline at end of file diff --git a/weblog-web/src/main/resources/logback-weblog.xml b/weblog-web/src/main/resources/logback-weblog.xml index b17ce1c..5bf098d 100644 --- a/weblog-web/src/main/resources/logback-weblog.xml +++ b/weblog-web/src/main/resources/logback-weblog.xml @@ -7,7 +7,8 @@ - + @@ -31,7 +32,7 @@ - +