@llryiop/avatar-boot-cli 1.0.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.
Files changed (77) hide show
  1. package/README.md +309 -0
  2. package/bin/cli.js +3 -0
  3. package/docs/plans/2026-03-12-avatar-boot-cli-design.md +73 -0
  4. package/docs/plans/2026-03-12-avatar-boot-cli-plan.md +681 -0
  5. package/package.json +28 -0
  6. package/src/index.js +78 -0
  7. package/src/prompts.js +78 -0
  8. package/src/template.js +37 -0
  9. package/src/transform.js +172 -0
  10. package/src/utils.js +34 -0
  11. package/templates/.claude/rules/architecture-redlines.md +146 -0
  12. package/templates/.claude/rules/code-review-standards.md +137 -0
  13. package/templates/.claude/rules/coding-standards.md +56 -0
  14. package/templates/.claude/rules/git-commit.md +59 -0
  15. package/templates/.claude/rules/layered-architecture.md +201 -0
  16. package/templates/.claude/rules/mybatis-plus.md +263 -0
  17. package/templates/.claude/rules/tech-stack.md +41 -0
  18. package/templates/.claude/rules/version.md +467 -0
  19. package/templates/.claude/settings.local.json +18 -0
  20. package/templates/.claude/skills/ai-tool-guide/SKILL.md +314 -0
  21. package/templates/.claude/skills/api-design/SKILL.md +200 -0
  22. package/templates/.claude/skills/api-doc-generator/SKILL.md +380 -0
  23. package/templates/.claude/skills/api-service-module-creator/SKILL.md +1114 -0
  24. package/templates/.claude/skills/avatar-boot-starter-feign/SKILL.md +243 -0
  25. package/templates/.claude/skills/avatar-boot-starter-job/SKILL.md +437 -0
  26. package/templates/.claude/skills/avatar-boot-starter-kafka/SKILL.md +580 -0
  27. package/templates/.claude/skills/avatar-boot-starter-mysql/SKILL.md +572 -0
  28. package/templates/.claude/skills/avatar-boot-starter-nacos/SKILL.md +901 -0
  29. package/templates/.claude/skills/avatar-boot-starter-oss/SKILL.md +594 -0
  30. package/templates/.claude/skills/avatar-boot-starter-redis/SKILL.md +586 -0
  31. package/templates/.claude/skills/avatar-boot-starter-rocketmq/SKILL.md +662 -0
  32. package/templates/.claude/skills/avatar-boot-starter-web/SKILL.md +1007 -0
  33. package/templates/.claude/skills/changelog-generator/SKILL.md +114 -0
  34. package/templates/.claude/skills/code-review/SKILL.md +239 -0
  35. package/templates/.claude/skills/crud-generator/SKILL.md +824 -0
  36. package/templates/.claude/skills/database-design/SKILL.md +377 -0
  37. package/templates/.claude/skills/deployment-config/SKILL.md +277 -0
  38. package/templates/.claude/skills/incident-analysis/SKILL.md +241 -0
  39. package/templates/.claude/skills/integration-test-generator/SKILL.md +496 -0
  40. package/templates/.claude/skills/prompt-engineering/SKILL.md +249 -0
  41. package/templates/.claude/skills/requirement-management/SKILL.md +244 -0
  42. package/templates/.claude/skills/security-audit/SKILL.md +330 -0
  43. package/templates/.claude/skills/test-case-design/SKILL.md +257 -0
  44. package/templates/.claude/skills/testing-workflow/SKILL.md +68 -0
  45. package/templates/.claude/skills/troubleshooting/SKILL.md +240 -0
  46. package/templates/CLAUDE.md +173 -0
  47. package/templates/README.md +303 -0
  48. package/templates/avatar-scaffold-api/pom.xml +41 -0
  49. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/api/LoginFeignClient.java +40 -0
  50. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/constant/LoginConstant.java +21 -0
  51. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/LoginRequest.java +17 -0
  52. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/RefreshTokenRequest.java +14 -0
  53. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/LoginResponse.java +31 -0
  54. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/TokenInfoResponse.java +25 -0
  55. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/enums/LoginTypeEnum.java +23 -0
  56. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/exception/LoginException.java +23 -0
  57. package/templates/avatar-scaffold-service/k8s-app/Dockerfile +14 -0
  58. package/templates/avatar-scaffold-service/k8s-app/Dockerfile-arm64 +14 -0
  59. package/templates/avatar-scaffold-service/packaging/assembly.xml +16 -0
  60. package/templates/avatar-scaffold-service/pom.xml +150 -0
  61. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/Application.java +21 -0
  62. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/config/LoginConfig.java +20 -0
  63. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/controller/LoginController.java +37 -0
  64. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/converter/LoginConverter.java +54 -0
  65. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/feign/DemoFeign.java +21 -0
  66. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserLoginEntity.java +33 -0
  67. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserTokenEntity.java +39 -0
  68. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/mapper/UserLoginMapper.java +20 -0
  69. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/LoginService.java +22 -0
  70. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/impl/LoginServiceImpl.java +43 -0
  71. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/utils/LoginUtils.java +31 -0
  72. package/templates/avatar-scaffold-service/src/main/resources/application-dev.yaml +29 -0
  73. package/templates/avatar-scaffold-service/src/main/resources/application-local.yaml +61 -0
  74. package/templates/avatar-scaffold-service/src/main/resources/application-prod.yaml +28 -0
  75. package/templates/avatar-scaffold-service/src/main/resources/application-test.yaml +28 -0
  76. package/templates/avatar-scaffold-service/src/main/resources/application.yaml +12 -0
  77. package/templates/pom.xml +98 -0
@@ -0,0 +1,330 @@
1
+ ---
2
+ name: security-audit
3
+ description: |
4
+ 安全审计与安全检查技能。当用户提到以下关键词时触发:
5
+ security audit, 安全审计, 安全检查, OWASP, 漏洞扫描,
6
+ SQL注入, XSS, 权限检查, 数据脱敏, 依赖漏洞
7
+
8
+ 本技能提供基于 OWASP Top 10 的 Spring Boot 安全审计清单、
9
+ MyBatis 注入检测、日志脱敏、依赖漏洞检查和认证授权审计等完整指导。
10
+ ---
11
+
12
+ # 安全审计技能
13
+
14
+ ## 适用场景
15
+
16
+ - 代码安全审查
17
+ - 上线前安全检查
18
+ - 依赖漏洞扫描
19
+ - 敏感数据处理审计
20
+ - 认证授权机制审查
21
+
22
+ ## OWASP Top 10 检查清单(Spring Boot 适配)
23
+
24
+ ### A01 — 权限控制失效(Broken Access Control)
25
+
26
+ **检查项:**
27
+
28
+ - [ ] 接口是否有权限注解(`@PreAuthorize`, `@Secured`)
29
+ - [ ] 是否存在越权访问(水平越权:访问他人数据;垂直越权:普通用户执行管理操作)
30
+ - [ ] 是否对资源 ID 进行归属校验
31
+
32
+ ```java
33
+ // 错误:未校验数据归属
34
+ @GetMapping("/orders/{id}")
35
+ public Result<OrderVO> getOrder(@PathVariable Long id) {
36
+ return Result.success(orderService.getById(id)); // 任何用户都能查任意订单
37
+ }
38
+
39
+ // 正确:校验数据归属
40
+ @GetMapping("/orders/{id}")
41
+ public Result<OrderVO> getOrder(@PathVariable Long id) {
42
+ Long currentUserId = SecurityUtils.getCurrentUserId();
43
+ OrderVO order = orderService.getByIdAndUserId(id, currentUserId);
44
+ if (order == null) {
45
+ throw new BusinessException("订单不存在或无权访问");
46
+ }
47
+ return Result.success(order);
48
+ }
49
+ ```
50
+
51
+ ### A02 — 加密机制失效(Cryptographic Failures)
52
+
53
+ **检查项:**
54
+
55
+ - [ ] 密码是否使用 BCrypt 等强哈希算法存储
56
+ - [ ] 敏感配置是否加密(数据库密码、API Key)
57
+ - [ ] 是否使用 HTTPS 传输敏感数据
58
+ - [ ] 对称加密是否使用 AES-256 以上
59
+
60
+ ```java
61
+ // 错误:明文存储密码
62
+ user.setPassword(rawPassword);
63
+
64
+ // 正确:BCrypt 加密
65
+ @Autowired
66
+ private PasswordEncoder passwordEncoder;
67
+
68
+ user.setPassword(passwordEncoder.encode(rawPassword));
69
+ ```
70
+
71
+ ### A03 — 注入攻击(Injection)
72
+
73
+ **检查项:**
74
+
75
+ - [ ] MyBatis 是否使用 `${}` 拼接用户输入(见下方详细检测)
76
+ - [ ] 是否存在 JPQL/HQL 拼接
77
+ - [ ] OS 命令执行是否拼接用户输入
78
+ - [ ] LDAP 查询是否拼接用户输入
79
+
80
+ ### A04 — 不安全的设计(Insecure Design)
81
+
82
+ **检查项:**
83
+
84
+ - [ ] 是否有接口限流(防刷)
85
+ - [ ] 敏感操作是否有二次确认
86
+ - [ ] 业务逻辑是否有防重放机制
87
+
88
+ ### A05 — 安全配置错误(Security Misconfiguration)
89
+
90
+ **检查项:**
91
+
92
+ - [ ] 生产环境是否关闭 Swagger/SpringDoc
93
+ - [ ] 是否暴露 Actuator 端点
94
+ - [ ] 错误响应是否泄露堆栈信息
95
+ - [ ] CORS 配置是否过于宽松
96
+
97
+ ```yaml
98
+ # 生产环境关闭 Actuator 敏感端点
99
+ management:
100
+ endpoints:
101
+ web:
102
+ exposure:
103
+ include: health,info
104
+ endpoint:
105
+ health:
106
+ show-details: never
107
+ ```
108
+
109
+ ### A06 — 存在漏洞的组件(Vulnerable Components)
110
+
111
+ 详见下方「依赖漏洞检查」章节。
112
+
113
+ ### A07 — 认证失效(Authentication Failures)
114
+
115
+ **检查项:**
116
+
117
+ - [ ] 登录是否有失败次数限制
118
+ - [ ] Token 是否有过期时间
119
+ - [ ] 是否支持 Token 主动失效(登出)
120
+ - [ ] 密码强度是否有要求
121
+
122
+ ### A08 — 数据完整性失效(Data Integrity Failures)
123
+
124
+ **检查项:**
125
+
126
+ - [ ] 反序列化是否限制类型
127
+ - [ ] 是否信任客户端传入的关键数据(如价格、权限标识)
128
+
129
+ ### A09 — 安全日志和监控失效(Logging & Monitoring Failures)
130
+
131
+ **检查项:**
132
+
133
+ - [ ] 登录、登出、权限变更是否有审计日志
134
+ - [ ] 敏感操作是否记录操作人和操作内容
135
+ - [ ] 日志中是否脱敏处理敏感数据(见下方详细规则)
136
+
137
+ ### A10 — 服务端请求伪造 SSRF
138
+
139
+ **检查项:**
140
+
141
+ - [ ] 是否存在用户输入 URL 后服务端请求的场景
142
+ - [ ] 是否限制请求目标的域名/IP 白名单
143
+
144
+ ## MyBatis ${} 注入检测
145
+
146
+ ### 检测规则
147
+
148
+ 在 MyBatis XML 和注解中搜索 `${}` 使用:
149
+
150
+ ```
151
+ 高危:用户输入直接通过 ${} 拼接
152
+ - ${username}, ${keyword}, ${searchText} 等
153
+
154
+ 允许:非用户输入的动态 SQL
155
+ - ${tableName}(来自系统配置,非用户输入)
156
+ - ${orderByColumn}(需严格白名单校验)
157
+ ```
158
+
159
+ ### 修复方案
160
+
161
+ ```xml
162
+ <!-- 错误:SQL 注入风险 -->
163
+ <select id="findByName" resultType="User">
164
+ SELECT * FROM user WHERE name LIKE '%${name}%'
165
+ </select>
166
+
167
+ <!-- 正确:使用 #{} 参数绑定 -->
168
+ <select id="findByName" resultType="User">
169
+ SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')
170
+ </select>
171
+ ```
172
+
173
+ ```xml
174
+ <!-- 错误:动态排序字段注入 -->
175
+ <select id="listUsers" resultType="User">
176
+ SELECT * FROM user ORDER BY ${orderBy}
177
+ </select>
178
+
179
+ <!-- 正确:白名单校验排序字段 -->
180
+ <!-- 在 Java 代码中校验 orderBy 值 -->
181
+ ```
182
+
183
+ ```java
184
+ // 排序字段白名单校验
185
+ private static final Set<String> ALLOWED_ORDER_FIELDS =
186
+ Set.of("create_time", "update_time", "username");
187
+
188
+ public void validateOrderBy(String orderBy) {
189
+ if (!ALLOWED_ORDER_FIELDS.contains(orderBy)) {
190
+ throw new BusinessException("不支持的排序字段: " + orderBy);
191
+ }
192
+ }
193
+ ```
194
+
195
+ ## 日志脱敏规则
196
+
197
+ ### 必须脱敏的字段
198
+
199
+ | 字段类型 | 原始值 | 脱敏后 | 规则 |
200
+ |-----------|------------------------|------------------------|---------------------------|
201
+ | 手机号 | `13812345678` | `138****5678` | 保留前3后4,中间4位星号 |
202
+ | 身份证号 | `110101199001011234` | `110101****1234` | 保留前6后4,中间星号 |
203
+ | 密码 | `myP@ssw0rd` | `******` | 全部替换为星号 |
204
+ | 银行卡号 | `6222021234567890` | `6222****7890` | 保留前4后4,中间星号 |
205
+ | 邮箱 | `user@example.com` | `u***@example.com` | @前保留首字符,其余星号 |
206
+ | 地址 | `北京市朝阳区xxx路` | `北京市朝阳区***` | 保留省市区,详细地址星号 |
207
+
208
+ ### 实现方式
209
+
210
+ ```java
211
+ /**
212
+ * 日志脱敏工具类
213
+ */
214
+ public class DesensitizeUtils {
215
+
216
+ /** 手机号脱敏 */
217
+ public static String phone(String phone) {
218
+ if (StringUtils.isBlank(phone) || phone.length() < 11) {
219
+ return phone;
220
+ }
221
+ return phone.substring(0, 3) + "****" + phone.substring(7);
222
+ }
223
+
224
+ /** 身份证脱敏 */
225
+ public static String idCard(String idCard) {
226
+ if (StringUtils.isBlank(idCard) || idCard.length() < 15) {
227
+ return idCard;
228
+ }
229
+ return idCard.substring(0, 6) + "****" + idCard.substring(idCard.length() - 4);
230
+ }
231
+
232
+ /** 密码脱敏 */
233
+ public static String password(String password) {
234
+ return "******";
235
+ }
236
+ }
237
+ ```
238
+
239
+ ### 日志输出规范
240
+
241
+ ```java
242
+ // 错误:日志输出明文敏感信息
243
+ log.info("用户登录: phone={}, password={}", phone, password);
244
+
245
+ // 正确:脱敏后输出
246
+ log.info("用户登录: phone={}", DesensitizeUtils.phone(phone));
247
+ // 密码绝不输出到日志
248
+ ```
249
+
250
+ ## 依赖漏洞检查
251
+
252
+ ### OWASP Dependency-Check 集成
253
+
254
+ ```xml
255
+ <!-- pom.xml -->
256
+ <plugin>
257
+ <groupId>org.owasp</groupId>
258
+ <artifactId>dependency-check-maven</artifactId>
259
+ <version>9.0.9</version>
260
+ <configuration>
261
+ <failBuildOnCVSS>7</failBuildOnCVSS>
262
+ <formats>
263
+ <format>HTML</format>
264
+ <format>JSON</format>
265
+ </formats>
266
+ </configuration>
267
+ </plugin>
268
+ ```
269
+
270
+ ```bash
271
+ # 执行漏洞扫描
272
+ mvn dependency-check:check
273
+
274
+ # 查看报告
275
+ open target/dependency-check-report.html
276
+ ```
277
+
278
+ ### 常见高危依赖
279
+
280
+ - **Log4j** — 确保使用 2.17+ 版本
281
+ - **Jackson** — 确保禁用 `defaultTyping`
282
+ - **FastJSON** — 建议替换为 Jackson
283
+ - **Spring Framework** — 及时更新安全补丁
284
+
285
+ ## 认证授权检查要点
286
+
287
+ 1. **Token 管理**
288
+ - Token 是否设置合理的过期时间
289
+ - 是否支持 Token 刷新机制
290
+ - 登出时是否主动失效 Token
291
+
292
+ 2. **接口权限**
293
+ - 每个 Controller 方法是否有权限控制
294
+ - 公开接口是否在白名单中明确列出
295
+ - 内部服务间调用是否有鉴权
296
+
297
+ 3. **数据权限**
298
+ - 是否实现数据行级权限控制
299
+ - 多租户场景是否有租户隔离
300
+
301
+ ## 敏感数据处理规则
302
+
303
+ 1. **传输层** — 敏感接口强制 HTTPS
304
+ 2. **存储层** — 密码 BCrypt,敏感字段 AES 加密
305
+ 3. **展示层** — 前端展示脱敏,VO 中处理
306
+ 4. **日志层** — 日志输出脱敏
307
+ 5. **配置层** — 数据库密码等使用加密配置或环境变量
308
+
309
+ ## 最佳实践
310
+
311
+ 1. **最小权限原则** — 接口、数据库账号、服务间调用都遵循最小权限
312
+ 2. **纵深防御** — 不依赖单一安全措施,多层防护
313
+ 3. **安全左移** — 在编码阶段就进行安全审查,而非上线前
314
+ 4. **定期扫描** — CI/CD 中集成依赖漏洞扫描
315
+ 5. **安全配置** — 生产环境关闭调试端点和详细错误信息
316
+ 6. **输入校验** — 所有外部输入都进行合法性校验
317
+ 7. **审计日志** — 关键操作留痕,便于事后追溯
318
+
319
+ ## 安全审计检查清单
320
+
321
+ - [ ] OWASP Top 10 逐项检查通过
322
+ - [ ] MyBatis XML 中无 `${}` 拼接用户输入
323
+ - [ ] 日志输出已脱敏敏感字段
324
+ - [ ] 依赖漏洞扫描无高危漏洞(CVSS < 7)
325
+ - [ ] 所有接口有权限控制
326
+ - [ ] 密码使用 BCrypt 存储
327
+ - [ ] 生产环境关闭 Swagger 和 Actuator 敏感端点
328
+ - [ ] 敏感配置使用环境变量或加密方式
329
+ - [ ] 关键操作有审计日志
330
+ - [ ] CORS 配置限制了允许的域名
@@ -0,0 +1,257 @@
1
+ ---
2
+ name: test-case-design
3
+ description: |
4
+ 测试用例设计技能。当用户提到以下关键词时触发:
5
+ test case, 测试用例, test design, 用例设计, 测试场景,
6
+ 边界值, 等价类, 测试覆盖
7
+
8
+ 本技能提供 Given-When-Then 结构化用例设计、边界值分析、
9
+ 等价类划分、异常场景覆盖和 JUnit 5 代码模板等完整指导。
10
+ ---
11
+
12
+ # 测试用例设计技能
13
+
14
+ ## 适用场景
15
+
16
+ - 为新功能设计测试用例
17
+ - 审查现有测试的完整性
18
+ - 补充边界和异常测试场景
19
+ - 制定测试策略和覆盖目标
20
+
21
+ ## Given-When-Then 结构模板
22
+
23
+ 每个测试用例遵循 **Given-When-Then** 三段式结构:
24
+
25
+ ```
26
+ Given(前置条件): 描述测试的初始状态和准备数据
27
+ When (触发动作): 描述被测试的操作
28
+ Then (预期结果): 描述期望的输出或状态变化
29
+ ```
30
+
31
+ ### 用例编写示例
32
+
33
+ ```
34
+ 用例编号: TC-USER-001
35
+ 用例名称: 正常创建用户
36
+ 优先级: P0
37
+ 前提条件: 数据库中不存在用户名为 "zhangsan" 的用户
38
+
39
+ Given: 准备合法的用户创建请求
40
+ - username = "zhangsan"
41
+ - phone = "13812345678"
42
+ - email = "zhangsan@example.com"
43
+
44
+ When: 调用 POST /api/users 接口
45
+
46
+ Then:
47
+ - 响应状态码为 200
48
+ - Result.code 为 0
49
+ - Result.data 返回新创建的用户 ID
50
+ - 数据库中存在对应用户记录
51
+ - 用户创建时间不为空
52
+ ```
53
+
54
+ ## 边界值分析
55
+
56
+ ### 分析方法
57
+
58
+ 对于每个输入参数,分析其有效边界和无效边界:
59
+
60
+ ```
61
+ 参数: username(用户名)
62
+ 约束: 长度 1-50,只允许字母、数字、下划线
63
+
64
+ 边界值测试点:
65
+ ┌─────────────────┬──────────────┬──────────┐
66
+ │ 测试点 │ 输入值 │ 期望结果 │
67
+ ├─────────────────┼──────────────┼──────────┤
68
+ │ 最小有效长度 │ "a" │ 成功 │
69
+ │ 最大有效长度 │ 50个字符的串 │ 成功 │
70
+ │ 低于最小长度 │ "" │ 失败 │
71
+ │ 超过最大长度 │ 51个字符的串 │ 失败 │
72
+ │ 有效字符组合 │ "user_123" │ 成功 │
73
+ │ 包含非法字符 │ "user@name" │ 失败 │
74
+ │ null 值 │ null │ 失败 │
75
+ └─────────────────┴──────────────┴──────────┘
76
+ ```
77
+
78
+ ### 数值型参数边界
79
+
80
+ ```
81
+ 参数: pageSize(每页条数)
82
+ 约束: 最小 1,最大 100,默认 10
83
+
84
+ 测试点:
85
+ - pageSize = 0 → 校验失败
86
+ - pageSize = 1 → 成功(最小边界)
87
+ - pageSize = 100 → 成功(最大边界)
88
+ - pageSize = 101 → 校验失败
89
+ - pageSize = -1 → 校验失败
90
+ - pageSize = null → 使用默认值 10
91
+ ```
92
+
93
+ ## 等价类划分
94
+
95
+ ### 划分方法
96
+
97
+ 将输入数据划分为有效等价类和无效等价类,每类取一个代表值进行测试:
98
+
99
+ ```
100
+ 参数: 手机号(phone)
101
+
102
+ 有效等价类:
103
+ ┌─────┬───────────────────┬──────────────┐
104
+ │ 编号 │ 描述 │ 代表值 │
105
+ ├─────┼───────────────────┼──────────────┤
106
+ │ V1 │ 合法的11位手机号 │ 13812345678 │
107
+ │ V2 │ 不同运营商号段 │ 15912345678 │
108
+ │ V3 │ 新号段 │ 19912345678 │
109
+ └─────┴───────────────────┴──────────────┘
110
+
111
+ 无效等价类:
112
+ ┌─────┬───────────────────┬──────────────┐
113
+ │ 编号 │ 描述 │ 代表值 │
114
+ ├─────┼───────────────────┼──────────────┤
115
+ │ I1 │ 少于11位 │ 1381234567 │
116
+ │ I2 │ 多于11位 │ 138123456789 │
117
+ │ I3 │ 非1开头 │ 23812345678 │
118
+ │ I4 │ 包含字母 │ 138abcd5678 │
119
+ │ I5 │ 空值 │ null / "" │
120
+ └─────┴───────────────────┴──────────────┘
121
+ ```
122
+
123
+ ## 异常与边缘场景
124
+
125
+ ### 必须覆盖的场景类型
126
+
127
+ 1. **空值场景** — null、空字符串、空集合
128
+ 2. **重复数据** — 唯一约束冲突
129
+ 3. **并发场景** — 同时操作同一资源
130
+ 4. **权限场景** — 无权限、越权操作
131
+ 5. **数据状态** — 已删除/已禁用的数据操作
132
+ 6. **关联数据** — 被引用数据的删除
133
+ 7. **数据量** — 空列表、超大列表
134
+ 8. **特殊字符** — SQL 注入字符、XSS 字符、emoji
135
+
136
+ ### 示例:用户删除接口的异常场景
137
+
138
+ ```
139
+ TC-USER-DEL-001: 删除不存在的用户
140
+ Given: 用户 ID = 999999 不存在于数据库
141
+ When: 调用 DELETE /api/users/999999
142
+ Then: 返回错误,code 非 0,提示"用户不存在"
143
+
144
+ TC-USER-DEL-002: 删除已被订单关联的用户
145
+ Given: 用户 ID = 1 存在且有未完成订单
146
+ When: 调用 DELETE /api/users/1
147
+ Then: 返回错误,提示"该用户存在关联订单,无法删除"
148
+
149
+ TC-USER-DEL-003: 重复删除同一用户
150
+ Given: 用户 ID = 1 已被删除(逻辑删除)
151
+ When: 再次调用 DELETE /api/users/1
152
+ Then: 返回错误,提示"用户不存在"
153
+
154
+ TC-USER-DEL-004: 无权限删除用户
155
+ Given: 当前登录用户为普通角色
156
+ When: 调用 DELETE /api/users/1
157
+ Then: 返回 403 无权限
158
+ ```
159
+
160
+ ## JUnit 5 测试代码模板
161
+
162
+ ```java
163
+ @DisplayName("用户创建接口测试")
164
+ class UserCreateTest {
165
+
166
+ @Test
167
+ @DisplayName("正常创建用户 - 应返回用户ID")
168
+ void should_return_user_id_when_create_with_valid_data() {
169
+ // Given
170
+ UserInfoCreateDTO dto = new UserInfoCreateDTO();
171
+ dto.setUsername("zhangsan");
172
+ dto.setPhone("13812345678");
173
+ dto.setEmail("zhangsan@example.com");
174
+
175
+ // When
176
+ Result<Long> result = userService.create(dto);
177
+
178
+ // Then
179
+ assertThat(result.getCode()).isEqualTo(0);
180
+ assertThat(result.getData()).isNotNull();
181
+ assertThat(result.getData()).isPositive();
182
+ }
183
+
184
+ @Test
185
+ @DisplayName("用户名为空时 - 应返回参数错误")
186
+ void should_fail_when_username_is_blank() {
187
+ // Given
188
+ UserInfoCreateDTO dto = new UserInfoCreateDTO();
189
+ dto.setUsername("");
190
+ dto.setPhone("13812345678");
191
+
192
+ // When & Then
193
+ assertThatThrownBy(() -> userService.create(dto))
194
+ .isInstanceOf(ConstraintViolationException.class);
195
+ }
196
+
197
+ @Test
198
+ @DisplayName("用户名重复时 - 应返回业务异常")
199
+ void should_fail_when_username_already_exists() {
200
+ // Given
201
+ UserInfoCreateDTO dto = new UserInfoCreateDTO();
202
+ dto.setUsername("existing_user");
203
+ dto.setPhone("13812345678");
204
+
205
+ // When & Then
206
+ assertThatThrownBy(() -> userService.create(dto))
207
+ .isInstanceOf(BusinessException.class)
208
+ .hasMessageContaining("用户名已存在");
209
+ }
210
+
211
+ @ParameterizedTest
212
+ @DisplayName("手机号格式校验 - 非法手机号应失败")
213
+ @ValueSource(strings = {"1381234567", "138123456789", "23812345678", "abc"})
214
+ void should_fail_when_phone_format_invalid(String phone) {
215
+ // Given
216
+ UserInfoCreateDTO dto = new UserInfoCreateDTO();
217
+ dto.setUsername("testuser");
218
+ dto.setPhone(phone);
219
+
220
+ // When & Then
221
+ assertThatThrownBy(() -> userService.create(dto))
222
+ .isInstanceOf(ConstraintViolationException.class);
223
+ }
224
+ }
225
+ ```
226
+
227
+ ### 测试方法命名规范
228
+
229
+ ```
230
+ should_[预期行为]_when_[条件]
231
+
232
+ 示例:
233
+ should_return_user_when_id_exists
234
+ should_throw_exception_when_username_is_blank
235
+ should_return_empty_list_when_no_data
236
+ ```
237
+
238
+ ## 覆盖率要求
239
+
240
+ | 指标 | 最低要求 | 推荐目标 |
241
+ |-----------------|-----------|-----------|
242
+ | 行覆盖率 | 60% | 80% |
243
+ | 分支覆盖率 | 50% | 70% |
244
+ | 核心业务方法覆盖 | 90% | 100% |
245
+ | Controller 层 | 80% | 95% |
246
+ | Service 层 | 70% | 90% |
247
+
248
+ ## 最佳实践
249
+
250
+ 1. **用例优先级** — P0(核心流程)> P1(重要分支)> P2(边界条件)> P3(极端场景)
251
+ 2. **独立性** — 每个测试用例互不依赖,可独立运行
252
+ 3. **可重复性** — 多次执行结果一致,不依赖外部状态
253
+ 4. **一测一验** — 每个测试方法只验证一个行为
254
+ 5. **命名清晰** — 测试方法名要能描述测试场景和预期结果
255
+ 6. **数据隔离** — 测试数据在测试前后自动清理
256
+ 7. **参数化测试** — 相似场景使用 `@ParameterizedTest` 减少重复代码
257
+ 8. **先写失败用例** — 确保测试确实能发现问题
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: testing-workflow
3
+ description: 测试工作流技能。当用户需要编写测试、运行测试、检查测试覆盖率,或提到 JUnit、Mockito、MockMvc、测试规范时触发。
4
+ ---
5
+
6
+ # 测试工作流
7
+
8
+ ## 测试框架
9
+
10
+ - **单元测试**: JUnit 5 + Mockito
11
+ - **集成测试**: Spring Boot Test
12
+ - **API 测试**: MockMvc / RestAssured
13
+
14
+ ## 覆盖率要求
15
+
16
+ - 单元测试覆盖率 > 70%
17
+ - 核心业务逻辑覆盖率 > 90%
18
+ - 所有 Controller 方法必须有测试
19
+
20
+ ## 测试命名规范
21
+
22
+ ```java
23
+ @Test
24
+ @DisplayName("当用户ID存在时,应该返回用户信息")
25
+ void shouldReturnUserWhenUserIdExists() {
26
+ // given
27
+ Long userId = 1L;
28
+
29
+ // when
30
+ Result<UserResponse> result = userController.getUser(userId);
31
+
32
+ // then
33
+ assertThat(result.getCode()).isEqualTo(200);
34
+ assertThat(result.getData()).isNotNull();
35
+ }
36
+ ```
37
+
38
+ ## 测试结构规则
39
+
40
+ - 使用 Given-When-Then 结构
41
+ - 每个测试只验证一个场景
42
+ - 测试方法名清晰表达测试意图
43
+
44
+ ## 测试命令
45
+
46
+ ```bash
47
+ # 运行所有测试
48
+ mvn clean test
49
+
50
+ # 运行单个测试类
51
+ mvn test -Dtest=UserControllerTest
52
+
53
+ # 生成测试覆盖率报告
54
+ mvn clean test jacoco:report
55
+ ```
56
+
57
+ ## 工作流步骤
58
+
59
+ 1. 确认被测类的职责范围
60
+ 2. 为每个公共方法编写至少一个正向测试
61
+ 3. 为边界条件和异常路径编写测试
62
+ 4. 运行 `mvn clean test` 确认全部通过
63
+ 5. 检查覆盖率是否达标
64
+
65
+ ## 相关规则
66
+
67
+ - [coding-standards.md](../../rules/coding-standards.md)
68
+ - [architecture-redlines.md](../../rules/architecture-redlines.md)