@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,75 @@
|
|
|
1
|
+
# @LogInfo 方法日志注解
|
|
2
|
+
|
|
3
|
+
## 功能说明
|
|
4
|
+
|
|
5
|
+
`@LogInfo` 是方法级别的日志注解,基于 AOP 实现,支持:
|
|
6
|
+
- 记录方法入参和返回值
|
|
7
|
+
- 统计方法执行时间
|
|
8
|
+
- 慢方法检测与告警
|
|
9
|
+
- 联动 Micrometer 指标监控
|
|
10
|
+
- 异常自动记录
|
|
11
|
+
|
|
12
|
+
## 基本用法
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
import com.iflytek.avatar.boot.anno.LogInfo;
|
|
16
|
+
|
|
17
|
+
@Service
|
|
18
|
+
public class OrderService {
|
|
19
|
+
|
|
20
|
+
// 记录请求参数和响应结果,启用指标监控
|
|
21
|
+
@LogInfo(request = true, response = true, metrics = true)
|
|
22
|
+
public OrderInfo createOrder(OrderRequest request) {
|
|
23
|
+
// 业务逻辑...
|
|
24
|
+
return orderInfo;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 只记录慢方法(超过 1000ms 才输出日志)
|
|
28
|
+
@LogInfo(costTime = 1000)
|
|
29
|
+
public void processHeavyTask() {
|
|
30
|
+
// 耗时操作...
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 只记录异常,不记录正常调用
|
|
34
|
+
@LogInfo
|
|
35
|
+
public void riskyOperation() {
|
|
36
|
+
// 可能抛出异常的操作...
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 注解参数说明
|
|
42
|
+
|
|
43
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
44
|
+
|:--|:--|:--|:--|
|
|
45
|
+
| `request` | boolean | false | 是否记录方法入参 |
|
|
46
|
+
| `response` | boolean | false | 是否记录方法返回值 |
|
|
47
|
+
| `metrics` | boolean | false | 是否上报 Micrometer 指标 |
|
|
48
|
+
| `costTime` | long | 0 | 慢方法阈值(ms),0 表示始终记录 |
|
|
49
|
+
|
|
50
|
+
## 日志输出示例
|
|
51
|
+
|
|
52
|
+
正常调用:
|
|
53
|
+
```
|
|
54
|
+
2026-03-05 10:30:15.123 [http-nio-8080-exec-1] [abc123def456] INFO c.i.a.b.anno.LogInfoAspect - [OrderService.createOrder] Status: SUCCESS, Time: 245ms
|
|
55
|
+
→ Request: {"userId":123,"productId":456,"quantity":2}
|
|
56
|
+
→ Response: {"orderId":"ORD20260305001","status":"CREATED"}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
慢方法告警:
|
|
60
|
+
```
|
|
61
|
+
2026-03-05 10:35:20.789 [http-nio-8080-exec-2] [def456ghi789] WARN c.i.a.b.anno.LogInfoAspect - [OrderService.processHeavyTask] Status: SUCCESS, Time: 5234ms (⚠️ threshold: 3000ms)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
异常记录:
|
|
65
|
+
```
|
|
66
|
+
2026-03-05 10:40:00.000 [http-nio-8080-exec-3] [ghi789jkl012] ERROR c.i.a.b.anno.LogInfoAspect - [OrderService.riskyOperation] Status: FAILED, Time: 12ms
|
|
67
|
+
→ Exception: java.lang.IllegalStateException: 状态不合法
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 注意事项
|
|
71
|
+
|
|
72
|
+
- JSON 序列化自动截断超过 2000 字符的内容,避免日志过大
|
|
73
|
+
- `metrics = true` 需要引入 `spring-boot-starter-actuator` 才生效
|
|
74
|
+
- 注解基于 Spring AOP,**不支持同类内部方法调用**(self-invocation 问题)
|
|
75
|
+
- 建议在 Service 层使用,Controller 层的日志由拦截器统一处理
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# 全局异常处理
|
|
2
|
+
|
|
3
|
+
## 功能说明
|
|
4
|
+
|
|
5
|
+
模块内置 `GlobalExceptionHandler`,自动处理以下异常类型并返回统一的 `Result` 格式响应:
|
|
6
|
+
|
|
7
|
+
| 异常类型 | 触发场景 | 响应码 |
|
|
8
|
+
|:--|:--|:--|
|
|
9
|
+
| `BusinessException` | 业务逻辑异常 | 自定义码 |
|
|
10
|
+
| `BadRequestException` | 请求参数错误 | 400 |
|
|
11
|
+
| `MethodArgumentNotValidException` | `@RequestBody` 校验失败 | 400 |
|
|
12
|
+
| `BindException` | `@ModelAttribute` 校验失败 | 400 |
|
|
13
|
+
| `ConstraintViolationException` | `@RequestParam` 校验失败 | 400 |
|
|
14
|
+
| `Exception` | 未捕获的系统异常 | 500 |
|
|
15
|
+
|
|
16
|
+
## 统一响应格式
|
|
17
|
+
|
|
18
|
+
所有接口响应均为 `Result<T>` 结构:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"code": 0,
|
|
23
|
+
"message": "success",
|
|
24
|
+
"data": { ... }
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 抛出业务异常
|
|
29
|
+
|
|
30
|
+
```java
|
|
31
|
+
import com.iflytek.avatar.boot.exception.BusinessException;
|
|
32
|
+
import com.iflytek.avatar.boot.entity.response.ResultCode;
|
|
33
|
+
|
|
34
|
+
@Service
|
|
35
|
+
public class UserService {
|
|
36
|
+
|
|
37
|
+
public void updateUser(Long userId) {
|
|
38
|
+
if (userId == null) {
|
|
39
|
+
throw new BusinessException(ResultCode.PARAM_ERROR, "用户ID不能为空");
|
|
40
|
+
}
|
|
41
|
+
// 业务逻辑...
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 抛出请求参数异常
|
|
47
|
+
|
|
48
|
+
```java
|
|
49
|
+
import com.iflytek.avatar.boot.exception.BadRequestException;
|
|
50
|
+
|
|
51
|
+
@Service
|
|
52
|
+
public class OrderService {
|
|
53
|
+
|
|
54
|
+
public void createOrder(OrderRequest request) {
|
|
55
|
+
if (request.getAmount() <= 0) {
|
|
56
|
+
throw new BadRequestException("订单金额必须大于0");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 参数校验异常(自动处理)
|
|
63
|
+
|
|
64
|
+
使用 `@Valid` 或 `@Validated` 注解,校验失败时自动返回 400 响应:
|
|
65
|
+
|
|
66
|
+
```java
|
|
67
|
+
@RestController
|
|
68
|
+
public class UserController {
|
|
69
|
+
|
|
70
|
+
@PostMapping("/user")
|
|
71
|
+
public Result<Void> createUser(@RequestBody @Valid CreateUserRequest request) {
|
|
72
|
+
// 校验失败时自动返回 Result{code:400, message:"字段校验信息"}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```java
|
|
78
|
+
public class CreateUserRequest {
|
|
79
|
+
@NotBlank(message = "用户名不能为空")
|
|
80
|
+
private String username;
|
|
81
|
+
|
|
82
|
+
@Email(message = "邮箱格式不正确")
|
|
83
|
+
private String email;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 注意事项
|
|
88
|
+
|
|
89
|
+
- 全局异常处理器可通过 `avatar.web.exception-handler-enabled: false` 关闭
|
|
90
|
+
- 自定义异常处理器继承 `GlobalExceptionHandler` 即可扩展,参见「自定义扩展指南」
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 指标监控(Micrometer)
|
|
2
|
+
|
|
3
|
+
## 功能说明
|
|
4
|
+
|
|
5
|
+
模块集成 Micrometer,通过 `@LogInfo(metrics = true)` 注解自动记录方法级指标,与 Spring Boot Actuator 无缝集成。
|
|
6
|
+
|
|
7
|
+
## 启用 Actuator 暴露指标
|
|
8
|
+
|
|
9
|
+
在 `application.yml` 中配置:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
management:
|
|
13
|
+
endpoints:
|
|
14
|
+
web:
|
|
15
|
+
exposure:
|
|
16
|
+
include: health,info,metrics,prometheus
|
|
17
|
+
metrics:
|
|
18
|
+
export:
|
|
19
|
+
prometheus:
|
|
20
|
+
enabled: true
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 使用 @LogInfo 记录指标
|
|
24
|
+
|
|
25
|
+
```java
|
|
26
|
+
@Service
|
|
27
|
+
public class OrderService {
|
|
28
|
+
|
|
29
|
+
@LogInfo(metrics = true)
|
|
30
|
+
public OrderInfo createOrder(OrderRequest request) {
|
|
31
|
+
// 自动记录调用次数和执行时间
|
|
32
|
+
return orderInfo;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 可用指标
|
|
38
|
+
|
|
39
|
+
| 指标名 | 类型 | 标签 | 说明 |
|
|
40
|
+
|:--|:--|:--|:--|
|
|
41
|
+
| `method.invocation.count` | Counter | `class`, `method`, `status` | 方法调用次数 |
|
|
42
|
+
| `method.execution.time` | Timer | `class`, `method`, `status` | 方法执行时间 |
|
|
43
|
+
|
|
44
|
+
`status` 标签值:`success` / `failure`
|
|
45
|
+
|
|
46
|
+
## 查询指标
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 查看所有可用指标
|
|
50
|
+
curl http://localhost:8080/actuator/metrics
|
|
51
|
+
|
|
52
|
+
# 查看方法调用次数
|
|
53
|
+
curl http://localhost:8080/actuator/metrics/method.invocation.count
|
|
54
|
+
|
|
55
|
+
# 按标签过滤(查看某个方法的成功调用次数)
|
|
56
|
+
curl "http://localhost:8080/actuator/metrics/method.invocation.count?tag=method:createOrder&tag=status:success"
|
|
57
|
+
|
|
58
|
+
# 查看方法执行时间
|
|
59
|
+
curl http://localhost:8080/actuator/metrics/method.execution.time
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Prometheus 集成
|
|
63
|
+
|
|
64
|
+
启用 Prometheus 后,指标通过 `/actuator/prometheus` 端点暴露,可直接接入 Grafana 监控面板:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
curl http://localhost:8080/actuator/prometheus | grep method_
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 注意事项
|
|
71
|
+
|
|
72
|
+
- 需要引入 `spring-boot-starter-actuator` 才能启用指标功能(模块已自动引入)
|
|
73
|
+
- 指标使用缓存机制,避免重复注册导致的异常
|
|
74
|
+
- 可通过 `avatar.web.metrics.enabled: false` 全局关闭指标功能
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 结构化日志体系
|
|
2
|
+
|
|
3
|
+
## 日志格式
|
|
4
|
+
|
|
5
|
+
### 控制台 / 文件格式
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
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
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
格式说明:`时间 [线程名] [traceId] 级别 类名 - 消息`
|
|
12
|
+
|
|
13
|
+
### JSON 格式(test/prod 环境)
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"timestamp": "2026-03-03T17:04:15.123+08:00",
|
|
18
|
+
"level": "INFO",
|
|
19
|
+
"thread": "http-nio-8080-exec-1",
|
|
20
|
+
"logger": "com.iflytek.avatar.boot.controller.UserController",
|
|
21
|
+
"message": "Request started: GET /user/info from 192.168.1.100",
|
|
22
|
+
"traceId": "abc123def456",
|
|
23
|
+
"app": "avatar-app"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 日志文件
|
|
28
|
+
|
|
29
|
+
| 文件 | 内容 |
|
|
30
|
+
|:--|:--|
|
|
31
|
+
| `logs/${spring.application.name}.log` | 所有日志 |
|
|
32
|
+
| `logs/${spring.application.name}-error.log` | 仅错误日志 |
|
|
33
|
+
| `logs/${spring.application.name}-json.log` | JSON 格式日志 |
|
|
34
|
+
|
|
35
|
+
## 环境分级配置
|
|
36
|
+
|
|
37
|
+
| 环境 | 控制台输出 | 文件输出 | JSON 输出 |
|
|
38
|
+
|:--|:--|:--|:--|
|
|
39
|
+
| `dev` / `local` | ✅ | ✅ | ❌ |
|
|
40
|
+
| `test` | ✅ | ✅ | ✅ |
|
|
41
|
+
| `prod` | ❌ | ✅ | ✅ |
|
|
42
|
+
|
|
43
|
+
通过 `spring.profiles.active` 切换环境配置。
|
|
44
|
+
|
|
45
|
+
## 慢请求日志
|
|
46
|
+
|
|
47
|
+
请求耗时超过阈值(默认 3000ms)时,自动以 WARN 级别输出并标记 `[SLOW]`:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
2026-03-03 17:04:18.456 [http-nio-8080-exec-1] [abc123def456] WARN c.i.a.b.interceptor.RequestContextInterceptor - [SLOW] Request completed: GET /user/list - 3245ms - status: 200
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 异步日志
|
|
54
|
+
|
|
55
|
+
日志使用异步 Appender 输出,不阻塞业务线程,对接口性能影响极小。
|
|
56
|
+
|
|
57
|
+
## 在代码中使用 TraceId
|
|
58
|
+
|
|
59
|
+
TraceId 已自动注入 MDC,直接使用 SLF4J 即可在日志中看到 traceId:
|
|
60
|
+
|
|
61
|
+
```java
|
|
62
|
+
import org.slf4j.Logger;
|
|
63
|
+
import org.slf4j.LoggerFactory;
|
|
64
|
+
|
|
65
|
+
@Service
|
|
66
|
+
public class UserService {
|
|
67
|
+
private static final Logger log = LoggerFactory.getLogger(UserService.class);
|
|
68
|
+
|
|
69
|
+
public void doSomething() {
|
|
70
|
+
log.info("处理用户请求"); // 日志中自动包含当前请求的 traceId
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 请求上下文(AvatarContext)
|
|
2
|
+
|
|
3
|
+
## 功能说明
|
|
4
|
+
|
|
5
|
+
每个 HTTP 请求进入时,拦截器自动初始化 `AvatarContext` 并绑定到当前线程,请求结束后自动清理,防止内存泄漏。
|
|
6
|
+
|
|
7
|
+
## 获取请求上下文
|
|
8
|
+
|
|
9
|
+
```java
|
|
10
|
+
import com.iflytek.avatar.boot.context.AvatarContext;
|
|
11
|
+
import com.iflytek.avatar.boot.context.ContextManager;
|
|
12
|
+
|
|
13
|
+
@RestController
|
|
14
|
+
public class UserController {
|
|
15
|
+
|
|
16
|
+
@GetMapping("/user/info")
|
|
17
|
+
public Result<UserInfo> getUserInfo() {
|
|
18
|
+
AvatarContext context = ContextManager.get();
|
|
19
|
+
|
|
20
|
+
String traceId = context.getTraceId(); // 请求追踪 ID
|
|
21
|
+
String clientIp = context.getClientIp(); // 客户端真实 IP
|
|
22
|
+
long startTime = context.getStartTime(); // 请求开始时间(毫秒)
|
|
23
|
+
|
|
24
|
+
return Result.success(userInfo);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## TraceId 说明
|
|
30
|
+
|
|
31
|
+
### 自动生成
|
|
32
|
+
请求头中没有 `X-Trace-Id` 时,系统自动生成 UUID 作为 TraceId。
|
|
33
|
+
|
|
34
|
+
### 客户端传递
|
|
35
|
+
```bash
|
|
36
|
+
curl -H "X-Trace-Id: my-custom-trace-id" http://localhost:8080/api/user/info
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 响应头回传
|
|
40
|
+
系统自动将 TraceId 写入响应头 `X-Trace-Id`,方便客户端追踪链路。
|
|
41
|
+
|
|
42
|
+
### 日志自动注入
|
|
43
|
+
TraceId 通过 MDC 自动注入日志,无需手动处理:
|
|
44
|
+
```
|
|
45
|
+
2026-03-05 10:30:15.123 [http-nio-8080-exec-1] [abc123def456] INFO c.i.a.b.controller.UserController - ...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 客户端 IP 获取
|
|
49
|
+
|
|
50
|
+
支持多级代理场景,按以下顺序取值:
|
|
51
|
+
1. `X-Forwarded-For`(取第一个非内网 IP)
|
|
52
|
+
2. `X-Real-IP`
|
|
53
|
+
3. `RemoteAddr`
|
|
54
|
+
|
|
55
|
+
## 异步线程中传递 TraceId
|
|
56
|
+
|
|
57
|
+
在异步线程中使用 `MDCTaskDecorator` 传递 TraceId:
|
|
58
|
+
|
|
59
|
+
```java
|
|
60
|
+
@Configuration
|
|
61
|
+
public class AsyncConfig implements AsyncConfigurer {
|
|
62
|
+
|
|
63
|
+
@Override
|
|
64
|
+
public Executor getAsyncExecutor() {
|
|
65
|
+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
|
66
|
+
executor.setTaskDecorator(new MDCTaskDecorator()); // 传递 MDC 上下文
|
|
67
|
+
executor.initialize();
|
|
68
|
+
return executor;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 注意事项
|
|
74
|
+
|
|
75
|
+
- `ContextManager.get()` 在拦截器作用范围外(如定时任务)返回空上下文,注意判空
|
|
76
|
+
- 拦截器默认拦截 `/**`,排除 `/error`、`/actuator/**`、`/swagger-ui/**`
|
|
77
|
+
- 可通过配置调整拦截路径,参见「配置参考」
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 快速接入指南
|
|
2
|
+
|
|
3
|
+
## 1. 添加 Maven 依赖
|
|
4
|
+
|
|
5
|
+
在你的服务 `pom.xml` 中添加:
|
|
6
|
+
|
|
7
|
+
```xml
|
|
8
|
+
<dependency>
|
|
9
|
+
<groupId>com.iflytek.avatar.boot</groupId>
|
|
10
|
+
<artifactId>avatar-boot-starter-web</artifactId>
|
|
11
|
+
</dependency>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> 版本由 `avatar-boot-dependency` BOM 统一管理,无需指定版本号。
|
|
15
|
+
|
|
16
|
+
## 2. 无需额外配置
|
|
17
|
+
|
|
18
|
+
所有功能默认启用,引入依赖后即可使用:
|
|
19
|
+
|
|
20
|
+
- ✅ 全局异常处理自动生效
|
|
21
|
+
- ✅ 请求上下文(TraceId、客户端 IP)自动注入
|
|
22
|
+
- ✅ 结构化日志自动配置
|
|
23
|
+
- ✅ 指标监控自动注册(需有 Actuator)
|
|
24
|
+
|
|
25
|
+
## 3. 验证接入是否成功
|
|
26
|
+
|
|
27
|
+
启动应用后,发起任意 HTTP 请求,检查响应头中是否包含 `X-Trace-Id`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
curl -v http://localhost:8080/your-api
|
|
31
|
+
# 响应头应包含:X-Trace-Id: xxxxxxxxxxxxxxxx
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
检查日志输出格式是否包含 traceId 字段:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
2026-03-05 10:30:15.123 [http-nio-8080-exec-1] [abc123def456] INFO c.i.a.b.controller.YourController - ...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 4. 模块依赖说明
|
|
41
|
+
|
|
42
|
+
本模块自动引入以下依赖,无需重复声明:
|
|
43
|
+
|
|
44
|
+
| 依赖 | 用途 |
|
|
45
|
+
|:--|:--|
|
|
46
|
+
| `avatar-boot-core` | 核心功能(AvatarContext、Result 等) |
|
|
47
|
+
| `spring-boot-starter-web` | Spring MVC |
|
|
48
|
+
| `spring-boot-starter-aop` | AOP 支持(@LogInfo 注解) |
|
|
49
|
+
| `spring-boot-starter-actuator` | 监控端点(含 Micrometer) |
|
|
50
|
+
| `spring-boot-starter-validation` | 参数校验 |
|
|
51
|
+
| `logstash-logback-encoder` | JSON 日志格式化 |
|
|
52
|
+
| `hutool-all` | 工具类库 |
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 注意事项
|
|
2
|
+
|
|
3
|
+
## ThreadLocal 清理
|
|
4
|
+
|
|
5
|
+
拦截器在请求结束后自动清理 `AvatarContext`,无需手动处理。但在以下场景需注意:
|
|
6
|
+
|
|
7
|
+
- **定时任务**:不经过拦截器,`ContextManager.get()` 返回空上下文,使用前需判空
|
|
8
|
+
- **消息队列消费**:同上,建议手动初始化上下文或不依赖 `AvatarContext`
|
|
9
|
+
|
|
10
|
+
## 异步线程 TraceId 传递
|
|
11
|
+
|
|
12
|
+
默认情况下,异步线程不会继承父线程的 MDC(TraceId)。需要使用 `MDCTaskDecorator`:
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
@Configuration
|
|
16
|
+
public class AsyncConfig implements AsyncConfigurer {
|
|
17
|
+
@Override
|
|
18
|
+
public Executor getAsyncExecutor() {
|
|
19
|
+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
|
20
|
+
executor.setTaskDecorator(new MDCTaskDecorator());
|
|
21
|
+
executor.initialize();
|
|
22
|
+
return executor;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## @LogInfo 同类调用失效
|
|
28
|
+
|
|
29
|
+
`@LogInfo` 基于 Spring AOP,**同一个类内部方法互相调用时注解不生效**:
|
|
30
|
+
|
|
31
|
+
```java
|
|
32
|
+
@Service
|
|
33
|
+
public class OrderService {
|
|
34
|
+
|
|
35
|
+
public void createOrder() {
|
|
36
|
+
processOrder(); // ❌ @LogInfo 不生效,因为是内部调用
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@LogInfo(request = true)
|
|
40
|
+
public void processOrder() { ... }
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
解决方案:将被调用方法抽取到另一个 Bean,或通过 `ApplicationContext` 获取代理对象。
|
|
45
|
+
|
|
46
|
+
## 日志性能
|
|
47
|
+
|
|
48
|
+
- 使用异步 Appender,不阻塞业务线程
|
|
49
|
+
- `@LogInfo` 的 JSON 序列化自动截断超过 2000 字符的内容
|
|
50
|
+
- 生产环境建议关闭 `request-enabled` 和 `response-enabled`,避免敏感数据写入日志
|
|
51
|
+
|
|
52
|
+
## CORS 处理
|
|
53
|
+
|
|
54
|
+
模块不处理跨域问题,建议在 Nginx 或 API 网关层统一配置 CORS,避免在每个服务中重复配置。
|
|
55
|
+
|
|
56
|
+
## 指标监控依赖
|
|
57
|
+
|
|
58
|
+
`@LogInfo(metrics = true)` 需要 `spring-boot-starter-actuator` 在 classpath 中。模块已自动引入,但如果手动排除了 actuator 依赖,指标功能将不可用。
|
|
59
|
+
|
|
60
|
+
## IP 获取准确性
|
|
61
|
+
|
|
62
|
+
客户端 IP 通过请求头获取,在多级代理场景下取 `X-Forwarded-For` 的第一个非内网 IP。如果代理未正确转发请求头,IP 可能不准确,需在代理层配置 `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for`。
|
|
63
|
+
|
|
64
|
+
## 版本兼容性
|
|
65
|
+
|
|
66
|
+
- 本模块仅支持 Spring Boot 3.x,不兼容 Spring Boot 2.x
|
|
67
|
+
- 使用 `jakarta.*` 命名空间,不支持 `javax.*`
|
|
68
|
+
- 需要 Java 21+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# 自定义扩展指南
|
|
2
|
+
|
|
3
|
+
## 自定义异常处理器
|
|
4
|
+
|
|
5
|
+
继承 `GlobalExceptionHandler`,添加新的异常类型处理:
|
|
6
|
+
|
|
7
|
+
```java
|
|
8
|
+
import com.iflytek.avatar.boot.exception.GlobalExceptionHandler;
|
|
9
|
+
import com.iflytek.avatar.boot.entity.response.Result;
|
|
10
|
+
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
11
|
+
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
12
|
+
|
|
13
|
+
@RestControllerAdvice
|
|
14
|
+
public class CustomExceptionHandler extends GlobalExceptionHandler {
|
|
15
|
+
|
|
16
|
+
// 处理自定义业务异常
|
|
17
|
+
@ExceptionHandler(CustomException.class)
|
|
18
|
+
public Result<Void> handleCustomException(CustomException e) {
|
|
19
|
+
log.warn("自定义异常: {}", e.getMessage());
|
|
20
|
+
return Result.error(e.getCode(), e.getMessage());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 处理第三方 SDK 异常
|
|
24
|
+
@ExceptionHandler(ThirdPartyException.class)
|
|
25
|
+
public Result<Void> handleThirdPartyException(ThirdPartyException e) {
|
|
26
|
+
log.error("第三方服务异常", e);
|
|
27
|
+
return Result.error(500, "第三方服务暂时不可用,请稍后重试");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> 注意:继承后父类的所有异常处理仍然生效,子类只需添加新的处理方法。
|
|
33
|
+
|
|
34
|
+
## 自定义拦截器
|
|
35
|
+
|
|
36
|
+
实现 `HandlerInterceptor` 并注册:
|
|
37
|
+
|
|
38
|
+
```java
|
|
39
|
+
import jakarta.servlet.http.HttpServletRequest;
|
|
40
|
+
import jakarta.servlet.http.HttpServletResponse;
|
|
41
|
+
import org.springframework.web.servlet.HandlerInterceptor;
|
|
42
|
+
|
|
43
|
+
public class AuthInterceptor implements HandlerInterceptor {
|
|
44
|
+
|
|
45
|
+
@Override
|
|
46
|
+
public boolean preHandle(HttpServletRequest request,
|
|
47
|
+
HttpServletResponse response,
|
|
48
|
+
Object handler) throws Exception {
|
|
49
|
+
String token = request.getHeader("Authorization");
|
|
50
|
+
if (token == null || !isValid(token)) {
|
|
51
|
+
response.setStatus(401);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private boolean isValid(String token) {
|
|
58
|
+
// 鉴权逻辑...
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```java
|
|
65
|
+
import org.springframework.context.annotation.Configuration;
|
|
66
|
+
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
|
67
|
+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
68
|
+
|
|
69
|
+
@Configuration
|
|
70
|
+
public class WebMvcConfig implements WebMvcConfigurer {
|
|
71
|
+
|
|
72
|
+
@Override
|
|
73
|
+
public void addInterceptors(InterceptorRegistry registry) {
|
|
74
|
+
registry.addInterceptor(new AuthInterceptor())
|
|
75
|
+
.addPathPatterns("/api/**")
|
|
76
|
+
.excludePathPatterns("/api/public/**");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 自定义响应格式
|
|
82
|
+
|
|
83
|
+
如需修改 `Result` 的字段结构,可在项目中定义自己的响应类,并在自定义异常处理器中使用:
|
|
84
|
+
|
|
85
|
+
```java
|
|
86
|
+
// 自定义响应结构
|
|
87
|
+
@Data
|
|
88
|
+
@AllArgsConstructor
|
|
89
|
+
public class ApiResponse<T> {
|
|
90
|
+
private int status;
|
|
91
|
+
private String msg;
|
|
92
|
+
private T result;
|
|
93
|
+
private long timestamp = System.currentTimeMillis();
|
|
94
|
+
|
|
95
|
+
public static <T> ApiResponse<T> ok(T data) {
|
|
96
|
+
return new ApiResponse<>(200, "ok", data, System.currentTimeMillis());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
> 建议:优先使用框架提供的 `Result<T>`,保持团队内响应格式统一。
|
|
102
|
+
|
|
103
|
+
## 注意事项
|
|
104
|
+
|
|
105
|
+
- 自定义拦截器与框架内置拦截器**并行生效**,注意执行顺序
|
|
106
|
+
- 使用 `jakarta.*` 命名空间,不要使用 `javax.*`
|
|
107
|
+
- 自定义异常处理器的 `@RestControllerAdvice` 优先级高于父类,同类型异常以子类为准
|