607 lines
12 KiB
Markdown
607 lines
12 KiB
Markdown
# 开发规范
|
||
|
||
> **文档版本**: v1.0.0
|
||
> **创建日期**: 2026-05-25
|
||
> **适用范围**: FischerX 项目开发人员
|
||
|
||
## 目录
|
||
|
||
- [代码规范](#代码规范)
|
||
- [Git 规范](#git-规范)
|
||
- [PR 规范](#pr-规范)
|
||
|
||
---
|
||
|
||
## 代码规范
|
||
|
||
### TypeScript 规范
|
||
|
||
#### 类型定义
|
||
|
||
- 使用 `type` 定义对象类型,使用 `interface` 定义类接口或需要扩展的类型
|
||
- 始终使用严格的类型检查,避免使用 `any`
|
||
- 优先使用联合类型和字面量类型
|
||
|
||
```typescript
|
||
// 好的做法
|
||
type User = {
|
||
id: string;
|
||
name: string;
|
||
email: string;
|
||
role: 'admin' | 'user' | 'guest';
|
||
};
|
||
|
||
interface Repository {
|
||
findById(id: string): Promise<User | null>;
|
||
save(user: User): Promise<User>;
|
||
}
|
||
|
||
// 避免
|
||
type User = any;
|
||
```
|
||
|
||
#### 命名规范
|
||
|
||
- 类型名使用 PascalCase
|
||
- 常量使用 UPPER_SNAKE_CASE
|
||
- 变量和函数使用 camelCase
|
||
- 类使用 PascalCase
|
||
- React 组件使用 PascalCase
|
||
|
||
```typescript
|
||
// 类型
|
||
type UserProfile = { ... };
|
||
|
||
// 常量
|
||
const MAX_RETRY_COUNT = 3;
|
||
const API_BASE_URL = 'https://api.example.com';
|
||
|
||
// 变量和函数
|
||
const userName = 'John';
|
||
function calculateTotal(price: number, quantity: number): number { ... }
|
||
|
||
// 类
|
||
class UserService { ... }
|
||
|
||
// React 组件
|
||
function UserProfileCard() { ... }
|
||
```
|
||
|
||
#### 文件组织
|
||
|
||
- 一个文件一个主要导出
|
||
- 使用 index.ts 作为模块入口
|
||
- 按功能组织文件,而不是按类型
|
||
|
||
```
|
||
// 好的做法
|
||
user/
|
||
├── index.ts
|
||
├── types.ts
|
||
├── service.ts
|
||
├── repository.ts
|
||
└── hooks.ts
|
||
```
|
||
|
||
---
|
||
|
||
### React/Next.js 规范
|
||
|
||
#### 组件设计
|
||
|
||
- 使用函数组件,避免类组件
|
||
- 组件文件使用 `.tsx` 扩展名
|
||
- 组件名与文件名保持一致
|
||
|
||
```tsx
|
||
// components/UserCard.tsx
|
||
interface UserCardProps {
|
||
user: User;
|
||
onEdit?: (user: User) => void;
|
||
}
|
||
|
||
export function UserCard({ user, onEdit }: UserCardProps) {
|
||
return (
|
||
<div className="user-card">
|
||
<h3>{user.name}</h3>
|
||
<p>{user.email}</p>
|
||
{onEdit && <button onClick={() => onEdit(user)}>Edit</button>}
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
#### Hooks 规范
|
||
|
||
- 自定义 Hook 以 `use` 开头
|
||
- Hook 应该只负责单一职责
|
||
- 使用 React Query 进行数据获取
|
||
- 使用 Zustand 进行状态管理
|
||
|
||
```tsx
|
||
// hooks/useUser.ts
|
||
import { useQuery } from '@tanstack/react-query';
|
||
import { userApi } from '@/lib/api';
|
||
|
||
export function useUser(userId: string) {
|
||
return useQuery({
|
||
queryKey: ['user', userId],
|
||
queryFn: () => userApi.getById(userId),
|
||
enabled: !!userId,
|
||
});
|
||
}
|
||
```
|
||
|
||
#### 表单处理
|
||
|
||
- 使用 React Hook Form
|
||
- 使用 Zod 进行验证
|
||
- 显示友好的错误信息
|
||
|
||
```tsx
|
||
import { useForm } from 'react-hook-form';
|
||
import { zodResolver } from '@hookform/resolvers/zod';
|
||
import * as z from 'zod';
|
||
|
||
const formSchema = z.object({
|
||
name: z.string().min(2, 'Name too short'),
|
||
email: z.string().email('Invalid email'),
|
||
});
|
||
|
||
type FormValues = z.infer<typeof formSchema>;
|
||
|
||
function UserForm() {
|
||
const form = useForm<FormValues>({
|
||
resolver: zodResolver(formSchema),
|
||
});
|
||
|
||
const onSubmit = (data: FormValues) => {
|
||
console.log(data);
|
||
};
|
||
|
||
return <form onSubmit={form.handleSubmit(onSubmit)}>{/* ... */}</form>;
|
||
}
|
||
```
|
||
|
||
#### 样式规范
|
||
|
||
- 使用 Tailwind CSS
|
||
- 组件内部样式放在 `className` 中
|
||
- 复杂样式使用 `clsx` 或 `tailwind-merge`
|
||
|
||
```tsx
|
||
import { clsx } from 'clsx';
|
||
import { twMerge } from 'tailwind-merge';
|
||
|
||
function cn(...inputs: any[]) {
|
||
return twMerge(clsx(inputs));
|
||
}
|
||
|
||
function Button({
|
||
children,
|
||
variant = 'primary',
|
||
className,
|
||
}: {
|
||
children: React.ReactNode;
|
||
variant?: 'primary' | 'secondary' | 'danger';
|
||
className?: string;
|
||
}) {
|
||
const variants = {
|
||
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
||
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
|
||
danger: 'bg-red-600 text-white hover:bg-red-700',
|
||
};
|
||
|
||
return (
|
||
<button
|
||
className={cn(
|
||
'px-4 py-2 rounded font-medium transition-colors',
|
||
variants[variant],
|
||
className
|
||
)}
|
||
>
|
||
{children}
|
||
</button>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### NestJS 规范
|
||
|
||
#### 模块结构
|
||
|
||
- 按功能组织模块
|
||
- 每个模块包含 Controller、Service、Module
|
||
- 使用 Prisma 作为数据访问层
|
||
|
||
```
|
||
user/
|
||
├── user.module.ts
|
||
├── user.controller.ts
|
||
├── user.service.ts
|
||
├── dto/
|
||
│ ├── create-user.dto.ts
|
||
│ └── update-user.dto.ts
|
||
└── entities/
|
||
└── user.entity.ts
|
||
```
|
||
|
||
#### 控制器规范
|
||
|
||
- Controller 只负责请求/响应处理
|
||
- 业务逻辑委托给 Service
|
||
- 使用 DTO 进行验证
|
||
- 使用 Swagger 装饰器生成文档
|
||
|
||
```typescript
|
||
import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common';
|
||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||
import { UserService } from './user.service';
|
||
import { CreateUserDto } from './dto/create-user.dto';
|
||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||
|
||
@ApiTags('users')
|
||
@Controller('users')
|
||
export class UserController {
|
||
constructor(private readonly userService: UserService) {}
|
||
|
||
@Post()
|
||
@ApiOperation({ summary: 'Create user' })
|
||
@ApiResponse({ status: 201, description: 'User created' })
|
||
create(@Body() createUserDto: CreateUserDto) {
|
||
return this.userService.create(createUserDto);
|
||
}
|
||
|
||
@Get(':id')
|
||
@UseGuards(JwtAuthGuard)
|
||
@ApiOperation({ summary: 'Get user by ID' })
|
||
findOne(@Param('id') id: string) {
|
||
return this.userService.findOne(id);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 服务规范
|
||
|
||
- Service 包含业务逻辑
|
||
- 使用依赖注入
|
||
- 抛出业务异常
|
||
|
||
```typescript
|
||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||
import { PrismaService } from '../prisma/prisma.service';
|
||
import { CreateUserDto } from './dto/create-user.dto';
|
||
import * as bcrypt from 'bcrypt';
|
||
|
||
@Injectable()
|
||
export class UserService {
|
||
constructor(private prisma: PrismaService) {}
|
||
|
||
async create(createUserDto: CreateUserDto) {
|
||
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
|
||
|
||
return this.prisma.user.create({
|
||
data: {
|
||
...createUserDto,
|
||
password: hashedPassword,
|
||
},
|
||
});
|
||
}
|
||
|
||
async findOne(id: string) {
|
||
const user = await this.prisma.user.findUnique({ where: { id } });
|
||
if (!user) {
|
||
throw new NotFoundException(`User with ID ${id} not found`);
|
||
}
|
||
return user;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### DTO 规范
|
||
|
||
- 使用 class-validator 进行验证
|
||
- 使用 class-transformer 进行转换
|
||
- DTO 放在 dto 目录下
|
||
|
||
```typescript
|
||
import { IsEmail, IsString, MinLength } from 'class-validator';
|
||
import { ApiProperty } from '@nestjs/swagger';
|
||
|
||
export class CreateUserDto {
|
||
@ApiProperty()
|
||
@IsString()
|
||
@MinLength(2)
|
||
name: string;
|
||
|
||
@ApiProperty()
|
||
@IsEmail()
|
||
email: string;
|
||
|
||
@ApiProperty()
|
||
@IsString()
|
||
@MinLength(8)
|
||
password: string;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Git 规范
|
||
|
||
### 分支策略
|
||
|
||
我们使用 Git Flow 工作流:
|
||
|
||
```
|
||
main (生产环境)
|
||
└── develop (开发环境)
|
||
├── feature/feature-name (功能分支)
|
||
├── bugfix/bug-name (修复分支)
|
||
├── hotfix/hotfix-name (紧急修复)
|
||
└── release/release-x.y.z (发布分支)
|
||
```
|
||
|
||
#### 分支说明
|
||
|
||
| 分支类型 | 命名规范 | 说明 |
|
||
|---------|---------|------|
|
||
| main | `main` | 生产环境代码,始终稳定 |
|
||
| develop | `develop` | 开发环境代码,集成新功能 |
|
||
| feature | `feature/xxx-yyy` | 新功能开发 |
|
||
| bugfix | `bugfix/xxx-yyy` | Bug 修复 |
|
||
| hotfix | `hotfix/xxx-yyy` | 生产环境紧急修复 |
|
||
| release | `release/x.y.z` | 发布准备 |
|
||
|
||
#### 创建分支
|
||
|
||
```bash
|
||
# 从 develop 创建功能分支
|
||
git checkout develop
|
||
git pull origin develop
|
||
git checkout -b feature/user-authentication
|
||
|
||
# 从 main 创建紧急修复分支
|
||
git checkout main
|
||
git pull origin main
|
||
git checkout -b hotfix/critical-login-bug
|
||
```
|
||
|
||
---
|
||
|
||
### 提交规范
|
||
|
||
我们使用 Conventional Commits 规范:
|
||
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
|
||
<footer>
|
||
```
|
||
|
||
#### Type 类型
|
||
|
||
| 类型 | 说明 |
|
||
|-----|------|
|
||
| feat | 新功能 |
|
||
| fix | Bug 修复 |
|
||
| docs | 文档更新 |
|
||
| style | 代码格式调整 |
|
||
| refactor | 重构 |
|
||
| perf | 性能优化 |
|
||
| test | 测试相关 |
|
||
| chore | 构建/工具相关 |
|
||
|
||
#### Scope 范围
|
||
|
||
- `web`: Web 应用
|
||
- `admin`: 管理后台
|
||
- `api`: API 服务
|
||
- `ui`: UI 组件库
|
||
- `core`: 核心业务包
|
||
- `types`: 类型定义
|
||
- `utils`: 工具函数
|
||
- `config`: 配置
|
||
|
||
#### 提交示例
|
||
|
||
```bash
|
||
# 新功能
|
||
git commit -m "feat(web): add user login page"
|
||
|
||
# Bug 修复
|
||
git commit -m "fix(api): handle null user in authentication"
|
||
|
||
# 文档更新
|
||
git commit -m "docs: update quick start guide"
|
||
|
||
# 重构
|
||
git commit -m "refactor(ui): restructure button component"
|
||
|
||
# 带正文的提交
|
||
git commit -m "feat(web): add password reset
|
||
|
||
- Add password reset form
|
||
- Add email verification
|
||
- Update API client
|
||
|
||
Closes #123"
|
||
```
|
||
|
||
---
|
||
|
||
## PR 规范
|
||
|
||
### PR 流程
|
||
|
||
1. 创建分支并开发
|
||
2. 提交代码
|
||
3. 推送分支到远程
|
||
4. 创建 Pull Request
|
||
5. CI 检查
|
||
6. 代码审查
|
||
7. 修改(如果需要)
|
||
8. 合并
|
||
|
||
### PR 模板
|
||
|
||
```markdown
|
||
## 描述
|
||
简要描述这个 PR 的改动。
|
||
|
||
## 类型
|
||
- [ ] feat (新功能)
|
||
- [ ] fix (Bug 修复)
|
||
- [ ] docs (文档更新)
|
||
- [ ] style (代码格式)
|
||
- [ ] refactor (重构)
|
||
- [ ] perf (性能优化)
|
||
- [ ] test (测试)
|
||
- [ ] chore (构建/工具)
|
||
|
||
## 改动范围
|
||
- [ ] apps/web
|
||
- [ ] apps/admin
|
||
- [ ] services/api
|
||
- [ ] packages/ui
|
||
- [ ] packages/core
|
||
- [ ] packages/types
|
||
- [ ] packages/utils
|
||
- [ ] 其他: ___
|
||
|
||
## 测试
|
||
- [ ] 单元测试已添加/更新
|
||
- [ ] 集成测试已添加/更新
|
||
- [ ] E2E 测试已添加/更新
|
||
- [ ] 手动测试已完成
|
||
|
||
## 截图(如果适用)
|
||
添加 UI 变化的截图。
|
||
|
||
## 相关 Issue
|
||
Closes #123, #456
|
||
|
||
## 检查清单
|
||
- [ ] 代码符合项目规范
|
||
- [ ] 已添加必要的测试
|
||
- [ ] 文档已更新
|
||
- [ ] CI 检查通过
|
||
```
|
||
|
||
### 代码审查清单
|
||
|
||
审查者应该检查以下内容:
|
||
|
||
#### 功能正确性
|
||
- [ ] 代码实现了预期功能
|
||
- [ ] 边界情况已处理
|
||
- [ ] 错误处理正确
|
||
|
||
#### 代码质量
|
||
- [ ] 代码清晰易读
|
||
- [ ] 命名有意义
|
||
- [ ] 没有重复代码
|
||
- [ ] 复杂度适中
|
||
|
||
#### 性能
|
||
- [ ] 没有明显的性能问题
|
||
- [ ] 数据库查询优化
|
||
- [ ] 缓存使用合理
|
||
|
||
#### 安全
|
||
- [ ] 输入已验证
|
||
- [ ] 没有 SQL 注入风险
|
||
- [ ] 敏感数据已保护
|
||
- [ ] 权限控制正确
|
||
|
||
#### 测试
|
||
- [ ] 测试覆盖充分
|
||
- [ ] 测试可以通过
|
||
- [ ] 测试有意义
|
||
|
||
#### 文档
|
||
- [ ] 代码注释充分
|
||
- [ ] API 文档已更新
|
||
- [ ] 使用文档已更新
|
||
|
||
### 合并策略
|
||
|
||
- **Squash Merge**: 功能分支合并到 develop,保持提交历史整洁
|
||
- **Rebase Merge**: 保持线性历史
|
||
- **Merge Commit**: 保留完整提交历史(用于 main 分支)
|
||
|
||
```bash
|
||
# Squash 合并示例
|
||
git checkout develop
|
||
git merge --squash feature/user-authentication
|
||
git commit -m "feat(web): add user authentication"
|
||
```
|
||
|
||
---
|
||
|
||
## 最佳实践提示
|
||
|
||
### 开发工作流
|
||
|
||
1. **开始工作前**
|
||
```bash
|
||
git checkout develop
|
||
git pull origin develop
|
||
```
|
||
|
||
2. **创建分支**
|
||
```bash
|
||
git checkout -b feature/your-feature-name
|
||
```
|
||
|
||
3. **定期提交**
|
||
```bash
|
||
git add .
|
||
git commit -m "feat: your commit message"
|
||
```
|
||
|
||
4. **推送到远程**
|
||
```bash
|
||
git push -u origin feature/your-feature-name
|
||
```
|
||
|
||
5. **创建 PR**
|
||
- 在 GitHub/GitLab 上创建 PR
|
||
- 填写 PR 模板
|
||
- 请求审查
|
||
|
||
6. **更新分支**
|
||
```bash
|
||
git checkout develop
|
||
git pull origin develop
|
||
git checkout feature/your-feature-name
|
||
git rebase develop
|
||
```
|
||
|
||
### 避免常见错误
|
||
|
||
- ❌ 不要直接提交到 main 或 develop
|
||
- ❌ 不要提交大文件(使用 Git LFS)
|
||
- ❌ 不要提交敏感信息(API 密钥、密码等)
|
||
- ❌ 不要忽略测试失败
|
||
- ✅ 保持提交小而专注
|
||
- ✅ 写有意义的提交信息
|
||
- ✅ 及时更新文档
|
||
|
||
---
|
||
|
||
## 下一步
|
||
|
||
- [最佳实践](./best-practices.md) - 学习最佳实践
|
||
- [常见问题](./troubleshooting.md) - 查看常见问题
|
||
- [贡献指南](./contributing.md) - 了解如何贡献
|
||
|
||
---
|
||
|
||
> **文档维护**: 本文档由开发团队维护,如有问题或建议请提交 Issue。
|
||
> **最后更新**: 2026-05-25
|