6.7 KiB
6.7 KiB
比赛评委存储设计说明
📋 设计决策
问题:是否需要专门的评委表?
结论:需要创建 ContestJudge 关联表,但不需要单独的评委信息表。
🎯 设计分析
1. 为什么需要 ContestJudge 关联表?
1.1 业务需求
-
比赛与评委的多对多关系
- 一个比赛可以有多个评委
- 一个评委可以评审多个比赛
- 需要管理这种多对多关系
-
评委管理功能
- 查询某个比赛的所有评委列表
- 批量添加/删除比赛的评委
- 管理评委在特定比赛中的特殊信息
-
评委特殊属性
- 评审专业领域(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关联表:管理比赛与评委的多对多关系 - ❌ 不需要 单独的评委信息表:评委信息通过
User和Teacher表存储
核心设计原则:
- 评委就是用户,统一用户体系
- 通过关联表管理比赛与评委的关系
- 支持评委在特定比赛中的特殊属性
- 保证数据一致性和查询效率