2025 lines
50 KiB
Markdown
2025 lines
50 KiB
Markdown
# AI 代码规范模板
|
||
|
||
> 本文档是通用的 AI 协作与代码规范模板,适用于 TypeScript、Java、React、Vue 等技术栈。
|
||
> 使用时请根据项目实际情况进行调整。
|
||
|
||
---
|
||
|
||
## 📋 目录
|
||
|
||
### 第一部分:通用规范
|
||
- [1. AI 协作基本原则](#1-ai-协作基本原则)
|
||
- [2. 通用编码规范](#2-通用编码规范)
|
||
- [3. 文件与文档管理](#3-文件与文档管理)
|
||
|
||
### 第二部分:语言特定规范
|
||
- [4. TypeScript 规范](#4-typescript-规范)
|
||
- [5. Java 规范](#5-java-规范)
|
||
|
||
### 第三部分:框架特定规范
|
||
- [6. React 规范](#6-react-规范)
|
||
- [7. Vue 规范](#7-vue-规范)
|
||
|
||
### 第四部分:工程化规范
|
||
- [8. Git 提交规范](#8-git-提交规范)
|
||
- [9. API 设计规范](#9-api-设计规范)
|
||
- [10. 测试规范](#10-测试规范)
|
||
|
||
### 附录
|
||
- [附录 A:代码审查清单](#附录-a代码审查清单)
|
||
- [附录 B:常见任务快速参考](#附录-b常见任务快速参考)
|
||
- [附录 C:模板使用说明](#附录-c模板使用说明)
|
||
|
||
---
|
||
|
||
# 第一部分:通用规范
|
||
|
||
## 1. AI 协作基本原则
|
||
|
||
### 1.1 语言要求(强制)
|
||
|
||
#### 输出语言
|
||
- **所有输出必须使用中文**,包括:
|
||
- 代码注释(**强制:所有代码注释必须使用中文,解释清晰详细**)
|
||
- 文档说明
|
||
- 与用户交流
|
||
- 错误信息和日志输出
|
||
|
||
#### 思考语言
|
||
- **思考过程也必须使用中文**:
|
||
- 分析问题时用中文思考
|
||
- 解释代码逻辑时用中文
|
||
- 讨论设计方案时用中文
|
||
|
||
#### 代码中的字符串
|
||
- 根据业务需求决定(支持国际化的项目使用翻译键)
|
||
- 注释和文档必须使用中文
|
||
|
||
### 1.2 思考与沟通方式
|
||
|
||
#### 思考过程要求
|
||
- 分析问题时,用中文描述问题、分析思路、得出结论
|
||
- 解释代码时,用中文说明逻辑、设计意图、实现方式
|
||
- 讨论方案时,用中文描述优缺点、选择理由
|
||
|
||
#### 工作流程
|
||
1. **理解需求**:用中文理解用户需求,明确任务目标
|
||
2. **分析问题**:用中文分析问题,梳理相关代码和架构
|
||
3. **设计方案**:用中文设计解决方案,考虑边界情况
|
||
4. **实现代码**:编写代码,添加中文注释
|
||
5. **测试验证**:用中文描述测试结果和发现的问题
|
||
6. **更新文档**:用中文更新相关文档
|
||
|
||
### 1.3 问答与开发流程
|
||
|
||
#### 用户询问"如何实现"时的处理流程
|
||
|
||
**当用户询问如何实现某个功能时,必须遵循以下流程:**
|
||
|
||
1. **分析需求**:用中文理解用户需求,明确要实现的功能
|
||
|
||
2. **制定详细开发计划**:必须包含以下内容:
|
||
- **需要修改的文件列表**
|
||
- **需要新增的文件列表**
|
||
- **需要删除的文件列表**
|
||
- **每个文件的作用说明**
|
||
- **对整体结构的影响**
|
||
|
||
3. **展示开发计划**:以清晰的格式展示给用户
|
||
|
||
4. **等待用户确认**:**在用户明确同意之前,绝对不能修改任何代码**
|
||
|
||
5. **用户同意后执行**:只有在用户明确表示同意后,才能开始修改代码
|
||
|
||
#### 开发计划格式模板
|
||
|
||
```markdown
|
||
## 开发计划
|
||
|
||
### 需要修改的文件
|
||
1. `src/xxx/xxx.ts`
|
||
- 修改:添加新的 `xxxMethod()` 方法
|
||
- 作用:提供某某功能
|
||
|
||
### 需要新增的文件
|
||
1. `src/xxx/index.ts`
|
||
- 作用:实现某某功能
|
||
- 内容:包含 XxxClass 类
|
||
|
||
### 需要删除的文件
|
||
1. `src/old/xxx.ts`
|
||
- 原因:已被新组件替代
|
||
- **处理方式**:移动到 `.recycle/YYYY-MM-DD/` 文件夹
|
||
|
||
### 对整体结构的影响
|
||
- **架构影响**:说明对现有架构的影响
|
||
- **功能影响**:说明对现有功能的影响
|
||
- **文档更新**:需要更新哪些文档
|
||
- **风险点**:可能的风险和注意事项
|
||
|
||
请确认是否按照此计划进行开发?
|
||
```
|
||
|
||
#### 用户直接要求修改时的处理
|
||
|
||
**当用户直接要求修改代码时**(如"帮我修改XXX"、"实现XXX功能"),可以:
|
||
- 直接执行代码修改
|
||
- 但仍需遵循其他规范
|
||
- 删除文件时仍需移动到回收文件夹
|
||
|
||
---
|
||
|
||
## 2. 通用编码规范
|
||
|
||
### 2.1 代码可读性
|
||
|
||
#### 基本原则
|
||
- **优先保证代码可读性**,有时可以不使用高级函数,使用低级函数
|
||
- **必须添加详细的中文注释**,解释代码逻辑和设计意图
|
||
- 函数、类、接口必须有文档注释(使用中文)
|
||
- 复杂逻辑必须添加行内注释说明
|
||
|
||
#### 代码组织
|
||
- 单个文件不宜超过 **500 行**
|
||
- 单个函数不宜超过 **50 行**
|
||
- 单行代码不宜超过 **120 字符**
|
||
- 嵌套层级不宜超过 **4 层**
|
||
|
||
### 2.2 命名规范
|
||
|
||
#### 通用命名规则
|
||
|
||
| 类型 | 命名方式 | 示例 |
|
||
|-----|---------|------|
|
||
| 类名 | 大驼峰 (PascalCase) | `UserManager`, `OrderService` |
|
||
| 接口名 | 大驼峰,可选 `I` 前缀 | `IUserService`, `UserRepository` |
|
||
| 类型/枚举 | 大驼峰 | `UserStatus`, `OrderType` |
|
||
| 函数/方法 | 小驼峰 (camelCase) | `getUserInfo`, `handleSubmit` |
|
||
| 变量 | 小驼峰 | `userName`, `isActive`, `itemCount` |
|
||
| 常量 | 全大写下划线 | `MAX_COUNT`, `API_BASE_URL` |
|
||
| 私有属性 | 下划线前缀或 `#` | `_cache`, `#privateField` |
|
||
| 文件名 | 小写短横线或小写 | `user-manager.ts`, `index.ts` |
|
||
| CSS 类名 | 小写短横线 (kebab-case) | `user-card`, `btn-primary` |
|
||
|
||
#### 命名语义化
|
||
|
||
```typescript
|
||
// ✅ 好的命名 - 语义清晰
|
||
const userList: User[] = [];
|
||
const isLoading: boolean = false;
|
||
const maxRetryCount: number = 3;
|
||
function calculateTotalPrice(items: Item[]): number { }
|
||
function validateUserInput(input: string): boolean { }
|
||
|
||
// ❌ 不好的命名 - 语义不清
|
||
const list: any[] = [];
|
||
const flag: boolean = false;
|
||
const num: number = 3;
|
||
function calc(arr: any[]): number { }
|
||
function check(str: string): boolean { }
|
||
```
|
||
|
||
#### 布尔值命名
|
||
- 使用 `is`、`has`、`can`、`should`、`will` 等前缀
|
||
- 例如:`isActive`、`hasPermission`、`canEdit`、`shouldUpdate`
|
||
|
||
#### 函数命名
|
||
- 动词开头,表明行为
|
||
- 查询:`get`、`find`、`query`、`fetch`、`load`
|
||
- 创建:`create`、`add`、`insert`、`save`
|
||
- 更新:`update`、`modify`、`set`、`change`
|
||
- 删除:`delete`、`remove`、`clear`、`reset`
|
||
- 判断:`is`、`has`、`can`、`should`、`validate`、`check`
|
||
- 转换:`to`、`convert`、`parse`、`format`
|
||
- 事件:`handle`、`on`、`trigger`、`emit`
|
||
|
||
### 2.3 注释规范
|
||
|
||
#### 文档注释(JSDoc / JavaDoc)
|
||
|
||
**TypeScript / JavaScript:**
|
||
```typescript
|
||
/**
|
||
* 计算订单总价
|
||
*
|
||
* @description 根据商品列表计算总价,包含折扣计算
|
||
* @param items - 商品列表
|
||
* @param discount - 折扣比例(0-1),默认为 1(无折扣)
|
||
* @returns 计算后的总价
|
||
* @throws {Error} 当商品列表为空时抛出错误
|
||
* @example
|
||
* const total = calculateTotal(items, 0.8); // 8折
|
||
*/
|
||
function calculateTotal(items: CartItem[], discount: number = 1): number {
|
||
if (items.length === 0) {
|
||
throw new Error('商品列表不能为空');
|
||
}
|
||
// 计算原价总和
|
||
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
||
// 应用折扣
|
||
return subtotal * discount;
|
||
}
|
||
```
|
||
|
||
**Java:**
|
||
```java
|
||
/**
|
||
* 计算订单总价
|
||
*
|
||
* <p>根据商品列表计算总价,包含折扣计算</p>
|
||
*
|
||
* @param items 商品列表
|
||
* @param discount 折扣比例(0-1),默认为 1(无折扣)
|
||
* @return 计算后的总价
|
||
* @throws IllegalArgumentException 当商品列表为空时抛出
|
||
* @since 1.0.0
|
||
* @author AI Assistant
|
||
*/
|
||
public BigDecimal calculateTotal(List<CartItem> items, BigDecimal discount) {
|
||
// 实现逻辑
|
||
}
|
||
```
|
||
|
||
#### 行内注释
|
||
```typescript
|
||
// 单行注释:解释接下来一行或几行代码的作用
|
||
|
||
/*
|
||
* 多行注释:
|
||
* 用于解释复杂的逻辑块
|
||
* 或临时禁用代码
|
||
*/
|
||
|
||
// TODO: 待完成的功能
|
||
// FIXME: 需要修复的问题
|
||
// HACK: 临时解决方案,需要优化
|
||
// NOTE: 重要说明
|
||
// DEPRECATED: 已废弃,请使用新方法
|
||
```
|
||
|
||
#### 注释原则
|
||
- **解释为什么,而不是是什么**:代码本身说明做什么,注释说明为什么
|
||
- **保持注释与代码同步**:代码修改后必须更新注释
|
||
- **不要注释显而易见的代码**:避免无意义的注释
|
||
- **复杂业务逻辑必须注释**:特别是涉及特定业务规则的代码
|
||
|
||
### 2.4 错误处理
|
||
|
||
#### 基本原则
|
||
- 所有异步操作必须有错误处理
|
||
- 错误信息必须清晰、有意义,使用中文
|
||
- 区分可恢复错误和不可恢复错误
|
||
- 不要吞掉异常(空 catch 块)
|
||
|
||
#### TypeScript 错误处理
|
||
```typescript
|
||
// ✅ 正确的错误处理
|
||
async function fetchUserData(userId: string): Promise<User> {
|
||
try {
|
||
const response = await fetch(`/api/users/${userId}`);
|
||
|
||
if (!response.ok) {
|
||
// 根据状态码抛出具体错误
|
||
if (response.status === 404) {
|
||
throw new Error(`用户不存在:${userId}`);
|
||
}
|
||
throw new Error(`请求失败:${response.status}`);
|
||
}
|
||
|
||
return await response.json();
|
||
} catch (error) {
|
||
// 记录错误日志
|
||
console.error('获取用户数据失败:', error);
|
||
// 重新抛出或返回默认值
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// ❌ 错误的错误处理
|
||
async function badFetch(userId: string) {
|
||
try {
|
||
const res = await fetch(`/api/users/${userId}`);
|
||
return await res.json();
|
||
} catch (e) {
|
||
// 空 catch 块,吞掉了错误
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Java 错误处理
|
||
```java
|
||
// ✅ 正确的错误处理
|
||
public User getUserById(Long id) {
|
||
try {
|
||
return userRepository.findById(id)
|
||
.orElseThrow(() -> new BusinessException("用户不存在:" + id));
|
||
} catch (DataAccessException e) {
|
||
log.error("查询用户失败,ID:{}", id, e);
|
||
throw new ServiceException("数据库查询异常", e);
|
||
}
|
||
}
|
||
|
||
// 自定义业务异常
|
||
public class BusinessException extends RuntimeException {
|
||
private final String code;
|
||
|
||
public BusinessException(String message) {
|
||
super(message);
|
||
this.code = "BUSINESS_ERROR";
|
||
}
|
||
|
||
public BusinessException(String code, String message) {
|
||
super(message);
|
||
this.code = code;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.5 日志规范
|
||
|
||
#### 日志级别
|
||
| 级别 | 使用场景 |
|
||
|-----|---------|
|
||
| `ERROR` | 错误,影响功能正常运行 |
|
||
| `WARN` | 警告,潜在问题但不影响运行 |
|
||
| `INFO` | 重要业务信息,如用户操作、接口调用 |
|
||
| `DEBUG` | 调试信息,开发环境使用 |
|
||
| `TRACE` | 详细追踪信息,极少使用 |
|
||
|
||
#### 日志内容要求
|
||
```typescript
|
||
// ✅ 好的日志
|
||
logger.info(`用户登录成功,用户ID:${userId},IP:${clientIp}`);
|
||
logger.error(`订单创建失败,订单号:${orderId},原因:${error.message}`, error);
|
||
logger.warn(`库存不足,商品ID:${productId},当前库存:${stock},请求数量:${quantity}`);
|
||
|
||
// ❌ 不好的日志
|
||
logger.info('success'); // 信息不完整
|
||
logger.error(error); // 缺少上下文
|
||
console.log('here'); // 调试代码未清理
|
||
```
|
||
|
||
#### 日志原则
|
||
- 包含足够的上下文信息(ID、参数等)
|
||
- 敏感信息脱敏(密码、手机号等)
|
||
- 生产环境不使用 `console.log`
|
||
- 异常日志要包含堆栈信息
|
||
|
||
### 2.6 安全规范
|
||
|
||
#### 输入验证
|
||
```typescript
|
||
// ✅ 验证所有外部输入
|
||
function processUserInput(input: unknown): string {
|
||
// 类型检查
|
||
if (typeof input !== 'string') {
|
||
throw new Error('输入必须是字符串');
|
||
}
|
||
// 长度检查
|
||
if (input.length > 1000) {
|
||
throw new Error('输入长度超出限制');
|
||
}
|
||
// 内容清理
|
||
return sanitize(input);
|
||
}
|
||
```
|
||
|
||
#### XSS 防护
|
||
```typescript
|
||
// ❌ 危险:直接插入 HTML
|
||
element.innerHTML = userInput;
|
||
|
||
// ✅ 安全:使用 textContent 或转义
|
||
element.textContent = userInput;
|
||
// 或
|
||
element.innerHTML = escapeHtml(userInput);
|
||
```
|
||
|
||
#### 敏感数据处理
|
||
```typescript
|
||
// ❌ 不要在日志中输出敏感信息
|
||
console.log(`用户密码:${password}`);
|
||
|
||
// ✅ 脱敏处理
|
||
console.log(`手机号:${phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')}`);
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 文件与文档管理
|
||
|
||
### 3.1 文件删除规范(强制)
|
||
|
||
**严禁直接删除文件,必须遵循以下流程:**
|
||
|
||
#### 步骤 1:创建回收文件夹
|
||
在项目根目录创建 `.recycle/` 文件夹(如果不存在)
|
||
|
||
#### 步骤 2:按日期组织
|
||
- 在 `.recycle/` 下按日期创建子文件夹
|
||
- 格式:`YYYY-MM-DD`(如 `2024-01-15`)
|
||
- 同一天删除的文件存放在同一个日期文件夹中
|
||
|
||
#### 步骤 3:移动文件而非删除
|
||
|
||
```bash
|
||
# ❌ 错误方式
|
||
rm src/old-file.ts
|
||
|
||
# ✅ 正确方式
|
||
DATE=$(date +%Y-%m-%d)
|
||
mkdir -p .recycle/$DATE/src
|
||
mv src/old-file.ts .recycle/$DATE/src/old-file.ts
|
||
```
|
||
|
||
#### 步骤 4:保持目录结构
|
||
在日期文件夹中保持原文件的目录结构
|
||
|
||
例如:`src/components/old.ts` → `.recycle/2024-01-15/src/components/old.ts`
|
||
|
||
#### 回收文件夹结构示例
|
||
```
|
||
.recycle/
|
||
├── 2024-01-15/
|
||
│ ├── README.md # 可选:记录删除原因
|
||
│ └── src/
|
||
│ └── components/
|
||
│ └── old-component.ts
|
||
└── 2024-02-20/
|
||
└── src/
|
||
└── services/
|
||
└── old-service.ts
|
||
```
|
||
|
||
### 3.2 文档更新规则
|
||
|
||
#### 必须更新的情况
|
||
|
||
| 操作 | 需要更新的文档 |
|
||
|-----|--------------|
|
||
| 新增文件 | 在相关文档中添加文件描述 |
|
||
| 删除文件 | 从文档中移除对应条目 |
|
||
| 修改文件功能 | 更新对应文件的描述 |
|
||
| 新增组件/模块 | 添加说明,创建详细文档 |
|
||
| 修改 API | 同步更新 API 文档 |
|
||
| 修改架构 | 更新架构设计部分 |
|
||
| 修改配置 | 更新配置说明 |
|
||
|
||
#### 更新方式
|
||
1. 修改代码后,**立即**更新相关文档
|
||
2. 确保文档与代码保持一致
|
||
3. 如果发现文档过时,优先更新文档
|
||
|
||
---
|
||
|
||
# 第二部分:语言特定规范
|
||
|
||
## 4. TypeScript 规范
|
||
|
||
### 4.1 类型定义规范
|
||
|
||
#### 优先使用 interface 定义对象类型
|
||
```typescript
|
||
// ✅ 推荐:使用 interface
|
||
interface UserInfo {
|
||
id: string;
|
||
name: string;
|
||
email: string;
|
||
age?: number; // 可选属性
|
||
readonly createdAt: Date; // 只读属性
|
||
}
|
||
|
||
// 接口继承
|
||
interface AdminUser extends UserInfo {
|
||
role: 'admin';
|
||
permissions: string[];
|
||
}
|
||
```
|
||
|
||
#### 使用 type 定义联合类型、交叉类型
|
||
```typescript
|
||
// 联合类型
|
||
type Status = 'pending' | 'processing' | 'completed' | 'failed';
|
||
type ID = string | number;
|
||
|
||
// 交叉类型
|
||
type UserWithMeta = UserInfo & {
|
||
metadata: Record<string, unknown>;
|
||
};
|
||
|
||
// 工具类型
|
||
type PartialUser = Partial<UserInfo>;
|
||
type RequiredUser = Required<UserInfo>;
|
||
type ReadonlyUser = Readonly<UserInfo>;
|
||
type UserKeys = keyof UserInfo;
|
||
```
|
||
|
||
### 4.2 避免 any,使用正确的类型
|
||
|
||
```typescript
|
||
// ❌ 避免使用 any
|
||
function processData(data: any): any {
|
||
return data.value;
|
||
}
|
||
|
||
// ✅ 使用 unknown + 类型守卫
|
||
function processData(data: unknown): string {
|
||
if (typeof data === 'object' && data !== null && 'value' in data) {
|
||
return String((data as { value: unknown }).value);
|
||
}
|
||
throw new Error('无效的数据格式');
|
||
}
|
||
|
||
// ✅ 使用泛型
|
||
function processData<T extends { value: string }>(data: T): string {
|
||
return data.value;
|
||
}
|
||
```
|
||
|
||
### 4.3 泛型使用
|
||
|
||
```typescript
|
||
// 基础泛型
|
||
function identity<T>(value: T): T {
|
||
return value;
|
||
}
|
||
|
||
// 泛型约束
|
||
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
||
return obj[key];
|
||
}
|
||
|
||
// 泛型接口
|
||
interface Repository<T> {
|
||
findById(id: string): Promise<T | null>;
|
||
findAll(): Promise<T[]>;
|
||
save(entity: T): Promise<T>;
|
||
delete(id: string): Promise<void>;
|
||
}
|
||
|
||
// 泛型类
|
||
class BaseService<T> {
|
||
constructor(private repository: Repository<T>) {}
|
||
|
||
async getById(id: string): Promise<T | null> {
|
||
return this.repository.findById(id);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4 模块导入导出
|
||
|
||
```typescript
|
||
// ✅ 导入顺序(按此顺序组织)
|
||
// 1. Node.js 内置模块
|
||
import path from 'path';
|
||
import fs from 'fs';
|
||
|
||
// 2. 第三方库
|
||
import React from 'react';
|
||
import axios from 'axios';
|
||
|
||
// 3. 项目内部模块 - 绝对路径
|
||
import { UserService } from '@/services/user';
|
||
import { formatDate } from '@/utils/date';
|
||
|
||
// 4. 项目内部模块 - 相对路径
|
||
import { Button } from '../components/Button';
|
||
import { useAuth } from './hooks/useAuth';
|
||
|
||
// 5. 类型导入
|
||
import type { User, UserRole } from '@/types';
|
||
|
||
// 6. 样式文件
|
||
import './styles.css';
|
||
```
|
||
|
||
```typescript
|
||
// ✅ 导出方式
|
||
// 命名导出 - 推荐用于工具函数、类型等
|
||
export function formatDate(date: Date): string { }
|
||
export interface UserInfo { }
|
||
export const MAX_COUNT = 100;
|
||
|
||
// 默认导出 - 推荐用于组件、类
|
||
export default class UserService { }
|
||
export default function UserCard() { }
|
||
|
||
// 重新导出
|
||
export { UserService } from './user-service';
|
||
export * from './types';
|
||
export { default as Button } from './Button';
|
||
```
|
||
|
||
### 4.5 异步编程
|
||
|
||
```typescript
|
||
// ✅ 使用 async/await
|
||
async function fetchUserData(userId: string): Promise<User> {
|
||
try {
|
||
const response = await fetch(`/api/users/${userId}`);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error: ${response.status}`);
|
||
}
|
||
return await response.json();
|
||
} catch (error) {
|
||
console.error('获取用户数据失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// ✅ 并行请求
|
||
async function fetchMultipleUsers(ids: string[]): Promise<User[]> {
|
||
const promises = ids.map(id => fetchUserData(id));
|
||
return Promise.all(promises);
|
||
}
|
||
|
||
// ✅ 带超时的请求
|
||
async function fetchWithTimeout<T>(
|
||
promise: Promise<T>,
|
||
timeoutMs: number
|
||
): Promise<T> {
|
||
const timeout = new Promise<never>((_, reject) => {
|
||
setTimeout(() => reject(new Error('请求超时')), timeoutMs);
|
||
});
|
||
return Promise.race([promise, timeout]);
|
||
}
|
||
```
|
||
|
||
### 4.6 枚举与常量
|
||
|
||
```typescript
|
||
// ✅ 字符串枚举 - 推荐
|
||
enum OrderStatus {
|
||
Pending = 'PENDING',
|
||
Processing = 'PROCESSING',
|
||
Completed = 'COMPLETED',
|
||
Cancelled = 'CANCELLED'
|
||
}
|
||
|
||
// ✅ const 枚举 - 编译时内联
|
||
const enum Direction {
|
||
Up = 'UP',
|
||
Down = 'DOWN',
|
||
Left = 'LEFT',
|
||
Right = 'RIGHT'
|
||
}
|
||
|
||
// ✅ 常量对象 + as const - 更灵活
|
||
const HTTP_STATUS = {
|
||
OK: 200,
|
||
CREATED: 201,
|
||
BAD_REQUEST: 400,
|
||
UNAUTHORIZED: 401,
|
||
NOT_FOUND: 404,
|
||
INTERNAL_ERROR: 500
|
||
} as const;
|
||
|
||
type HttpStatusCode = typeof HTTP_STATUS[keyof typeof HTTP_STATUS];
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Java 规范
|
||
|
||
### 5.1 包结构与命名
|
||
|
||
#### 标准包结构
|
||
```
|
||
com.company.project
|
||
├── controller/ # 控制器层
|
||
│ └── UserController.java
|
||
├── service/ # 服务层
|
||
│ ├── UserService.java # 接口
|
||
│ └── impl/
|
||
│ └── UserServiceImpl.java # 实现
|
||
├── repository/ # 数据访问层
|
||
│ └── UserRepository.java
|
||
├── model/ # 数据模型
|
||
│ ├── entity/ # 数据库实体
|
||
│ │ └── User.java
|
||
│ ├── dto/ # 数据传输对象
|
||
│ │ ├── UserDTO.java
|
||
│ │ └── UserCreateRequest.java
|
||
│ └── vo/ # 视图对象
|
||
│ └── UserVO.java
|
||
├── config/ # 配置类
|
||
│ └── WebConfig.java
|
||
├── common/ # 公共模块
|
||
│ ├── exception/ # 异常类
|
||
│ ├── util/ # 工具类
|
||
│ └── constant/ # 常量类
|
||
└── Application.java # 启动类
|
||
```
|
||
|
||
#### 类命名规范
|
||
|
||
| 类型 | 命名规则 | 示例 |
|
||
|-----|---------|------|
|
||
| 控制器 | `Xxx Controller` | `UserController` |
|
||
| 服务接口 | `XxxService` | `UserService` |
|
||
| 服务实现 | `XxxServiceImpl` | `UserServiceImpl` |
|
||
| 数据访问 | `XxxRepository` / `XxxMapper` | `UserRepository` |
|
||
| 实体类 | `Xxx` | `User`, `Order` |
|
||
| DTO | `XxxDTO` / `XxxRequest` / `XxxResponse` | `UserDTO` |
|
||
| VO | `XxxVO` | `UserVO` |
|
||
| 工具类 | `XxxUtil` / `XxxUtils` / `XxxHelper` | `DateUtils` |
|
||
| 常量类 | `XxxConstants` | `ApiConstants` |
|
||
| 枚举 | `XxxEnum` / `XxxType` / `XxxStatus` | `OrderStatus` |
|
||
| 异常 | `XxxException` | `BusinessException` |
|
||
|
||
### 5.2 类设计原则
|
||
|
||
#### 实体类
|
||
```java
|
||
/**
|
||
* 用户实体
|
||
*
|
||
* @author AI Assistant
|
||
* @since 1.0.0
|
||
*/
|
||
@Data
|
||
@Entity
|
||
@Table(name = "t_user")
|
||
public class User implements Serializable {
|
||
|
||
private static final long serialVersionUID = 1L;
|
||
|
||
/**
|
||
* 用户ID
|
||
*/
|
||
@Id
|
||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||
private Long id;
|
||
|
||
/**
|
||
* 用户名
|
||
*/
|
||
@Column(nullable = false, length = 50)
|
||
private String username;
|
||
|
||
/**
|
||
* 邮箱
|
||
*/
|
||
@Column(nullable = false, unique = true)
|
||
private String email;
|
||
|
||
/**
|
||
* 状态:0-禁用,1-启用
|
||
*/
|
||
private Integer status;
|
||
|
||
/**
|
||
* 创建时间
|
||
*/
|
||
@CreatedDate
|
||
private LocalDateTime createdAt;
|
||
|
||
/**
|
||
* 更新时间
|
||
*/
|
||
@LastModifiedDate
|
||
private LocalDateTime updatedAt;
|
||
}
|
||
```
|
||
|
||
#### 服务类
|
||
```java
|
||
/**
|
||
* 用户服务实现
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class UserServiceImpl implements UserService {
|
||
|
||
private final UserRepository userRepository;
|
||
private final PasswordEncoder passwordEncoder;
|
||
|
||
/**
|
||
* 根据ID获取用户
|
||
*
|
||
* @param id 用户ID
|
||
* @return 用户信息
|
||
* @throws BusinessException 用户不存在时抛出
|
||
*/
|
||
@Override
|
||
@Transactional(readOnly = true)
|
||
public UserVO getUserById(Long id) {
|
||
log.info("查询用户信息,ID:{}", id);
|
||
|
||
User user = userRepository.findById(id)
|
||
.orElseThrow(() -> new BusinessException("用户不存在:" + id));
|
||
|
||
return convertToVO(user);
|
||
}
|
||
|
||
/**
|
||
* 创建用户
|
||
*
|
||
* @param request 创建请求
|
||
* @return 用户ID
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public Long createUser(UserCreateRequest request) {
|
||
log.info("创建用户,用户名:{}", request.getUsername());
|
||
|
||
// 检查用户名是否存在
|
||
if (userRepository.existsByUsername(request.getUsername())) {
|
||
throw new BusinessException("用户名已存在");
|
||
}
|
||
|
||
// 创建用户实体
|
||
User user = new User();
|
||
user.setUsername(request.getUsername());
|
||
user.setEmail(request.getEmail());
|
||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||
user.setStatus(1);
|
||
|
||
// 保存并返回ID
|
||
User saved = userRepository.save(user);
|
||
log.info("用户创建成功,ID:{}", saved.getId());
|
||
|
||
return saved.getId();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 异常处理
|
||
|
||
#### 自定义异常体系
|
||
```java
|
||
/**
|
||
* 基础业务异常
|
||
*/
|
||
@Getter
|
||
public class BusinessException extends RuntimeException {
|
||
|
||
private final String code;
|
||
private final String message;
|
||
|
||
public BusinessException(String message) {
|
||
super(message);
|
||
this.code = "BUSINESS_ERROR";
|
||
this.message = message;
|
||
}
|
||
|
||
public BusinessException(String code, String message) {
|
||
super(message);
|
||
this.code = code;
|
||
this.message = message;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 全局异常处理器
|
||
*/
|
||
@Slf4j
|
||
@RestControllerAdvice
|
||
public class GlobalExceptionHandler {
|
||
|
||
/**
|
||
* 处理业务异常
|
||
*/
|
||
@ExceptionHandler(BusinessException.class)
|
||
public Result<Void> handleBusinessException(BusinessException e) {
|
||
log.warn("业务异常:{}", e.getMessage());
|
||
return Result.fail(e.getCode(), e.getMessage());
|
||
}
|
||
|
||
/**
|
||
* 处理参数校验异常
|
||
*/
|
||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||
public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
|
||
String message = e.getBindingResult().getFieldErrors().stream()
|
||
.map(error -> error.getField() + ": " + error.getDefaultMessage())
|
||
.collect(Collectors.joining(", "));
|
||
log.warn("参数校验失败:{}", message);
|
||
return Result.fail("VALIDATION_ERROR", message);
|
||
}
|
||
|
||
/**
|
||
* 处理未知异常
|
||
*/
|
||
@ExceptionHandler(Exception.class)
|
||
public Result<Void> handleException(Exception e) {
|
||
log.error("系统异常:", e);
|
||
return Result.fail("SYSTEM_ERROR", "系统繁忙,请稍后重试");
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.4 Controller 规范
|
||
|
||
```java
|
||
/**
|
||
* 用户管理接口
|
||
*/
|
||
@Slf4j
|
||
@RestController
|
||
@RequestMapping("/api/v1/users")
|
||
@RequiredArgsConstructor
|
||
@Tag(name = "用户管理", description = "用户相关接口")
|
||
public class UserController {
|
||
|
||
private final UserService userService;
|
||
|
||
/**
|
||
* 获取用户详情
|
||
*
|
||
* @param id 用户ID
|
||
* @return 用户信息
|
||
*/
|
||
@GetMapping("/{id}")
|
||
@Operation(summary = "获取用户详情")
|
||
public Result<UserVO> getUser(@PathVariable Long id) {
|
||
return Result.success(userService.getUserById(id));
|
||
}
|
||
|
||
/**
|
||
* 获取用户列表
|
||
*
|
||
* @param query 查询条件
|
||
* @return 用户列表
|
||
*/
|
||
@GetMapping
|
||
@Operation(summary = "获取用户列表")
|
||
public Result<PageResult<UserVO>> listUsers(@Valid UserQuery query) {
|
||
return Result.success(userService.listUsers(query));
|
||
}
|
||
|
||
/**
|
||
* 创建用户
|
||
*
|
||
* @param request 创建请求
|
||
* @return 用户ID
|
||
*/
|
||
@PostMapping
|
||
@Operation(summary = "创建用户")
|
||
public Result<Long> createUser(@Valid @RequestBody UserCreateRequest request) {
|
||
return Result.success(userService.createUser(request));
|
||
}
|
||
|
||
/**
|
||
* 更新用户
|
||
*
|
||
* @param id 用户ID
|
||
* @param request 更新请求
|
||
* @return 操作结果
|
||
*/
|
||
@PutMapping("/{id}")
|
||
@Operation(summary = "更新用户")
|
||
public Result<Void> updateUser(
|
||
@PathVariable Long id,
|
||
@Valid @RequestBody UserUpdateRequest request) {
|
||
userService.updateUser(id, request);
|
||
return Result.success();
|
||
}
|
||
|
||
/**
|
||
* 删除用户
|
||
*
|
||
* @param id 用户ID
|
||
* @return 操作结果
|
||
*/
|
||
@DeleteMapping("/{id}")
|
||
@Operation(summary = "删除用户")
|
||
public Result<Void> deleteUser(@PathVariable Long id) {
|
||
userService.deleteUser(id);
|
||
return Result.success();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.5 统一响应格式
|
||
|
||
```java
|
||
/**
|
||
* 统一响应结果
|
||
*
|
||
* @param <T> 数据类型
|
||
*/
|
||
@Data
|
||
@NoArgsConstructor
|
||
@AllArgsConstructor
|
||
public class Result<T> implements Serializable {
|
||
|
||
private static final long serialVersionUID = 1L;
|
||
|
||
/**
|
||
* 状态码
|
||
*/
|
||
private String code;
|
||
|
||
/**
|
||
* 消息
|
||
*/
|
||
private String message;
|
||
|
||
/**
|
||
* 数据
|
||
*/
|
||
private T data;
|
||
|
||
/**
|
||
* 时间戳
|
||
*/
|
||
private long timestamp = System.currentTimeMillis();
|
||
|
||
/**
|
||
* 成功响应
|
||
*/
|
||
public static <T> Result<T> success(T data) {
|
||
Result<T> result = new Result<>();
|
||
result.setCode("SUCCESS");
|
||
result.setMessage("操作成功");
|
||
result.setData(data);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 成功响应(无数据)
|
||
*/
|
||
public static <T> Result<T> success() {
|
||
return success(null);
|
||
}
|
||
|
||
/**
|
||
* 失败响应
|
||
*/
|
||
public static <T> Result<T> fail(String code, String message) {
|
||
Result<T> result = new Result<>();
|
||
result.setCode(code);
|
||
result.setMessage(message);
|
||
return result;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# 第三部分:框架特定规范
|
||
|
||
## 6. React 规范
|
||
|
||
### 6.1 组件设计原则
|
||
|
||
#### 函数组件 + TypeScript
|
||
```tsx
|
||
/**
|
||
* 用户卡片组件
|
||
*
|
||
* @description 展示用户基本信息的卡片组件
|
||
*/
|
||
interface UserCardProps {
|
||
/** 用户信息 */
|
||
user: User;
|
||
/** 是否显示详情 */
|
||
showDetails?: boolean;
|
||
/** 编辑回调 */
|
||
onEdit?: (user: User) => void;
|
||
/** 删除回调 */
|
||
onDelete?: (userId: string) => void;
|
||
/** 子元素 */
|
||
children?: React.ReactNode;
|
||
}
|
||
|
||
const UserCard: React.FC<UserCardProps> = ({
|
||
user,
|
||
showDetails = false,
|
||
onEdit,
|
||
onDelete,
|
||
children
|
||
}) => {
|
||
// 组件逻辑
|
||
return (
|
||
<div className="user-card">
|
||
{/* 组件内容 */}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default UserCard;
|
||
```
|
||
|
||
#### 组件文件结构
|
||
```
|
||
components/
|
||
└── UserCard/
|
||
├── index.tsx # 组件主文件
|
||
├── UserCard.tsx # 组件实现(可选,复杂组件时分离)
|
||
├── UserCard.module.css # 样式文件(CSS Modules)
|
||
├── UserCard.test.tsx # 测试文件
|
||
├── types.ts # 类型定义(复杂时分离)
|
||
└── hooks.ts # 组件专用 Hooks(可选)
|
||
```
|
||
|
||
### 6.2 Hooks 使用规范
|
||
|
||
#### Hooks 使用顺序
|
||
```tsx
|
||
function UserProfile({ userId }: { userId: string }) {
|
||
// 1. useState - 状态定义
|
||
const [user, setUser] = useState<User | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<Error | null>(null);
|
||
|
||
// 2. useRef - 引用
|
||
const containerRef = useRef<HTMLDivElement>(null);
|
||
|
||
// 3. useContext - 上下文
|
||
const { theme } = useContext(ThemeContext);
|
||
|
||
// 4. 自定义 Hooks
|
||
const { data, isLoading } = useQuery(['user', userId], fetchUser);
|
||
|
||
// 5. useCallback - 缓存回调
|
||
const handleEdit = useCallback(() => {
|
||
// 处理编辑
|
||
}, [userId]);
|
||
|
||
// 6. useMemo - 缓存计算结果
|
||
const displayName = useMemo(() => {
|
||
return user ? `${user.firstName} ${user.lastName}` : '';
|
||
}, [user]);
|
||
|
||
// 7. useEffect - 副作用
|
||
useEffect(() => {
|
||
// 副作用逻辑
|
||
return () => {
|
||
// 清理函数
|
||
};
|
||
}, [userId]);
|
||
|
||
// 8. 条件渲染和返回
|
||
if (loading) return <Loading />;
|
||
if (error) return <Error message={error.message} />;
|
||
|
||
return <div ref={containerRef}>{/* 内容 */}</div>;
|
||
}
|
||
```
|
||
|
||
#### 自定义 Hook 规范
|
||
```tsx
|
||
/**
|
||
* 用户数据获取 Hook
|
||
*
|
||
* @param userId 用户ID
|
||
* @returns 用户数据、加载状态、错误信息
|
||
*/
|
||
function useUser(userId: string) {
|
||
const [user, setUser] = useState<User | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<Error | null>(null);
|
||
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
|
||
async function fetchUser() {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const data = await userApi.getUser(userId);
|
||
if (!cancelled) {
|
||
setUser(data);
|
||
}
|
||
} catch (err) {
|
||
if (!cancelled) {
|
||
setError(err instanceof Error ? err : new Error('未知错误'));
|
||
}
|
||
} finally {
|
||
if (!cancelled) {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
fetchUser();
|
||
|
||
// 清理函数:防止组件卸载后设置状态
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [userId]);
|
||
|
||
return { user, loading, error };
|
||
}
|
||
```
|
||
|
||
### 6.3 状态管理
|
||
|
||
#### 本地状态 vs 全局状态
|
||
```tsx
|
||
// ✅ 本地状态:组件内部使用,不需要共享
|
||
const [isOpen, setIsOpen] = useState(false);
|
||
const [inputValue, setInputValue] = useState('');
|
||
|
||
// ✅ 全局状态:多个组件共享,使用状态管理库
|
||
// 例如:用户信息、主题设置、购物车等
|
||
```
|
||
|
||
#### 使用 Zustand(推荐)
|
||
```tsx
|
||
import { create } from 'zustand';
|
||
|
||
/**
|
||
* 用户状态 Store
|
||
*/
|
||
interface UserStore {
|
||
user: User | null;
|
||
isAuthenticated: boolean;
|
||
login: (user: User) => void;
|
||
logout: () => void;
|
||
updateUser: (updates: Partial<User>) => void;
|
||
}
|
||
|
||
const useUserStore = create<UserStore>((set) => ({
|
||
user: null,
|
||
isAuthenticated: false,
|
||
|
||
login: (user) => set({ user, isAuthenticated: true }),
|
||
|
||
logout: () => set({ user: null, isAuthenticated: false }),
|
||
|
||
updateUser: (updates) => set((state) => ({
|
||
user: state.user ? { ...state.user, ...updates } : null
|
||
}))
|
||
}));
|
||
|
||
// 使用
|
||
function UserProfile() {
|
||
const { user, logout } = useUserStore();
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 6.4 性能优化
|
||
|
||
```tsx
|
||
// ✅ 使用 React.memo 避免不必要的重渲染
|
||
const UserCard = React.memo(function UserCard({ user }: UserCardProps) {
|
||
return <div>{user.name}</div>;
|
||
});
|
||
|
||
// ✅ 使用 useCallback 缓存回调函数
|
||
const handleClick = useCallback((id: string) => {
|
||
// 处理点击
|
||
}, [/* 依赖项 */]);
|
||
|
||
// ✅ 使用 useMemo 缓存计算结果
|
||
const sortedItems = useMemo(() => {
|
||
return [...items].sort((a, b) => a.name.localeCompare(b.name));
|
||
}, [items]);
|
||
|
||
// ✅ 列表渲染使用稳定的 key
|
||
{items.map((item) => (
|
||
<ListItem key={item.id} item={item} /> // ✅ 使用唯一 ID
|
||
// <ListItem key={index} item={item} /> // ❌ 避免使用索引
|
||
))}
|
||
|
||
// ✅ 懒加载组件
|
||
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
|
||
|
||
function App() {
|
||
return (
|
||
<Suspense fallback={<Loading />}>
|
||
<HeavyComponent />
|
||
</Suspense>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Vue 规范
|
||
|
||
### 7.1 组件设计(Vue 3 Composition API)
|
||
|
||
#### 单文件组件结构
|
||
```vue
|
||
<script setup lang="ts">
|
||
/**
|
||
* 用户卡片组件
|
||
*
|
||
* @description 展示用户基本信息的卡片组件
|
||
*/
|
||
import { ref, computed, onMounted, watch } from 'vue';
|
||
import type { User } from '@/types';
|
||
|
||
// ============ Props 定义 ============
|
||
interface Props {
|
||
/** 用户信息 */
|
||
user: User;
|
||
/** 是否显示详情 */
|
||
showDetails?: boolean;
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
showDetails: false
|
||
});
|
||
|
||
// ============ Emits 定义 ============
|
||
const emit = defineEmits<{
|
||
/** 编辑事件 */
|
||
(e: 'edit', user: User): void;
|
||
/** 删除事件 */
|
||
(e: 'delete', userId: string): void;
|
||
}>();
|
||
|
||
// ============ 响应式数据 ============
|
||
const isExpanded = ref(false);
|
||
const loading = ref(false);
|
||
|
||
// ============ 计算属性 ============
|
||
const displayName = computed(() => {
|
||
return `${props.user.firstName} ${props.user.lastName}`;
|
||
});
|
||
|
||
const cardClasses = computed(() => ({
|
||
'user-card': true,
|
||
'user-card--expanded': isExpanded.value,
|
||
'user-card--loading': loading.value
|
||
}));
|
||
|
||
// ============ 方法 ============
|
||
function handleEdit() {
|
||
emit('edit', props.user);
|
||
}
|
||
|
||
function handleDelete() {
|
||
emit('delete', props.user.id);
|
||
}
|
||
|
||
function toggleExpand() {
|
||
isExpanded.value = !isExpanded.value;
|
||
}
|
||
|
||
// ============ 生命周期 ============
|
||
onMounted(() => {
|
||
console.log('组件已挂载');
|
||
});
|
||
|
||
// ============ 侦听器 ============
|
||
watch(
|
||
() => props.user.id,
|
||
(newId, oldId) => {
|
||
console.log(`用户ID变更:${oldId} -> ${newId}`);
|
||
}
|
||
);
|
||
|
||
// ============ 暴露给父组件的方法 ============
|
||
defineExpose({
|
||
toggleExpand
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div :class="cardClasses">
|
||
<div class="user-card__header">
|
||
<h3>{{ displayName }}</h3>
|
||
<button @click="toggleExpand">
|
||
{{ isExpanded ? '收起' : '展开' }}
|
||
</button>
|
||
</div>
|
||
|
||
<div v-if="showDetails || isExpanded" class="user-card__details">
|
||
<p>邮箱:{{ user.email }}</p>
|
||
<p>电话:{{ user.phone }}</p>
|
||
</div>
|
||
|
||
<div class="user-card__actions">
|
||
<button @click="handleEdit">编辑</button>
|
||
<button @click="handleDelete">删除</button>
|
||
</div>
|
||
|
||
<!-- 插槽 -->
|
||
<slot name="footer" />
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.user-card {
|
||
padding: 16px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.user-card--expanded {
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.user-card--loading {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
### 7.2 组合式函数(Composables)
|
||
|
||
```typescript
|
||
// composables/useUser.ts
|
||
import { ref, computed, watch } from 'vue';
|
||
import type { Ref } from 'vue';
|
||
import type { User } from '@/types';
|
||
import { userApi } from '@/api';
|
||
|
||
/**
|
||
* 用户数据组合式函数
|
||
*
|
||
* @param userId 用户ID(响应式引用)
|
||
* @returns 用户数据、加载状态、错误信息、刷新方法
|
||
*/
|
||
export function useUser(userId: Ref<string> | string) {
|
||
const user = ref<User | null>(null);
|
||
const loading = ref(false);
|
||
const error = ref<Error | null>(null);
|
||
|
||
// 获取用户数据
|
||
async function fetchUser(id: string) {
|
||
loading.value = true;
|
||
error.value = null;
|
||
|
||
try {
|
||
user.value = await userApi.getUser(id);
|
||
} catch (err) {
|
||
error.value = err instanceof Error ? err : new Error('获取用户失败');
|
||
user.value = null;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
// 刷新数据
|
||
function refresh() {
|
||
const id = typeof userId === 'string' ? userId : userId.value;
|
||
return fetchUser(id);
|
||
}
|
||
|
||
// 计算属性
|
||
const displayName = computed(() => {
|
||
if (!user.value) return '';
|
||
return `${user.value.firstName} ${user.value.lastName}`;
|
||
});
|
||
|
||
// 侦听 userId 变化
|
||
if (typeof userId !== 'string') {
|
||
watch(userId, (newId) => {
|
||
if (newId) {
|
||
fetchUser(newId);
|
||
}
|
||
}, { immediate: true });
|
||
} else {
|
||
// 立即获取数据
|
||
fetchUser(userId);
|
||
}
|
||
|
||
return {
|
||
user,
|
||
loading,
|
||
error,
|
||
displayName,
|
||
refresh
|
||
};
|
||
}
|
||
```
|
||
|
||
### 7.3 Pinia 状态管理
|
||
|
||
```typescript
|
||
// stores/user.ts
|
||
import { defineStore } from 'pinia';
|
||
import type { User } from '@/types';
|
||
import { userApi } from '@/api';
|
||
|
||
/**
|
||
* 用户状态 Store
|
||
*/
|
||
export const useUserStore = defineStore('user', {
|
||
// 状态
|
||
state: () => ({
|
||
currentUser: null as User | null,
|
||
isAuthenticated: false,
|
||
loading: false
|
||
}),
|
||
|
||
// 计算属性
|
||
getters: {
|
||
/** 用户显示名称 */
|
||
displayName(): string {
|
||
if (!this.currentUser) return '游客';
|
||
return `${this.currentUser.firstName} ${this.currentUser.lastName}`;
|
||
},
|
||
|
||
/** 是否是管理员 */
|
||
isAdmin(): boolean {
|
||
return this.currentUser?.role === 'admin';
|
||
}
|
||
},
|
||
|
||
// 方法
|
||
actions: {
|
||
/**
|
||
* 用户登录
|
||
*/
|
||
async login(credentials: { username: string; password: string }) {
|
||
this.loading = true;
|
||
try {
|
||
const user = await userApi.login(credentials);
|
||
this.currentUser = user;
|
||
this.isAuthenticated = true;
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 用户登出
|
||
*/
|
||
logout() {
|
||
this.currentUser = null;
|
||
this.isAuthenticated = false;
|
||
},
|
||
|
||
/**
|
||
* 更新用户信息
|
||
*/
|
||
updateUser(updates: Partial<User>) {
|
||
if (this.currentUser) {
|
||
this.currentUser = { ...this.currentUser, ...updates };
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// 使用 Setup Store 语法(更灵活)
|
||
export const useUserStore2 = defineStore('user', () => {
|
||
// 状态
|
||
const currentUser = ref<User | null>(null);
|
||
const isAuthenticated = ref(false);
|
||
|
||
// 计算属性
|
||
const displayName = computed(() => {
|
||
return currentUser.value?.name ?? '游客';
|
||
});
|
||
|
||
// 方法
|
||
async function login(credentials: LoginCredentials) {
|
||
const user = await userApi.login(credentials);
|
||
currentUser.value = user;
|
||
isAuthenticated.value = true;
|
||
}
|
||
|
||
function logout() {
|
||
currentUser.value = null;
|
||
isAuthenticated.value = false;
|
||
}
|
||
|
||
return {
|
||
currentUser,
|
||
isAuthenticated,
|
||
displayName,
|
||
login,
|
||
logout
|
||
};
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
# 第四部分:工程化规范
|
||
|
||
## 8. Git 提交规范
|
||
|
||
### 8.1 提交信息格式
|
||
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
|
||
<footer>
|
||
```
|
||
|
||
### 8.2 Type 类型说明
|
||
|
||
| 类型 | 说明 | 示例 |
|
||
|-----|------|------|
|
||
| `feat` | 新功能 | `feat(user): 添加用户登录功能` |
|
||
| `fix` | 修复 Bug | `fix(cart): 修复购物车数量计算错误` |
|
||
| `docs` | 文档更新 | `docs: 更新 README 安装说明` |
|
||
| `style` | 代码格式(不影响功能) | `style: 格式化代码` |
|
||
| `refactor` | 重构(不是新功能也不是修复) | `refactor(api): 重构请求封装` |
|
||
| `perf` | 性能优化 | `perf: 优化列表渲染性能` |
|
||
| `test` | 测试相关 | `test(user): 添加用户服务单元测试` |
|
||
| `chore` | 构建/工具/依赖更新 | `chore: 升级 webpack 到 5.x` |
|
||
| `revert` | 回滚提交 | `revert: 回滚 feat(user) 提交` |
|
||
| `ci` | CI/CD 配置 | `ci: 添加 GitHub Actions 配置` |
|
||
| `build` | 构建系统或外部依赖 | `build: 更新构建脚本` |
|
||
|
||
### 8.3 提交示例
|
||
|
||
```bash
|
||
# 简短提交
|
||
git commit -m "feat(user): 添加用户注册功能"
|
||
|
||
# 详细提交
|
||
git commit -m "feat(user): 添加用户注册功能
|
||
|
||
- 实现用户名密码注册
|
||
- 添加邮箱验证
|
||
- 添加表单校验
|
||
|
||
Closes #123"
|
||
|
||
# 修复 Bug
|
||
git commit -m "fix(order): 修复订单金额计算错误
|
||
|
||
订单金额在应用折扣后计算不正确,
|
||
原因是折扣百分比未正确转换。
|
||
|
||
Fixes #456"
|
||
|
||
# 破坏性变更
|
||
git commit -m "feat(api): 重构用户 API 接口
|
||
|
||
BREAKING CHANGE: 用户 API 响应格式已变更
|
||
- data 字段改为 result
|
||
- 添加 pagination 字段"
|
||
```
|
||
|
||
### 8.4 分支命名规范
|
||
|
||
| 分支类型 | 命名格式 | 示例 |
|
||
|---------|---------|------|
|
||
| 主分支 | `main` / `master` | `main` |
|
||
| 开发分支 | `develop` / `dev` | `develop` |
|
||
| 功能分支 | `feature/<name>` | `feature/user-login` |
|
||
| 修复分支 | `fix/<name>` 或 `bugfix/<name>` | `fix/cart-calculation` |
|
||
| 发布分支 | `release/<version>` | `release/1.2.0` |
|
||
| 热修复分支 | `hotfix/<name>` | `hotfix/security-patch` |
|
||
|
||
---
|
||
|
||
## 9. API 设计规范
|
||
|
||
### 9.1 RESTful API 设计
|
||
|
||
#### URL 设计原则
|
||
```
|
||
# 资源命名使用复数名词
|
||
GET /api/v1/users # 获取用户列表
|
||
GET /api/v1/users/:id # 获取单个用户
|
||
POST /api/v1/users # 创建用户
|
||
PUT /api/v1/users/:id # 更新用户(全量)
|
||
PATCH /api/v1/users/:id # 更新用户(部分)
|
||
DELETE /api/v1/users/:id # 删除用户
|
||
|
||
# 资源关联
|
||
GET /api/v1/users/:id/orders # 获取用户的订单
|
||
POST /api/v1/users/:id/orders # 为用户创建订单
|
||
|
||
# 查询参数
|
||
GET /api/v1/users?page=1&size=10 # 分页
|
||
GET /api/v1/users?sort=createdAt&order=desc # 排序
|
||
GET /api/v1/users?status=active&role=admin # 筛选
|
||
GET /api/v1/users?keyword=john # 搜索
|
||
```
|
||
|
||
#### HTTP 状态码
|
||
| 状态码 | 说明 | 使用场景 |
|
||
|-------|------|---------|
|
||
| 200 | OK | 请求成功 |
|
||
| 201 | Created | 创建成功 |
|
||
| 204 | No Content | 删除成功 |
|
||
| 400 | Bad Request | 请求参数错误 |
|
||
| 401 | Unauthorized | 未认证 |
|
||
| 403 | Forbidden | 无权限 |
|
||
| 404 | Not Found | 资源不存在 |
|
||
| 409 | Conflict | 资源冲突 |
|
||
| 422 | Unprocessable Entity | 参数校验失败 |
|
||
| 500 | Internal Server Error | 服务器错误 |
|
||
|
||
### 9.2 请求/响应格式
|
||
|
||
#### 统一响应格式
|
||
```typescript
|
||
// 成功响应
|
||
{
|
||
"code": "SUCCESS",
|
||
"message": "操作成功",
|
||
"data": {
|
||
// 业务数据
|
||
},
|
||
"timestamp": 1642234567890
|
||
}
|
||
|
||
// 分页响应
|
||
{
|
||
"code": "SUCCESS",
|
||
"message": "操作成功",
|
||
"data": {
|
||
"list": [...],
|
||
"pagination": {
|
||
"page": 1,
|
||
"size": 10,
|
||
"total": 100,
|
||
"totalPages": 10
|
||
}
|
||
},
|
||
"timestamp": 1642234567890
|
||
}
|
||
|
||
// 错误响应
|
||
{
|
||
"code": "VALIDATION_ERROR",
|
||
"message": "参数校验失败",
|
||
"errors": [
|
||
{ "field": "email", "message": "邮箱格式不正确" },
|
||
{ "field": "password", "message": "密码长度至少6位" }
|
||
],
|
||
"timestamp": 1642234567890
|
||
}
|
||
```
|
||
|
||
### 9.3 错误码设计
|
||
|
||
```typescript
|
||
// 错误码枚举
|
||
enum ErrorCode {
|
||
// 成功
|
||
SUCCESS = 'SUCCESS',
|
||
|
||
// 客户端错误 (4xx)
|
||
BAD_REQUEST = 'BAD_REQUEST',
|
||
UNAUTHORIZED = 'UNAUTHORIZED',
|
||
FORBIDDEN = 'FORBIDDEN',
|
||
NOT_FOUND = 'NOT_FOUND',
|
||
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
||
|
||
// 业务错误
|
||
USER_NOT_FOUND = 'USER_NOT_FOUND',
|
||
USER_ALREADY_EXISTS = 'USER_ALREADY_EXISTS',
|
||
INVALID_PASSWORD = 'INVALID_PASSWORD',
|
||
ORDER_NOT_FOUND = 'ORDER_NOT_FOUND',
|
||
INSUFFICIENT_STOCK = 'INSUFFICIENT_STOCK',
|
||
|
||
// 服务端错误 (5xx)
|
||
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
||
SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
|
||
DATABASE_ERROR = 'DATABASE_ERROR'
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 测试规范
|
||
|
||
### 10.1 单元测试
|
||
|
||
#### 测试文件命名
|
||
```
|
||
src/
|
||
├── utils/
|
||
│ ├── format.ts
|
||
│ └── format.test.ts # 或 format.spec.ts
|
||
├── components/
|
||
│ └── UserCard/
|
||
│ ├── index.tsx
|
||
│ └── UserCard.test.tsx
|
||
```
|
||
|
||
#### 测试用例命名
|
||
```typescript
|
||
// 使用 describe + it 结构
|
||
describe('formatDate', () => {
|
||
it('应该正确格式化日期', () => {
|
||
// 测试代码
|
||
});
|
||
|
||
it('当输入为空时应该返回空字符串', () => {
|
||
// 测试代码
|
||
});
|
||
|
||
it('当输入无效时应该抛出错误', () => {
|
||
// 测试代码
|
||
});
|
||
});
|
||
|
||
// 使用中文描述测试场景
|
||
describe('用户服务', () => {
|
||
describe('创建用户', () => {
|
||
it('应该成功创建用户并返回用户ID', async () => {
|
||
// 测试代码
|
||
});
|
||
|
||
it('当用户名已存在时应该抛出错误', async () => {
|
||
// 测试代码
|
||
});
|
||
});
|
||
});
|
||
```
|
||
|
||
#### 测试结构(AAA 模式)
|
||
```typescript
|
||
it('应该正确计算订单总价', () => {
|
||
// Arrange(准备)
|
||
const items = [
|
||
{ name: '商品A', price: 100, quantity: 2 },
|
||
{ name: '商品B', price: 50, quantity: 3 }
|
||
];
|
||
|
||
// Act(执行)
|
||
const total = calculateTotal(items);
|
||
|
||
// Assert(断言)
|
||
expect(total).toBe(350);
|
||
});
|
||
```
|
||
|
||
### 10.2 React 组件测试
|
||
|
||
```tsx
|
||
import { render, screen, fireEvent } from '@testing-library/react';
|
||
import userEvent from '@testing-library/user-event';
|
||
import UserCard from './UserCard';
|
||
|
||
describe('UserCard 组件', () => {
|
||
const mockUser = {
|
||
id: '1',
|
||
name: '张三',
|
||
email: 'zhangsan@example.com'
|
||
};
|
||
|
||
it('应该正确渲染用户信息', () => {
|
||
render(<UserCard user={mockUser} />);
|
||
|
||
expect(screen.getByText('张三')).toBeInTheDocument();
|
||
expect(screen.getByText('zhangsan@example.com')).toBeInTheDocument();
|
||
});
|
||
|
||
it('点击编辑按钮应该触发 onEdit 回调', async () => {
|
||
const handleEdit = jest.fn();
|
||
render(<UserCard user={mockUser} onEdit={handleEdit} />);
|
||
|
||
await userEvent.click(screen.getByRole('button', { name: '编辑' }));
|
||
|
||
expect(handleEdit).toHaveBeenCalledWith(mockUser);
|
||
});
|
||
|
||
it('当 showDetails 为 false 时不应该显示详情', () => {
|
||
render(<UserCard user={mockUser} showDetails={false} />);
|
||
|
||
expect(screen.queryByText('详情')).not.toBeInTheDocument();
|
||
});
|
||
});
|
||
```
|
||
|
||
### 10.3 Vue 组件测试
|
||
|
||
```typescript
|
||
import { mount } from '@vue/test-utils';
|
||
import { describe, it, expect, vi } from 'vitest';
|
||
import UserCard from './UserCard.vue';
|
||
|
||
describe('UserCard 组件', () => {
|
||
const mockUser = {
|
||
id: '1',
|
||
name: '张三',
|
||
email: 'zhangsan@example.com'
|
||
};
|
||
|
||
it('应该正确渲染用户信息', () => {
|
||
const wrapper = mount(UserCard, {
|
||
props: { user: mockUser }
|
||
});
|
||
|
||
expect(wrapper.text()).toContain('张三');
|
||
expect(wrapper.text()).toContain('zhangsan@example.com');
|
||
});
|
||
|
||
it('点击编辑按钮应该触发 edit 事件', async () => {
|
||
const wrapper = mount(UserCard, {
|
||
props: { user: mockUser }
|
||
});
|
||
|
||
await wrapper.find('[data-test="edit-btn"]').trigger('click');
|
||
|
||
expect(wrapper.emitted('edit')).toBeTruthy();
|
||
expect(wrapper.emitted('edit')![0]).toEqual([mockUser]);
|
||
});
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
# 附录
|
||
|
||
## 附录 A:代码审查清单
|
||
|
||
### A.1 开发流程检查
|
||
- [ ] 如果是询问"如何实现",是否先提供了详细的开发计划
|
||
- [ ] 是否在用户同意后才修改代码
|
||
- [ ] 删除的文件是否移动到 `.recycle/` 文件夹而非直接删除
|
||
|
||
### A.2 代码质量检查
|
||
- [ ] 命名是否清晰、语义化
|
||
- [ ] 注释是否完整且为中文
|
||
- [ ] 错误处理是否完善
|
||
- [ ] 类型定义是否完整(TypeScript)
|
||
- [ ] 是否有重复代码可以抽取
|
||
|
||
### A.3 TypeScript 检查
|
||
- [ ] 是否避免使用 `any`
|
||
- [ ] 接口和类型是否正确定义
|
||
- [ ] 泛型使用是否恰当
|
||
- [ ] 导入导出是否规范
|
||
|
||
### A.4 React/Vue 检查
|
||
- [ ] 组件职责是否单一
|
||
- [ ] Props 和事件是否正确定义
|
||
- [ ] Hooks 使用是否规范
|
||
- [ ] 是否有性能问题(不必要的重渲染)
|
||
|
||
### A.5 Java 检查
|
||
- [ ] 包结构是否规范
|
||
- [ ] 类命名是否符合约定
|
||
- [ ] 是否正确使用注解
|
||
- [ ] 异常处理是否完善
|
||
|
||
### A.6 安全检查
|
||
- [ ] 是否验证了所有外部输入
|
||
- [ ] 是否避免了 XSS 风险
|
||
- [ ] 敏感数据是否正确处理
|
||
- [ ] 错误信息是否避免泄露敏感信息
|
||
|
||
### A.7 文档检查
|
||
- [ ] 是否更新了相关文档
|
||
- [ ] API 文档是否同步更新
|
||
- [ ] README 是否需要更新
|
||
|
||
---
|
||
|
||
## 附录 B:常见任务快速参考
|
||
|
||
### B.1 添加新文件
|
||
1. 创建文件,添加必要的类型定义
|
||
2. 添加中文注释
|
||
3. 在相关入口文件中导出(如需要)
|
||
4. 更新文档
|
||
|
||
### B.2 删除文件
|
||
1. 确认文件不再使用
|
||
2. 创建回收目录:`.recycle/YYYY-MM-DD/`
|
||
3. 移动文件到回收目录(保持目录结构)
|
||
4. 更新相关导入和引用
|
||
5. 更新文档
|
||
|
||
### B.3 重构代码
|
||
1. 先与用户确认重构范围
|
||
2. 制定详细的重构计划
|
||
3. 分步骤执行,每步验证
|
||
4. 确保测试通过
|
||
5. 更新文档
|
||
|
||
### B.4 添加新 API
|
||
1. 设计 API 接口(URL、参数、响应)
|
||
2. 定义 TypeScript 类型
|
||
3. 实现后端接口
|
||
4. 实现前端调用
|
||
5. 添加错误处理
|
||
6. 编写测试
|
||
7. 更新 API 文档
|
||
|
||
---
|
||
|
||
## 附录 C:模板使用说明
|
||
|
||
本模板可直接复制到新项目中使用。使用时请注意:
|
||
|
||
1. **根据项目调整**:
|
||
- 删除不适用的语言/框架章节
|
||
- 根据团队习惯调整具体规范
|
||
|
||
2. **补充项目特定内容**:
|
||
- 项目特定的架构规范
|
||
- 项目特定的命名规范
|
||
- 项目特定的目录结构
|
||
|
||
3. **定期更新**:
|
||
- 随着项目发展,及时更新规范内容
|
||
- 根据团队反馈优化规范
|
||
|
||
4. **配合其他文档使用**:
|
||
- 项目 README
|
||
- API 文档
|
||
- 架构设计文档
|
||
- 部署文档
|
||
|
||
---
|
||
|
||
**文档版本**:v2.0
|
||
**最后更新**:2026-01-21
|