ai-engineering-init 1.7.0 → 1.10.0
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/.claude/agents/bug-analyzer.md +103 -0
- package/.claude/agents/code-reviewer.md +115 -5
- package/.claude/agents/image-reader.md +154 -0
- package/.claude/agents/loki-runner.md +80 -0
- package/.claude/agents/mysql-runner.md +81 -0
- package/.claude/agents/requirements-analyzer.md +162 -0
- package/.claude/agents/task-fetcher.md +75 -0
- package/.claude/commands/dev.md +29 -0
- package/.claude/commands/next.md +31 -1
- package/.claude/commands/progress.md +23 -1
- package/.claude/hooks/skill-forced-eval.js +46 -62
- package/.claude/settings.json +10 -1
- package/.claude/skills/api-development/SKILL.md +179 -130
- package/.claude/skills/architecture-design/SKILL.md +102 -212
- package/.claude/skills/backend-annotations/SKILL.md +166 -220
- package/.claude/skills/bug-detective/SKILL.md +225 -186
- package/.claude/skills/code-patterns/SKILL.md +127 -244
- package/.claude/skills/collaborating-with-codex/SKILL.md +96 -113
- package/.claude/skills/crud-development/SKILL.md +226 -307
- package/.claude/skills/data-permission/SKILL.md +131 -202
- package/.claude/skills/database-ops/SKILL.md +158 -355
- package/.claude/skills/error-handler/SKILL.md +224 -285
- package/.claude/skills/file-oss-management/SKILL.md +174 -169
- package/.claude/skills/git-workflow/SKILL.md +123 -341
- package/.claude/skills/json-serialization/SKILL.md +121 -137
- package/.claude/skills/performance-doctor/SKILL.md +83 -89
- package/.claude/skills/redis-cache/SKILL.md +134 -185
- package/.claude/skills/scheduled-jobs/SKILL.md +187 -224
- package/.claude/skills/security-guard/SKILL.md +168 -276
- package/.claude/skills/sms-mail/SKILL.md +266 -228
- package/.claude/skills/social-login/SKILL.md +257 -195
- package/.claude/skills/tenant-management/SKILL.md +172 -188
- package/.claude/skills/utils-toolkit/SKILL.md +214 -222
- package/.claude/skills/websocket-sse/SKILL.md +251 -172
- package/.claude/skills/workflow-engine/SKILL.md +178 -250
- package/.codex/skills/api-development/SKILL.md +179 -130
- package/.codex/skills/architecture-design/SKILL.md +102 -212
- package/.codex/skills/backend-annotations/SKILL.md +166 -220
- package/.codex/skills/bug-detective/SKILL.md +225 -186
- package/.codex/skills/code-patterns/SKILL.md +127 -244
- package/.codex/skills/collaborating-with-codex/SKILL.md +96 -113
- package/.codex/skills/crud-development/SKILL.md +226 -307
- package/.codex/skills/data-permission/SKILL.md +131 -202
- package/.codex/skills/database-ops/SKILL.md +158 -355
- package/.codex/skills/dev/SKILL.md +476 -131
- package/.codex/skills/error-handler/SKILL.md +224 -285
- package/.codex/skills/file-oss-management/SKILL.md +174 -169
- package/.codex/skills/git-workflow/SKILL.md +123 -341
- package/.codex/skills/json-serialization/SKILL.md +121 -137
- package/.codex/skills/next/SKILL.md +186 -42
- package/.codex/skills/performance-doctor/SKILL.md +83 -89
- package/.codex/skills/progress/SKILL.md +147 -76
- package/.codex/skills/redis-cache/SKILL.md +134 -185
- package/.codex/skills/scheduled-jobs/SKILL.md +187 -224
- package/.codex/skills/security-guard/SKILL.md +168 -276
- package/.codex/skills/sms-mail/SKILL.md +266 -228
- package/.codex/skills/social-login/SKILL.md +257 -195
- package/.codex/skills/tenant-management/SKILL.md +172 -188
- package/.codex/skills/utils-toolkit/SKILL.md +214 -222
- package/.codex/skills/websocket-sse/SKILL.md +251 -172
- package/.codex/skills/workflow-engine/SKILL.md +178 -250
- package/.cursor/agents/bug-analyzer.md +102 -0
- package/.cursor/agents/code-reviewer.md +80 -97
- package/.cursor/agents/image-reader.md +154 -0
- package/.cursor/agents/loki-runner.md +80 -0
- package/.cursor/agents/mysql-runner.md +81 -0
- package/.cursor/agents/project-manager.md +1 -1
- package/.cursor/agents/requirements-analyzer.md +141 -0
- package/.cursor/agents/task-fetcher.md +75 -0
- package/.cursor/hooks/cursor-skill-eval.js +66 -6
- package/.cursor/skills/api-development/SKILL.md +179 -130
- package/.cursor/skills/architecture-design/SKILL.md +102 -212
- package/.cursor/skills/backend-annotations/SKILL.md +166 -220
- package/.cursor/skills/bug-detective/SKILL.md +225 -186
- package/.cursor/skills/code-patterns/SKILL.md +127 -244
- package/.cursor/skills/collaborating-with-codex/SKILL.md +96 -113
- package/.cursor/skills/crud-development/SKILL.md +226 -307
- package/.cursor/skills/data-permission/SKILL.md +131 -202
- package/.cursor/skills/database-ops/SKILL.md +158 -355
- package/.cursor/skills/error-handler/SKILL.md +224 -285
- package/.cursor/skills/file-oss-management/SKILL.md +174 -169
- package/.cursor/skills/git-workflow/SKILL.md +123 -341
- package/.cursor/skills/json-serialization/SKILL.md +121 -137
- package/.cursor/skills/performance-doctor/SKILL.md +83 -89
- package/.cursor/skills/redis-cache/SKILL.md +134 -185
- package/.cursor/skills/scheduled-jobs/SKILL.md +187 -224
- package/.cursor/skills/security-guard/SKILL.md +168 -276
- package/.cursor/skills/sms-mail/SKILL.md +266 -228
- package/.cursor/skills/social-login/SKILL.md +257 -195
- package/.cursor/skills/tenant-management/SKILL.md +172 -188
- package/.cursor/skills/utils-toolkit/SKILL.md +214 -222
- package/.cursor/skills/websocket-sse/SKILL.md +251 -172
- package/.cursor/skills/workflow-engine/SKILL.md +178 -250
- package/AGENTS.md +117 -540
- package/CLAUDE.md +105 -117
- package/README.md +37 -6
- package/bin/index.js +5 -1
- package/package.json +1 -1
- package/src/skills/api-development/SKILL.md +179 -130
- package/src/skills/architecture-design/SKILL.md +102 -212
- package/src/skills/backend-annotations/SKILL.md +166 -220
- package/src/skills/bug-detective/SKILL.md +225 -186
- package/src/skills/code-patterns/SKILL.md +127 -244
- package/src/skills/collaborating-with-codex/SKILL.md +96 -113
- package/src/skills/crud-development/SKILL.md +226 -307
- package/src/skills/data-permission/SKILL.md +131 -202
- package/src/skills/database-ops/SKILL.md +158 -355
- package/src/skills/error-handler/SKILL.md +224 -285
- package/src/skills/file-oss-management/SKILL.md +174 -169
- package/src/skills/git-workflow/SKILL.md +123 -341
- package/src/skills/json-serialization/SKILL.md +121 -137
- package/src/skills/performance-doctor/SKILL.md +83 -89
- package/src/skills/redis-cache/SKILL.md +134 -185
- package/src/skills/scheduled-jobs/SKILL.md +187 -224
- package/src/skills/security-guard/SKILL.md +168 -276
- package/src/skills/sms-mail/SKILL.md +266 -228
- package/src/skills/social-login/SKILL.md +257 -195
- package/src/skills/tenant-management/SKILL.md +172 -188
- package/src/skills/utils-toolkit/SKILL.md +214 -222
- package/src/skills/websocket-sse/SKILL.md +251 -172
- package/src/skills/workflow-engine/SKILL.md +178 -250
- package/.claude/skills/skill-creator/LICENSE.txt +0 -202
- package/.claude/skills/skill-creator/SKILL.md +0 -479
- package/.claude/skills/skill-creator/agents/analyzer.md +0 -274
- package/.claude/skills/skill-creator/agents/comparator.md +0 -202
- package/.claude/skills/skill-creator/agents/grader.md +0 -223
- package/.claude/skills/skill-creator/assets/eval_review.html +0 -146
- package/.claude/skills/skill-creator/eval-viewer/generate_review.py +0 -471
- package/.claude/skills/skill-creator/eval-viewer/viewer.html +0 -1325
- package/.claude/skills/skill-creator/references/schemas.md +0 -430
- package/.claude/skills/skill-creator/scripts/__init__.py +0 -0
- package/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
- package/.claude/skills/skill-creator/scripts/generate_report.py +0 -326
- package/.claude/skills/skill-creator/scripts/improve_description.py +0 -248
- package/.claude/skills/skill-creator/scripts/package_skill.py +0 -136
- package/.claude/skills/skill-creator/scripts/quick_validate.py +0 -103
- package/.claude/skills/skill-creator/scripts/run_eval.py +0 -310
- package/.claude/skills/skill-creator/scripts/run_loop.py +0 -332
- package/.claude/skills/skill-creator/scripts/utils.py +0 -47
|
@@ -1,371 +1,310 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: error-handler
|
|
3
3
|
description: |
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
- 全局异常处理器配置
|
|
9
|
-
- 参数校验异常处理
|
|
10
|
-
- 日志记录规范
|
|
11
|
-
- 错误码设计与国际化
|
|
12
|
-
- 事务异常处理
|
|
13
|
-
|
|
14
|
-
触发词:异常、ServiceException、throw、错误处理、全局异常、@Validated、参数校验、日志、log、错误码、事务、@Transactional、try-catch、异常捕获
|
|
15
|
-
|
|
16
|
-
注意:
|
|
17
|
-
- 如果是安全相关(认证授权、数据脱敏),请使用 security-guard。
|
|
18
|
-
- 如果是数据权限(@DataPermission),请使用 data-permission。
|
|
4
|
+
通用异常处理指南。涵盖自定义业务异常、全局异常处理器、参数校验等。
|
|
5
|
+
触发场景:异常设计、错误处理、参数校验、全局异常捕获。
|
|
6
|
+
触发词:异常处理、错误处理、参数校验、validation、异常捕获。
|
|
7
|
+
注意:如果项目有专属技能(如 `leniu-error-handler`),优先使用专属版本。
|
|
19
8
|
---
|
|
20
9
|
|
|
21
|
-
#
|
|
10
|
+
# 异常处理指南
|
|
22
11
|
|
|
23
|
-
>
|
|
12
|
+
> 通用模板。如果项目有专属技能(如 `leniu-error-handler`),优先使用。
|
|
24
13
|
|
|
25
|
-
|
|
14
|
+
## 核心规范
|
|
26
15
|
|
|
27
|
-
|
|
16
|
+
### 异常分层设计
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
18
|
+
```
|
|
19
|
+
RuntimeException
|
|
20
|
+
└── BusinessException # 业务异常基类
|
|
21
|
+
├── NotFoundException # 资源不存在 (404)
|
|
22
|
+
├── ForbiddenException # 无权限 (403)
|
|
23
|
+
├── BadRequestException # 参数错误 (400)
|
|
24
|
+
└── ConflictException # 数据冲突 (409)
|
|
25
|
+
```
|
|
36
26
|
|
|
37
|
-
|
|
27
|
+
### 异常处理原则
|
|
28
|
+
|
|
29
|
+
1. **业务异常用自定义异常类**,不要直接抛 `RuntimeException`
|
|
30
|
+
2. **全局统一捕获**,通过 `@RestControllerAdvice` 处理
|
|
31
|
+
3. **区分异常层级**:Controller 层不 try-catch(交给全局处理器),Service 层只捕获需要转换的异常
|
|
32
|
+
4. **异常信息面向用户**:不暴露堆栈、SQL 等技术细节
|
|
33
|
+
5. **日志记录完整**:异常日志包含完整上下文和堆栈
|
|
38
34
|
|
|
39
|
-
##
|
|
35
|
+
## 代码示例
|
|
40
36
|
|
|
41
|
-
###
|
|
37
|
+
### 1. 自定义业务异常
|
|
42
38
|
|
|
43
39
|
```java
|
|
44
|
-
|
|
40
|
+
package [你的包名].exception;
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
throw new ServiceException("用户不存在");
|
|
42
|
+
import lombok.Getter;
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
throw new ServiceException("订单 {} 状态 {} 无法支付", orderId, status);
|
|
44
|
+
@Getter
|
|
45
|
+
public class BusinessException extends RuntimeException {
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
throw new ServiceException("用户不存在", 200101);
|
|
47
|
+
private final int code;
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
49
|
+
public BusinessException(String message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.code = 500;
|
|
52
|
+
}
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
public BusinessException(int code, String message) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.code = code;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public BusinessException(int code, String message, Throwable cause) {
|
|
60
|
+
super(message, cause);
|
|
61
|
+
this.code = code;
|
|
62
|
+
}
|
|
64
63
|
}
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
> **🔴 重要**:ServiceException **没有静态工厂方法**(如 `of()`)和条件检查方法(如 `throwIf()`, `notNull()`),必须使用 `new` 关键字创建异常对象。
|
|
70
|
-
|
|
71
|
-
| 构造函数 | 说明 | 示例 |
|
|
72
|
-
|---------|------|------|
|
|
73
|
-
| `new ServiceException(String message)` | 只有错误消息 | `new ServiceException("操作失败")` |
|
|
74
|
-
| `new ServiceException(String message, Object... args)` | 带占位符参数 | `new ServiceException("用户{}不存在", userId)` |
|
|
75
|
-
| `new ServiceException(String message, Integer code)` | 带错误码 | `new ServiceException("用户不存在", 200101)` |
|
|
76
|
-
|
|
77
|
-
**链式调用方法**:
|
|
78
|
-
- `setMessage(String message)`: 设置错误消息
|
|
79
|
-
- `setDetailMessage(String detailMessage)`: 设置详细错误(用于内部调试)
|
|
80
|
-
|
|
81
|
-
**源码位置**: `ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java`
|
|
66
|
+
```java
|
|
67
|
+
package [你的包名].exception;
|
|
82
68
|
|
|
83
|
-
|
|
69
|
+
public class NotFoundException extends BusinessException {
|
|
84
70
|
|
|
85
|
-
|
|
71
|
+
public NotFoundException(String message) {
|
|
72
|
+
super(404, message);
|
|
73
|
+
}
|
|
86
74
|
|
|
87
|
-
|
|
75
|
+
public static NotFoundException of(String resource, Object id) {
|
|
76
|
+
return new NotFoundException(resource + " 不存在: " + id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
88
80
|
|
|
89
|
-
|
|
81
|
+
### 2. 全局异常处理器
|
|
90
82
|
|
|
91
|
-
|
|
83
|
+
```java
|
|
84
|
+
package [你的包名].handler;
|
|
92
85
|
|
|
93
|
-
|
|
86
|
+
import [你的包名].exception.BusinessException;
|
|
87
|
+
import [你的包名].exception.NotFoundException;
|
|
88
|
+
import jakarta.validation.ConstraintViolation;
|
|
89
|
+
import jakarta.validation.ConstraintViolationException;
|
|
90
|
+
import lombok.extern.slf4j.Slf4j;
|
|
91
|
+
import org.springframework.http.HttpStatus;
|
|
92
|
+
import org.springframework.http.ResponseEntity;
|
|
93
|
+
import org.springframework.validation.FieldError;
|
|
94
|
+
import org.springframework.web.bind.MethodArgumentNotValidException;
|
|
95
|
+
import org.springframework.web.bind.MissingServletRequestParameterException;
|
|
96
|
+
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
97
|
+
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
| `ServiceException` | 返回业务错误信息 | 自定义 code 或 500 |
|
|
98
|
-
| `BindException` | 返回参数绑定错误 | 500 |
|
|
99
|
-
| `ConstraintViolationException` | 返回参数校验错误 | 500 |
|
|
100
|
-
| `MethodArgumentNotValidException` | 返回参数校验错误 | 500 |
|
|
101
|
-
| `HandlerMethodValidationException` | 返回 @Validated 校验错误 | 500 |
|
|
102
|
-
| `HttpRequestMethodNotSupportedException` | 请求方式不支持 | 405 |
|
|
103
|
-
| `NoHandlerFoundException` | 路由不存在 | 404 |
|
|
104
|
-
| `JsonParseException` | JSON 解析失败 | 400 |
|
|
105
|
-
| `HttpMessageNotReadableException` | 请求参数格式错误 | 400 |
|
|
106
|
-
| `ExpressionException` | SpEL 表达式解析失败 | 500 |
|
|
107
|
-
| `RuntimeException` / `Exception` | 系统错误 | 500 |
|
|
99
|
+
import java.util.Map;
|
|
100
|
+
import java.util.stream.Collectors;
|
|
108
101
|
|
|
109
|
-
|
|
102
|
+
@Slf4j
|
|
103
|
+
@RestControllerAdvice
|
|
104
|
+
public class GlobalExceptionHandler {
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 业务异常
|
|
108
|
+
*/
|
|
109
|
+
@ExceptionHandler(BusinessException.class)
|
|
110
|
+
public ResponseEntity<Result<Void>> handleBusinessException(BusinessException e) {
|
|
111
|
+
log.warn("业务异常: {}", e.getMessage());
|
|
112
|
+
return ResponseEntity
|
|
113
|
+
.status(HttpStatus.BAD_REQUEST)
|
|
114
|
+
.body(Result.fail(e.getCode(), e.getMessage()));
|
|
115
|
+
}
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
/**
|
|
118
|
+
* 资源不存在
|
|
119
|
+
*/
|
|
120
|
+
@ExceptionHandler(NotFoundException.class)
|
|
121
|
+
public ResponseEntity<Result<Void>> handleNotFoundException(NotFoundException e) {
|
|
122
|
+
log.warn("资源不存在: {}", e.getMessage());
|
|
123
|
+
return ResponseEntity
|
|
124
|
+
.status(HttpStatus.NOT_FOUND)
|
|
125
|
+
.body(Result.fail(404, e.getMessage()));
|
|
126
|
+
}
|
|
112
127
|
|
|
113
|
-
|
|
128
|
+
/**
|
|
129
|
+
* @RequestBody 参数校验失败
|
|
130
|
+
*/
|
|
131
|
+
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
132
|
+
public ResponseEntity<Result<Map<String, String>>> handleValidationException(
|
|
133
|
+
MethodArgumentNotValidException e) {
|
|
134
|
+
Map<String, String> errors = e.getBindingResult().getFieldErrors().stream()
|
|
135
|
+
.collect(Collectors.toMap(
|
|
136
|
+
FieldError::getField,
|
|
137
|
+
fe -> fe.getDefaultMessage() != null ? fe.getDefaultMessage() : "校验失败",
|
|
138
|
+
(v1, v2) -> v1
|
|
139
|
+
));
|
|
140
|
+
log.warn("参数校验失败: {}", errors);
|
|
141
|
+
return ResponseEntity
|
|
142
|
+
.status(HttpStatus.BAD_REQUEST)
|
|
143
|
+
.body(Result.fail(400, "参数校验失败"));
|
|
144
|
+
}
|
|
114
145
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
146
|
+
/**
|
|
147
|
+
* @RequestParam / @PathVariable 校验失败
|
|
148
|
+
*/
|
|
149
|
+
@ExceptionHandler(ConstraintViolationException.class)
|
|
150
|
+
public ResponseEntity<Result<Void>> handleConstraintViolation(ConstraintViolationException e) {
|
|
151
|
+
String message = e.getConstraintViolations().stream()
|
|
152
|
+
.map(ConstraintViolation::getMessage)
|
|
153
|
+
.collect(Collectors.joining("; "));
|
|
154
|
+
log.warn("约束校验失败: {}", message);
|
|
155
|
+
return ResponseEntity
|
|
156
|
+
.status(HttpStatus.BAD_REQUEST)
|
|
157
|
+
.body(Result.fail(400, message));
|
|
158
|
+
}
|
|
118
159
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
160
|
+
/**
|
|
161
|
+
* 缺少请求参数
|
|
162
|
+
*/
|
|
163
|
+
@ExceptionHandler(MissingServletRequestParameterException.class)
|
|
164
|
+
public ResponseEntity<Result<Void>> handleMissingParam(MissingServletRequestParameterException e) {
|
|
165
|
+
log.warn("缺少请求参数: {}", e.getParameterName());
|
|
166
|
+
return ResponseEntity
|
|
167
|
+
.status(HttpStatus.BAD_REQUEST)
|
|
168
|
+
.body(Result.fail(400, "缺少参数: " + e.getParameterName()));
|
|
169
|
+
}
|
|
126
170
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
171
|
+
/**
|
|
172
|
+
* 兜底:未知异常
|
|
173
|
+
*/
|
|
174
|
+
@ExceptionHandler(Exception.class)
|
|
175
|
+
public ResponseEntity<Result<Void>> handleException(Exception e) {
|
|
176
|
+
log.error("系统异常", e);
|
|
177
|
+
return ResponseEntity
|
|
178
|
+
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
179
|
+
.body(Result.fail(500, "系统繁忙,请稍后重试"));
|
|
180
|
+
}
|
|
130
181
|
}
|
|
131
182
|
```
|
|
132
183
|
|
|
133
|
-
###
|
|
184
|
+
### 3. 参数校验(jakarta.validation)
|
|
134
185
|
|
|
135
186
|
```java
|
|
136
|
-
|
|
187
|
+
package [你的包名].dto;
|
|
137
188
|
|
|
138
|
-
|
|
139
|
-
|
|
189
|
+
import jakarta.validation.constraints.*;
|
|
190
|
+
import lombok.Data;
|
|
140
191
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
private String name;
|
|
192
|
+
@Data
|
|
193
|
+
public class UserCreateDTO {
|
|
144
194
|
|
|
195
|
+
@NotBlank(message = "用户名不能为空")
|
|
196
|
+
@Size(min = 2, max = 32, message = "用户名长度 2-32 位")
|
|
197
|
+
private String username;
|
|
198
|
+
|
|
199
|
+
@NotBlank(message = "邮箱不能为空")
|
|
145
200
|
@Email(message = "邮箱格式不正确")
|
|
146
201
|
private String email;
|
|
147
202
|
|
|
203
|
+
@NotNull(message = "年龄不能为空")
|
|
204
|
+
@Min(value = 1, message = "年龄最小为 1")
|
|
205
|
+
@Max(value = 150, message = "年龄最大为 150")
|
|
206
|
+
private Integer age;
|
|
207
|
+
|
|
148
208
|
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
|
149
209
|
private String phone;
|
|
150
|
-
|
|
151
|
-
@Min(value = 0, message = "数量不能小于0")
|
|
152
|
-
@Max(value = 9999, message = "数量不能大于9999")
|
|
153
|
-
private Integer count;
|
|
154
210
|
}
|
|
155
211
|
```
|
|
156
212
|
|
|
157
|
-
|
|
158
|
-
|
|
213
|
+
**Controller 中使用**:
|
|
159
214
|
```java
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
ValidatorUtils.validate(bo, EditGroup.class);
|
|
215
|
+
@PostMapping
|
|
216
|
+
public ResponseEntity<Result<Long>> create(@Valid @RequestBody UserCreateDTO dto) {
|
|
217
|
+
return ResponseEntity.ok(Result.ok(userService.create(dto)));
|
|
218
|
+
}
|
|
165
219
|
```
|
|
166
220
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
## 4. 日志规范
|
|
170
|
-
|
|
171
|
-
### 日志级别
|
|
172
|
-
|
|
173
|
-
| 级别 | 使用场景 | 示例 |
|
|
174
|
-
|------|---------|------|
|
|
175
|
-
| ERROR | 系统错误、业务异常 | 数据库连接失败、第三方接口超时 |
|
|
176
|
-
| WARN | 警告信息、潜在问题 | 缓存未命中、重试操作 |
|
|
177
|
-
| INFO | 重要业务流程、操作记录 | 用户登录、订单创建 |
|
|
178
|
-
| DEBUG | 开发调试信息 | 方法入参、中间变量 |
|
|
179
|
-
| TRACE | 详细追踪信息 | 循环内部数据 |
|
|
180
|
-
|
|
181
|
-
### 日志最佳实践
|
|
221
|
+
### 4. 分组校验
|
|
182
222
|
|
|
183
223
|
```java
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
@Slf4j
|
|
187
|
-
@Service
|
|
188
|
-
public class XxxServiceImpl implements IXxxService {
|
|
224
|
+
public interface CreateGroup {}
|
|
225
|
+
public interface UpdateGroup {}
|
|
189
226
|
|
|
190
|
-
|
|
191
|
-
|
|
227
|
+
@Data
|
|
228
|
+
public class UserDTO {
|
|
192
229
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// ✅ 好的:异常日志带堆栈(第三个参数传异常对象)
|
|
197
|
-
log.error("处理失败: {}", e.getMessage(), e);
|
|
198
|
-
|
|
199
|
-
// ❌ 不好:只记录消息,丢失堆栈
|
|
200
|
-
log.error("处理失败: {}", e.getMessage());
|
|
230
|
+
@Null(groups = CreateGroup.class, message = "创建时不能指定 ID")
|
|
231
|
+
@NotNull(groups = UpdateGroup.class, message = "更新时必须指定 ID")
|
|
232
|
+
private Long id;
|
|
201
233
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
234
|
+
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
|
|
235
|
+
private String username;
|
|
236
|
+
}
|
|
206
237
|
|
|
207
|
-
|
|
208
|
-
|
|
238
|
+
// Controller 使用
|
|
239
|
+
@PostMapping
|
|
240
|
+
public Result<Long> create(@Validated(CreateGroup.class) @RequestBody UserDTO dto) { ... }
|
|
209
241
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
242
|
+
@PutMapping("/{id}")
|
|
243
|
+
public Result<Void> update(@Validated(UpdateGroup.class) @RequestBody UserDTO dto) { ... }
|
|
213
244
|
```
|
|
214
245
|
|
|
215
|
-
### Service
|
|
246
|
+
### 5. Service 层异常使用
|
|
216
247
|
|
|
217
248
|
```java
|
|
218
|
-
@Slf4j
|
|
219
|
-
@RequiredArgsConstructor
|
|
220
249
|
@Service
|
|
221
|
-
public class
|
|
222
|
-
|
|
223
|
-
private final SysUserMapper baseMapper;
|
|
250
|
+
public class UserServiceImpl implements IUserService {
|
|
224
251
|
|
|
225
252
|
@Override
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
// 1. 业务校验
|
|
231
|
-
SysUser existUser = baseMapper.selectUserByUserName(bo.getUserName());
|
|
232
|
-
if (ObjectUtil.isNotNull(existUser)) {
|
|
233
|
-
throw new ServiceException("用户名 {} 已存在", bo.getUserName());
|
|
253
|
+
public UserVO getById(Long id) {
|
|
254
|
+
User user = userMapper.selectById(id);
|
|
255
|
+
if (user == null) {
|
|
256
|
+
throw NotFoundException.of("用户", id);
|
|
234
257
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
SysUser user = MapstructUtils.convert(bo, SysUser.class);
|
|
238
|
-
baseMapper.insert(user);
|
|
239
|
-
|
|
240
|
-
log.info("新增用户成功,用户ID: {}, 用户名: {}", user.getUserId(), user.getUserName());
|
|
241
|
-
return user.getUserId();
|
|
258
|
+
// ... 转换为 VO
|
|
259
|
+
return userVO;
|
|
242
260
|
}
|
|
243
261
|
|
|
244
262
|
@Override
|
|
245
|
-
public
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
263
|
+
public void updateEmail(Long id, String email) {
|
|
264
|
+
// 检查邮箱是否已被使用
|
|
265
|
+
User existing = userMapper.selectByEmail(email);
|
|
266
|
+
if (existing != null && !existing.getId().equals(id)) {
|
|
267
|
+
throw new BusinessException(409, "邮箱已被其他用户使用");
|
|
249
268
|
}
|
|
250
|
-
|
|
269
|
+
// ... 更新逻辑
|
|
251
270
|
}
|
|
252
271
|
}
|
|
253
272
|
```
|
|
254
273
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
## 5. 错误码设计
|
|
258
|
-
|
|
259
|
-
### 错误码规范
|
|
274
|
+
### 6. 日志规范
|
|
260
275
|
|
|
261
276
|
```java
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
public class ErrorCode {
|
|
267
|
-
// 通用错误
|
|
268
|
-
public static final int SUCCESS = 200;
|
|
269
|
-
public static final int ERROR = 500;
|
|
270
|
-
public static final int UNAUTHORIZED = 401;
|
|
271
|
-
public static final int FORBIDDEN = 403;
|
|
272
|
-
|
|
273
|
-
// 用户模块 20xxxx
|
|
274
|
-
public static final int USER_NOT_FOUND = 200201; // 用户不存在
|
|
275
|
-
public static final int USER_PASSWORD_ERROR = 200202; // 密码错误
|
|
276
|
-
public static final int USER_DISABLED = 200203; // 用户已禁用
|
|
277
|
-
|
|
278
|
-
// 订单模块 30xxxx
|
|
279
|
-
public static final int ORDER_NOT_FOUND = 300201; // 订单不存在
|
|
280
|
-
public static final int ORDER_STATUS_ERROR = 300202; // 订单状态错误
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// 使用示例
|
|
284
|
-
throw new ServiceException("用户不存在", ErrorCode.USER_NOT_FOUND);
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### 错误消息国际化
|
|
288
|
-
|
|
289
|
-
```java
|
|
290
|
-
import org.dromara.common.core.utils.MessageUtils;
|
|
291
|
-
|
|
292
|
-
// 使用 MessageUtils.message() 获取国际化消息
|
|
293
|
-
throw new ServiceException(MessageUtils.message("user.not.exists"));
|
|
294
|
-
|
|
295
|
-
// 带参数的国际化消息
|
|
296
|
-
throw new ServiceException(MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount));
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
## 6. 用户友好提示
|
|
302
|
-
|
|
303
|
-
### 错误提示规范
|
|
304
|
-
|
|
305
|
-
```java
|
|
306
|
-
// ✅ 好的:用户友好提示
|
|
307
|
-
throw new ServiceException("订单已发货,无法取消");
|
|
308
|
-
throw new ServiceException("库存不足,请减少购买数量");
|
|
309
|
-
throw new ServiceException("验证码已过期,请重新获取");
|
|
310
|
-
throw new ServiceException("该用户名已被注册,请换一个试试");
|
|
311
|
-
|
|
312
|
-
// ❌ 不好:技术术语
|
|
313
|
-
throw new ServiceException("order.status.invalid");
|
|
314
|
-
throw new ServiceException("NullPointerException at line 123");
|
|
315
|
-
throw new ServiceException("数据库连接失败");
|
|
316
|
-
throw new ServiceException("Duplicate entry for key 'uk_username'");
|
|
317
|
-
```
|
|
277
|
+
@Slf4j
|
|
278
|
+
@Service
|
|
279
|
+
public class OrderServiceImpl {
|
|
318
280
|
|
|
319
|
-
|
|
281
|
+
// 使用占位符(性能更好)
|
|
282
|
+
log.info("创建订单: orderNo={}, amount={}", dto.getOrderNo(), dto.getAmount());
|
|
320
283
|
|
|
321
|
-
|
|
284
|
+
// 异常日志带堆栈(第三个参数传异常对象)
|
|
285
|
+
log.error("处理失败: {}", e.getMessage(), e);
|
|
322
286
|
|
|
323
|
-
|
|
324
|
-
@Transactional(rollbackFor = Exception.class)
|
|
325
|
-
public void
|
|
326
|
-
|
|
287
|
+
// 事务方法:所有异常都回滚
|
|
288
|
+
@Transactional(rollbackFor = Exception.class)
|
|
289
|
+
public void createOrder(OrderCreateDTO dto) {
|
|
290
|
+
log.info("开始创建订单, orderNo={}", dto.getOrderNo());
|
|
291
|
+
// ... 业务逻辑
|
|
292
|
+
log.info("订单创建成功, id={}", order.getId());
|
|
293
|
+
}
|
|
327
294
|
}
|
|
328
|
-
|
|
329
|
-
// ✅ 好的:指定回滚异常类型
|
|
330
|
-
@Transactional(rollbackFor = Exception.class)
|
|
331
|
-
|
|
332
|
-
// ❌ 不好:使用默认(只回滚 RuntimeException)
|
|
333
|
-
@Transactional
|
|
334
295
|
```
|
|
335
296
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
## 错误处理检查清单
|
|
339
|
-
|
|
340
|
-
- [ ] 业务异常使用 `new ServiceException()`(不是 `ServiceException.of()`)
|
|
341
|
-
- [ ] 条件检查使用 `if + ObjectUtil.isNull()` 判断
|
|
342
|
-
- [ ] 参数校验使用 `@Validated(XxxGroup.class)`
|
|
343
|
-
- [ ] 事务方法添加 `@Transactional(rollbackFor = Exception.class)`
|
|
344
|
-
- [ ] 日志记录异常堆栈:`log.error("msg: {}", e.getMessage(), e)`
|
|
345
|
-
- [ ] 日志使用占位符 `{}`,不使用字符串拼接
|
|
346
|
-
- [ ] 敏感信息脱敏后再记录日志
|
|
347
|
-
- [ ] 重要操作记录 INFO 日志
|
|
348
|
-
- [ ] 错误提示使用用户友好语言
|
|
349
|
-
|
|
350
|
-
---
|
|
297
|
+
## 常见错误
|
|
351
298
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
|
355
|
-
|
|
356
|
-
|
|
|
357
|
-
| `
|
|
358
|
-
|
|
|
359
|
-
|
|
|
360
|
-
|
|
|
361
|
-
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
## 相关技能
|
|
366
|
-
|
|
367
|
-
| 需要了解 | 激活 Skill |
|
|
368
|
-
|---------|-----------|
|
|
369
|
-
| Java 异常规范 | `java-exception` |
|
|
370
|
-
| Service 层规范 | `java-service` |
|
|
371
|
-
| Controller 层规范 | `java-controller` |
|
|
299
|
+
| 错误 | 正确做法 |
|
|
300
|
+
|------|---------|
|
|
301
|
+
| 抛 `RuntimeException("xxx")` | 使用自定义业务异常类 |
|
|
302
|
+
| Controller 里 try-catch 所有异常 | 交给 `@RestControllerAdvice` 统一处理 |
|
|
303
|
+
| 异常信息暴露 SQL / 堆栈 | 对用户返回友好提示,日志记录完整信息 |
|
|
304
|
+
| 用 `javax.validation` 包 | JDK 17+ 使用 `jakarta.validation` |
|
|
305
|
+
| 吞掉异常:`catch (Exception e) {}` | 至少记录日志 `log.error("...", e)` |
|
|
306
|
+
| 所有异常都返回 200 状态码 | 根据异常类型返回对应 HTTP 状态码 |
|
|
307
|
+
| 用 `e.getMessage()` 直接返回给用户 | 第三方异常信息可能包含敏感信息,需要包装 |
|
|
308
|
+
| 校验逻辑写在 Controller 里 | 用 `@Valid` + DTO 注解声明式校验 |
|
|
309
|
+
| 日志用字符串拼接 `"失败:" + msg` | 用占位符 `log.error("失败: {}", msg, e)` |
|
|
310
|
+
| `@Transactional` 不指定回滚 | 加 `rollbackFor = Exception.class` |
|