@llryiop/avatar-boot-cli 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/exam-question-generate-api.md +163 -0
- package/package.json +1 -1
- package/src/prompts.js +3 -3
- package/src/transform.js +1 -1
- package/templates/.claude/skills/avatar-boot-starter-feign/README.md +243 -0
- package/templates/.claude/skills/avatar-boot-starter-feign/SKILL.md +47 -219
- package/templates/.claude/skills/avatar-boot-starter-feign/references//345/212/237/350/203/275/350/257/246/350/247/243.md +65 -0
- package/templates/.claude/skills/avatar-boot-starter-feign/references//345/277/253/351/200/237/346/216/245/345/205/245/346/214/207/345/215/227.md +75 -0
- package/templates/.claude/skills/avatar-boot-starter-feign/references//351/205/215/347/275/256/345/217/202/350/200/203.md +70 -0
- package/templates/.claude/skills/avatar-boot-starter-job/README.md +437 -0
- package/templates/.claude/skills/avatar-boot-starter-job/SKILL.md +35 -414
- package/templates/.claude/skills/avatar-boot-starter-job/references//345/270/270/350/247/201/351/227/256/351/242/230.md +55 -0
- package/templates/.claude/skills/avatar-boot-starter-job/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/351/205/215/347/275/256.md +124 -0
- package/templates/.claude/skills/avatar-boot-starter-job/references//347/233/221/346/216/247/346/214/207/346/240/207.md +72 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/README.md +580 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/SKILL.md +36 -560
- package/templates/.claude/skills/avatar-boot-starter-kafka/references//346/234/200/344/275/263/345/256/236/350/267/265.md +43 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/references//346/240/270/345/277/203/345/212/237/350/203/275.md +117 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/references//351/205/215/347/275/256/345/217/202/350/200/203.md +54 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/README.md +572 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/SKILL.md +40 -550
- package/templates/.claude/skills/avatar-boot-starter-mysql/references//345/256/236/344/275/223/344/270/216/345/212/237/350/203/275.md +96 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/346/225/260/346/215/256/346/272/220.md +91 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/references//351/253/230/347/272/247/347/211/271/346/200/247/344/270/216/351/205/215/347/275/256.md +59 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/README.md +901 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/SKILL.md +40 -879
- package/templates/.claude/skills/avatar-boot-starter-nacos/references//345/212/237/350/203/275/344/275/277/347/224/250.md +134 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/351/205/215/347/275/256.md +96 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/references//346/225/205/351/232/234/346/216/222/346/237/245.md +64 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/README.md +594 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/SKILL.md +52 -570
- package/templates/.claude/skills/avatar-boot-starter-oss/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/351/205/215/347/275/256.md +77 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/references//346/240/270/345/277/203/345/212/237/350/203/275.md +94 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/references//350/247/204/350/214/203/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.md +61 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/README.md +586 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/SKILL.md +42 -566
- package/templates/.claude/skills/avatar-boot-starter-redis/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/351/205/215/347/275/256.md +78 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/references//346/225/260/346/215/256/346/223/215/344/275/234.md +111 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/references//351/253/230/347/272/247/345/212/237/350/203/275.md +90 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/README.md +662 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/SKILL.md +48 -640
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/references//346/240/270/345/277/203/345/212/237/350/203/275.md +101 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/references//351/205/215/347/275/256/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.md +44 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/references//351/253/230/347/272/247/347/211/271/346/200/247.md +71 -0
- package/templates/.claude/skills/avatar-boot-starter-web/README.md +1007 -0
- package/templates/.claude/skills/avatar-boot-starter-web/SKILL.md +150 -1003
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-LogInfo/346/263/250/350/247/243.md +75 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-/345/205/250/345/261/200/345/274/202/345/270/270/345/244/204/347/220/206.md +90 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-/346/214/207/346/240/207/347/233/221/346/216/247.md +74 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-/346/227/245/345/277/227/344/275/223/347/263/273.md +73 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-/350/257/267/346/261/202/344/270/212/344/270/213/346/226/207.md +77 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/277/253/351/200/237/346/216/245/345/205/245/346/214/207/345/215/227.md +52 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//346/263/250/346/204/217/344/272/213/351/241/271.md +68 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//350/207/252/345/256/232/344/271/211/346/211/251/345/261/225/346/214/207/345/215/227.md +107 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//351/205/215/347/275/256/345/217/202/350/200/203.md +107 -0
- package/templates/.claude/skills/crud-generator/SKILL.md +133 -64
- package/templates/.claude/skills/database-design/README.md +207 -0
- package/templates/.claude/skills/database-design/SKILL.md +469 -82
- package/templates/.claude/skills/database-design/references//345/221/275/345/220/215/350/247/204/350/214/203.md +232 -0
- package/templates/.claude/skills/database-design/references//345/255/227/346/256/265/347/261/273/345/236/213/350/247/204/350/214/203.md +400 -0
- package/templates/.claude/skills/database-design/references//347/264/242/345/274/225/350/247/204/350/214/203.md +506 -0
- package/templates/README.md +65 -100
- package/templates/avatar-scaffold-api/pom.xml +0 -5
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/api/LoginFeignClient.java +2 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/exception/LoginErrorCode.java +25 -0
- package/templates/avatar-scaffold-service/pom.xml +25 -87
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/feign/DemoFeign.java +4 -1
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/UserLoginRepository.java +10 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/mapper/UserLoginMapper.java +4 -1
- package/templates/avatar-scaffold-service/src/main/resources/application-dev.yaml +3 -5
- package/templates/avatar-scaffold-service/src/main/resources/application-local.yaml +21 -21
- package/templates/pom.xml +9 -18
|
@@ -0,0 +1,1007 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: avatar-boot-starter-web
|
|
3
|
+
description: 当使用 avatar-boot-starter-web 模块时使用此技能 - 为 Spring Boot 3.5.3 应用提供全面的 Web 开发基础设施,包括请求上下文管理、结构化日志、全局异常处理、指标集成。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Avatar Boot Starter Web 技能
|
|
7
|
+
|
|
8
|
+
## 何时使用此技能
|
|
9
|
+
|
|
10
|
+
当用户出现以下情况时调用此技能:
|
|
11
|
+
|
|
12
|
+
- 提到 "avatar-boot-starter-web"、"web starter" 或 "web 模块"
|
|
13
|
+
- 询问请求上下文、TraceId 或请求追踪
|
|
14
|
+
- 想要配置或排查 Avatar Boot 应用中的日志问题
|
|
15
|
+
- 询问全局异常处理或错误响应
|
|
16
|
+
- 需要为 Web 应用配置 CORS
|
|
17
|
+
- 想要检测或监控慢请求
|
|
18
|
+
- 询问客户端 IP 提取或代理处理
|
|
19
|
+
- 提到 `@LogInfo` 注解或方法级日志
|
|
20
|
+
- 想要集成 Micrometer 指标/监控
|
|
21
|
+
- 正在使用 Avatar Boot 创建新的 Web 应用
|
|
22
|
+
- 询问结构化日志或 JSON 日志格式
|
|
23
|
+
- 需要在业务逻辑中访问请求上下文
|
|
24
|
+
- 排查 TraceId 传播问题
|
|
25
|
+
- 询问异步场景下的 TraceID 传播(1.0.0-SNAPSHOT+)
|
|
26
|
+
- 使用 @Async、CompletableFuture 或自定义线程池(1.0.0-SNAPSHOT+)
|
|
27
|
+
- 想要自定义 TraceID 生成策略(1.0.0-SNAPSHOT+)
|
|
28
|
+
- 需要集成 Micrometer Tracing 或分布式追踪系统(1.0.0-SNAPSHOT+)
|
|
29
|
+
|
|
30
|
+
## 模块概述
|
|
31
|
+
|
|
32
|
+
`avatar-boot-starter-web` 模块是一个 Spring Boot starter,为 Avatar Boot 应用提供生产就绪的 Web 开发基础设施。它遵循 Spring Boot 自动配置约定,无需任何配置即可开始使用。
|
|
33
|
+
|
|
34
|
+
**核心能力:**
|
|
35
|
+
- 自动初始化请求上下文并生成 TraceId
|
|
36
|
+
- 结构化日志并自动注入 TraceId
|
|
37
|
+
- 全局异常处理并返回标准化响应
|
|
38
|
+
- 使用 Micrometer 集成指标
|
|
39
|
+
- CORS 支持(默认禁用)
|
|
40
|
+
- 慢请求检测和监控
|
|
41
|
+
- 代理感知的客户端 IP 提取
|
|
42
|
+
|
|
43
|
+
## 核心功能
|
|
44
|
+
|
|
45
|
+
### 1. 请求上下文管理
|
|
46
|
+
|
|
47
|
+
**功能说明:**
|
|
48
|
+
- 为每个 HTTP 请求自动初始化 `AvatarContext`
|
|
49
|
+
- 从 `X-Trace-Id` 请求头生成或传播 TraceId
|
|
50
|
+
- 提取客户端 IP(代理感知,支持 X-Forwarded-For)
|
|
51
|
+
- 追踪请求耗时并检测慢请求
|
|
52
|
+
- 自动清理 ThreadLocal 以防止内存泄漏
|
|
53
|
+
|
|
54
|
+
**核心组件:**
|
|
55
|
+
- `RequestContextInterceptor` - 拦截所有请求(可配置路径)
|
|
56
|
+
- `AvatarContext` - 保存请求元数据(traceId、uri、method、clientIp、startTime)
|
|
57
|
+
- `ContextManager` - 基于 ThreadLocal 的上下文访问器
|
|
58
|
+
|
|
59
|
+
**使用示例:**
|
|
60
|
+
引入pom依赖:
|
|
61
|
+
```xml
|
|
62
|
+
<dependency>
|
|
63
|
+
<groupId>com.iflytek.avatar.boot</groupId>
|
|
64
|
+
<artifactId>avatar-boot-starter-web</artifactId>
|
|
65
|
+
<version>${avatar.boot.version}</version>
|
|
66
|
+
</dependency>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```java
|
|
70
|
+
import com.iflytek.avatar.boot.context.AvatarContext;
|
|
71
|
+
import com.iflytek.avatar.boot.context.ContextManager;
|
|
72
|
+
|
|
73
|
+
@RestController
|
|
74
|
+
public class UserController {
|
|
75
|
+
|
|
76
|
+
@GetMapping("/user/info")
|
|
77
|
+
public Result<UserInfo> getUserInfo() {
|
|
78
|
+
// 访问当前请求上下文
|
|
79
|
+
AvatarContext context = ContextManager.get();
|
|
80
|
+
String traceId = context.getTraceId();
|
|
81
|
+
String clientIp = context.getClientIp();
|
|
82
|
+
|
|
83
|
+
log.info("Processing request for user from IP: {}", clientIp);
|
|
84
|
+
return Result.success(userInfo);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 2. 结构化日志
|
|
90
|
+
|
|
91
|
+
**功能说明:**
|
|
92
|
+
- 自动将 TraceId 注入所有日志消息
|
|
93
|
+
- 使用 Logstash Logback Encoder 提供 JSON 日志格式
|
|
94
|
+
- 支持多种日志输出(控制台、文件、JSON 文件)
|
|
95
|
+
- 使用异步 appender 实现高性能
|
|
96
|
+
- 自动为慢请求添加 `[SLOW]` 前缀标记
|
|
97
|
+
|
|
98
|
+
**日志格式:**
|
|
99
|
+
|
|
100
|
+
控制台/文件格式:
|
|
101
|
+
```
|
|
102
|
+
2026-03-03 17:04:15.123 [http-nio-8080-exec-1] [abc123def456] INFO c.i.a.b.controller.UserController - Request started: GET /user/info from 192.168.1.100
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
JSON 格式:
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"timestamp": "2026-03-03T17:04:15.123+08:00",
|
|
109
|
+
"level": "INFO",
|
|
110
|
+
"thread": "http-nio-8080-exec-1",
|
|
111
|
+
"logger": "com.iflytek.avatar.boot.controller.UserController",
|
|
112
|
+
"message": "Request started: GET /user/info from 192.168.1.100",
|
|
113
|
+
"traceId": "abc123def456",
|
|
114
|
+
"app": "avatar-app"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**日志文件:**
|
|
119
|
+
- `logs/${spring.application.name}.log` - 所有日志
|
|
120
|
+
- `logs/${spring.application.name}-error.log` - 仅错误日志
|
|
121
|
+
- `logs/${spring.application.name}-json.log` - JSON 格式日志
|
|
122
|
+
|
|
123
|
+
**环境特定行为:**
|
|
124
|
+
- **dev/local**:控制台 + 文件输出
|
|
125
|
+
- **test**:控制台 + 文件 + JSON 输出
|
|
126
|
+
- **prod**:仅文件 + JSON 输出(无控制台)
|
|
127
|
+
|
|
128
|
+
### 3. @LogInfo 注解
|
|
129
|
+
|
|
130
|
+
**功能说明:**
|
|
131
|
+
- 通过 AOP 提供方法级日志
|
|
132
|
+
- 记录请求参数和响应结果
|
|
133
|
+
- 追踪方法执行时间
|
|
134
|
+
- 集成指标记录
|
|
135
|
+
- 自动记录异常及堆栈信息
|
|
136
|
+
|
|
137
|
+
**注解参数:**
|
|
138
|
+
```java
|
|
139
|
+
@LogInfo(
|
|
140
|
+
request = true, // 记录请求参数(默认:false)
|
|
141
|
+
response = true, // 记录响应结果(默认:false)
|
|
142
|
+
metrics = true, // 记录指标(默认:false)
|
|
143
|
+
costTime = 3000 // 慢方法阈值(毫秒)(默认:3000)
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**使用示例:**
|
|
148
|
+
```java
|
|
149
|
+
@Service
|
|
150
|
+
public class UserService {
|
|
151
|
+
|
|
152
|
+
@LogInfo(request = true, response = true, metrics = true, costTime = 2000)
|
|
153
|
+
public UserInfo getUserById(Long userId) {
|
|
154
|
+
// 业务逻辑
|
|
155
|
+
return userInfo;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**日志输出:**
|
|
161
|
+
```
|
|
162
|
+
[UserService.getUserById] Status: SUCCESS, Time: 156ms
|
|
163
|
+
→ Request: [123]
|
|
164
|
+
→ Response: {"id":123,"name":"John Doe"}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**慢方法警告:**
|
|
168
|
+
```
|
|
169
|
+
[UserService.getUserById] Status: SUCCESS, Time: 2345ms (⚠️ threshold: 2000ms)
|
|
170
|
+
→ Request: [123]
|
|
171
|
+
→ Response: {"id":123,"name":"John Doe"}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 4. 全局异常处理
|
|
175
|
+
|
|
176
|
+
**功能说明:**
|
|
177
|
+
- 捕获所有异常并返回标准化的 `Result<?>` 响应
|
|
178
|
+
- 处理业务异常、参数校验错误和系统错误
|
|
179
|
+
- 记录异常及请求上下文(URI、方法)
|
|
180
|
+
- 返回适当的 HTTP 状态码
|
|
181
|
+
|
|
182
|
+
**支持的异常类型:**
|
|
183
|
+
- `BusinessException` - 业务逻辑错误(HTTP 200 带错误码)
|
|
184
|
+
- `MethodArgumentNotValidException` - @RequestBody 校验(HTTP 400)
|
|
185
|
+
- `BindException` - @ModelAttribute 校验(HTTP 400)
|
|
186
|
+
- `ConstraintViolationException` - @RequestParam 校验(HTTP 400)
|
|
187
|
+
- `RuntimeException` - 运行时错误(HTTP 500)
|
|
188
|
+
- `Exception` - 所有其他异常(HTTP 500)
|
|
189
|
+
|
|
190
|
+
**使用示例:**
|
|
191
|
+
```java
|
|
192
|
+
import com.iflytek.avatar.boot.exception.BusinessException;
|
|
193
|
+
import com.iflytek.avatar.boot.entity.response.ResultCode;
|
|
194
|
+
|
|
195
|
+
@Service
|
|
196
|
+
public class UserService {
|
|
197
|
+
|
|
198
|
+
public void updateUser(Long userId) {
|
|
199
|
+
if (userId == null) {
|
|
200
|
+
throw new BusinessException(ResultCode.PARAM_ERROR, "用户ID不能为空");
|
|
201
|
+
}
|
|
202
|
+
// 业务逻辑
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**响应格式:**
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"code": 400,
|
|
211
|
+
"message": "用户ID不能为空",
|
|
212
|
+
"data": null,
|
|
213
|
+
"success": false
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 5. 指标集成
|
|
218
|
+
|
|
219
|
+
**功能说明:**
|
|
220
|
+
- 记录方法调用次数和执行时间
|
|
221
|
+
- 使用 Micrometer 进行指标收集
|
|
222
|
+
- 缓存指标实例以避免重复注册
|
|
223
|
+
- 支持成功/失败状态追踪
|
|
224
|
+
|
|
225
|
+
**记录的指标:**
|
|
226
|
+
- `method.invocation.count` - 计数器,标签:class、method、status
|
|
227
|
+
- `method.execution.time` - 计时器,标签:class、method、status
|
|
228
|
+
|
|
229
|
+
**使用方式:**
|
|
230
|
+
使用 `@LogInfo(metrics = true)` 时会自动记录指标。
|
|
231
|
+
|
|
232
|
+
**手动记录:**
|
|
233
|
+
```java
|
|
234
|
+
@Autowired
|
|
235
|
+
private MetricsRecorder metricsRecorder;
|
|
236
|
+
|
|
237
|
+
public void someMethod() {
|
|
238
|
+
long startTime = System.currentTimeMillis();
|
|
239
|
+
try {
|
|
240
|
+
// 业务逻辑
|
|
241
|
+
long costTime = System.currentTimeMillis() - startTime;
|
|
242
|
+
metricsRecorder.recordMethodMetrics("MyClass", "someMethod", costTime, null);
|
|
243
|
+
} catch (Exception e) {
|
|
244
|
+
long costTime = System.currentTimeMillis() - startTime;
|
|
245
|
+
metricsRecorder.recordMethodMetrics("MyClass", "someMethod", costTime, e);
|
|
246
|
+
throw e;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 6. CORS 支持
|
|
252
|
+
|
|
253
|
+
**功能说明:**
|
|
254
|
+
- 提供可配置的跨域资源共享
|
|
255
|
+
- 出于安全考虑默认禁用
|
|
256
|
+
- 支持自定义源、方法、请求头和凭证
|
|
257
|
+
|
|
258
|
+
**配置示例:**
|
|
259
|
+
```yaml
|
|
260
|
+
avatar:
|
|
261
|
+
web:
|
|
262
|
+
cors-enabled: true
|
|
263
|
+
cors:
|
|
264
|
+
allowed-origins:
|
|
265
|
+
- "https://example.com"
|
|
266
|
+
- "https://app.example.com"
|
|
267
|
+
allowed-methods:
|
|
268
|
+
- GET
|
|
269
|
+
- POST
|
|
270
|
+
- PUT
|
|
271
|
+
- DELETE
|
|
272
|
+
allowed-headers:
|
|
273
|
+
- "*"
|
|
274
|
+
exposed-headers:
|
|
275
|
+
- X-Trace-Id
|
|
276
|
+
allow-credentials: true
|
|
277
|
+
max-age: 3600
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### 7. TraceID 异步支持与 Micrometer 集成
|
|
281
|
+
|
|
282
|
+
**版本要求:** Avatar Boot 1.0.0-SNAPSHOT+
|
|
283
|
+
|
|
284
|
+
**功能说明:**
|
|
285
|
+
- 使用 TransmittableThreadLocal 自动传播 TraceID 到异步线程
|
|
286
|
+
- 支持 @Async、CompletableFuture、自定义线程池
|
|
287
|
+
- 可插拔的 TraceID 生成器(UUID、Snowflake、自定义)
|
|
288
|
+
- 可选的 Micrometer Tracing 集成(支持 Zipkin、Jaeger)
|
|
289
|
+
- 向后兼容,无需修改现有代码
|
|
290
|
+
|
|
291
|
+
**核心改进:**
|
|
292
|
+
|
|
293
|
+
#### 7.1 异步场景自动支持
|
|
294
|
+
|
|
295
|
+
**问题:** 之前使用普通 ThreadLocal,无法在异步线程中传播 TraceID
|
|
296
|
+
|
|
297
|
+
**解决方案:**
|
|
298
|
+
- 使用阿里巴巴的 TransmittableThreadLocal (TTL)
|
|
299
|
+
- 自动配置 TaskDecorator 装饰异步任务
|
|
300
|
+
- 支持 @Async、CompletableFuture、自定义线程池
|
|
301
|
+
|
|
302
|
+
**使用示例:**
|
|
303
|
+
|
|
304
|
+
```java
|
|
305
|
+
@Service
|
|
306
|
+
public class UserService {
|
|
307
|
+
|
|
308
|
+
@Async
|
|
309
|
+
public CompletableFuture<User> getUserAsync(String userId) {
|
|
310
|
+
// TraceID 自动传播到异步线程
|
|
311
|
+
String traceId = ContextManager.get().getTraceId();
|
|
312
|
+
log.info("Async thread TraceID: {}", traceId);
|
|
313
|
+
|
|
314
|
+
User user = userRepository.findById(userId);
|
|
315
|
+
return CompletableFuture.completedFuture(user);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**日志输出:**
|
|
321
|
+
```
|
|
322
|
+
[main] [abc123] Request started: GET /users/1
|
|
323
|
+
[avatar-async-1] [abc123] Async thread TraceID: abc123
|
|
324
|
+
[main] [abc123] Request completed: GET /users/1 - 150ms
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### 7.2 可插拔的 TraceID 生成器
|
|
328
|
+
|
|
329
|
+
**默认实现:** 使用 UUID(去除连字符)
|
|
330
|
+
|
|
331
|
+
**自定义实现:**
|
|
332
|
+
|
|
333
|
+
```java
|
|
334
|
+
@Component
|
|
335
|
+
public class SnowflakeTraceIdGenerator implements TraceIdGenerator {
|
|
336
|
+
|
|
337
|
+
private final Snowflake snowflake = IdUtil.getSnowflake(1, 1);
|
|
338
|
+
|
|
339
|
+
@Override
|
|
340
|
+
public String generate() {
|
|
341
|
+
return String.valueOf(snowflake.nextId());
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### 7.3 Micrometer Tracing 集成(可选)
|
|
347
|
+
|
|
348
|
+
**启用方式:**
|
|
349
|
+
|
|
350
|
+
```yaml
|
|
351
|
+
avatar:
|
|
352
|
+
web:
|
|
353
|
+
tracing:
|
|
354
|
+
micrometer-enabled: true
|
|
355
|
+
|
|
356
|
+
management:
|
|
357
|
+
tracing:
|
|
358
|
+
enabled: true
|
|
359
|
+
sampling:
|
|
360
|
+
probability: 1.0
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**收益:**
|
|
364
|
+
- 与 Spring Cloud 生态无缝集成
|
|
365
|
+
- 支持 Zipkin、Jaeger 等分布式追踪系统
|
|
366
|
+
- 自动传播 W3C Trace Context 和 B3 格式
|
|
367
|
+
|
|
368
|
+
**异步配置:**
|
|
369
|
+
|
|
370
|
+
```yaml
|
|
371
|
+
avatar:
|
|
372
|
+
web:
|
|
373
|
+
async:
|
|
374
|
+
enabled: true # 是否启用异步支持(默认:true)
|
|
375
|
+
core-pool-size: 10 # 核心线程数
|
|
376
|
+
max-pool-size: 50 # 最大线程数
|
|
377
|
+
queue-capacity: 200 # 队列容量
|
|
378
|
+
thread-name-prefix: avatar-async-
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**追踪配置:**
|
|
382
|
+
|
|
383
|
+
```yaml
|
|
384
|
+
avatar:
|
|
385
|
+
web:
|
|
386
|
+
tracing:
|
|
387
|
+
micrometer-enabled: false # 是否启用 Micrometer 集成(默认:false)
|
|
388
|
+
generator-type: uuid # TraceID 生成策略(uuid, snowflake, custom)
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**性能影响:**
|
|
392
|
+
- 内存开销:每个异步任务额外复制一份上下文(约 1KB)
|
|
393
|
+
- CPU 开销:上下文复制和恢复(< 1ms)
|
|
394
|
+
- 总体影响:< 5%(压测验证)
|
|
395
|
+
|
|
396
|
+
**依赖版本:**
|
|
397
|
+
- TransmittableThreadLocal: 2.14.5
|
|
398
|
+
- Micrometer Tracing: 1.4.3(可选)
|
|
399
|
+
|
|
400
|
+
## 配置指南
|
|
401
|
+
|
|
402
|
+
### 基础配置(最小化)
|
|
403
|
+
|
|
404
|
+
```yaml
|
|
405
|
+
spring:
|
|
406
|
+
application:
|
|
407
|
+
name: my-app
|
|
408
|
+
|
|
409
|
+
# 所有功能默认启用,无需额外配置
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### 高级配置(所有选项)
|
|
413
|
+
|
|
414
|
+
```yaml
|
|
415
|
+
avatar:
|
|
416
|
+
web:
|
|
417
|
+
# 功能开关
|
|
418
|
+
interceptor-enabled: true # 启用请求拦截器(默认:true)
|
|
419
|
+
exception-handler-enabled: true # 启用全局异常处理器(默认:true)
|
|
420
|
+
cors-enabled: false # 启用 CORS(默认:false)
|
|
421
|
+
|
|
422
|
+
# 拦截器配置
|
|
423
|
+
interceptor:
|
|
424
|
+
trace-id-header: X-Trace-Id # TraceId 请求头名称(默认:X-Trace-Id)
|
|
425
|
+
include-paths: # 拦截路径(默认:/**)
|
|
426
|
+
- /**
|
|
427
|
+
exclude-paths: # 排除路径
|
|
428
|
+
- /error
|
|
429
|
+
- /actuator/**
|
|
430
|
+
- /favicon.ico
|
|
431
|
+
- /webjars/**
|
|
432
|
+
- /swagger-ui/**
|
|
433
|
+
- /v3/api-docs/**
|
|
434
|
+
|
|
435
|
+
# 日志配置
|
|
436
|
+
log:
|
|
437
|
+
request-enabled: true # 记录请求开始(默认:true)
|
|
438
|
+
response-enabled: true # 记录请求完成(默认:true)
|
|
439
|
+
slow-request-threshold: 3000 # 慢请求阈值(毫秒)(默认:3000)
|
|
440
|
+
|
|
441
|
+
# 异步配置(1.0.0-SNAPSHOT+)
|
|
442
|
+
async:
|
|
443
|
+
enabled: true # 启用异步支持(默认:true)
|
|
444
|
+
core-pool-size: 10 # 核心线程数(默认:10)
|
|
445
|
+
max-pool-size: 50 # 最大线程数(默认:50)
|
|
446
|
+
queue-capacity: 200 # 队列容量(默认:200)
|
|
447
|
+
thread-name-prefix: avatar-async- # 线程名前缀(默认:avatar-async-)
|
|
448
|
+
|
|
449
|
+
# 追踪配置(1.0.0-SNAPSHOT+)
|
|
450
|
+
tracing:
|
|
451
|
+
micrometer-enabled: false # 启用 Micrometer 集成(默认:false)
|
|
452
|
+
generator-type: uuid # TraceID 生成策略(uuid, snowflake, custom)
|
|
453
|
+
|
|
454
|
+
# CORS 配置(仅当 cors-enabled: true 时生效)
|
|
455
|
+
cors:
|
|
456
|
+
allowed-origins:
|
|
457
|
+
- "*"
|
|
458
|
+
allowed-methods:
|
|
459
|
+
- GET
|
|
460
|
+
- POST
|
|
461
|
+
- PUT
|
|
462
|
+
- DELETE
|
|
463
|
+
- OPTIONS
|
|
464
|
+
allowed-headers:
|
|
465
|
+
- "*"
|
|
466
|
+
exposed-headers:
|
|
467
|
+
- X-Trace-Id
|
|
468
|
+
allow-credentials: true
|
|
469
|
+
max-age: 3600
|
|
470
|
+
|
|
471
|
+
# 日志配置
|
|
472
|
+
logging:
|
|
473
|
+
level:
|
|
474
|
+
root: INFO
|
|
475
|
+
com.iflytek.avatar.boot: DEBUG
|
|
476
|
+
file:
|
|
477
|
+
path: logs
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### 环境特定配置
|
|
481
|
+
|
|
482
|
+
**开发环境(dev):**
|
|
483
|
+
```yaml
|
|
484
|
+
spring:
|
|
485
|
+
config:
|
|
486
|
+
activate:
|
|
487
|
+
on-profile: dev
|
|
488
|
+
|
|
489
|
+
avatar:
|
|
490
|
+
web:
|
|
491
|
+
log:
|
|
492
|
+
slow-request-threshold: 5000 # 开发环境放宽阈值
|
|
493
|
+
|
|
494
|
+
logging:
|
|
495
|
+
level:
|
|
496
|
+
root: INFO
|
|
497
|
+
com.iflytek.avatar.boot: DEBUG
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**生产环境(prod):**
|
|
501
|
+
```yaml
|
|
502
|
+
spring:
|
|
503
|
+
config:
|
|
504
|
+
activate:
|
|
505
|
+
on-profile: prod
|
|
506
|
+
|
|
507
|
+
avatar:
|
|
508
|
+
web:
|
|
509
|
+
cors-enabled: false # 生产环境禁用 CORS
|
|
510
|
+
log:
|
|
511
|
+
slow-request-threshold: 2000 # 生产环境更严格的阈值
|
|
512
|
+
|
|
513
|
+
logging:
|
|
514
|
+
level:
|
|
515
|
+
root: INFO
|
|
516
|
+
com.iflytek.avatar.boot: INFO
|
|
517
|
+
file:
|
|
518
|
+
path: /var/log/app
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## 常见使用场景
|
|
522
|
+
|
|
523
|
+
### 场景 1:API 网关集成
|
|
524
|
+
|
|
525
|
+
在 API 网关(Kong、Nginx)后构建服务时:
|
|
526
|
+
|
|
527
|
+
```yaml
|
|
528
|
+
avatar:
|
|
529
|
+
web:
|
|
530
|
+
interceptor:
|
|
531
|
+
trace-id-header: X-Request-Id # 匹配网关的请求头名称
|
|
532
|
+
exclude-paths:
|
|
533
|
+
- /health
|
|
534
|
+
- /metrics
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 场景 2:微服务分布式追踪
|
|
538
|
+
|
|
539
|
+
```java
|
|
540
|
+
@Service
|
|
541
|
+
public class OrderService {
|
|
542
|
+
|
|
543
|
+
@Autowired
|
|
544
|
+
private RestTemplate restTemplate;
|
|
545
|
+
|
|
546
|
+
public void createOrder(OrderRequest request) {
|
|
547
|
+
// TraceId 自动可用
|
|
548
|
+
AvatarContext context = ContextManager.get();
|
|
549
|
+
String traceId = context.getTraceId();
|
|
550
|
+
|
|
551
|
+
// 将 TraceId 传播到下游服务
|
|
552
|
+
HttpHeaders headers = new HttpHeaders();
|
|
553
|
+
headers.set("X-Trace-Id", traceId);
|
|
554
|
+
|
|
555
|
+
HttpEntity<OrderRequest> entity = new HttpEntity<>(request, headers);
|
|
556
|
+
restTemplate.postForObject("http://inventory-service/api/reserve", entity, Void.class);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### 场景 3:性能监控
|
|
562
|
+
|
|
563
|
+
```java
|
|
564
|
+
@RestController
|
|
565
|
+
@RequestMapping("/api/reports")
|
|
566
|
+
public class ReportController {
|
|
567
|
+
|
|
568
|
+
@LogInfo(request = true, response = false, metrics = true, costTime = 5000)
|
|
569
|
+
@GetMapping("/sales")
|
|
570
|
+
public Result<SalesReport> generateSalesReport(@RequestParam String startDate,
|
|
571
|
+
@RequestParam String endDate) {
|
|
572
|
+
// 重计算
|
|
573
|
+
SalesReport report = reportService.generateReport(startDate, endDate);
|
|
574
|
+
return Result.success(report);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### 场景 4:自定义异常处理
|
|
580
|
+
|
|
581
|
+
```java
|
|
582
|
+
@Component
|
|
583
|
+
public class CustomExceptionHandler extends GlobalExceptionHandler {
|
|
584
|
+
|
|
585
|
+
@ExceptionHandler(AuthenticationException.class)
|
|
586
|
+
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
|
587
|
+
public Result<?> handleAuthenticationException(AuthenticationException e) {
|
|
588
|
+
log.error("Authentication failed: {}", e.getMessage());
|
|
589
|
+
return Result.fail(401, "认证失败,请重新登录");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### 场景 5:@Async 异步方法(1.0.0-SNAPSHOT+)
|
|
595
|
+
|
|
596
|
+
**自动支持 TraceID 传播**:
|
|
597
|
+
|
|
598
|
+
```java
|
|
599
|
+
@Service
|
|
600
|
+
public class NotificationService {
|
|
601
|
+
|
|
602
|
+
@Async
|
|
603
|
+
public void sendNotification(String userId, String message) {
|
|
604
|
+
// TraceID 自动传播到异步线程
|
|
605
|
+
String traceId = ContextManager.get().getTraceId();
|
|
606
|
+
log.info("Sending notification with TraceID: {}", traceId);
|
|
607
|
+
// ... 发送通知
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### 场景 6:CompletableFuture 并行处理(1.0.0-SNAPSHOT+)
|
|
613
|
+
|
|
614
|
+
**自动支持 TraceID 传播**:
|
|
615
|
+
|
|
616
|
+
```java
|
|
617
|
+
@Service
|
|
618
|
+
public class OrderService {
|
|
619
|
+
|
|
620
|
+
@Autowired
|
|
621
|
+
private Executor asyncExecutor;
|
|
622
|
+
|
|
623
|
+
public Order createOrder(OrderRequest request) {
|
|
624
|
+
String traceId = ContextManager.get().getTraceId();
|
|
625
|
+
|
|
626
|
+
CompletableFuture<Void> inventoryCheck = CompletableFuture.runAsync(() -> {
|
|
627
|
+
// TraceID 自动传播
|
|
628
|
+
log.info("Checking inventory with TraceID: {}", traceId);
|
|
629
|
+
inventoryService.checkStock(request.getProductId());
|
|
630
|
+
}, asyncExecutor);
|
|
631
|
+
|
|
632
|
+
CompletableFuture<Void> paymentProcess = CompletableFuture.runAsync(() -> {
|
|
633
|
+
// TraceID 自动传播
|
|
634
|
+
log.info("Processing payment with TraceID: {}", traceId);
|
|
635
|
+
paymentService.process(request.getPayment());
|
|
636
|
+
}, asyncExecutor);
|
|
637
|
+
|
|
638
|
+
CompletableFuture.allOf(inventoryCheck, paymentProcess).join();
|
|
639
|
+
|
|
640
|
+
return orderRepository.save(new Order(request));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### 场景 7:自定义线程池(1.0.0-SNAPSHOT+)
|
|
646
|
+
|
|
647
|
+
**需要使用 MdcTaskDecorator**:
|
|
648
|
+
|
|
649
|
+
```java
|
|
650
|
+
@Configuration
|
|
651
|
+
public class CustomThreadPoolConfig {
|
|
652
|
+
|
|
653
|
+
@Autowired
|
|
654
|
+
private MdcTaskDecorator mdcTaskDecorator;
|
|
655
|
+
|
|
656
|
+
@Bean
|
|
657
|
+
public ThreadPoolTaskExecutor customExecutor() {
|
|
658
|
+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
|
659
|
+
executor.setCorePoolSize(5);
|
|
660
|
+
executor.setMaxPoolSize(10);
|
|
661
|
+
executor.setQueueCapacity(100);
|
|
662
|
+
executor.setThreadNamePrefix("custom-");
|
|
663
|
+
|
|
664
|
+
// 应用 MDC 任务装饰器以传播 TraceID
|
|
665
|
+
executor.setTaskDecorator(mdcTaskDecorator);
|
|
666
|
+
|
|
667
|
+
executor.initialize();
|
|
668
|
+
return executor;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
## 故障排查
|
|
674
|
+
|
|
675
|
+
### 问题 1:日志中未显示 TraceId
|
|
676
|
+
|
|
677
|
+
**症状:**
|
|
678
|
+
- 日志中没有显示 TraceId(方括号内)
|
|
679
|
+
- JSON 日志中缺少 traceId 字段
|
|
680
|
+
|
|
681
|
+
**可能原因:**
|
|
682
|
+
1. 请求拦截器被禁用
|
|
683
|
+
2. 请求路径被排除
|
|
684
|
+
3. Logback 配置不正确
|
|
685
|
+
|
|
686
|
+
**解决方案:**
|
|
687
|
+
```yaml
|
|
688
|
+
# 1. 确保拦截器已启用
|
|
689
|
+
avatar:
|
|
690
|
+
web:
|
|
691
|
+
interceptor-enabled: true
|
|
692
|
+
|
|
693
|
+
# 2. 检查排除路径
|
|
694
|
+
avatar:
|
|
695
|
+
web:
|
|
696
|
+
interceptor:
|
|
697
|
+
exclude-paths:
|
|
698
|
+
- /actuator/** # 确保你的路径没有被排除
|
|
699
|
+
|
|
700
|
+
# 3. 验证 logback-spring.xml 包含 TraceId 模式
|
|
701
|
+
# 模式应包含:[%X{traceId}]
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### 问题 2:CORS 错误
|
|
705
|
+
|
|
706
|
+
**症状:**
|
|
707
|
+
- 浏览器显示 CORS 策略错误
|
|
708
|
+
- 预检 OPTIONS 请求失败
|
|
709
|
+
|
|
710
|
+
**解决方案:**
|
|
711
|
+
```yaml
|
|
712
|
+
# 显式启用 CORS
|
|
713
|
+
avatar:
|
|
714
|
+
web:
|
|
715
|
+
cors-enabled: true
|
|
716
|
+
cors:
|
|
717
|
+
allowed-origins:
|
|
718
|
+
- "https://your-frontend-domain.com" # 使用凭证时不要用 "*"
|
|
719
|
+
allow-credentials: true
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
**重要提示:** 如果 `allow-credentials: true`,则不能使用 `allowed-origins: ["*"]`。必须指定确切的源。
|
|
723
|
+
|
|
724
|
+
### 问题 3:慢请求误报
|
|
725
|
+
|
|
726
|
+
**症状:**
|
|
727
|
+
- 许多请求被标记为 [SLOW],但实际上并不慢
|
|
728
|
+
- 阈值对你的使用场景来说太低
|
|
729
|
+
|
|
730
|
+
**解决方案:**
|
|
731
|
+
```yaml
|
|
732
|
+
# 根据环境调整阈值
|
|
733
|
+
avatar:
|
|
734
|
+
web:
|
|
735
|
+
log:
|
|
736
|
+
slow-request-threshold: 5000 # 增加到 5 秒
|
|
737
|
+
|
|
738
|
+
# 或禁用慢请求日志
|
|
739
|
+
avatar:
|
|
740
|
+
web:
|
|
741
|
+
log:
|
|
742
|
+
response-enabled: false
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### 问题 4:ThreadLocal 内存泄漏
|
|
746
|
+
|
|
747
|
+
**症状:**
|
|
748
|
+
- 内存使用随时间增长
|
|
749
|
+
- 生产环境出现 OutOfMemoryError
|
|
750
|
+
|
|
751
|
+
**诊断:**
|
|
752
|
+
拦截器会在 `afterCompletion()` 中自动清理 ThreadLocal。如果出现内存泄漏:
|
|
753
|
+
|
|
754
|
+
1. 检查是否手动设置上下文但未清理:
|
|
755
|
+
```java
|
|
756
|
+
// ❌ 错误 - 手动设置但未清理
|
|
757
|
+
ContextManager.set(context);
|
|
758
|
+
// ... 业务逻辑
|
|
759
|
+
// 缺失:ContextManager.clear();
|
|
760
|
+
|
|
761
|
+
// ✅ 正确 - 让拦截器处理
|
|
762
|
+
AvatarContext context = ContextManager.get();
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
2. 验证拦截器已注册:
|
|
766
|
+
```yaml
|
|
767
|
+
avatar:
|
|
768
|
+
web:
|
|
769
|
+
interceptor-enabled: true # 必须为 true
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### 问题 5:客户端 IP 显示为代理 IP
|
|
773
|
+
|
|
774
|
+
**症状:**
|
|
775
|
+
- `context.getClientIp()` 返回代理/负载均衡器 IP
|
|
776
|
+
- 未捕获真实客户端 IP
|
|
777
|
+
|
|
778
|
+
**解决方案:**
|
|
779
|
+
确保你的代理/负载均衡器设置了正确的请求头:
|
|
780
|
+
|
|
781
|
+
```nginx
|
|
782
|
+
# Nginx 配置
|
|
783
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
784
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
拦截器按以下顺序检查请求头:
|
|
788
|
+
1. `X-Forwarded-For`(如果有多个则取第一个 IP)
|
|
789
|
+
2. `X-Real-IP`
|
|
790
|
+
3. `Proxy-Client-IP`
|
|
791
|
+
4. `WL-Proxy-Client-IP`
|
|
792
|
+
5. `request.getRemoteAddr()`
|
|
793
|
+
|
|
794
|
+
### 问题 6:@LogInfo 不工作
|
|
795
|
+
|
|
796
|
+
**症状:**
|
|
797
|
+
- 使用 @LogInfo 注解的方法没有日志
|
|
798
|
+
- 指标未记录
|
|
799
|
+
|
|
800
|
+
**可能原因:**
|
|
801
|
+
1. AOP 未启用
|
|
802
|
+
2. 方法未通过 Spring 代理调用
|
|
803
|
+
3. LogInfoAspect 未注册
|
|
804
|
+
|
|
805
|
+
**解决方案:**
|
|
806
|
+
```java
|
|
807
|
+
// ❌ 错误 - 直接调用方法会绕过 AOP
|
|
808
|
+
@Service
|
|
809
|
+
public class UserService {
|
|
810
|
+
@LogInfo
|
|
811
|
+
public void method1() { }
|
|
812
|
+
|
|
813
|
+
public void method2() {
|
|
814
|
+
this.method1(); // 直接调用,AOP 不会拦截
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// ✅ 正确 - 通过 Spring 代理调用
|
|
819
|
+
@Service
|
|
820
|
+
@RequiredArgsConstructor
|
|
821
|
+
public class UserService {
|
|
822
|
+
private final UserService self; // 注入自身
|
|
823
|
+
|
|
824
|
+
@LogInfo
|
|
825
|
+
public void method1() { }
|
|
826
|
+
|
|
827
|
+
public void method2() {
|
|
828
|
+
self.method1(); // 代理调用,AOP 生效
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### 问题 7:异步线程中 TraceID 为 null(1.0.0-SNAPSHOT+)
|
|
834
|
+
|
|
835
|
+
**症状:**
|
|
836
|
+
- 异步方法中 `ContextManager.get().getTraceId()` 返回 null
|
|
837
|
+
- 异步线程的日志中没有 TraceID
|
|
838
|
+
|
|
839
|
+
**可能原因:**
|
|
840
|
+
1. 使用了自定义线程池但未应用 MdcTaskDecorator
|
|
841
|
+
2. 异步支持被禁用
|
|
842
|
+
3. 使用了不受管理的线程(如 new Thread())
|
|
843
|
+
|
|
844
|
+
**解决方案:**
|
|
845
|
+
|
|
846
|
+
**方案 1:使用配置的异步执行器**
|
|
847
|
+
```java
|
|
848
|
+
@Service
|
|
849
|
+
public class MyService {
|
|
850
|
+
@Autowired
|
|
851
|
+
private Executor asyncExecutor; // 使用 Avatar Boot 配置的执行器
|
|
852
|
+
|
|
853
|
+
public void doWork() {
|
|
854
|
+
CompletableFuture.runAsync(() -> {
|
|
855
|
+
// TraceID 自动传播
|
|
856
|
+
String traceId = ContextManager.get().getTraceId();
|
|
857
|
+
}, asyncExecutor);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
**方案 2:自定义线程池应用 MdcTaskDecorator**
|
|
863
|
+
```java
|
|
864
|
+
@Configuration
|
|
865
|
+
public class ThreadPoolConfig {
|
|
866
|
+
@Autowired
|
|
867
|
+
private MdcTaskDecorator mdcTaskDecorator;
|
|
868
|
+
|
|
869
|
+
@Bean
|
|
870
|
+
public ThreadPoolTaskExecutor customExecutor() {
|
|
871
|
+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
|
872
|
+
executor.setTaskDecorator(mdcTaskDecorator); // 关键:应用装饰器
|
|
873
|
+
executor.initialize();
|
|
874
|
+
return executor;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
**方案 3:检查异步支持是否启用**
|
|
880
|
+
```yaml
|
|
881
|
+
avatar:
|
|
882
|
+
web:
|
|
883
|
+
async:
|
|
884
|
+
enabled: true # 确保为 true
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
### 问题 8:如何验证异步 TraceID 传播是否生效(1.0.0-SNAPSHOT+)
|
|
888
|
+
|
|
889
|
+
**验证方法:**
|
|
890
|
+
|
|
891
|
+
```java
|
|
892
|
+
@RestController
|
|
893
|
+
public class TestController {
|
|
894
|
+
|
|
895
|
+
@Autowired
|
|
896
|
+
private Executor asyncExecutor;
|
|
897
|
+
|
|
898
|
+
@GetMapping("/test-async")
|
|
899
|
+
public String testAsync() {
|
|
900
|
+
String mainTraceId = ContextManager.get().getTraceId();
|
|
901
|
+
log.info("Main thread TraceID: {}", mainTraceId);
|
|
902
|
+
|
|
903
|
+
CompletableFuture.runAsync(() -> {
|
|
904
|
+
String asyncTraceId = ContextManager.get().getTraceId();
|
|
905
|
+
log.info("Async thread TraceID: {}", asyncTraceId);
|
|
906
|
+
}, asyncExecutor).join();
|
|
907
|
+
|
|
908
|
+
return "Check logs";
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
**预期日志输出:**
|
|
914
|
+
```
|
|
915
|
+
[http-nio-8080-exec-1] [abc123] Main thread TraceID: abc123
|
|
916
|
+
[avatar-async-1] [abc123] Async thread TraceID: abc123
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
如果异步线程的 TraceID 为 null 或不一致,说明传播未生效。
|
|
920
|
+
|
|
921
|
+
## 组件参考
|
|
922
|
+
|
|
923
|
+
### 核心类
|
|
924
|
+
|
|
925
|
+
| 类 | 包 | 用途 |
|
|
926
|
+
|-------|---------|---------|
|
|
927
|
+
| `WebAutoConfiguration` | `com.iflytek.avatar.boot.config` | 主自动配置类 |
|
|
928
|
+
| `WebProperties` | `com.iflytek.avatar.boot.config` | 配置属性 |
|
|
929
|
+
| `WebMvcConfiguration` | `com.iflytek.avatar.boot.config` | MVC 配置(拦截器、CORS) |
|
|
930
|
+
| `MetricsConfiguration` | `com.iflytek.avatar.boot.config` | 指标记录器配置 |
|
|
931
|
+
| `RequestContextInterceptor` | `com.iflytek.avatar.boot.interceptor` | 请求上下文初始化 |
|
|
932
|
+
| `GlobalExceptionHandler` | `com.iflytek.avatar.boot.exception` | 全局异常处理 |
|
|
933
|
+
| `LogInfo` | `com.iflytek.avatar.boot.anno` | 方法级日志注解 |
|
|
934
|
+
| `LogInfoAspect` | `com.iflytek.avatar.boot.anno` | @LogInfo 的 AOP 切面 |
|
|
935
|
+
| `MetricsRecorder` | `com.iflytek.avatar.boot.metrics` | 指标记录工具 |
|
|
936
|
+
| `AvatarContext` | `com.iflytek.avatar.boot.context` | 请求上下文持有者(在 core 中) |
|
|
937
|
+
| `ContextManager` | `com.iflytek.avatar.boot.context` | ThreadLocal 上下文管理器(在 core 中) |
|
|
938
|
+
|
|
939
|
+
### 配置属性
|
|
940
|
+
|
|
941
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
942
|
+
|----------|------|---------|-------------|
|
|
943
|
+
| `avatar.web.interceptor-enabled` | boolean | true | 启用请求拦截器 |
|
|
944
|
+
| `avatar.web.exception-handler-enabled` | boolean | true | 启用全局异常处理器 |
|
|
945
|
+
| `avatar.web.cors-enabled` | boolean | false | 启用 CORS |
|
|
946
|
+
| `avatar.web.interceptor.trace-id-header` | String | X-Trace-Id | TraceId 请求头名称 |
|
|
947
|
+
| `avatar.web.interceptor.include-paths` | List<String> | ["/**"] | 拦截路径 |
|
|
948
|
+
| `avatar.web.interceptor.exclude-paths` | List<String> | ["/error", "/actuator/**", ...] | 排除路径 |
|
|
949
|
+
| `avatar.web.log.request-enabled` | boolean | true | 记录请求开始 |
|
|
950
|
+
| `avatar.web.log.response-enabled` | boolean | true | 记录请求完成 |
|
|
951
|
+
| `avatar.web.log.slow-request-threshold` | long | 3000 | 慢请求阈值(毫秒) |
|
|
952
|
+
| `avatar.web.async.enabled` | boolean | true | 启用异步支持(1.0.0-SNAPSHOT+) |
|
|
953
|
+
| `avatar.web.async.core-pool-size` | int | 10 | 异步执行器核心线程数(1.0.0-SNAPSHOT+) |
|
|
954
|
+
| `avatar.web.async.max-pool-size` | int | 50 | 异步执行器最大线程数(1.0.0-SNAPSHOT+) |
|
|
955
|
+
| `avatar.web.async.queue-capacity` | int | 200 | 异步执行器队列容量(1.0.0-SNAPSHOT+) |
|
|
956
|
+
| `avatar.web.async.thread-name-prefix` | String | avatar-async- | 异步线程名前缀(1.0.0-SNAPSHOT+) |
|
|
957
|
+
| `avatar.web.tracing.micrometer-enabled` | boolean | false | 启用 Micrometer 集成(1.0.0-SNAPSHOT+) |
|
|
958
|
+
| `avatar.web.tracing.generator-type` | String | uuid | TraceID 生成策略(1.0.0-SNAPSHOT+) |
|
|
959
|
+
| `avatar.web.cors.allowed-origins` | List<String> | ["*"] | 允许的源 |
|
|
960
|
+
| `avatar.web.cors.allowed-methods` | List<String> | ["GET", "POST", ...] | 允许的 HTTP 方法 |
|
|
961
|
+
| `avatar.web.cors.allowed-headers` | List<String> | ["*"] | 允许的请求头 |
|
|
962
|
+
| `avatar.web.cors.exposed-headers` | List<String> | ["X-Trace-Id"] | 暴露的响应头 |
|
|
963
|
+
| `avatar.web.cors.allow-credentials` | boolean | true | 允许凭证 |
|
|
964
|
+
| `avatar.web.cors.max-age` | long | 3600 | 预检缓存时间(秒) |
|
|
965
|
+
|
|
966
|
+
## 依赖
|
|
967
|
+
|
|
968
|
+
此模块依赖于:
|
|
969
|
+
|
|
970
|
+
- `avatar-boot-core` - 核心功能(AvatarContext、Result 等)
|
|
971
|
+
- `spring-boot-starter-web` - Spring MVC
|
|
972
|
+
- `spring-boot-starter-validation` - 参数校验
|
|
973
|
+
- `spring-boot-starter-aop` - @LogInfo 的 AOP 支持
|
|
974
|
+
- `micrometer-core` - 指标(可选)
|
|
975
|
+
- `logstash-logback-encoder` - JSON 日志格式化
|
|
976
|
+
- `hutool-all` - 工具库(UUID、字符串处理)
|
|
977
|
+
|
|
978
|
+
## 最佳实践
|
|
979
|
+
|
|
980
|
+
1. **始终使用 Result<T> 作为 API 响应** - 确保响应格式一致
|
|
981
|
+
2. **生产环境不要禁用拦截器** - TraceId 对故障排查至关重要
|
|
982
|
+
3. **谨慎使用 @LogInfo** - 仅在关键业务方法上使用,避免日志膨胀
|
|
983
|
+
4. **根据环境配置慢请求阈值** - 开发环境可以放宽,生产环境应严格
|
|
984
|
+
5. **生产环境永远不要使用 CORS "*" 配合凭证** - 安全风险
|
|
985
|
+
6. **始终将 TraceId 传播到下游服务** - 启用分布式追踪
|
|
986
|
+
7. **使用环境特定的日志级别** - 开发环境用 DEBUG,生产环境用 INFO
|
|
987
|
+
8. **在生产环境监控指标** - 为高失败率或慢方法设置告警
|
|
988
|
+
9. **不要捕获并吞掉异常** - 让 GlobalExceptionHandler 处理它们
|
|
989
|
+
10. **在 finally 块中清理资源** - 尽管拦截器会处理上下文清理
|
|
990
|
+
|
|
991
|
+
## 版本要求
|
|
992
|
+
|
|
993
|
+
- Java 21+
|
|
994
|
+
- Spring Boot 3.5.3+
|
|
995
|
+
- Maven 3.8.6+
|
|
996
|
+
|
|
997
|
+
## 相关技能
|
|
998
|
+
|
|
999
|
+
- `avatar-boot-sdk-development` - 用于整体 Avatar Boot 架构和依赖管理
|
|
1000
|
+
- `java-spring-boot` - 用于通用 Spring Boot 开发模式
|
|
1001
|
+
- `systematic-debugging` - 用于排查此模块的问题
|
|
1002
|
+
|
|
1003
|
+
## 其他资源
|
|
1004
|
+
|
|
1005
|
+
- 模块 README:`avatar-boot-starter-web/README.md`
|
|
1006
|
+
- 配置示例:`avatar-boot-starter-web/src/main/resources/application-example.yml`
|
|
1007
|
+
- Logback 配置:`avatar-boot-starter-web/src/main/resources/logback-spring.xml`
|