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

27 KiB
Raw Blame History

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 集成 腾讯混元 -

核心依赖

{
  "@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 签发与验证
  • 权限验证(基于装饰器)
  • 角色验证
  • 多租户识别

核心代码示例:

// 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 删除用户

特色功能:

// 用户类型统计(仅超管)
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   │
                                  └─────────────┘

核心服务方法:

// 判断活动当前所处阶段
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

// create-score.dto.ts
export class CreateScoreDto {
  @IsInt()
  workId: number;

  @IsInt()
  judgeId: number;

  @IsNumber()
  score: number;

  @IsString()
  @IsOptional()
  comment: string;  // 评语
}

3.5 结果管理 (results/)

功能:

  • 手动设置奖项
  • 批量设置奖项
  • 自动设置奖项(按分数排名)
// 自动设置奖项 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

// 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

权限装饰器:

@RequirePermission('user:create')
async createUser(...) {
  // 只有拥有 user:create 权限的用户才能访问
}

7.2 菜单管理 (menus/)

功能:

  • 动态菜单配置
  • 菜单权限绑定
  • 租户菜单定制(TenantMenu

7.3 数据字典 (dict/)

功能:

  • 常用数据字典配置
  • 支持租户自定义字典

7.4 系统配置 (config/)

功能:

  • 系统参数配置
  • 租户级配置隔离
  • 配置验证接口

8. 文件存储模块 (upload/, oss/)

腾讯云 COS 集成:

// 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)

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)

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)

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)

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)

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 格式

六、多租户架构

租户类型

类型 说明 权限
超级租户 平台管理员 访问所有数据
机构租户 学校/图书馆等 访问本机构数据
评委租户 评委专用 访问评审相关数据
公共租户 公共资源 访问公开数据

数据隔离实现

// 所有业务查询必须包含租户 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)

@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)

@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)

@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,
      })),
    );
  }
}

八、初始化脚本

项目提供了丰富的初始化脚本:

# 初始化管理员账户
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