fix(提交统计): 按 user_id 归属提交,不再按提交时冻结的用户名
submission.username 是提交那一刻的快照。教师统计全程拿它当主键用 —— ilike 过滤、 group by、再拿结果去 user 表 join 班级 —— 学生改名之后旧提交还挂着旧名字,一条都 匹配不上。 生产快照实测(2026-09-08):24 级数媒两个班改成编号制用户名之后,查 ks249 旧口径 0 条、新口径 7 条,整个班 48 人全掉进「一条没交」;查 ks248 是 20 条 / 54 条, 13 个人的成绩查不出来。ks248林依晨那 8 条提交原来一条看不到,人在「一条没交」栏, 现在落到「交了没完成」并带出最后一次失败在 1082 题。 四条路径一起改: - /submissions/statistics 的过滤、聚合、花名册全部改按 user_id,用户名从 user 表 join 出来。已删号的学生 user 表里没有行,退回提交里冻结的名字(personCount 那句兜底就是给这种情况的) - /submissions/statistics/items 展开行先把用户名解析成 user_id - /submissions 和 /contests/:id/submissions 的用户名筛选取并集:user_id 匹配到的 账号,加上按冻结用户名匹配的(已删号的 28 个账号只剩这一份名字,顺带「按记得的 旧名字搜」也还查得到)。列表显示的名字改成当前用户名,否则筛 ks248 出来一堆写着 ks24数媒1班ks的行,看着像筛错了 - /rankings/activity 同样按 user_id 分组。这条眼下是预防性的:改过名又有 AC 记录的 4 个人只有 1~2 题,够不到前 10,但真上榜会显示旧名字或被拆成两条 顺带把统计接口的四次全表扫换成索引扫:ilike 走不了索引,换成 user_id in (...) 之后单条 18448 → 537 buffers,整个接口查一个班 120~250ms → 10ms 上下。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqjE6qPo67fqVDKn6Bx7yd
This commit is contained in:
@@ -260,7 +260,14 @@ async function myLeaderboardRank(userId: number | undefined) {
|
||||
accountRoutes.get("/rankings/activity", async (c) => {
|
||||
const start = c.req.query("start")
|
||||
if (!start || Number.isNaN(Date.parse(start))) return failure(c, 400, "invalid-start", "start time is required")
|
||||
const rows = await db.select({ username: schema.submission.username, value: countDistinct(schema.submission.problemId) })
|
||||
/**
|
||||
* 按 **user_id** 聚合,名字从 user 表取。按 `submission.username` 分组的话,
|
||||
* 改过名的学生会裂成新旧两条各算各的 AC 题数 —— 排名被拆低,运气不好还会以
|
||||
* 两个名字同时挂在榜上。同 `/submissions/statistics` 那批。
|
||||
*
|
||||
* innerJoin user 顺带把已删号学生的孤儿提交挡在外面,不用再兜底名字。
|
||||
*/
|
||||
const rows = await db.select({ username: schema.user.username, value: countDistinct(schema.submission.problemId) })
|
||||
.from(schema.submission)
|
||||
.innerJoin(schema.user, eq(schema.submission.userId, schema.user.id))
|
||||
.where(and(
|
||||
@@ -270,7 +277,8 @@ accountRoutes.get("/rankings/activity", async (c) => {
|
||||
eq(schema.user.isDisabled, false),
|
||||
ne(schema.user.adminType, "Super Admin"),
|
||||
))
|
||||
.groupBy(schema.submission.username).orderBy(desc(countDistinct(schema.submission.problemId))).limit(10)
|
||||
.groupBy(schema.submission.userId, schema.user.username)
|
||||
.orderBy(desc(countDistinct(schema.submission.problemId))).limit(10)
|
||||
return success(c, rows.map((row) => activityRankItemSchema.parse({ username: row.username, count: row.value })))
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user