@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,1114 @@
1
+ ---
2
+ name: api-service-module-creator
3
+ description: 在项目中创建标准「API + Service」双模块,用于构建项目目录体系标准
4
+ ---
5
+
6
+ # API + Service 双模块创建技能
7
+
8
+ ## Overview(概述)
9
+
10
+ 本技能用于指导在 Avatar Boot 框架下创建标准的 API + Service 双模块项目结构。这种架构模式将接口定义(API)与实现(Service)分离,使得其他服务可以通过引入 API 模块来调用服务,而无需依赖具体实现。
11
+
12
+ **重要**:API 模块使用 `avatar-boot-starter-feign` 实现 Feign 客户端,提供开箱即用的 HTTP 客户端功能,包含自动日志记录、TraceId 传播和性能指标监控。详见:[avatar-boot-starter-feign 参考文档](../template/reference/avatar-boot-starter-feign.md)
13
+
14
+ ### 核心特点
15
+
16
+ **API 模块**:
17
+ - 包含 Feign 客户端接口,使用 `avatar-boot-starter-feign` 实现远程调用
18
+ - 自动集成请求日志、TraceId 传播、性能指标等功能
19
+ - 包含基础 POJO、请求/响应对象、常量定义
20
+ - 依赖最小化,仅包含必要的接口定义
21
+ - 被其他服务引入时,在不配置的情况下能正常启动(使用 `@ConditionalOnProperty` 等条件注解)
22
+ - 模块名是项目名后面加"-api"后缀
23
+
24
+ **Service 模块(service 模块)**:
25
+ - 实现具体业务功能(Controller、Service、Mapper)
26
+ - 依赖 API 模块
27
+ - 包含完整的业务逻辑和数据访问层
28
+ - 模块名是项目名后面加"-service"后缀
29
+
30
+ **代码组织原则**:
31
+ - 按功能单元组织代码,而非按技术层次
32
+ - 每个功能单元包含完整的 controller、service、mapper、entity
33
+ - 功能单元之间职责清晰,低耦合
34
+
35
+ 示例目录结构:
36
+ ```
37
+ avatar-scaffold-project/
38
+ ├── avatar-scaffold-api/ # API 模块(接口定义)
39
+ │ └── src/main/java/com/iflytek/avatar/
40
+ │ ├── common/ # 公共模块(跨功能共享)
41
+ │ │ ├── constant/ # 系统级常量
42
+ │ │ │ └── ApiConstant.java
43
+ │ │ ├── enums/ # 公共枚举
44
+ │ │ │ └── ResultCodeEnum.java
45
+ │ │ ├── exception/ # 公共异常
46
+ │ │ │ ├── ScaffoldException.java
47
+ │ │ │ └── ErrorCode.java
48
+ │ │ └── dto/ # 公共DTO
49
+ │ │ ├── PageRequest.java # 分页请求基类
50
+ │ │ └── PageResponse.java # 分页响应基类
51
+ │ │
52
+ │ ├── login/ # 登录功能模块
53
+ │ │ ├── api/ # Feign接口定义
54
+ │ │ │ └── LoginFeignClient.java
55
+ │ │ ├── constant/ # 模块常量
56
+ │ │ │ └── LoginConstant.java
57
+ │ │ ├── enums/ # 模块枚举
58
+ │ │ │ └── LoginTypeEnum.java
59
+ │ │ ├── exception/ # 模块异常
60
+ │ │ │ └── LoginException.java
61
+ │ │ └── dto/ # **DTO目录(统一命名)**
62
+ │ │ ├── request/ # 请求DTO
63
+ │ │ │ ├── LoginRequest.java
64
+ │ │ │ └── RefreshTokenRequest.java
65
+ │ │ └── response/ # 响应DTO
66
+ │ │ ├── LoginResponse.java
67
+ │ │ └── TokenInfoResponse.java
68
+ │ │
69
+ │ └── user/ # 用户功能模块
70
+ │ ├── api/
71
+ │ │ └── UserFeignClient.java
72
+ │ ├── constant/
73
+ │ │ └── UserConstant.java
74
+ │ ├── enums/
75
+ │ │ └── UserStatusEnum.java
76
+ │ ├── exception/
77
+ │ │ └── UserException.java
78
+ │ └── dto/
79
+ │ ├── request/
80
+ │ │ ├── CreateUserRequest.java
81
+ │ │ ├── UpdateUserRequest.java
82
+ │ │ └── UserQueryRequest.java
83
+ │ └── response/
84
+ │ ├── UserDetailResponse.java
85
+ │ └── UserSimpleResponse.java
86
+
87
+ └── avatar-scaffold-service/ # Service 模块(业务实现)
88
+ └── src/main/java/com/iflytek/avatar/
89
+ ├── common/ # 公共实现
90
+ │ ├── config/ # 全局配置
91
+ │ │ ├── MyBatisConfig.java
92
+ │ │ ├── RedisConfig.java
93
+ │ │ └── SwaggerConfig.java
94
+ │ ├── utils/ # 公共工具类
95
+ │ │ ├── JwtUtils.java
96
+ │ │ └── RedisUtils.java
97
+ │ └── interceptor/ # 全局拦截器
98
+ │ └── LoginInterceptor.java
99
+
100
+ ├── login/ # 登录功能实现
101
+ │ ├── config/ # 模块配置
102
+ │ │ └── LoginConfig.java
103
+ │ ├── controller/ # REST控制器(实现API)
104
+ │ │ └── LoginController.java
105
+ │ ├── service/ # 业务逻辑层
106
+ │ │ ├── LoginService.java # 接口
107
+ │ │ └── impl/
108
+ │ │ └── LoginServiceImpl.java # 实现
109
+ │ ├── repository/ # **数据访问层(使用repository)**
110
+ │ │ ├── mapper/ # MyBatis Mapper接口
111
+ │ │ │ └── UserLoginMapper.java
112
+ │ │ └── entity/ # **数据库实体(统一用entity)**
113
+ │ │ ├── UserLoginEntity.java # 用户登录表实体
114
+ │ │ └── UserTokenEntity.java # 用户Token表实体
115
+ │ ├── converter/ # **对象转换器(DO <-> DTO)**
116
+ │ │ └── LoginConverter.java
117
+ │ └── utils/ # 模块工具类
118
+ │ └── LoginUtils.java
119
+
120
+ └── user/ # 用户功能实现
121
+ ├── controller/
122
+ │ └── UserController.java
123
+ ├── service/
124
+ │ ├── UserService.java
125
+ │ └── impl/
126
+ │ └── UserServiceImpl.java
127
+ ├── repository/
128
+ │ ├── mapper/
129
+ │ │ └── UserMapper.java
130
+ │ └── entity/
131
+ │ ├── UserEntity.java # 用户主表实体
132
+ │ └── UserProfileEntity.java # 用户档案表实体
133
+ ├── converter/
134
+ │ └── UserConverter.java
135
+ └── validator/ # **参数校验器**
136
+ └── UserValidator.java
137
+ ```
138
+
139
+ ### 架构规则
140
+
141
+ 1. **功能隔离**:每个业务功能(如 login、user)都有独立的包结构,功能之间完全隔离
142
+ 2. **垂直划分**:功能包内包含该功能的所有层次(controller、service、dao、entity),避免水平分层
143
+ 3. **API 分离**:API 模块只包含接口定义和数据传输对象(DTO),不包含业务逻辑
144
+ 4. **Service 实现**:Service 模块包含具体的业务逻辑实现,实现 API 模块定义的接口
145
+ 5. **统一返回格式**:所有 API 接口必须使用 `avatar-boot-core` 包中的 `Result<T>` 类作为返回类型,确保响应格式统一
146
+
147
+ ### 包结构说明
148
+
149
+ 以下说明针对**每个功能模块包**(如 `login/`、`user/`)内的子包。**common/** 为跨功能共享:API 的 common 含 `constant/`、`enums/`、`exception/`、`dto/`(如分页基类);Service 的 common 含 `config/`、`utils/`、`interceptor/`。详见上文示例目录。
150
+
151
+ **API 模块包结构**(avatar-scaffold-api,每个功能包下):
152
+ - `api/` - Feign 客户端接口(如 `LoginFeignClient.java`,基于 `avatar-boot-starter-feign`)
153
+ - `constant/` - 模块常量(如 `LoginConstant.java`)
154
+ - `enums/` - 模块枚举(如 `LoginTypeEnum.java`)
155
+ - `exception/` - 模块异常(如 `LoginException.java`)
156
+ - `dto/` - 数据传输对象(统一命名)
157
+ - `dto/request/` - 请求 DTO(如 `LoginRequest.java`)
158
+ - `dto/response/` - 响应 DTO(如 `LoginResponse.java`,被调用方需用 `Result<T>` 包装)
159
+
160
+ **Service 模块包结构**(avatar-scaffold-service,每个功能包下):
161
+ - `config/` - 模块配置(如 `LoginConfig.java`)
162
+ - `controller/` - REST 控制器(实现 API 契约,返回 `Result<T>`)
163
+ - `service/` - 业务逻辑层(接口如 `LoginService.java`,实现在 `service/impl/`)
164
+ - `repository/` - 数据访问层(统一使用 repository)
165
+ - `repository/mapper/` - MyBatis Mapper 接口(如 `UserLoginMapper.java`)
166
+ - `repository/entity/` - 数据库实体(如 `UserLoginEntity.java`,统一用 entity)
167
+ - `converter/` - 对象转换器(DO/Entity 与 DTO 互转,如 `LoginConverter.java`)
168
+ - `utils/` - 模块工具类(如 `LoginUtils.java`)
169
+ - `validator/` - 参数校验器(可选,如 `UserValidator.java`)
170
+
171
+ **Result 类使用规范**:
172
+ - 所有 Controller 方法必须返回 `com.iflytek.avatar.boot.entity.response.Result<T>` 类型
173
+ - 成功响应:`Result.success(data)` 或 `Result.success()`
174
+ - 失败响应:`Result.fail(errorCode, errorMessage)` 或 `Result.fail(errorMessage)`
175
+ - Response DTO 作为泛型参数:`Result<UserResponse>`
176
+ - 统一的响应格式确保前后端接口规范一致
177
+
178
+ ## When to Use(使用场景)
179
+
180
+ ### 适用场景
181
+
182
+ - **创建新的业务模块**:如认证模块、文件服务模块、支付模块等
183
+ - **需要被其他微服务调用**:功能需要通过 Feign 客户端被其他服务调用
184
+ - **需要清晰的接口定义和实现分离**:API 模块作为契约,Service 模块作为实现
185
+ - **多团队协作开发**:API 模块可以先定义好接口,多个团队并行开发
186
+ - **功能模块化管理**:按业务功能组织代码,便于维护和扩展
187
+
188
+ ### 不适用场景
189
+
190
+ - **简单的单体应用**:如果不需要微服务调用,可以使用单模块结构
191
+ - **纯内部工具类**:不需要对外提供接口的工具类模块
192
+ - **临时测试项目**:快速验证想法的临时项目
193
+
194
+ ## Core Pattern(核心模式)
195
+
196
+ **关键原则**:
197
+ - 功能内聚:一个功能单元包含该功能的所有代码
198
+ - 职责单一:每个功能单元只负责一个业务领域
199
+ - 低耦合:功能单元之间通过接口交互
200
+
201
+ ## Implementation(实施步骤)
202
+
203
+ ### 步骤 1:创建父模块 POM
204
+
205
+ 创建父模块的 `pom.xml`,定义子模块和公共依赖:
206
+
207
+ ```xml
208
+ <?xml version="1.0" encoding="UTF-8"?>
209
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
210
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
211
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
212
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
213
+ <modelVersion>4.0.0</modelVersion>
214
+
215
+ <parent>
216
+ <groupId>com.iflytek</groupId>
217
+ <artifactId>avatar-boot-parent</artifactId>
218
+ <version>1.0.0-SNAPSHOT</version>
219
+ </parent>
220
+
221
+ <artifactId>user-service</artifactId>
222
+ <packaging>pom</packaging>
223
+ <version>1.0.0-SNAPSHOT</version>
224
+
225
+ <modules>
226
+ <module>user-service-api</module>
227
+ <module>user-service-biz</module>
228
+ </modules>
229
+
230
+ <properties>
231
+ <java.version>21</java.version>
232
+ <spring-boot.version>3.5.3</spring-boot.version>
233
+ </properties>
234
+ </project>
235
+ ```
236
+
237
+ ### 步骤 2:创建 API 模块
238
+
239
+ #### 2.1 创建 API 模块的 pom.xml
240
+
241
+ **重要**:使用 `avatar-boot-starter-feign` 替代原生 Spring Cloud OpenFeign,获得开箱即用的日志、TraceId 传播和性能监控功能。
242
+
243
+ ```xml
244
+ <?xml version="1.0" encoding="UTF-8"?>
245
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
246
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
247
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
248
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
249
+ <modelVersion>4.0.0</modelVersion>
250
+
251
+ <parent>
252
+ <groupId>com.iflytek</groupId>
253
+ <artifactId>user-service</artifactId>
254
+ <version>1.0.0-SNAPSHOT</version>
255
+ </parent>
256
+
257
+ <artifactId>user-service-api</artifactId>
258
+
259
+ <dependencies>
260
+ <!-- Avatar Boot Starter Feign(推荐使用,包含日志、TraceId、指标等功能) -->
261
+ <dependency>
262
+ <groupId>com.iflytek.avatar.boot</groupId>
263
+ <artifactId>avatar-boot-starter-feign</artifactId>
264
+ </dependency>
265
+
266
+ <!-- Validation API -->
267
+ <dependency>
268
+ <groupId>jakarta.validation</groupId>
269
+ <artifactId>jakarta.validation-api</artifactId>
270
+ </dependency>
271
+
272
+ <!-- Lombok(可选) -->
273
+ <dependency>
274
+ <groupId>org.projectlombok</groupId>
275
+ <artifactId>lombok</artifactId>
276
+ <optional>true</optional>
277
+ </dependency>
278
+ </dependencies>
279
+ </project>
280
+ ```
281
+
282
+ **说明**:
283
+ - `avatar-boot-starter-feign` 已包含 `spring-cloud-starter-openfeign` 依赖
284
+ - 自动提供请求日志记录、TraceId 传播、性能指标监控等功能
285
+ - 详细配置参见:[avatar-boot-starter-feign 参考文档](../template/reference/avatar-boot-starter-feign.md)
286
+
287
+ #### 2.2 创建 Feign 客户端接口
288
+
289
+ ```java
290
+ package com.iflytek.user.client;
291
+
292
+ import com.iflytek.user.dto.UserDTO;
293
+ import com.iflytek.user.request.LoginRequest;
294
+ import com.iflytek.user.response.LoginResponse;
295
+ import org.springframework.cloud.openfeign.FeignClient;
296
+ import org.springframework.web.bind.annotation.*;
297
+
298
+ /**
299
+ * 用户服务 Feign 客户端
300
+ *
301
+ * 注意:使用 @ConditionalOnProperty 确保在未配置时不会启动失败
302
+ */
303
+ @FeignClient(
304
+ name = "user-service",
305
+ path = "/api/user",
306
+ // 使用条件注解,只有配置了才会启用
307
+ configuration = UserClientConfiguration.class
308
+ )
309
+ public interface UserClient {
310
+
311
+ /**
312
+ * 用户登录
313
+ */
314
+ @PostMapping("/login")
315
+ LoginResponse login(@RequestBody LoginRequest request);
316
+
317
+ /**
318
+ * 根据 ID 获取用户信息
319
+ */
320
+ @GetMapping("/{userId}")
321
+ UserDTO getUserById(@PathVariable("userId") Long userId);
322
+
323
+ /**
324
+ * 创建用户
325
+ */
326
+ @PostMapping
327
+ UserDTO createUser(@RequestBody UserDTO userDTO);
328
+ }
329
+ ```
330
+
331
+ #### 2.3 创建 Feign 客户端配置类
332
+
333
+ ```java
334
+ package com.iflytek.user.client;
335
+
336
+ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
337
+ import org.springframework.context.annotation.Configuration;
338
+
339
+ /**
340
+ * Feign 客户端配置
341
+ *
342
+ * 只有在配置了 feign.client.user-service.enabled=true 时才会启用
343
+ */
344
+ @Configuration
345
+ @ConditionalOnProperty(
346
+ prefix = "feign.client.user-service",
347
+ name = "enabled",
348
+ havingValue = "true",
349
+ matchIfMissing = false // 默认不启用
350
+ )
351
+ public class UserClientConfiguration {
352
+ // Feign 客户端的自定义配置
353
+ }
354
+ ```
355
+
356
+ #### 2.4 创建 DTO 和请求/响应对象
357
+
358
+ ```java
359
+ package com.iflytek.user.dto;
360
+
361
+ import lombok.Data;
362
+ import jakarta.validation.constraints.NotBlank;
363
+ import jakarta.validation.constraints.Email;
364
+
365
+ /**
366
+ * 用户数据传输对象
367
+ */
368
+ @Data
369
+ public class UserDTO {
370
+
371
+ private Long id;
372
+
373
+ @NotBlank(message = "用户名不能为空")
374
+ private String username;
375
+
376
+ @Email(message = "邮箱格式不正确")
377
+ private String email;
378
+
379
+ private String phone;
380
+
381
+ private Integer status;
382
+ }
383
+ ```
384
+
385
+ ```java
386
+ package com.iflytek.user.request;
387
+
388
+ import lombok.Data;
389
+ import jakarta.validation.constraints.NotBlank;
390
+
391
+ /**
392
+ * 登录请求
393
+ */
394
+ @Data
395
+ public class LoginRequest {
396
+
397
+ @NotBlank(message = "用户名不能为空")
398
+ private String username;
399
+
400
+ @NotBlank(message = "密码不能为空")
401
+ private String password;
402
+ }
403
+ ```
404
+
405
+ ```java
406
+ package com.iflytek.user.response;
407
+
408
+ import lombok.Data;
409
+
410
+ /**
411
+ * 登录响应
412
+ */
413
+ @Data
414
+ public class LoginResponse {
415
+
416
+ private String token;
417
+
418
+ private Long userId;
419
+
420
+ private String username;
421
+
422
+ private Long expireTime;
423
+ }
424
+ ```
425
+
426
+ ### 步骤 3:创建 Service 实现模块(biz 模块)
427
+
428
+ #### 3.1 创建 biz 模块的 pom.xml
429
+
430
+ ```xml
431
+ <?xml version="1.0" encoding="UTF-8"?>
432
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
433
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
434
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
435
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
436
+ <modelVersion>4.0.0</modelVersion>
437
+
438
+ <parent>
439
+ <groupId>com.iflytek</groupId>
440
+ <artifactId>user-service</artifactId>
441
+ <version>1.0.0-SNAPSHOT</version>
442
+ </parent>
443
+
444
+ <artifactId>user-service-biz</artifactId>
445
+
446
+ <dependencies>
447
+ <!-- 依赖 API 模块 -->
448
+ <dependency>
449
+ <groupId>com.iflytek</groupId>
450
+ <artifactId>user-service-api</artifactId>
451
+ <version>${project.version}</version>
452
+ </dependency>
453
+
454
+ <!-- Spring Boot Web -->
455
+ <dependency>
456
+ <groupId>org.springframework.boot</groupId>
457
+ <artifactId>spring-boot-starter-web</artifactId>
458
+ </dependency>
459
+
460
+ <!-- MyBatis Plus -->
461
+ <dependency>
462
+ <groupId>com.baomidou</groupId>
463
+ <artifactId>mybatis-plus-boot-starter</artifactId>
464
+ </dependency>
465
+
466
+ <!-- MySQL Driver -->
467
+ <dependency>
468
+ <groupId>com.mysql</groupId>
469
+ <artifactId>mysql-connector-j</artifactId>
470
+ </dependency>
471
+
472
+ <!-- Redis -->
473
+ <dependency>
474
+ <groupId>org.springframework.boot</groupId>
475
+ <artifactId>spring-boot-starter-data-redis</artifactId>
476
+ </dependency>
477
+
478
+ <!-- Lombok -->
479
+ <dependency>
480
+ <groupId>org.projectlombok</groupId>
481
+ <artifactId>lombok</artifactId>
482
+ <optional>true</optional>
483
+ </dependency>
484
+ </dependencies>
485
+
486
+ <build>
487
+ <plugins>
488
+ <plugin>
489
+ <groupId>org.springframework.boot</groupId>
490
+ <artifactId>spring-boot-maven-plugin</artifactId>
491
+ </plugin>
492
+ </plugins>
493
+ </build>
494
+ </project>
495
+ ```
496
+
497
+ ### 步骤 4:按功能单元组织代码
498
+
499
+ #### 4.1 创建 login 功能单元
500
+
501
+ **LoginController.java**:
502
+
503
+ ```java
504
+ package com.iflytek.user.login.controller;
505
+
506
+ import com.iflytek.user.client.UserClient;
507
+ import com.iflytek.user.login.service.LoginService;
508
+ import com.iflytek.user.request.LoginRequest;
509
+ import com.iflytek.user.response.LoginResponse;
510
+ import lombok.RequiredArgsConstructor;
511
+ import org.springframework.validation.annotation.Validated;
512
+ import org.springframework.web.bind.annotation.*;
513
+
514
+ /**
515
+ * 登录控制器
516
+ *
517
+ * 实现 API 模块定义的接口
518
+ */
519
+ @RestController
520
+ @RequestMapping("/api/user")
521
+ @RequiredArgsConstructor
522
+ public class LoginController implements UserClient {
523
+
524
+ private final LoginService loginService;
525
+
526
+ @Override
527
+ @PostMapping("/login")
528
+ public LoginResponse login(@Validated @RequestBody LoginRequest request) {
529
+ return loginService.login(request);
530
+ }
531
+
532
+ // 实现 UserClient 的其他方法...
533
+ }
534
+ ```
535
+
536
+ **LoginService.java**:
537
+
538
+ ```java
539
+ package com.iflytek.user.login.service;
540
+
541
+ import com.iflytek.user.request.LoginRequest;
542
+ import com.iflytek.user.response.LoginResponse;
543
+
544
+ /**
545
+ * 登录服务接口
546
+ */
547
+ public interface LoginService {
548
+
549
+ /**
550
+ * 用户登录
551
+ */
552
+ LoginResponse login(LoginRequest request);
553
+ }
554
+ ```
555
+
556
+ **LoginServiceImpl.java**:
557
+
558
+ ```java
559
+ package com.iflytek.user.login.service.impl;
560
+
561
+ import com.iflytek.user.login.mapper.UserMapper;
562
+ import com.iflytek.user.login.entity.User;
563
+ import com.iflytek.user.login.service.LoginService;
564
+ import com.iflytek.user.request.LoginRequest;
565
+ import com.iflytek.user.response.LoginResponse;
566
+ import lombok.RequiredArgsConstructor;
567
+ import lombok.extern.slf4j.Slf4j;
568
+ import org.springframework.stereotype.Service;
569
+ import org.springframework.transaction.annotation.Transactional;
570
+
571
+ /**
572
+ * 登录服务实现
573
+ */
574
+ @Slf4j
575
+ @Service
576
+ @RequiredArgsConstructor
577
+ public class LoginServiceImpl implements LoginService {
578
+
579
+ private final UserMapper userMapper;
580
+
581
+ @Override
582
+ @Transactional(rollbackFor = Exception.class)
583
+ public LoginResponse login(LoginRequest request) {
584
+ // 1. 查询用户
585
+ User user = userMapper.selectByUsername(request.getUsername());
586
+ if (user == null) {
587
+ throw new RuntimeException("用户不存在");
588
+ }
589
+
590
+ // 2. 验证密码
591
+ if (!user.getPassword().equals(request.getPassword())) {
592
+ throw new RuntimeException("密码错误");
593
+ }
594
+
595
+ // 3. 生成 token
596
+ String token = generateToken(user);
597
+
598
+ // 4. 构建响应
599
+ LoginResponse response = new LoginResponse();
600
+ response.setToken(token);
601
+ response.setUserId(user.getId());
602
+ response.setUsername(user.getUsername());
603
+ response.setExpireTime(System.currentTimeMillis() + 7200000); // 2小时
604
+
605
+ return response;
606
+ }
607
+
608
+ private String generateToken(User user) {
609
+ // Token 生成逻辑
610
+ return "token_" + user.getId() + "_" + System.currentTimeMillis();
611
+ }
612
+ }
613
+ ```
614
+
615
+ **UserMapper.java**:
616
+
617
+ ```java
618
+ package com.iflytek.user.login.mapper;
619
+
620
+ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
621
+ import com.iflytek.user.login.entity.User;
622
+ import org.apache.ibatis.annotations.Mapper;
623
+ import org.apache.ibatis.annotations.Param;
624
+
625
+ /**
626
+ * 用户 Mapper
627
+ */
628
+ @Mapper
629
+ public interface UserMapper extends BaseMapper<User> {
630
+
631
+ /**
632
+ * 根据用户名查询用户
633
+ */
634
+ User selectByUsername(@Param("username") String username);
635
+ }
636
+ ```
637
+
638
+ **User.java**(实体类):
639
+
640
+ ```java
641
+ package com.iflytek.user.login.entity;
642
+
643
+ import com.baomidou.mybatisplus.annotation.TableId;
644
+ import com.baomidou.mybatisplus.annotation.TableName;
645
+ import lombok.Data;
646
+
647
+ /**
648
+ * 用户实体
649
+ */
650
+ @Data
651
+ @TableName("t_user")
652
+ public class User {
653
+
654
+ @TableId
655
+ private Long id;
656
+
657
+ private String username;
658
+
659
+ private String password;
660
+
661
+ private String email;
662
+
663
+ private String phone;
664
+
665
+ private Integer status;
666
+ }
667
+ ```
668
+
669
+ ### 步骤 5:配置自动装配(可选)
670
+
671
+ 如果需要将模块作为 starter 提供给其他项目使用,可以添加自动配置:
672
+
673
+ **UserServiceAutoConfiguration.java**:
674
+
675
+ ```java
676
+ package com.iflytek.user.config;
677
+
678
+ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
679
+ import org.springframework.context.annotation.ComponentScan;
680
+ import org.springframework.context.annotation.Configuration;
681
+
682
+ /**
683
+ * 用户服务自动配置
684
+ */
685
+ @Configuration
686
+ @ConditionalOnProperty(
687
+ prefix = "user.service",
688
+ name = "enabled",
689
+ havingValue = "true",
690
+ matchIfMissing = true // 默认启用
691
+ )
692
+ @ComponentScan(basePackages = "com.iflytek.user")
693
+ public class UserServiceAutoConfiguration {
694
+ // 自动配置逻辑
695
+ }
696
+ ```
697
+
698
+ 在 `src/main/resources/META-INF/spring/` 目录下创建 `org.springframework.boot.autoconfigure.AutoConfiguration.imports`:
699
+
700
+ ```
701
+ com.iflytek.user.config.UserServiceAutoConfiguration
702
+ ```
703
+
704
+ #### 5.1 配置 avatar-boot-starter-feign(推荐)
705
+
706
+ 在 Service 模块的 `application.yml` 中配置 Feign 日志和性能监控:
707
+
708
+ ```yaml
709
+ # Feign 客户端配置
710
+ avatar:
711
+ feign:
712
+ logging:
713
+ enabled: true # 启用日志拦截器
714
+ request-enabled: true # 记录请求参数
715
+ response-enabled: true # 记录响应数据
716
+ slow-request-threshold: 3000 # 慢请求阈值(毫秒)
717
+ max-body-length: 1000 # 最大请求体长度
718
+ trace-id-header: X-Trace-Id # TraceId 请求头名称
719
+ metrics:
720
+ enabled: true # 启用指标记录
721
+
722
+ # 针对特定客户端的配置
723
+ feign:
724
+ client:
725
+ config:
726
+ user-service:
727
+ connectTimeout: 3000
728
+ readTimeout: 5000
729
+ loggerLevel: FULL
730
+ ```
731
+
732
+ **说明**:
733
+ - `avatar-boot-starter-feign` 自动提供请求日志、TraceId 传播、性能指标等功能
734
+ - 可以针对特定客户端进行细粒度配置
735
+ - 详细配置参见:[avatar-boot-starter-feign 参考文档](../template/reference/avatar-boot-starter-feign.md)
736
+
737
+ ### 步骤 6:更新父 POM
738
+
739
+ 确保父模块的 `pom.xml` 包含所有子模块:
740
+
741
+ ```xml
742
+ <modules>
743
+ <module>user-service-api</module>
744
+ <module>user-service-biz</module>
745
+ </modules>
746
+ ```
747
+
748
+ ## Code Review Checklist(代码审查检查点)
749
+
750
+ ### API 模块检查点
751
+
752
+ - [ ] **使用 avatar-boot-starter-feign**:API 模块使用 `avatar-boot-starter-feign` 而非原生 OpenFeign
753
+ - [ ] **依赖最小化**:API 模块只依赖必要的接口定义库(avatar-boot-starter-feign、Validation)
754
+ - [ ] **无实现逻辑**:API 模块不包含任何业务实现代码
755
+ - [ ] **接口清晰**:Feign 接口定义清晰,方法命名规范
756
+ - [ ] **条件注解**:Feign 客户端使用 `@ConditionalOnProperty`,确保未配置时不启动失败
757
+ - [ ] **DTO 完整**:请求/响应对象包含必要的验证注解
758
+ - [ ] **常量定义**:常量集中定义在 constant 包中
759
+
760
+ ### Service 模块检查点
761
+
762
+ - [ ] **分层清晰**:Controller → Service → Mapper 分层明确
763
+ - [ ] **实现接口**:Controller 实现 API 模块的 Feign 接口
764
+ - [ ] **异常处理**:统一的异常处理机制
765
+ - [ ] **事务管理**:Service 方法正确使用 `@Transactional`
766
+ - [ ] **日志记录**:关键操作有日志记录
767
+ - [ ] **参数验证**:Controller 使用 `@Validated` 验证参数
768
+
769
+ ### 功能单元检查点
770
+
771
+ - [ ] **目录结构**:按功能单元组织,每个功能包含 controller、service、mapper、entity
772
+ - [ ] **命名规范**:类名、方法名、变量名符合规范
773
+ - [ ] **职责单一**:每个功能单元只负责一个业务领域
774
+ - [ ] **低耦合**:功能单元之间通过接口交互,避免直接依赖
775
+ - [ ] **代码复用**:公共逻辑抽取到 common 包或工具类
776
+
777
+ ### 配置检查点
778
+
779
+ - [ ] **Feign 配置**:正确配置 `avatar-boot-starter-feign` 的日志、TraceId、性能监控等功能
780
+ - [ ] **自动配置条件**:使用 `@ConditionalOnProperty` 等条件注解
781
+ - [ ] **配置属性**:配置项有默认值,有清晰的注释
782
+ - [ ] **文档完整**:README 说明模块功能、使用方式、配置项
783
+ - [ ] **参考文档**:引用 [avatar-boot-starter-feign 参考文档](../template/reference/avatar-boot-starter-feign.md)
784
+
785
+ ## Testing Guidelines(测试指南)
786
+
787
+ ### 测试编写时机
788
+
789
+ #### 单元测试
790
+ **时机**:每完成一个 Service 方法立即编写
791
+
792
+ ```java
793
+ package com.iflytek.user.login.service;
794
+
795
+ import com.iflytek.user.login.entity.User;
796
+ import com.iflytek.user.login.mapper.UserMapper;
797
+ import com.iflytek.user.login.service.impl.LoginServiceImpl;
798
+ import com.iflytek.user.request.LoginRequest;
799
+ import com.iflytek.user.response.LoginResponse;
800
+ import org.junit.jupiter.api.Test;
801
+ import org.junit.jupiter.api.extension.ExtendWith;
802
+ import org.mockito.InjectMocks;
803
+ import org.mockito.Mock;
804
+ import org.mockito.junit.jupiter.MockitoExtension;
805
+
806
+ import static org.junit.jupiter.api.Assertions.*;
807
+ import static org.mockito.ArgumentMatchers.anyString;
808
+ import static org.mockito.Mockito.when;
809
+
810
+ /**
811
+ * 登录服务单元测试
812
+ */
813
+ @ExtendWith(MockitoExtension.class)
814
+ class LoginServiceTest {
815
+
816
+ @Mock
817
+ private UserMapper userMapper;
818
+
819
+ @InjectMocks
820
+ private LoginServiceImpl loginService;
821
+
822
+ @Test
823
+ void testLogin_Success() {
824
+ // Given
825
+ LoginRequest request = new LoginRequest();
826
+ request.setUsername("testuser");
827
+ request.setPassword("password123");
828
+
829
+ User user = new User();
830
+ user.setId(1L);
831
+ user.setUsername("testuser");
832
+ user.setPassword("password123");
833
+
834
+ when(userMapper.selectByUsername(anyString())).thenReturn(user);
835
+
836
+ // When
837
+ LoginResponse response = loginService.login(request);
838
+
839
+ // Then
840
+ assertNotNull(response);
841
+ assertEquals(1L, response.getUserId());
842
+ assertEquals("testuser", response.getUsername());
843
+ assertNotNull(response.getToken());
844
+ }
845
+
846
+ @Test
847
+ void testLogin_UserNotFound() {
848
+ // Given
849
+ LoginRequest request = new LoginRequest();
850
+ request.setUsername("nonexistent");
851
+ request.setPassword("password123");
852
+
853
+ when(userMapper.selectByUsername(anyString())).thenReturn(null);
854
+
855
+ // When & Then
856
+ assertThrows(RuntimeException.class, () -> loginService.login(request));
857
+ }
858
+ }
859
+ ```
860
+
861
+ #### 集成测试
862
+ **时机**:完成一个功能单元后编写
863
+
864
+ ```java
865
+ package com.iflytek.user.login.controller;
866
+
867
+ import com.iflytek.user.request.LoginRequest;
868
+ import com.iflytek.user.response.LoginResponse;
869
+ import org.junit.jupiter.api.Test;
870
+ import org.springframework.beans.factory.annotation.Autowired;
871
+ import org.springframework.boot.test.context.SpringBootTest;
872
+ import org.springframework.boot.test.web.client.TestRestTemplate;
873
+ import org.springframework.http.HttpStatus;
874
+ import org.springframework.http.ResponseEntity;
875
+
876
+ import static org.junit.jupiter.api.Assertions.*;
877
+
878
+ /**
879
+ * 登录功能集成测试
880
+ */
881
+ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
882
+ class LoginIntegrationTest {
883
+
884
+ @Autowired
885
+ private TestRestTemplate restTemplate;
886
+
887
+ @Test
888
+ void testLoginEndpoint() {
889
+ // Given
890
+ LoginRequest request = new LoginRequest();
891
+ request.setUsername("testuser");
892
+ request.setPassword("password123");
893
+
894
+ // When
895
+ ResponseEntity<LoginResponse> response = restTemplate.postForEntity(
896
+ "/api/user/login",
897
+ request,
898
+ LoginResponse.class
899
+ );
900
+
901
+ // Then
902
+ assertEquals(HttpStatus.OK, response.getStatusCode());
903
+ assertNotNull(response.getBody());
904
+ assertNotNull(response.getBody().getToken());
905
+ }
906
+ }
907
+ ```
908
+
909
+ #### 端到端测试
910
+ **时机**:完成整个模块后编写
911
+
912
+ ```java
913
+ package com.iflytek.user;
914
+
915
+ import com.iflytek.user.client.UserClient;
916
+ import com.iflytek.user.request.LoginRequest;
917
+ import com.iflytek.user.response.LoginResponse;
918
+ import org.junit.jupiter.api.Test;
919
+ import org.springframework.beans.factory.annotation.Autowired;
920
+ import org.springframework.boot.test.context.SpringBootTest;
921
+ import org.springframework.cloud.openfeign.EnableFeignClients;
922
+
923
+ import static org.junit.jupiter.api.Assertions.*;
924
+
925
+ /**
926
+ * 用户服务端到端测试
927
+ */
928
+ @SpringBootTest
929
+ @EnableFeignClients(clients = UserClient.class)
930
+ class UserServiceE2ETest {
931
+
932
+ @Autowired
933
+ private UserClient userClient;
934
+
935
+ @Test
936
+ void testUserLoginFlow() {
937
+ // Given
938
+ LoginRequest request = new LoginRequest();
939
+ request.setUsername("testuser");
940
+ request.setPassword("password123");
941
+
942
+ // When
943
+ LoginResponse response = userClient.login(request);
944
+
945
+ // Then
946
+ assertNotNull(response);
947
+ assertNotNull(response.getToken());
948
+ assertEquals("testuser", response.getUsername());
949
+ }
950
+ }
951
+ ```
952
+
953
+ ### 测试覆盖率要求
954
+
955
+ - **单元测试**:Service 层覆盖率 ≥ 80%
956
+ - **集成测试**:Controller 层覆盖率 ≥ 70%
957
+ - **端到端测试**:核心业务流程覆盖率 100%
958
+
959
+ ## Common Mistakes(常见错误)
960
+
961
+ ### 错误 1:API 模块依赖过多
962
+
963
+ **错误示例**:
964
+ ```xml
965
+ <!-- API 模块不应该依赖这些 -->
966
+ <dependency>
967
+ <groupId>org.springframework.boot</groupId>
968
+ <artifactId>spring-boot-starter-web</artifactId>
969
+ </dependency>
970
+ <dependency>
971
+ <groupId>com.baomidou</groupId>
972
+ <artifactId>mybatis-plus-boot-starter</artifactId>
973
+ </dependency>
974
+ ```
975
+
976
+ **正确做法**:
977
+ API 模块只依赖接口定义所需的最小依赖(Feign、Validation)。
978
+
979
+ ### 错误 2:Controller 未实现 API 接口
980
+
981
+ **错误示例**:
982
+ ```java
983
+ @RestController
984
+ @RequestMapping("/api/user")
985
+ public class LoginController {
986
+ // 没有实现 UserClient 接口
987
+ @PostMapping("/login")
988
+ public LoginResponse login(@RequestBody LoginRequest request) {
989
+ // ...
990
+ }
991
+ }
992
+ ```
993
+
994
+ **正确做法**:
995
+ ```java
996
+ @RestController
997
+ @RequestMapping("/api/user")
998
+ public class LoginController implements UserClient {
999
+ // 实现 UserClient 接口
1000
+ }
1001
+ ```
1002
+
1003
+ ### 错误 3:功能代码混杂
1004
+
1005
+ **错误示例**:
1006
+ ```
1007
+ controller/
1008
+ ├── LoginController.java
1009
+ ├── UserController.java
1010
+ └── OrderController.java
1011
+ service/
1012
+ ├── LoginService.java
1013
+ ├── UserService.java
1014
+ └── OrderService.java
1015
+ ```
1016
+
1017
+ **正确做法**:
1018
+ ```
1019
+ login/
1020
+ ├── controller/
1021
+ ├── service/
1022
+ └── mapper/
1023
+ user/
1024
+ ├── controller/
1025
+ ├── service/
1026
+ └── mapper/
1027
+ ```
1028
+
1029
+ ### 错误 4:缺少自动配置条件
1030
+
1031
+ **错误示例**:
1032
+ ```java
1033
+ @FeignClient(name = "user-service")
1034
+ public interface UserClient {
1035
+ // 没有条件注解,引入后必须配置才能启动
1036
+ }
1037
+ ```
1038
+
1039
+ **正确做法**:
1040
+ ```java
1041
+ @FeignClient(
1042
+ name = "user-service",
1043
+ configuration = UserClientConfiguration.class
1044
+ )
1045
+ public interface UserClient {
1046
+ // ...
1047
+ }
1048
+
1049
+ @Configuration
1050
+ @ConditionalOnProperty(
1051
+ prefix = "feign.client.user-service",
1052
+ name = "enabled",
1053
+ havingValue = "true",
1054
+ matchIfMissing = false
1055
+ )
1056
+ public class UserClientConfiguration {
1057
+ // ...
1058
+ }
1059
+ ```
1060
+
1061
+ ### 错误 5:Service 方法直接调用绕过 AOP
1062
+
1063
+ **错误示例**:
1064
+ ```java
1065
+ @Service
1066
+ public class LoginServiceImpl implements LoginService {
1067
+
1068
+ @Transactional
1069
+ public LoginResponse login(LoginRequest request) {
1070
+ // 直接调用内部方法,事务不生效
1071
+ this.validateUser(request);
1072
+ // ...
1073
+ }
1074
+
1075
+ @Transactional
1076
+ private void validateUser(LoginRequest request) {
1077
+ // ...
1078
+ }
1079
+ }
1080
+ ```
1081
+
1082
+ **正确做法**:
1083
+ ```java
1084
+ @Service
1085
+ public class LoginServiceImpl implements LoginService {
1086
+
1087
+ @Autowired
1088
+ private UserValidator userValidator; // 注入其他 Bean
1089
+
1090
+ @Transactional
1091
+ public LoginResponse login(LoginRequest request) {
1092
+ // 通过注入的 Bean 调用,AOP 生效
1093
+ userValidator.validate(request);
1094
+ // ...
1095
+ }
1096
+ }
1097
+ ```
1098
+
1099
+ ## Summary(总结)
1100
+
1101
+ 本技能提供了在 Avatar Boot 框架下创建标准 API + Service 双模块结构的完整指南。关键要点:
1102
+
1103
+ 1. **模块分离**:API 模块定义接口,Service 模块实现功能
1104
+ 2. **使用 avatar-boot-starter-feign**:API 模块使用 `avatar-boot-starter-feign` 获得开箱即用的日志、TraceId 传播和性能监控功能
1105
+ 3. **功能组织**:按功能单元组织代码,而非技术层次
1106
+ 4. **条件注解**:确保 API 模块可独立引用
1107
+ 5. **Code Review**:每完成一个功能进行审查
1108
+ 6. **测试驱动**:在适当时机编写测试用例
1109
+
1110
+ 遵循本技能的指导,可以创建结构清晰、易于维护、可扩展的微服务模块。
1111
+
1112
+ **相关参考**:
1113
+ - [avatar-boot-starter-feign 参考文档](../template/reference/avatar-boot-starter-feign.md) - Feign 客户端配置和使用详解
1114
+