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

272 lines
6.7 KiB
Markdown
Raw Permalink 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.

# 比赛评委存储设计说明
## 📋 设计决策
### 问题:是否需要专门的评委表?
**结论:需要创建 `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. 保证数据一致性和查询效率