一、超管端设计优化 - 文档管理SOP体系建立,docs目录重组 - 统一用户管理:跨租户全局视角,合并用户管理+公众用户 - 活动监管全模块重构:全部活动(统计卡片+阶段筛选+SuperDetail详情页)、报名数据/作品数据/评审进度(两层合一扁平列表)、成果发布(去Tab+统计+隐藏写操作) - 菜单精简:移除评委管理/评审规则/通知管理 - Bug修复:租户编辑丢失隐藏菜单、pageSize限制、主色统一 二、UGC绘本创作社区P0 - 数据库:10张新表(user_works/user_work_pages/work_tags等) - 子女账号独立化:Child升级为独立User,家长切换+独立登录 - 用户作品库:CRUD+发布审核,8个API - AI创作流程:提交→生成→保存到作品库,4个API - 作品广场:首页改造为推荐流,标签+搜索+排序 - 内容审核(超管端):作品审核+作品管理+标签管理 - 活动联动:WorkSelector作品选择器 - 布局改造:底部5Tab(发现/创作/活动/作品库/我的) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
262 lines
5.9 KiB
Markdown
262 lines
5.9 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 内置异常
|