perf(教师统计): 展开行的明细改成按需拉,统计响应不再背着 4.9 万行没人看的数据
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
上一版把明细收成「只取有 AC 的人、每人最近 50 条」,拿生产快照(12.4 万条提交) 实测下来只从 105631 行降到 49108 行 —— 2.1 倍,不是一个数量级。原因是绝大多数人 本来就不到 50 条,每人截断那道闸在真实分布上基本没咬到,最坏情况响应体仍有 ~2.4MB。 真正的问题是形状不对:表格一次只展开一行(updateExpandedRowKeys 只留最后一个 key), 却给 1900 个人各准备了一份。所以明细整个从统计响应里拿掉,改成展开时按需拉: - 新端点 `GET /submissions/statistics/items`,要用户名 + 同一套时间窗和题号。 用户名这里是**精确匹配**,不是统计接口那种 ilike —— 那边填 ks251 要圈出整个班, 这边是「点开的这一行是谁」。上限 200 条,多取一条来判断 truncated,被截断时 展开行里说明「只显示最近 200 条」,免得老师以为这人就交了这么多。 - 时间窗和题号抽成共用的 statisticsScope,两个接口必须同一个范围,否则展开行 看到的是另一个窗口的数据。 - 前端按人缓存,收起再展开不重拉;每次重新统计(含 15 秒自动刷新)清缓存, 并把当前展开着的那一行重拉一遍 —— 展开行跟着一起活着,不然刷新之后上面的数 变了、下面的明细还是老的。 - 去重放在 loadItems 里:点一行会同时走 rowProps 的 onClick 和表格的 update:expanded-row-keys,两边都想拉,改之前真发出了两条一模一样的请求。 顺带把 submissionItems 从 submissionStatisticsUserSchema 里删掉。 生产快照上的验证(12.4 万条提交 / 1956 用户 / 961 题,恢复进一次性容器跑完即删): - 最坏情况(全部时段 + 不填条件)少搬 49108 行,约 2.4MB - 真实课堂量级(最忙的一小时:583 条提交 / 81 人)四条查询分别是 主聚合 45ms、明细 6.5ms、语法未过 2.4ms、最近错因 <1ms - 「已解决」那条口径修正的实际影响:13.3% 的「人×题」有重复 AC,同一题最多 AC 45 次; 按人看最夸张的是 419 条 AC 其实只有 38 道题 - 语法要求的题 15 道、result=10 共 57 条,其中「最后也没改对」的 18 个人×题 —— 角标会出现,但稀有 浏览器实跑:展开前不发明细请求;展开后 1 条、12 个按钮;收起 +0、再展开 +0(走缓存); 自动刷新时统计与明细 1:1 配对、间隔 15 秒,没有重复请求。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqjE6qPo67fqVDKn6Bx7yd
This commit is contained in:
@@ -172,9 +172,18 @@ export const submissionStatisticsUserSchema = z.object({
|
||||
judgingCount: z.number().int(),
|
||||
// 百分比数值,不带 %。旧后端返回 "85.5%" 字符串,展示格式化交给前端。
|
||||
correctRate: z.number(),
|
||||
submissionItems: z.array(
|
||||
z.object({ id: z.string(), result: judgeStatusSchema }),
|
||||
),
|
||||
})
|
||||
|
||||
/**
|
||||
* 展开行的明细,**按需拉**(GET /submissions/statistics/items)。
|
||||
*
|
||||
* 原来是随统计一起给每个人各带一份,可表格一次只展开一行 —— 生产快照上那是
|
||||
* 4.9 万行没人看的数据。`truncated` 为真时前端要说明「只显示最近 N 条」,
|
||||
* 免得老师以为这人就交了这么多。
|
||||
*/
|
||||
export const submissionStatisticsItemsSchema = z.object({
|
||||
items: z.array(z.object({ id: z.string(), result: judgeStatusSchema })),
|
||||
truncated: z.boolean(),
|
||||
})
|
||||
|
||||
export const submissionStatisticsSchema = z.object({
|
||||
@@ -214,6 +223,9 @@ export type SubmissionStatistics = z.infer<typeof submissionStatisticsSchema>
|
||||
export type SubmissionStatisticsUser = z.infer<
|
||||
typeof submissionStatisticsUserSchema
|
||||
>
|
||||
export type SubmissionStatisticsItems = z.infer<
|
||||
typeof submissionStatisticsItemsSchema
|
||||
>
|
||||
export type UnacceptedStudent = z.infer<typeof unacceptedStudentSchema>
|
||||
export type AttemptedStudent = z.infer<typeof attemptedStudentSchema>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user