From 03878f6cc200566eb181a83542a2988460d2bd92 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 26 Oct 2026 22:03:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=92=8C=E5=88=A0=E9=99=A4=E6=8F=90=E4=BA=A4=E7=9A=84=E6=85=A2?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=EF=BC=8C=E7=B4=A0=E6=9D=90=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E3=80=81=E7=99=BB=E5=87=BA=E5=92=8C=E5=87=A0=E5=A4=84=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=8A=A0=E8=BD=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 性能(6 万条提交、18 万条消息的测试库上测的) - 提交列表 join task_task 时用 +s.task_id,不再从任务表出发经 task_id 单列索引回表读 html/css/js 大行,改走 slim 覆盖索引:翻页 260-700ms → 4-18ms - 删除提交时先查出 id 再按等值删关联行:prompt_message.submission_id 几乎全是 NULL, 写成 IN (子查询) 时统计信息会让 SQLite 扫整张表 bug - 提交列表排序加 rowid 作次级键,同分的提交翻页不再重复或遗漏 - 素材文件名只允许字母数字和 - _ .(否则 422),覆盖同名文件改成写临时文件再 rename; 前端删除时对名字做 URL 编码 - 刷新 token 只是网络失败时不再强制登出;退出请求失败时界面也显示为已退出 - 历史面板加载中收到的刷新不再丢掉;成绩册只用最后一次请求的结果,打开时不再加载两次 Co-Authored-By: Claude Opus 5.5 --- client/src/api.ts | 16 +++++-- .../src/components/ai/PromptHistoryPanel.vue | 13 +++++- client/src/components/editor/Toolbar.vue | 10 +++-- client/src/pages/Gradebook.vue | 18 ++++++-- server/src/routes/files.ts | 45 +++++++++---------- server/src/routes/submission.ts | 7 ++- server/src/services/cascade.ts | 14 +++--- server/tests/api.test.ts | 42 +++++++++++++++++ 8 files changed, 124 insertions(+), 41 deletions(-) diff --git a/client/src/api.ts b/client/src/api.ts index 2e9dfa0..a196ad1 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -60,6 +60,8 @@ http.interceptors.response.use( ) { err.config._retried = true if (await refreshAccessToken()) return http(err.config) + // 刷新失败但 refresh token 还在,说明只是网络问题:保留登录,下次请求再试 + if (hasLogin()) return Promise.reject(err) } switch (err.response.status) { case 401: // 未授权 @@ -277,7 +279,7 @@ export const Submission = { username?: string user_id?: number flag?: string | null - zone?: string + zone?: string | null task_id?: number task_type?: string score_min?: number @@ -553,7 +555,11 @@ export const TaskAssets = { ).data }, async deleteChallenge(display: number, name: string) { - return (await http.delete(`/assets/challenge/${display}/${name}`)).data + return ( + await http.delete( + `/assets/challenge/${display}/${encodeURIComponent(name)}`, + ) + ).data }, async listTutorial(display: number): Promise { return (await http.get(`/assets/tutorial/${display}`)).data @@ -573,6 +579,10 @@ export const TaskAssets = { ).data }, async deleteTutorial(display: number, name: string) { - return (await http.delete(`/assets/tutorial/${display}/${name}`)).data + return ( + await http.delete( + `/assets/tutorial/${display}/${encodeURIComponent(name)}`, + ) + ).data }, } diff --git a/client/src/components/ai/PromptHistoryPanel.vue b/client/src/components/ai/PromptHistoryPanel.vue index 304d7a8..fa05257 100644 --- a/client/src/components/ai/PromptHistoryPanel.vue +++ b/client/src/components/ai/PromptHistoryPanel.vue @@ -210,8 +210,15 @@ function selectItem(item: HistoryViewItem) { }) } +// 加载中又要求刷新(比如自动提交了新代码、换了任务),等这次完成后再加载一次 +let reloadAfter = false + async function load(force = true) { - if (!props.taskId || loading.value) return + if (!props.taskId) return + if (loading.value) { + reloadAfter = true + return + } if (!force && loadedTaskId === props.taskId) return loading.value = true try { @@ -230,6 +237,10 @@ async function load(force = true) { loadedTaskId = props.taskId } finally { loading.value = false + if (reloadAfter) { + reloadAfter = false + load(true) + } } } diff --git a/client/src/components/editor/Toolbar.vue b/client/src/components/editor/Toolbar.vue index fa6e01e..8c10636 100644 --- a/client/src/components/editor/Toolbar.vue +++ b/client/src/components/editor/Toolbar.vue @@ -107,9 +107,13 @@ function handleLogin() { } async function handleLogout() { - await Account.logout() - user.username = "" - user.role = Role.Normal + // token 已经先清掉了,退出请求失败(比如断网)界面也要显示为已退出 + try { + await Account.logout() + } finally { + user.username = "" + user.role = Role.Normal + } }