262 lines
5.6 KiB
Markdown
262 lines
5.6 KiB
Markdown
|
|
# 后端接口生成规范
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
本规范用于快速生成 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 内置异常
|