fix(题单): 进度记账挪到判题这一路,不再靠前端回调
前端记账是这条链上最松的一环:SubmitCode.vue 看到 AC 就回调
PUT /problem-set-progress,而且只认路由参数里那一个题单。于是
· 从普通题库入口做出同一道题 → 不计进度
· 网络一抖、页面提前关掉 → 进度静默丢失
· 一道题同时在两个已加入的题单里 → 只有进去的那个记上
旧栈为此专门有个管理命令 fix_problemset_progress 定期按实际提交补账 ——
2026-05-22 00:50 那次一分钟内跨 7 个题单的批量补进度就是它跑的。
改成判题落库之后由 judge/run.ts 记账(recordSolvedProblem),记进该用户**所有**已加入
且包含这道题的题单。位置在最后那条 publishSubmissionUpdate("finished") 之前,所以前端
收到「判完了」时进度已经落库,跳回题单页看到的就是新数据。前端那次回调删掉。
不按 visible / status 过滤:进度是学生自己的记录,老师把题单藏起来不该让它停止累积;
更要紧的是这条口径必须和补账工具一致,否则补账工具会永远「发现」差异。
实跑(本地起 api + worker 真判一次):提交时**完全没带 problemSetId**,判完之后
progress 变成 1/1 100% 已完成、10 分、complete_time 落下、all_problems 奖章发出、
problemset_submission 也记上了。
## 补账那半
backfill-problemsets 前面加一道「按实际 AC 补进度」,移植自旧栈的
fix_problemset_progress,口径和 recordSolvedProblem 逐条对齐(非比赛提交、
ACCEPTED 或 AST_CHECK_FAILED、取最早那次)。补录的格子先并进 detail 再重算,
所以预演里的奖章名单是照着「补完账又重算过」的进度算的,和 --apply 的结果一致。
在生产快照上实跑:
合计:补录 10 道题,进度 497 条要重算(完成 +23 / -0),奖章补发 56 条、收回 0 条
已订正 11 个题单 → 复核通过
user_badge 1180 → 1236,已完成 621 → 644,problemset_submission 7735 → 7745
「未完成但有完成时间」仍是 4 条;重复跑幂等
补录的 10 道分布在题单 4/5/6/15/16(1/1/1/4/3),和离线独立算的数字逐个对上 ——
其中题单 15 那位 AC 了全部 12 题却一直显示未完成。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QqqZwxtXLo2GTqMi51C94D
This commit is contained in:
1 parent
74b97c610f
commit
9d9e104df6
4 files changed
+281
-55
No files matched your search
@@ -6,7 +6,8 @@ import { and, eq, inArray } from "drizzle-orm"
|
||||
import { config } from "../config"
|
||||
import { db, schema } from "../db"
|
||||
import { publishAchievementNotification } from "../events"
|
||||
import { updateAchievementsForSubmission } from "../services/achievements"
|
||||
import { updateAchievementsForProblemSet, updateAchievementsForSubmission } from "../services/achievements"
|
||||
import { recordSolvedProblem } from "../services/problemset"
|
||||
import { checkAst, type AstRule } from "./ast"
|
||||
import { publishSubmissionUpdate } from "./events"
|
||||
import type { JudgeJobData } from "./job"
|
||||
@@ -446,6 +447,45 @@ export async function judgeSubmission(job: JudgeJobData) {
|
||||
)
|
||||
if (!saved) return
|
||||
|
||||
// 题单记账挪到判题这一路。以前靠前端 AC 之后回调 PUT /problem-set-progress,
|
||||
// 只认路由参数里那一个题单:从普通题库入口做出同一道题不计进度,网络一抖就静默丢失。
|
||||
// 放在最后那条 publishSubmissionUpdate("finished") 之前 —— 前端收到「判完了」时
|
||||
// 进度已经落库,跳回题单页看到的就是新数据。
|
||||
// 比赛题不进题单(题单加题时卡了 contestId IS NULL),跳过。
|
||||
if (row.submission.contestId === null && isAccepted(result)) {
|
||||
try {
|
||||
const { updated, earned } = await recordSolvedProblem(
|
||||
row.submission.userId,
|
||||
row.problem.id,
|
||||
row.submission.id,
|
||||
row.submission.createTime,
|
||||
)
|
||||
if (earned.length > 0) {
|
||||
await publishAchievementNotification(row.submission.userId, earned.map((badge) => ({
|
||||
id: badge.id,
|
||||
name: badge.name,
|
||||
description: badge.description,
|
||||
icon: badge.icon,
|
||||
rarity: "bronze",
|
||||
kind: "badge",
|
||||
})))
|
||||
}
|
||||
if (updated > 0) {
|
||||
const unlocked = await updateAchievementsForProblemSet(row.submission.userId)
|
||||
await publishAchievementNotification(row.submission.userId, unlocked.map((achievement) => ({
|
||||
id: achievement.id,
|
||||
name: achievement.name,
|
||||
description: achievement.description,
|
||||
icon: achievement.icon,
|
||||
rarity: achievement.rarity,
|
||||
kind: "achievement",
|
||||
})))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to record problem set progress for ${row.submission.id}`, error)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const unlocked = await updateAchievementsForSubmission(row.submission.id)
|
||||
await publishAchievementNotification(row.submission.userId, unlocked.map((achievement) => ({
|
||||
|
||||
Reference in new issue
Block a user