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

+14 -5
View File
@@ -21,8 +21,8 @@ export interface WsData {
pendingUserMessageId: number | null
abort: AbortController
closed: boolean
// 串行处理消息(与 Django Channels 的行为一致)
queue: Promise<void>
// 正在处理一条消息(等 AI 回复);期间再发来的直接拒绝,不排队,免得一次塞几百条刷模型费用
busy: boolean
// 引导对话只在本次连接内保留上下文
guidanceHistory: ChatMessage[]
}
@@ -57,7 +57,7 @@ export async function upgrade(
pendingUserMessageId: null,
abort: new AbortController(),
closed: false,
queue: Promise.resolve(),
busy: false,
guidanceHistory: [],
},
})
@@ -185,6 +185,8 @@ export const websocket: WebSocketHandler<WsData> = {
// AI 生成可能要一两分钟,期间没有客户端消息
idleTimeout: 960,
sendPings: true,
// 提示词不会这么长;Bun 默认 16MB
maxPayloadLength: 64 * 1024,
open(ws) {
const d = ws.data
@@ -222,13 +224,20 @@ export const websocket: WebSocketHandler<WsData> = {
} catch {
return
}
if (ws.data.busy) {
send(ws, { type: "error", content: "上一条消息还在处理,请等 AI 回复完再发" })
return
}
const handler = ws.data.kind === "prompt" ? handlePrompt : handleGuidance
ws.data.queue = ws.data.queue
.then(() => (ws.data.closed ? undefined : handler(ws, data)))
ws.data.busy = true
handler(ws, data)
.catch((e) => {
console.error("websocket handler failed:", e)
send(ws, { type: "error", content: "服务器出错,请刷新页面重试" })
})
.finally(() => {
ws.data.busy = false
})
},
close(ws) {