- 根目录加入 docker-compose、Caddyfile、文档等原本不在仓库里的文件 - 前后端两份 GitHub Actions 合并成根目录的一个 deploy workflow: 构建前端上传到 build/,再在服务器上 git pull 并重建容器 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.8 KiB
2.8 KiB
挑战分数系统设计
日期: 2026-03-09 状态: 已审批
背景
Submission.score 已有加权平均评分逻辑,Profile.total_score 字段存在但从未激活。分数计算出来后无任何展示或用途,需要设计让分数真正发挥作用的功能。
目标
- 学生:看到自己各题得分和在全班的排名
- 老师:快速了解哪些学生表现好,可按条件导出成绩
- 为未来解锁机制预留接口
方案选择
采用方案 A(积分展示中心),在 Challenge 模型预留 pass_score 字段供日后解锁功能使用。
数据层
Profile.recalculate_total_score()
替换现有 update_total_score() 的累加逻辑,改为从数据库重算:
def recalculate_total_score(self):
from submission.models import Submission
from django.db.models import Max, Sum
# 每道挑战取最高分,再求和
total = (
Submission.objects
.filter(user=self.user, task__task_type='challenge')
.values('task_id')
.annotate(best=Max('score'))
.aggregate(total=Sum('best'))['total']
) or 0.0
self.total_score = total
self.save(update_fields=['total_score'])
Rating post_save 信号
在现有 update_submission_score_on_save 之后,追加调用:
instance.submission.user.profile.recalculate_total_score()
Challenge.pass_score 字段
pass_score = models.FloatField(null=True, blank=True, verbose_name="通过分数线")
默认 null,不影响现有功能,为解锁机制预留。
用户最佳分数定义
同一用户同一挑战取 score 最高的提交作为该题成绩。
API 层
1. 排行榜
GET /api/account/leaderboard/
- 无需登录
- 返回所有用户按
total_score降序,含排名、用户名、总分
2. 我的各题得分
GET /api/submission/my-scores/
- 需登录
- 返回当前用户每道挑战的最高分提交(challenge display、title、score、提交时间)
3. 成绩导出
GET /api/submission/export/?task_id=&username=
- 仅限 admin/super 角色
- 复用现有
SubmissionFilter参数 - 返回 CSV 文件流,每行:用户名、挑战题号、挑战标题、最高分、提交时间
前端
| 改动点 | 说明 |
|---|---|
| 挑战列表卡片 | 显示该用户最高得分(灰色小标,未评分不显示) |
新页面 /leaderboard |
排名/用户名/总分表格,所有人可访问,放主页导航 |
新页面 /my-scores |
我的各题得分列表,登录后可见,放主页用户区域 |
| Submissions 页面 | 现有筛选栏旁加"导出 CSV"按钮,触发浏览器下载 |
不在本期范围内
- 解锁机制(
pass_score字段已预留,后续单独设计) - 班级/分组维度的统计
- 实时分数推送(WebSocket)