library-picturebook-activity/.cursor/rules/backend-architecture.mdc

222 lines
4.3 KiB
Plaintext
Raw Normal View History

2025-12-09 11:10:36 +08:00
---
description: NestJS 后端架构规范和模块结构
globs:
- "backend/**/*.ts"
alwaysApply: false
---
# 后端架构规范
## 模块结构
每个功能模块应包含:
- `module.ts` - 模块定义
- `controller.ts` - 控制器
- `service.ts` - 服务层
- `dto/` - 数据传输对象目录
### 命名规范
- 模块命名使用复数形式:`users`, `roles`, `contests`
- 子模块放在父模块目录下:`contests/works/`, `contests/teams/`
### 目录结构示例
```
src/
├── contests/
│ ├── contests.module.ts
│ ├── contests/
│ │ ├── contests.module.ts
│ │ ├── contests.controller.ts
│ │ ├── contests.service.ts
│ │ └── dto/
│ ├── works/
│ │ ├── works.module.ts
│ │ ├── works.controller.ts
│ │ ├── works.service.ts
│ │ └── dto/
│ └── teams/
│ └── ...
```
## 服务层 (Service)
### 基本规范
- 使用 `@Injectable()` 装饰器
- 构造函数注入依赖,使用 `private readonly`
- 所有数据库操作通过 PrismaService
- **禁止直接使用 SQL**
### 标准方法命名
- `create` - 创建
- `findAll` - 查询列表(支持分页)
- `findOne` - 查询单个
- `update` - 更新
- `remove` - 删除(软删除或级联)
### 示例
```typescript
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
async create(createDto: CreateUserDto, tenantId: number) {
return this.prisma.user.create({
data: {
...createDto,
tenantId,
},
});
}
async findAll(tenantId: number, skip?: number, take?: number) {
return this.prisma.user.findMany({
where: { tenantId, validState: 1 },
skip,
take,
include: {
roles: {
include: { role: true },
},
},
});
}
}
```
## 控制器层 (Controller)
### 基本规范
- 使用 `@Controller()` 装饰器,路径使用复数形式
- 所有路由默认需要认证(除非使用 `@Public()` 装饰器)
- 使用 REST 风格的 HTTP 方法装饰器
### 装饰器使用
```typescript
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@RequirePermission("user:create")
async create(
@Body() createDto: CreateUserDto,
@CurrentTenantId() tenantId: number,
@CurrentUser() user: any
) {
return this.usersService.create(createDto, tenantId);
}
@Get()
@RequirePermission("user:read")
async findAll(
@CurrentTenantId() tenantId: number,
@Query("skip") skip?: number,
@Query("take") take?: number
) {
return this.usersService.findAll(tenantId, skip, take);
}
@Public()
@Get("public-info")
async getPublicInfo() {
return { version: "1.0.0" };
}
}
```
### 常用装饰器
- `@CurrentTenantId()` - 获取当前租户ID
- `@CurrentUser()` - 获取当前用户信息
- `@RequirePermission('module:action')` - 权限检查
- `@Public()` - 公开接口,无需认证
## DTO 规范
### 命名规范
- 创建:`CreateXxxDto`
- 更新:`UpdateXxxDto`
- 查询:`QueryXxxDto`
### 验证规则
使用 `class-validator` 装饰器:
```typescript
import {
IsString,
IsEmail,
IsOptional,
IsArray,
IsNumber,
} from "class-validator";
export class CreateUserDto {
@IsString()
username: string;
@IsString()
password: string;
@IsString()
nickname: string;
@IsEmail()
@IsOptional()
email?: string;
@IsArray()
@IsNumber({}, { each: true })
@IsOptional()
roleIds?: number[];
}
```
## 错误处理
使用 NestJS 内置异常,消息使用中文:
```typescript
import {
NotFoundException,
BadRequestException,
UnauthorizedException,
ForbiddenException,
} from "@nestjs/common";
// 示例
if (!user) {
throw new NotFoundException("用户不存在");
}
if (!isValid) {
throw new BadRequestException("数据验证失败");
}
```
## 权限控制
权限字符串格式:`模块:操作`
```typescript
@RequirePermission('contest:create') // 创建竞赛
@RequirePermission('user:update') // 更新用户
@RequirePermission('role:delete') // 删除角色
```
## 代码风格
- 导入顺序NestJS 核心 → 第三方库 → 本地模块
- 使用 async/await避免 Promise.then()
- 使用解构赋值提高代码可读性
- 复杂逻辑必须添加注释