@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.
- package/README.md +309 -0
- package/bin/cli.js +3 -0
- package/docs/plans/2026-03-12-avatar-boot-cli-design.md +73 -0
- package/docs/plans/2026-03-12-avatar-boot-cli-plan.md +681 -0
- package/package.json +28 -0
- package/src/index.js +78 -0
- package/src/prompts.js +78 -0
- package/src/template.js +37 -0
- package/src/transform.js +172 -0
- package/src/utils.js +34 -0
- package/templates/.claude/rules/architecture-redlines.md +146 -0
- package/templates/.claude/rules/code-review-standards.md +137 -0
- package/templates/.claude/rules/coding-standards.md +56 -0
- package/templates/.claude/rules/git-commit.md +59 -0
- package/templates/.claude/rules/layered-architecture.md +201 -0
- package/templates/.claude/rules/mybatis-plus.md +263 -0
- package/templates/.claude/rules/tech-stack.md +41 -0
- package/templates/.claude/rules/version.md +467 -0
- package/templates/.claude/settings.local.json +18 -0
- package/templates/.claude/skills/ai-tool-guide/SKILL.md +314 -0
- package/templates/.claude/skills/api-design/SKILL.md +200 -0
- package/templates/.claude/skills/api-doc-generator/SKILL.md +380 -0
- package/templates/.claude/skills/api-service-module-creator/SKILL.md +1114 -0
- package/templates/.claude/skills/avatar-boot-starter-feign/SKILL.md +243 -0
- package/templates/.claude/skills/avatar-boot-starter-job/SKILL.md +437 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/SKILL.md +580 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/SKILL.md +572 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/SKILL.md +901 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/SKILL.md +594 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/SKILL.md +586 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/SKILL.md +662 -0
- package/templates/.claude/skills/avatar-boot-starter-web/SKILL.md +1007 -0
- package/templates/.claude/skills/changelog-generator/SKILL.md +114 -0
- package/templates/.claude/skills/code-review/SKILL.md +239 -0
- package/templates/.claude/skills/crud-generator/SKILL.md +824 -0
- package/templates/.claude/skills/database-design/SKILL.md +377 -0
- package/templates/.claude/skills/deployment-config/SKILL.md +277 -0
- package/templates/.claude/skills/incident-analysis/SKILL.md +241 -0
- package/templates/.claude/skills/integration-test-generator/SKILL.md +496 -0
- package/templates/.claude/skills/prompt-engineering/SKILL.md +249 -0
- package/templates/.claude/skills/requirement-management/SKILL.md +244 -0
- package/templates/.claude/skills/security-audit/SKILL.md +330 -0
- package/templates/.claude/skills/test-case-design/SKILL.md +257 -0
- package/templates/.claude/skills/testing-workflow/SKILL.md +68 -0
- package/templates/.claude/skills/troubleshooting/SKILL.md +240 -0
- package/templates/CLAUDE.md +173 -0
- package/templates/README.md +303 -0
- package/templates/avatar-scaffold-api/pom.xml +41 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/api/LoginFeignClient.java +40 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/constant/LoginConstant.java +21 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/LoginRequest.java +17 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/RefreshTokenRequest.java +14 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/LoginResponse.java +31 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/TokenInfoResponse.java +25 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/enums/LoginTypeEnum.java +23 -0
- package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/exception/LoginException.java +23 -0
- package/templates/avatar-scaffold-service/k8s-app/Dockerfile +14 -0
- package/templates/avatar-scaffold-service/k8s-app/Dockerfile-arm64 +14 -0
- package/templates/avatar-scaffold-service/packaging/assembly.xml +16 -0
- package/templates/avatar-scaffold-service/pom.xml +150 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/Application.java +21 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/config/LoginConfig.java +20 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/controller/LoginController.java +37 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/converter/LoginConverter.java +54 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/feign/DemoFeign.java +21 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserLoginEntity.java +33 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserTokenEntity.java +39 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/mapper/UserLoginMapper.java +20 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/LoginService.java +22 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/impl/LoginServiceImpl.java +43 -0
- package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/utils/LoginUtils.java +31 -0
- package/templates/avatar-scaffold-service/src/main/resources/application-dev.yaml +29 -0
- package/templates/avatar-scaffold-service/src/main/resources/application-local.yaml +61 -0
- package/templates/avatar-scaffold-service/src/main/resources/application-prod.yaml +28 -0
- package/templates/avatar-scaffold-service/src/main/resources/application-test.yaml +28 -0
- package/templates/avatar-scaffold-service/src/main/resources/application.yaml +12 -0
- 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
|
+
|