---
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()
- 使用解构赋值提高代码可读性
- 复杂逻辑必须添加注释
