# 比赛评委存储设计说明 ## 📋 设计决策 ### 问题:是否需要专门的评委表? **结论:需要创建 `ContestJudge` 关联表,但不需要单独的评委信息表。** ## 🎯 设计分析 ### 1. 为什么需要 `ContestJudge` 关联表? #### 1.1 业务需求 1. **比赛与评委的多对多关系** - 一个比赛可以有多个评委 - 一个评委可以评审多个比赛 - 需要管理这种多对多关系 2. **评委管理功能** - 查询某个比赛的所有评委列表 - 批量添加/删除比赛的评委 - 管理评委在特定比赛中的特殊信息 3. **评委特殊属性** - 评审专业领域(specialty):如"创意设计"、"技术实现"等 - 评审权重(weight):用于加权平均计算最终得分 - 评委说明(description):在该比赛中的特殊说明 #### 1.2 当前设计的不足 **之前的设计**: - 评委信息只存储在 `ContestWorkJudgeAssignment` 表中 - 无法直接查询某个比赛有哪些评委 - 无法批量管理比赛的评委列表 - 无法存储评委在特定比赛中的特殊信息 **问题场景**: ```typescript // ❌ 无法直接查询比赛的评委列表 // 需要从作品分配表中去重查询,效率低且不准确 // ✅ 有了 ContestJudge 表后 const judges = await prisma.contestJudge.findMany({ where: { contestId: contestId } }); ``` ### 2. 为什么不需要单独的评委信息表? #### 2.1 评委就是用户 - 评委本身就是系统中的 `User` - 基本信息(姓名、账号等)存储在 `User` 表 - 如果是教师,详细信息存储在 `Teacher` 表 - 如果是外部专家,可以创建普通 `User` 账号 #### 2.2 统一用户体系 - 系统采用统一的用户体系 - 通过角色和权限区分用户身份 - 评委通过角色(如 `judge`)和权限(如 `review:score`)控制 ## 📊 数据模型设计 ### 1. 表结构 ```prisma 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. 添加评委到比赛 ```typescript // 1. 创建比赛评委关联 const contestJudge = await prisma.contestJudge.create({ data: { contestId: contestId, judgeId: userId, specialty: "创意设计", weight: 1.2, // 权重1.2倍 description: "专业设计领域评委" } }); ``` ### 2. 查询比赛的评委列表 ```typescript // 查询某个比赛的所有评委 const judges = await prisma.contestJudge.findMany({ where: { contestId: contestId, validState: 1 }, include: { judge: { include: { teacher: true // 如果是教师,获取教师信息 } } } }); ``` ### 3. 分配作品给评委 ```typescript // 分配作品时,验证评委是否属于该比赛 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. 计算加权平均分 ```typescript // 获取所有评委的评分和权重 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` 关联表:管理比赛与评委的多对多关系 - ❌ **不需要** 单独的评委信息表:评委信息通过 `User` 和 `Teacher` 表存储 **核心设计原则**: 1. 评委就是用户,统一用户体系 2. 通过关联表管理比赛与评委的关系 3. 支持评委在特定比赛中的特殊属性 4. 保证数据一致性和查询效率