fix: 修复中危问题:注册接口、模型名、LLM 超时、WebSocket 断线、未公开任务泄露等
服务端 - 删掉没人用的 /account/register,外网不能再批量注册小号 - 模型名只放行豆包和配置的 LLM_MODEL,浏览器里存的旧模型名换成默认模型; 聊天默认模型改读 LLM_MODEL - 流式生成改成 120 秒无数据才超时(另有 15 分钟总上限),思考模型不会被中途掐断; SSE 最后一行没有换行时不再丢内容 - WebSocket:token 无效 401、任务不存在 404,建对话失败时关闭连接; 引导出错时把没有回复的提问移出上下文 - 未公开的教程只有超管能看,未公开的挑战和素材管理员也能看,其他人 404 - Caddy:/media 加 CSP sandbox 和 nosniff,上传的 html/svg 直接打开不能跑脚本; 加 CORS,预览 iframe 里的代码可以读取字体和 fetch 素材 前端 - 新增 ReconnectingSocket:意外断开后退避重连,握手失败先刷新 token; 断线时清掉“生成中”状态,旧连接的事件不再改写新连接 - marked 扩展抽到 utils/markdown.ts:教程用独立实例,不再改全局 marked; 学生内容的渲染保留高亮,不带会渲染原始 HTML 的 preview 扩展 - 挑战页、创作页加载完成前离开时不再建立连接,离开时关闭引导弹窗 - 教程、提交列表、用户管理跟随浏览器前进后退;快速翻页只用最后一次结果, 改筛选条件不再请求两次 - 随机评分弹窗在 AI 回复完之后不再弹出 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
73788c4595
commit
cdeb7c18ef
22 files changed
+489
-210
No files matched your search
+20
-4
@@ -1,5 +1,5 @@
|
||||
import type { Server, ServerWebSocket, WebSocketHandler } from "bun"
|
||||
import { all, run, iso, uuidOut } from "./db"
|
||||
import { all, get, run, iso, uuidOut } from "./db"
|
||||
import { userFromAccessToken, type User } from "./auth"
|
||||
import { canAccessTask, getOrCreateActiveConversation, insertMessage } from "./services/prompt"
|
||||
import {
|
||||
@@ -41,8 +41,12 @@ export async function upgrade(
|
||||
const match = PATH_RE.exec(url.pathname)
|
||||
if (!match) return null
|
||||
const user = await userFromAccessToken(url.searchParams.get("token"))
|
||||
if (!user) return new Response("Unauthorized", { status: 401 })
|
||||
const taskId = Number(match[2])
|
||||
if (!user || !canAccessTask(user, taskId)) return new Response("Forbidden", { status: 403 })
|
||||
if (!get(`SELECT 1 FROM task_task WHERE id = $id`, { id: taskId })) {
|
||||
return new Response("Not Found", { status: 404 })
|
||||
}
|
||||
if (!canAccessTask(user, taskId)) return new Response("Forbidden", { status: 403 })
|
||||
|
||||
const ok = server.upgrade(req, {
|
||||
data: {
|
||||
@@ -155,6 +159,8 @@ async function handleGuidance(ws: WS, data: any) {
|
||||
try {
|
||||
full = await streamToClient(ws, streamGuidance(ws.data.guidanceHistory, ws.data.abort.signal))
|
||||
} catch (e) {
|
||||
// 这条提问没有回复,留在上下文里会出现连续两条用户消息
|
||||
ws.data.guidanceHistory.pop()
|
||||
send(ws, { type: "error", content: `AI 服务出错:${e instanceof Error ? e.message : e}` })
|
||||
return
|
||||
}
|
||||
@@ -182,7 +188,14 @@ export const websocket: WebSocketHandler<WsData> = {
|
||||
|
||||
open(ws) {
|
||||
const d = ws.data
|
||||
d.conversationId = getOrCreateActiveConversation(d.user.id, d.taskId)
|
||||
try {
|
||||
d.conversationId = getOrCreateActiveConversation(d.user.id, d.taskId)
|
||||
} catch (e) {
|
||||
// 比如任务刚被删掉;不关掉的话客户端会一直等 init
|
||||
console.error("websocket open failed:", e)
|
||||
ws.close(1011, "conversation unavailable")
|
||||
return
|
||||
}
|
||||
if (d.kind === "guidance") {
|
||||
send(ws, { type: "init", messages: [] })
|
||||
return
|
||||
@@ -212,7 +225,10 @@ export const websocket: WebSocketHandler<WsData> = {
|
||||
const handler = ws.data.kind === "prompt" ? handlePrompt : handleGuidance
|
||||
ws.data.queue = ws.data.queue
|
||||
.then(() => (ws.data.closed ? undefined : handler(ws, data)))
|
||||
.catch((e) => console.error("websocket handler failed:", e))
|
||||
.catch((e) => {
|
||||
console.error("websocket handler failed:", e)
|
||||
send(ws, { type: "error", content: "服务器出错,请刷新页面重试" })
|
||||
})
|
||||
},
|
||||
|
||||
close(ws) {
|
||||
|
||||
Reference in new issue
Block a user