library-picturebook-activity/backend/项目分析文档.md
En b805f456a6 feat: 完善后端基础架构和登录功能
- 添加 Lombok 配置支持
- 完善枚举类和常量定义
- 新增工具类(TraceId、限流、OSS 等)
- 添加切面(日志、限流、TraceId)
- 更新数据库索引规范(应用层防重)
- 登录页面样式优化
- 前后端项目文档补充
2026-03-31 13:58:28 +08:00

952 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# NestJS 后端项目分析报告
> 项目路径:`backend/`
> 分析日期2026-03-28
> 项目类型:多租户竞赛/活动管理系统
---
## 一、技术栈概览
| 组件 | 技术选型 | 版本 |
|------|----------|------|
| **框架** | NestJS | 10.3.3 |
| **语言** | TypeScript | 5.3.3 |
| **数据库** | MySQL | 8.0+ |
| **ORM** | Prisma | 6.19.0 |
| **认证** | JWT + Passport | - |
| **加密** | bcrypt | 6.0.0 |
| **文件存储** | 腾讯云 COS | - |
| **部署** | PM2 | - |
| **AI 集成** | 腾讯混元 | - |
### 核心依赖
```json
{
"@nestjs/common": "^10.3.3",
"@nestjs/core": "^10.3.3",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@prisma/client": "^6.19.0",
"passport-jwt": "^4.0.1",
"bcrypt": "^6.0.0",
"class-validator": "^0.14.1",
"class-transformer": "^0.5.1",
"cos-nodejs-sdk-v5": "^2.15.4"
}
```
---
## 二、项目架构
### 目录结构
```
backend/
├── src/
│ ├── ai-3d/ # AI 3D 生成模块
│ ├── auth/ # 认证授权模块
│ ├── common/ # 公共模块(过滤器、拦截器)
│ ├── config/ # 系统配置模块
│ ├── contests/ # 竞赛管理模块(核心)
│ │ ├── contests/ # 活动管理
│ │ ├── attachments/ # 附件管理
│ │ ├── judges/ # 评委管理
│ │ ├── notices/ # 公告管理
│ │ ├── preset-comments/ # 预设评语库
│ │ ├── registrations/ # 报名管理
│ │ ├── results/ # 结果管理
│ │ ├── review-rules/ # 评审规则
│ │ ├── reviews/ # 评审管理
│ │ ├── teams/ # 团队管理
│ │ └── works/ # 作品管理
│ ├── dict/ # 数据字典模块
│ ├── homework/ # 作业模块
│ │ ├── homeworks/ # 作业管理
│ │ ├── review-rules/ # 评审规则
│ │ ├── scores/ # 评分管理
│ │ └── submissions/ # 提交管理
│ ├── judges-management/ # 评委管理模块
│ ├── logs/ # 日志模块
│ ├── menus/ # 菜单管理模块
│ ├── oss/ # 对象存储模块
│ ├── permissions/ # 权限管理模块
│ ├── prisma/ # Prisma 服务
│ ├── public/ # 公共接口模块
│ ├── roles/ # 角色管理模块
│ ├── school/ # 学校管理模块
│ │ ├── schools/ # 学校管理
│ │ ├── grades/ # 年级管理
│ │ ├── classes/ # 班级管理
│ │ ├── departments/ # 部门管理
│ │ ├── teachers/ # 教师管理
│ │ └── students/ # 学生管理
│ ├── tenants/ # 租户管理模块
│ ├── upload/ # 文件上传模块
│ ├── users/ # 用户管理模块
│ ├── app.module.ts # 根模块
│ └── main.ts # 入口文件
├── prisma/
│ ├── schema.prisma # 数据库模型定义
│ └── migrations/ # 数据库迁移文件
├── scripts/ # 初始化脚本
├── test/ # 测试文件
└── package.json
```
---
## 三、核心功能模块详解
### 1. 认证授权模块 (`auth/`)
**文件结构:**
```
auth/
├── auth.controller.ts # 认证控制器
├── auth.service.ts # 认证服务
├── auth.module.ts # 认证模块
├── decorators/ # 装饰器
│ ├── current-tenant-id.decorator.ts # 当前租户 ID 装饰器
│ ├── public.decorator.ts # 公开接口装饰器
│ ├── require-permission.decorator.ts # 权限装饰器
│ └── roles.decorator.ts # 角色装饰器
├── guards/ # 守卫
│ ├── jwt-auth.guard.ts # JWT 认证守卫
│ ├── roles.guard.ts # 角色守卫
│ └── permissions.guard.ts # 权限守卫
└── strategies/ # Passport 策略
├── jwt.strategy.ts # JWT 策略
└── local.strategy.ts # 本地策略
```
**核心功能:**
- 用户名密码登录
- JWT Token 签发与验证
- 权限验证(基于装饰器)
- 角色验证
- 多租户识别
**核心代码示例:**
```typescript
// auth.service.ts - 登录逻辑
async login(user: any, tenantId?: number) {
// 验证租户有效
const tenant = await this.prisma.tenant.findUnique({
where: { id: finalTenantId },
});
// 签发 JWT Token
const payload = {
username: user.username,
sub: user.id,
tenantId: finalTenantId,
};
return {
token: this.jwtService.sign(payload),
user: {
id: user.id,
username: user.username,
roles: user.roles?.map((ur: any) => ur.role.code) || [],
permissions: await this.getUserPermissions(user.id),
},
};
}
```
---
### 2. 用户管理模块 (`users/`)
**核心功能:**
- 用户 CRUD 操作
- 用户状态管理(启用/禁用)
- 关键字搜索(用户名、昵称、邮箱、手机号)
- 超级租户跨租户查询
- 用户统计分析
**接口列表:**
| 接口 | 方法 | 权限 | 说明 |
|------|------|------|------|
| `POST /users` | POST | `user:create` | 创建用户 |
| `GET /users` | GET | `user:view` | 用户列表(分页) |
| `GET /users/stats` | GET | `super_admin` | 用户统计 |
| `GET /users/:id` | GET | `user:view` | 用户详情 |
| `PUT /users/:id` | PUT | `user:update` | 更新用户 |
| `PUT /users/:id/status` | PUT | `user:manage` | 切换状态 |
| `DELETE /users/:id` | DELETE | `user:delete` | 删除用户 |
**特色功能:**
```typescript
// 用户类型统计(仅超管)
async getStats() {
const superTenantIds = ...; // 平台租户 ID
const orgTenantIds = ...; // 机构租户 ID
const judgeTenantIds = ...; // 评委租户 ID
return {
total, // 总用户数
platform, // 平台用户数
org, // 机构用户数
judge, // 评委用户数
public: publicCount, // 公共用户数
};
}
```
---
### 3. 竞赛/活动管理模块 (`contests/`) ⭐核心业务
**模块结构:**
```
contests/
├── contests/ # 活动主体管理
├── registrations/ # 报名管理
├── teams/ # 团队管理
├── works/ # 作品管理
├── reviews/ # 评审管理
├── judges/ # 评委管理
├── results/ # 结果管理
├── review-rules/ # 评审规则
├── preset-comments/ # 预设评语库
├── notices/ # 公告管理
└── attachments/ # 附件管理
```
#### 3.1 活动管理 (`contests/contests/`)
**活动生命周期:**
```
┌─────────────┐
│ 未发布 │
│ unpublished │
└──────┬──────┘
│ 发布
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 已发布 │ ──► │ 报名中 │ ──► │ 作品提交中 │ ──► │ 评审中 │
│ published │ │ registering │ │ submitting │ │ reviewing │
└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘
┌─────────────┐ │
│ 已完结 │ ◄────────────────┘
│ finished │
└─────────────┘
```
**核心服务方法:**
```typescript
// 判断活动当前所处阶段
getContestStage(contest: any): string {
const now = new Date();
const regStart = new Date(contest.registerStartTime);
const regEnd = new Date(contest.registerEndTime);
const subStart = new Date(contest.submitStartTime);
const subEnd = new Date(contest.submitEndTime);
const revStart = new Date(contest.reviewStartTime);
const revEnd = new Date(contest.reviewEndTime);
if (now >= regStart && now <= regEnd) return 'registering';
if (now >= subStart && now <= subEnd) return 'submitting';
if (now >= revStart && now <= revEnd) return 'reviewing';
if (revEnd && now > revEnd) return 'finished';
return 'published';
}
```
#### 3.2 报名管理 (`registrations/`)
**功能:**
- 个人/团队报名
- 报名审核(需要审核的活动)
- 报名状态管理
#### 3.3 作品管理 (`works/`)
**功能:**
- 作品提交(支持多次提交配置)
- 作品版本管理(`isLatest` 标识最新版本)
- 作品状态流转
#### 3.4 评审管理 (`reviews/`)
**功能:**
- 评委分配(手动/批量/自动)
- 作品评分
- 分数统计
**评分 DTO**
```typescript
// create-score.dto.ts
export class CreateScoreDto {
@IsInt()
workId: number;
@IsInt()
judgeId: number;
@IsNumber()
score: number;
@IsString()
@IsOptional()
comment: string; // 评语
}
```
#### 3.5 结果管理 (`results/`)
**功能:**
- 手动设置奖项
- 批量设置奖项
- 自动设置奖项(按分数排名)
```typescript
// 自动设置奖项 DTO
export class AutoSetAwardsDto {
@IsInt()
contestId: number;
@IsInt()
firstPrizeCount: number; // 一等奖数量
@IsInt()
secondPrizeCount: number; // 二等奖数量
@IsInt()
thirdPrizeCount: number; // 三等奖数量
}
```
---
### 4. 学校管理模块 (`school/`)
**模块结构:**
```
school/
├── schools/ # 学校信息
├── grades/ # 年级管理
├── classes/ # 班级管理
├── departments/ # 部门管理
├── teachers/ # 教师管理
└── students/ # 学生管理
```
**数据库关系:**
```
Tenant (1) ── (1) School
Tenant (1) ── (N) Grade
Tenant (1) ── (N) Class
Tenant (1) ── (N) Teacher
Tenant (1) ── (N) Student
```
---
### 5. AI 3D 生成模块 (`ai-3d/`) ⭐特色功能
**文件结构:**
```
ai-3d/
├── ai-3d.controller.ts
├── ai-3d.service.ts
├── ai-3d.module.ts
├── providers/
│ ├── ai-3d-provider.interface.ts # AI 提供商接口
│ ├── hunyuan.provider.ts # 腾讯混元实现
│ └── mock.provider.ts # Mock 实现
└── utils/
├── tencent-cloud-sign.ts # 腾讯云签名
└── zip-handler.ts # ZIP 处理
```
**功能:**
- AI 生成 3D 模型(腾讯混元 API 集成)
- 任务创建与状态查询
- 模型文件压缩与下载
**DTO**
```typescript
// create-task.dto.ts
export class CreateTaskDto {
@IsString()
prompt: string; // 生成提示词
@IsString()
@IsOptional()
style?: string; // 风格(写实/卡通/低多边形)
@IsString()
@IsOptional()
negativePrompt?: string; // 负面提示词
}
```
---
### 6. 作业模块 (`homework/`)
**功能:**
- 作业发布与提交
- 作业评审
- 评分管理
**与竞赛模块的区别:**
- 作业更轻量化,适合日常教学场景
- 评审流程简化
- 支持班级维度布置
---
### 7. 系统管理模块
#### 7.1 角色权限 (`roles/`, `permissions/`)
**RBAC 模型:**
```
User ──(N)── UserRole ──(1)── Role
Role ──(N)── RolePermission ──(1)── Permission
```
**权限装饰器:**
```typescript
@RequirePermission('user:create')
async createUser(...) {
// 只有拥有 user:create 权限的用户才能访问
}
```
#### 7.2 菜单管理 (`menus/`)
**功能:**
- 动态菜单配置
- 菜单权限绑定
- 租户菜单定制(`TenantMenu`
#### 7.3 数据字典 (`dict/`)
**功能:**
- 常用数据字典配置
- 支持租户自定义字典
#### 7.4 系统配置 (`config/`)
**功能:**
- 系统参数配置
- 租户级配置隔离
- 配置验证接口
---
### 8. 文件存储模块 (`upload/`, `oss/`)
**腾讯云 COS 集成:**
```typescript
// oss.service.ts
async uploadFile(file: Express.Multer.File, dir: string) {
const client = new COS({
SecretId: this.secretId,
SecretKey: this.secretKey,
});
const key = `${dir}/${Date.now()}-${file.originalname}`;
await client.putObject({
Bucket: this.bucket,
Region: this.region,
Key: key,
Body: file.buffer,
});
return `https://${this.bucket}.cos.${this.region}.myqcloud.com/${key}`;
}
```
---
## 四、数据库设计
### 核心数据表
#### 1. 租户表 (`Tenant`)
```prisma
model Tenant {
id Int @id @default(autoincrement())
name String // 租户名称
code String @unique // 租户编码
domain String? @unique // 租户域名
description String?
isSuper Int @default(0) // 是否超级租户
tenantType String @default("other")
validState Int @default(1)
createTime DateTime @default(now())
modifyTime DateTime @updatedAt
users User[]
roles Role[]
contests Contest[]
school School?
// ...
}
```
#### 2. 用户表 (`User`)
```prisma
model User {
id Int @id @default(autoincrement())
tenantId Int
username String
password String
nickname String?
email String?
phone String?
avatar String?
status String @default("enabled")
validState Int @default(1)
createTime DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id])
roles UserRole[]
children User[] // 家长 - 孩子关联
parent User? @relation("ParentChild", fields: [parentId], references: [id])
contestRegistrations ContestRegistration[]
contestWorks ContestWork[]
// ...
}
```
#### 3. 活动表 (`Contest`)
```prisma
model Contest {
id Int @id @default(autoincrement())
contestName String
contestType String // 竞赛类型
contestState String @default("unpublished")
status String @default("ongoing")
visibility String @default("designated")
contestTenants String? // JSON: 可见租户 ID 列表
registerStartTime DateTime
registerEndTime DateTime
submitStartTime DateTime
submitEndTime DateTime
reviewStartTime DateTime
reviewEndTime DateTime
resultPublishTime DateTime?
teamMinMembers Int @default(1)
teamMaxMembers Int @default(1)
requireAudit Boolean @default(true)
attachments ContestAttachment[]
registrations ContestRegistration[]
teams ContestTeam[]
works ContestWork[]
judges ContestJudge[]
reviewRule ContestReviewRule?
notices ContestNotice[]
// ...
}
```
#### 4. 作品表 (`ContestWork`)
```prisma
model ContestWork {
id Int @id @default(autoincrement())
contestId Int
userId Int
teamId Int?
title String
description String?
coverUrl String?
workUrl String?
workType String // 作品类型
version Int @default(1)
isLatest Boolean @default(true)
status String @default("submitted")
contest Contest @relation(fields: [contestId], references: [id])
author User @relation(fields: [userId], references: [id])
team ContestTeam?
scores ContestWorkScore[]
attachments ContestWorkAttachment[]
// ...
}
```
#### 5. 评分表 (`ContestWorkScore`)
```prisma
model ContestWorkScore {
id Int @id @default(autoincrement())
workId Int
judgeId Int
contestId Int
score Decimal
comment String?
work ContestWork @relation(fields: [workId], references: [id])
judge User @relation(fields: [judgeId], references: [id])
contest Contest @relation(fields: [contestId], references: [id])
@@unique([workId, judgeId]) // 同一评委对同一作品只能评分一次
}
```
### 完整 ER 图(核心部分)
```
┌─────────────┐
│ Tenant │
│ (租户表) │
└──────┬──────┘
├───► ┌─────────────┐ ┌─────────────┐
│ │ User │──────►│ Role │
│ │ (用户表) │ │ (角色表) │
│ └──────┬──────┘ └─────────────┘
│ │
│ ├───► ┌─────────────┐ ┌─────────────┐
│ │ │ Contest │──────►│ContestWork │
│ │ │ (活动表) │ │ (作品表) │
│ │ └──────┬──────┘ └──────┬──────┘
│ │ │ │
│ │ ├───► ┌─────────────┐ │
│ │ │ │ContestTeam │◄┘
│ │ │ │ (团队表) │
│ │ │ └─────────────┘
│ │ │
│ │ ├───► ┌─────────────┐
│ │ │ │ContestJudge │
│ │ │ │ (评委表) │
│ │ │ └─────────────┘
│ │ │
│ │ └───► ┌─────────────┐
│ │ │ContestWork │
│ │ │ Score │
│ │ │ (评分表) │
│ │ └─────────────┘
│ │
│ └───► ┌─────────────┐
│ │ School │
│ │ (学校表) │
│ └──────┬──────┘
│ │
│ ├───► ┌─────────────┐
│ │ │ Teacher │
│ │ │ (教师表) │
│ │ └─────────────┘
│ │
│ └───► ┌─────────────┐
│ │ Student │
│ │ (学生表) │
│ └─────────────┘
└───► ┌─────────────┐
│ Permission │
│ (权限表) │
└─────────────┘
```
---
## 五、核心业务流程
### 1. 活动创建流程
```
1. 管理员创建活动
2. 配置报名参数(时间、人数限制、需审核等)
3. 配置评审规则(评分维度、权重)
4. 设置可见范围(公开/指定租户)
5. 发布活动
```
### 2. 用户参赛流程
```
1. 用户查看活动列表
2. 个人报名 / 创建团队
3. 等待审核(如需)
4. 提交作品(可多次提交,保留最新版本)
5. 等待评审
6. 查看结果
```
### 3. 作品评审流程
```
1. 系统/管理员分配评委到作品
2. 评委登录系统查看分配的作品
3. 评委评分(填写分数 + 评语)
4. 系统统计总分/平均分
5. 自动生成获奖名单
```
### 4. AI 3D 生成流程
```
1. 用户提交生成请求(提示词)
2. 系统调用腾讯混元 API
3. 轮询任务状态
4. 下载生成的 3D 模型
5. 可选:压缩为 ZIP 格式
```
---
## 六、多租户架构
### 租户类型
| 类型 | 说明 | 权限 |
|------|------|------|
| **超级租户** | 平台管理员 | 访问所有数据 |
| **机构租户** | 学校/图书馆等 | 访问本机构数据 |
| **评委租户** | 评委专用 | 访问评审相关数据 |
| **公共租户** | 公共资源 | 访问公开数据 |
### 数据隔离实现
```typescript
// 所有业务查询必须包含租户 ID
const contests = await prisma.contest.findMany({
where: {
tenantId: currentTenantId,
validState: 1,
},
});
// 超级租户跨租户查询
const allContests = await prisma.contest.findMany({
where: isSuperTenant
? { validState: 1 } // 超管查询全部
: { tenantId: currentTenantId, validState: 1 }, // 普通租户仅查询本租户
});
```
---
## 七、中间件与拦截器
### 1. JWT 认证守卫 (`JwtAuthGuard`)
```typescript
@Injectable()
export class JwtAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) throw new UnauthorizedException();
try {
const payload = this.jwtService.verify(token);
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
}
```
### 2. 权限守卫 (`PermissionsGuard`)
```typescript
@Injectable()
export class PermissionsGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const requiredPermission = this.reflector.get<string[]>(
REQUIRE_PERMISSION_KEY,
context.getHandler()
);
if (!requiredPermission) return true;
const { user } = context.switchToHttp().getRequest();
const permissions = await this.authService.getUserPermissions(user.sub);
return permissions.some(p => requiredPermission.includes(p));
}
}
```
### 3. 响应转换拦截器 (`TransformInterceptor`)
```typescript
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>> {
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(
map(data => ({
code: 200,
message: '操作成功',
data: data.data || data,
total: data.total,
})),
);
}
}
```
---
## 八、初始化脚本
项目提供了丰富的初始化脚本:
```bash
# 初始化管理员账户
pnpm init:admin
# 初始化菜单
pnpm init:menus
# 初始化超级租户
pnpm init:super-tenant
# 初始化租户管理员
pnpm init:tenant-admin
# 初始化角色权限
pnpm init:roles:all
# 数据库迁移
pnpm prisma:migrate
# Prisma Studio数据库可视化
pnpm prisma:studio
```
---
## 九、API 接口概览
### 认证相关
| 接口 | 方法 | 说明 |
|------|------|------|
| `POST /auth/login` | POST | 用户登录 |
| `POST /auth/logout` | POST | 用户登出 |
| `GET /auth/info` | GET | 获取当前用户信息 |
### 用户管理
| 接口 | 方法 | 说明 |
|------|------|------|
| `GET /users` | GET | 用户列表 |
| `POST /users` | POST | 创建用户 |
| `GET /users/:id` | GET | 用户详情 |
| `PUT /users/:id` | PUT | 更新用户 |
| `PUT /users/:id/status` | PUT | 切换状态 |
| `DELETE /users/:id` | DELETE | 删除用户 |
### 活动管理
| 接口 | 方法 | 说明 |
|------|------|------|
| `GET /contests` | GET | 活动列表 |
| `POST /contests` | POST | 创建活动 |
| `GET /contests/:id` | GET | 活动详情 |
| `PUT /contests/:id` | PUT | 更新活动 |
| `POST /contests/:id/publish` | POST | 发布/取消发布 |
| `DELETE /contests/:id` | DELETE | 删除活动 |
### 报名管理
| 接口 | 方法 | 说明 |
|------|------|------|
| `GET /contests/:contestId/registrations` | GET | 报名列表 |
| `POST /contests/:contestId/registrations` | POST | 创建报名 |
| `PUT /registrations/:id/review` | PUT | 审核报名 |
### 作品管理
| 接口 | 方法 | 说明 |
|------|------|------|
| `GET /contests/:contestId/works` | GET | 作品列表 |
| `POST /contests/:contestId/works` | POST | 提交作品 |
| `PUT /works/:id` | PUT | 更新作品 |
### 评审管理
| 接口 | 方法 | 说明 |
|------|------|------|
| `POST /contests/:contestId/reviews/assign` | POST | 分配评审 |
| `POST /reviews/scores` | POST | 提交评分 |
| `GET /contests/:contestId/reviews/stats` | GET | 评审统计 |
### 结果管理
| 接口 | 方法 | 说明 |
|------|------|------|
| `GET /contests/:contestId/results` | GET | 结果列表 |
| `POST /contests/:contestId/results/set-awards` | POST | 设置奖项 |
| `POST /contests/:contestId/results/auto-set-awards` | POST | 自动设置奖项 |
### AI 3D 生成
| 接口 | 方法 | 说明 |
|------|------|------|
| `POST /ai-3d/tasks` | POST | 创建生成任务 |
| `GET /ai-3d/tasks/:id` | GET | 查询任务状态 |
---
## 十、总结
### 项目特点
1. **完整的多租户 SaaS 架构** - 支持平台、机构、评委等多角色
2. **丰富的竞赛管理功能** - 从活动创建到评审颁奖全流程覆盖
3. **灵活的 RBAC 权限系统** - 支持细粒度权限控制
4. **AI 能力集成** - 腾讯混元 3D 模型生成
5. **完善的初始化脚本** - 快速部署和配置
### 适用场景
- 📚 图书馆绘本创作比赛
- 🏫 学校各类竞赛活动
- 🎨 艺术创作比赛
- 📖 作文/阅读比赛
- 🤖 科技创新大赛
### 技术亮点
- NestJS 模块化架构
- Prisma ORM 类型安全
- JWT 无状态认证
- 事务处理保证数据一致性
- 软删除保留数据追溯
---
> 文档生成时间2026-03-28
> 分析人AI Assistant