From 325474bdb7353f5e71a09b107efde4ad0122e6d9 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 26 Oct 2026 22:19:50 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E9=A6=96=E5=B1=8F=20JS=20=E5=87=8F?= =?UTF-8?q?=E5=B0=91=202/3=E3=80=81=E9=9A=8F=E6=9C=BA=E8=AF=84=E5=88=86?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=8F=90=E9=80=9F=EF=BC=8CWebSocket=20?= =?UTF-8?q?=E9=98=B2=E5=88=B7=E5=92=8C=E7=99=BB=E5=BD=95=E9=99=90=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prettier 点“整理”时才加载,编辑器页面按路由懒加载:首屏 JS 2345 KB → 766 KB - 随机评分先随机抽 64 个 rowid 挑选,抽不中再退回全表随机:6 万条提交时 50ms → 1ms - WebSocket 消息最大 64 KB;AI 回复中再发来的消息直接拒绝,不再排队 - 同一用户名 10 分钟失败 10 次、同一 IP 失败 100 次后暂停登录(返回 429,登录框显示提示) - 作品展的 iframe 加 loading="lazy" - 新增 ws.test.ts(正常生成、回复中再发消息、中途断开、握手失败)和随机评分、登录限流的测试 Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 2 +- client/src/components/Login.vue | 33 +++--- client/src/components/editor/Editors.vue | 14 ++- client/src/pages/Showcase.vue | 1 + client/src/router.ts | 3 +- server/src/routes/account.ts | 39 ++++++- server/src/routes/submission.ts | 28 +++-- server/src/ws.ts | 19 +++- server/tests/api.test.ts | 33 +++++- server/tests/ws.test.ts | 134 +++++++++++++++++++++++ 10 files changed, 269 insertions(+), 37 deletions(-) create mode 100644 server/tests/ws.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index b3b7e6a..914b72c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,7 @@ The backend was rewritten from Django in 2026-10 to cut memory use (~25 MB vs. 1 - `src/auth.ts` — Django-compatible password hashing; JWT login, no cookies: `POST /account/login` returns a 15-min HS256 `access_token` (sent as `Authorization: Bearer`, signed with `JWT_SECRET`, random per process if unset) and a 14-day opaque `refresh_token` (only its sha256 is stored, in `auth_refresh_token`; `POST /account/refresh` trades it for a new access token, logout / password change / deactivation delete it). An invalid or expired Bearer token is a 401 even on public endpoints so the client knows to refresh. `requireUser/requireAdmin/requireSuper` (401 / 403) - `src/routes/` — `account`, `tasks` (tutorial / challenge / creation), `files` (task assets + image upload), `submission`, `showcase` (mounted under `/submission/showcase`), `prompt` - `src/services/` — `score.ts` (rating weights super×0.5, admin×0.3, normal×0.2, then Bayesian smoothing with C=3 and zones), `gradebook.ts`, `llm.ts` (OpenAI-compatible streaming via fetch; DeepSeek default, ARK for doubao models), `classifier.ts` (Bloom-level L1–L6 prompt tagging, runs in background), `cascade.ts` (Django's CASCADE deletes, done by hand), `pyround.ts` (`pyRound`/`pySum` reproduce Python's `round()` and `sum()` so scores match the old system bit-for-bit) - - `src/ws.ts` — `/ws/prompt//?token=` (code generation chat) and `/ws/guidance//?token=...` (prompt coaching); messages per connection are processed serially; on disconnect the pending user message is deleted and the LLM request aborted + - `src/ws.ts` — `/ws/prompt//?token=` (code generation chat) and `/ws/guidance//?token=...` (prompt coaching); one message per connection at a time (a message sent while the AI is still replying gets an error, payloads capped at 64 KB); on disconnect the pending user message is deleted and the LLM request aborted - `scripts/` — one-off CLIs: `create-superuser.ts`, `classify-prompts.ts`, `recalculate-scores.ts` - Roles: `super`, `admin`, `normal`. Task model is multi-table: `task_task` + one of `task_tutorial` / `task_challenge` / `task_creation` (`task_ptr_id`) diff --git a/client/src/components/Login.vue b/client/src/components/Login.vue index 79e57ec..aa970c2 100644 --- a/client/src/components/Login.vue +++ b/client/src/components/Login.vue @@ -48,9 +48,9 @@ /> (null) const selectedUsername = ref(null) const studentPassword = ref("") const studentLoading = ref(false) -const showStudentError = ref(false) +const studentError = ref("") // Classes data const classes = ref([]) @@ -135,7 +135,7 @@ const nameOptions = computed(() => const adminName = ref("") const adminPassword = ref("") const adminLoading = ref(false) -const showAdminError = ref(false) +const adminError = ref("") // Load classes on mount (cached — not reloaded on tab switch) onMounted(async () => { @@ -155,13 +155,13 @@ function onTabChange() { selectedClass.value = null selectedUsername.value = null studentPassword.value = "" - showStudentError.value = false + studentError.value = "" namesError.value = false names.value = [] // Reset admin tab state adminName.value = "" adminPassword.value = "" - showAdminError.value = false + adminError.value = "" } async function onClassChange(classname: string) { @@ -178,10 +178,15 @@ async function onClassChange(classname: string) { } } +// 失败次数太多被暂时禁止登录时,显示服务端给的提示 +function loginError(err: any, fallback: string) { + return err.response?.status === 429 ? err.response.data.detail : fallback +} + async function submitStudent() { if (!selectedUsername.value || !studentPassword.value) return studentLoading.value = true - showStudentError.value = false + studentError.value = "" try { const data = await Account.login( selectedUsername.value, @@ -194,8 +199,8 @@ async function submitStudent() { router.replace( window.location.pathname + window.location.search + window.location.hash, ) - } catch { - showStudentError.value = true + } catch (err: any) { + studentError.value = loginError(err, "登录失败,请检查密码") } finally { studentLoading.value = false } @@ -204,7 +209,7 @@ async function submitStudent() { async function submitAdmin() { if (!adminName.value || !adminPassword.value) return adminLoading.value = true - showAdminError.value = false + adminError.value = "" try { const data = await Account.login(adminName.value, adminPassword.value) user.username = data.username @@ -214,8 +219,8 @@ async function submitAdmin() { router.replace( window.location.pathname + window.location.search + window.location.hash, ) - } catch { - showAdminError.value = true + } catch (err: any) { + adminError.value = loginError(err, "登录失败,请检查用户名和密码") } finally { adminLoading.value = false } diff --git a/client/src/components/editor/Editors.vue b/client/src/components/editor/Editors.vue index 25dfeb5..9705412 100644 --- a/client/src/components/editor/Editors.vue +++ b/client/src/components/editor/Editors.vue @@ -79,11 +79,6 @@