library-picturebook-activity/backend/docs/CONTEST_JUDGE_DESIGN.md

272 lines
6.7 KiB
Markdown
Raw Permalink Normal View History

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