library-picturebook-activity/backend/docs/CONTEST_JUDGE_DESIGN.md
2025-12-09 11:10:36 +08:00

6.7 KiB
Raw Blame History

比赛评委存储设计说明

📋 设计决策

问题:是否需要专门的评委表?

结论:需要创建 ContestJudge 关联表,但不需要单独的评委信息表。

🎯 设计分析

1. 为什么需要 ContestJudge 关联表?

1.1 业务需求

  1. 比赛与评委的多对多关系

    • 一个比赛可以有多个评委
    • 一个评委可以评审多个比赛
    • 需要管理这种多对多关系
  2. 评委管理功能

    • 查询某个比赛的所有评委列表
    • 批量添加/删除比赛的评委
    • 管理评委在特定比赛中的特殊信息
  3. 评委特殊属性

    • 评审专业领域specialty如"创意设计"、"技术实现"等
    • 评审权重weight用于加权平均计算最终得分
    • 评委说明description在该比赛中的特殊说明

1.2 当前设计的不足

之前的设计

  • 评委信息只存储在 ContestWorkJudgeAssignment 表中
  • 无法直接查询某个比赛有哪些评委
  • 无法批量管理比赛的评委列表
  • 无法存储评委在特定比赛中的特殊信息

问题场景

// ❌ 无法直接查询比赛的评委列表
// 需要从作品分配表中去重查询,效率低且不准确

// ✅ 有了 ContestJudge 表后
const judges = await prisma.contestJudge.findMany({
  where: { contestId: contestId }
});

2. 为什么不需要单独的评委信息表?

2.1 评委就是用户

  • 评委本身就是系统中的 User
  • 基本信息(姓名、账号等)存储在 User
  • 如果是教师,详细信息存储在 Teacher
  • 如果是外部专家,可以创建普通 User 账号

2.2 统一用户体系

  • 系统采用统一的用户体系
  • 通过角色和权限区分用户身份
  • 评委通过角色(如 judge)和权限(如 review:score)控制

📊 数据模型设计

1. 表结构

model ContestJudge {
  id          Int       @id @default(autoincrement())
  contestId   Int       @map("contest_id") /// 比赛id
  judgeId     Int       @map("judge_id") /// 评委用户id
  specialty   String? /// 评审专业领域(可选)
  weight      Decimal?  @db.Decimal(3, 2) /// 评审权重(可选)
  description String?   @db.Text /// 评委在该比赛中的说明
  creator     Int? /// 创建人ID
  modifier    Int? /// 修改人ID
  createTime  DateTime  @default(now()) @map("create_time")
  modifyTime  DateTime  @updatedAt @map("modify_time")
  validState  Int       @default(1) @map("valid_state")

  contest     Contest   @relation(fields: [contestId], references: [id])
  judge       User      @relation(fields: [judgeId], references: [id])
  
  @@unique([contestId, judgeId])
  @@index([contestId])
  @@index([judgeId])
}

2. 数据关系

Contest (比赛)
  ↓ (1:N)
ContestJudge (比赛评委关联)
  ↓ (N:1)
User (用户/评委)
  ↓ (1:1, 可选)
Teacher (教师信息,如果是教师评委)

3. 与其他表的关系

ContestJudge (比赛评委)
  ↓
ContestWorkJudgeAssignment (作品分配)
  ↓
ContestWorkScore (作品评分)

关系说明

  • ContestJudge:定义哪些评委可以评审某个比赛
  • ContestWorkJudgeAssignment:将具体作品分配给评委
  • ContestWorkScore:记录评委的评分结果

🔄 业务流程

1. 添加评委到比赛

// 1. 创建比赛评委关联
const contestJudge = await prisma.contestJudge.create({
  data: {
    contestId: contestId,
    judgeId: userId,
    specialty: "创意设计",
    weight: 1.2, // 权重1.2倍
    description: "专业设计领域评委"
  }
});

2. 查询比赛的评委列表

// 查询某个比赛的所有评委
const judges = await prisma.contestJudge.findMany({
  where: { 
    contestId: contestId,
    validState: 1 
  },
  include: {
    judge: {
      include: {
        teacher: true // 如果是教师,获取教师信息
      }
    }
  }
});

3. 分配作品给评委

// 分配作品时,验证评委是否属于该比赛
const contestJudge = await prisma.contestJudge.findFirst({
  where: {
    contestId: contestId,
    judgeId: judgeId,
    validState: 1
  }
});

if (!contestJudge) {
  throw new Error('该评委不属于此比赛');
}

// 创建作品分配记录
const assignment = await prisma.contestWorkJudgeAssignment.create({
  data: {
    contestId: contestId,
    workId: workId,
    judgeId: judgeId
  }
});

4. 计算加权平均分

// 获取所有评委的评分和权重
const scores = await prisma.contestWorkScore.findMany({
  where: { workId: workId },
  include: {
    judge: {
      include: {
        contestJudges: {
          where: { contestId: contestId }
        }
      }
    }
  }
});

// 计算加权平均分
let totalWeightedScore = 0;
let totalWeight = 0;

for (const score of scores) {
  const contestJudge = score.judge.contestJudges[0];
  const weight = contestJudge?.weight || 1.0;
  totalWeightedScore += score.totalScore * weight;
  totalWeight += weight;
}

const finalScore = totalWeightedScore / totalWeight;

设计优势

1. 清晰的业务逻辑

  • 比赛评委管理:通过 ContestJudge 表统一管理
  • 作品分配:通过 ContestWorkJudgeAssignment 表管理
  • 评分记录:通过 ContestWorkScore 表记录

2. 灵活的扩展性

  • 支持评委专业领域分类
  • 支持评审权重设置
  • 支持评委说明信息

3. 高效的查询

  • 直接查询比赛的评委列表
  • 支持评委维度的统计分析
  • 支持权重计算

4. 数据一致性

  • 通过外键约束保证数据完整性
  • 删除比赛时,级联删除评委关联
  • 删除用户时,级联删除评委关联

📝 使用建议

1. 评委添加流程

1. 确保用户已创建User 表)
2. 为用户分配评委角色和权限
3. 创建 ContestJudge 记录,关联比赛和用户
4. 可选:设置专业领域、权重等信息

2. 评委验证

在分配作品给评委时,应该验证:

  • 该评委是否属于该比赛(查询 ContestJudge 表)
  • 该评委是否有效(validState = 1

3. 权限控制

  • 只有比赛管理员可以添加/删除评委
  • 评委只能查看和评分分配给自己的作品
  • 通过 RBAC 权限系统控制访问

🔍 总结

评委存储方案

  • 需要 ContestJudge 关联表:管理比赛与评委的多对多关系
  • 不需要 单独的评委信息表:评委信息通过 UserTeacher 表存储

核心设计原则

  1. 评委就是用户,统一用户体系
  2. 通过关联表管理比赛与评委的关系
  3. 支持评委在特定比赛中的特殊属性
  4. 保证数据一致性和查询效率