# 后端接口生成规范

## 概述

本规范用于快速生成 NestJS 后端接口，包含：
- Controller（路由定义）
- Service（业务逻辑）
- DTO（参数校验）
- 前端 API 调用

## 快速生成指令格式

```
请生成后端接口：

模块名称：xxx
接口路径：GET/POST /api/xxx
功能描述：xxx

查询参数：
- 参数1（类型，必填/选填）
- 参数2（类型，必填/选填）

返回字段：
- 字段1（来源表.字段）
- 字段2（关联表.字段）

关联查询：
- 表1 -> 表2（关联字段）

排序：字段名 + 排序方式
分页：是/否
```

## 文件结构

```
backend/src/模块名/
├── 模块名.module.ts      # 模块定义
├── 模块名.controller.ts  # 路由控制器
├── 模块名.service.ts     # 业务逻辑
└── dto/
    ├── query-xxx.dto.ts  # 查询参数 DTO
    └── create-xxx.dto.ts # 创建参数 DTO

frontend/src/api/
└── 模块名.ts             # 前端 API 调用
```

## Controller 模板

```typescript
import { Controller, Get, Query, Param, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { XxxService } from './xxx.service';
import { QueryXxxDto } from './dto/query-xxx.dto';

@Controller('api/xxx')
@UseGuards(JwtAuthGuard)
export class XxxController {
  constructor(private readonly xxxService: XxxService) {}

  @Get(':id/list')
  async getList(
    @Param('id') id: string,
    @Query() queryDto: QueryXxxDto,
  ) {
    return this.xxxService.findAll(+id, queryDto);
  }
}
```

## Service 模板

```typescript
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { QueryXxxDto } from './dto/query-xxx.dto';

@Injectable()
export class XxxService {
  constructor(private prisma: PrismaService) {}

  async findAll(parentId: number, queryDto: QueryXxxDto) {
    const { page = 1, pageSize = 10, ...filters } = queryDto;
    const skip = (page - 1) * pageSize;

    // 构建查询条件
    const where: any = { parentId };

    if (filters.field1) {
      where.field1 = { contains: filters.field1 };
    }

    const [list, total] = await Promise.all([
      this.prisma.table.findMany({
        where,
        skip,
        take: pageSize,
        orderBy: { sortField: 'desc' },
        include: {
          // 关联查询
        },
      }),
      this.prisma.table.count({ where }),
    ]);

    return { list, total, page, pageSize };
  }
}
```

## DTO 模板

```typescript
import { IsOptional, IsString, IsInt, Min } from 'class-validator';
import { Type } from 'class-transformer';

export class QueryXxxDto {
  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  page?: number = 1;

  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  pageSize?: number = 10;

  @IsOptional()
  @IsString()
  field1?: string;

  @IsOptional()
  @IsString()
  field2?: string;
}
```

## 前端 API 模板

```typescript
import request from '@/utils/request'

export interface QueryXxxParams {
  page?: number
  pageSize?: number
  field1?: string
  field2?: string
}

export interface XxxItem {
  id: number
  // ... 其他字段
}

export interface XxxListResponse {
  list: XxxItem[]
  total: number
  page: number
  pageSize: number
}

export const xxxApi = {
  getList(parentId: number, params: QueryXxxParams): Promise<XxxListResponse> {
    return request.get(`/api/xxx/${parentId}/list`, { params })
  },
}
```

## Prisma 关联查询示例

### 常用关联模式

```typescript
// 用户信息 + 租户 + 学生班级年级
user: {
  include: {
    tenant: {
      select: { id: true, name: true },
    },
    student: {
      include: {
        class: {
          include: {
            grade: {
              select: { id: true, name: true },
            },
          },
        },
      },
    },
  },
}

// 指导老师列表
teachers: {
  include: {
    user: {
      select: { id: true, username: true, nickname: true },
    },
  },
}

// 报名信息 + 用户 + 团队
registration: {
  include: {
    user: {
      select: { id: true, username: true, nickname: true },
    },
    team: {
      select: { id: true, teamName: true },
    },
  },
}
```

## 示例：赛果发布详情接口

```
请生成后端接口：

模块名称：results（已存在，需修改）
接口路径：GET /api/contests/:id/results/works
功能描述：获取赛事的作品列表（用于赛果发布）

查询参数：
- page（number，选填，默认1）
- pageSize（number，选填，默认10）
- workNo（string，选填，作品编号模糊搜索）
- accountNo（string，选填，报名账号模糊搜索）

返回字段：
- id, workNo, title, finalScore（作品表）
- registration.user.nickname, username（用户表）
- registration.user.tenant.name（租户表）
- registration.user.student.class.name, grade.name（班级年级）
- registration.teachers[].user.nickname（指导老师）

关联查询：
- ContestWork -> ContestRegistration（registrationId）
- ContestRegistration -> User（userId）
- User -> Tenant（tenantId）
- User -> Student -> Class -> Grade
- ContestRegistration -> ContestRegistrationTeacher -> User

排序：finalScore DESC
分页：是
```

## 注意事项

1. **权限控制**：Controller 需要添加 `@UseGuards(JwtAuthGuard)` 和权限装饰器
2. **参数校验**：DTO 使用 class-validator 进行校验
3. **分页处理**：统一使用 page/pageSize 参数
4. **空值处理**：查询条件为空时不添加到 where 条件
5. **关联数据**：使用 Prisma 的 include 进行关联查询
6. **性能优化**：只 select 需要的字段，避免查询过多数据
7. **错误处理**：Service 层抛出 NestJS 内置异常
