perf: 首屏 JS 减少 2/3、随机评分查询提速,WebSocket 防刷和登录限流
Deploy / deploy (build, debian, 22) (push) Canceled after 0s

- 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 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-26 22:19:50 +08:00
1 parent bd84e3f8d1
commit 325474bdb7
10 files changed
+269 -37

No files matched your search

+32 -1
View File
@@ -5,7 +5,7 @@ import { config } from "../src/config"
import { mkdtemp, readFile, readdir, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { client, freshDb, makeSubmission, makeTask, makeUser, uuid } from "./helpers"
import { PASSWORD, client, freshDb, makeSubmission, makeTask, makeUser, uuid } from "./helpers"
beforeEach(freshDb)
@@ -50,6 +50,17 @@ describe("权限", () => {
expect(res.status).toBe(404)
})
test("同一用户名连续登录失败 10 次后暂停登录", async () => {
makeUser("brute")
const anon = await client()
for (let i = 0; i < 10; i++) {
expect((await anon("POST", "/api/account/login", { username: "brute", password: "wrong" })).status).toBe(401)
}
// 密码对了也要等窗口过去
const res = await anon("POST", "/api/account/login", { username: "brute", password: PASSWORD })
expect(res.status).toBe(429)
})
test("封号后 access token 和 refresh token 都失效", async () => {
const id = makeUser("stu")
makeUser("boss", "super")
@@ -176,6 +187,26 @@ describe("评分", () => {
})
})
describe("随机评分", () => {
test("不给自己的、已评过的;没有可评的返回 null", async () => {
const me = makeUser("me")
const other = makeUser("other")
const task = makeTask("T")
makeSubmission(me, task)
const subs = Array.from({ length: 5 }, () => makeSubmission(other, task))
const c = await client("me")
const seen = new Set<string>()
for (let i = 0; i < 5; i++) {
const res = await c("GET", "/api/submission/random-for-rating")
expect(subs.map(uuid)).toContain(res.json.submission_id)
expect(seen.has(res.json.submission_id)).toBe(false)
seen.add(res.json.submission_id)
await c("PUT", `/api/submission/${res.json.submission_id}/score`, { score: 4 })
}
expect((await c("GET", "/api/submission/random-for-rating")).json).toBeNull()
})
})
describe("提交与删除", () => {
test("手写提示词提交会生成对话,删除提交连带删除提示词", async () => {
makeUser("stu")