refactor(WebSocket): 判题/流程图推送改用驼峰,去掉没人读的三个字段
submission_id / time_cost / memory_cost / err_info 是 OJ2 两端自己定的线上格式,
没有第三方消费(/ws/submissions 只有 apps/web 一个客户端),没理由留着 snake。
flowchart 那条更别扭:同一个对象里 submission_id 是 snake、criteriaDetails 是驼峰。
三个字段直接删掉而不是改名 —— 它们是从 statistic_info 原样抄出来的一份,前端
一处都没读过(useSubmissionMonitor 只用 submissionId / result / status)。耗时和
错误信息在提交详情里本来就有,判完了去拉一次就是,不必让推送顺带背一份 JSONB
的形状。score 保留,它不涉及大小写。
**没动的都是有外部约束的**,别顺手一起改:
- 发给判题沙箱的请求体(language_config / max_cpu_time / max_memory /
test_case_id / io_mode)和它回的字段(cpu_time / memory / test_case)——
那是沙箱的 API,不是我们的
- 测试点 info 文件的键,沙箱直接读那个文件
- submission.statistic_info 里的 time_cost / err_info / ast_results ——
判题机按这套写,12 万条历史提交就是这形状
- acm_problems_status、progress_detail 这些存量 JSONB
- AST 规则键(for_loop)、成就指标(accepted_count)、reaction 语义键 ——
那是词汇表标识符不是字段名,for_loop 还要映射到 tree-sitter 的 while_statement
验证:起 dev 栈(api + worker + 沙箱),学生账号真提一次代码,抓 /ws/submissions
的帧:
{"type":"submission_update","submissionId":"bf13b7f0…","result":6,"status":"pending"}
{"type":"submission_update","submissionId":"bf13b7f0…","result":7,"status":"judging"}
{"type":"submission_update","submissionId":"bf13b7f0…","result":-2,"status":"finished","score":0}
subscribe 帧也换成 submissionId 并被接受(否则会回一个 error 帧,没有)。
流程图那条路径要 AI 评分才跑得起来,本机没配,只做了类型检查。
前后端同一个 docker 栈一起构建部署,没有版本错配窗口。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,7 @@ export async function evaluateFlowchart(job: FlowchartJobData) {
|
|||||||
}).where(eq(schema.flowchartSubmission.id, row.flowchart.id))
|
}).where(eq(schema.flowchartSubmission.id, row.flowchart.id))
|
||||||
await publishFlowchartUpdate(row.flowchart.userId, flowchartUpdateSchema.parse({
|
await publishFlowchartUpdate(row.flowchart.userId, flowchartUpdateSchema.parse({
|
||||||
type: "flowchart_evaluation_completed",
|
type: "flowchart_evaluation_completed",
|
||||||
submission_id: row.flowchart.id,
|
submissionId: row.flowchart.id,
|
||||||
score: result.score,
|
score: result.score,
|
||||||
grade: result.grade,
|
grade: result.grade,
|
||||||
feedback: result.feedback,
|
feedback: result.feedback,
|
||||||
@@ -69,7 +69,7 @@ export async function evaluateFlowchart(job: FlowchartJobData) {
|
|||||||
await db.update(schema.flowchartSubmission).set({ status: 3 }).where(eq(schema.flowchartSubmission.id, row.flowchart.id))
|
await db.update(schema.flowchartSubmission).set({ status: 3 }).where(eq(schema.flowchartSubmission.id, row.flowchart.id))
|
||||||
await publishFlowchartUpdate(row.flowchart.userId, flowchartUpdateSchema.parse({
|
await publishFlowchartUpdate(row.flowchart.userId, flowchartUpdateSchema.parse({
|
||||||
type: "flowchart_evaluation_failed",
|
type: "flowchart_evaluation_failed",
|
||||||
submission_id: row.flowchart.id,
|
submissionId: row.flowchart.id,
|
||||||
error: message,
|
error: message,
|
||||||
}))
|
}))
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -317,10 +317,9 @@ async function markSystemError(submissionId: string, userId: number, error: unkn
|
|||||||
if (updated.length > 0) {
|
if (updated.length > 0) {
|
||||||
await publishSubmissionUpdate(userId, {
|
await publishSubmissionUpdate(userId, {
|
||||||
type: "submission_update",
|
type: "submission_update",
|
||||||
submission_id: submissionId,
|
submissionId,
|
||||||
result: JudgeStatus.SYSTEM_ERROR,
|
result: JudgeStatus.SYSTEM_ERROR,
|
||||||
status: "error",
|
status: "error",
|
||||||
err_info: message,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,7 +352,7 @@ export async function judgeSubmission(job: JudgeJobData) {
|
|||||||
.where(eq(schema.submission.id, row.submission.id))
|
.where(eq(schema.submission.id, row.submission.id))
|
||||||
await publishSubmissionUpdate(row.submission.userId, {
|
await publishSubmissionUpdate(row.submission.userId, {
|
||||||
type: "submission_update",
|
type: "submission_update",
|
||||||
submission_id: row.submission.id,
|
submissionId: row.submission.id,
|
||||||
result: JudgeStatus.JUDGING,
|
result: JudgeStatus.JUDGING,
|
||||||
status: "judging",
|
status: "judging",
|
||||||
})
|
})
|
||||||
@@ -463,17 +462,9 @@ export async function judgeSubmission(job: JudgeJobData) {
|
|||||||
|
|
||||||
await publishSubmissionUpdate(row.submission.userId, {
|
await publishSubmissionUpdate(row.submission.userId, {
|
||||||
type: "submission_update",
|
type: "submission_update",
|
||||||
submission_id: row.submission.id,
|
submissionId: row.submission.id,
|
||||||
result,
|
result,
|
||||||
status: "finished",
|
status: "finished",
|
||||||
time_cost:
|
|
||||||
typeof statisticInfo.time_cost === "number"
|
|
||||||
? statisticInfo.time_cost
|
|
||||||
: undefined,
|
|
||||||
memory_cost:
|
|
||||||
typeof statisticInfo.memory_cost === "number"
|
|
||||||
? statisticInfo.memory_cost
|
|
||||||
: undefined,
|
|
||||||
score:
|
score:
|
||||||
typeof statisticInfo.score === "number" ? statisticInfo.score : undefined,
|
typeof statisticInfo.score === "number" ? statisticInfo.score : undefined,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ async function handleMessage(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let message: { type?: unknown; timestamp?: unknown; submission_id?: unknown }
|
let message: { type?: unknown; timestamp?: unknown; submissionId?: unknown }
|
||||||
try {
|
try {
|
||||||
message = JSON.parse(raw) as typeof message
|
message = JSON.parse(raw) as typeof message
|
||||||
} catch {
|
} catch {
|
||||||
@@ -79,7 +79,7 @@ async function handleMessage(
|
|||||||
ws.send(JSON.stringify({ type: "pong", timestamp: message.timestamp }))
|
ws.send(JSON.stringify({ type: "pong", timestamp: message.timestamp }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (message.type !== "subscribe" || typeof message.submission_id !== "string") {
|
if (message.type !== "subscribe" || typeof message.submissionId !== "string") {
|
||||||
ws.send(JSON.stringify({ type: "error", message: "Invalid message" }))
|
ws.send(JSON.stringify({ type: "error", message: "Invalid message" }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ async function handleMessage(
|
|||||||
.from(schema.submission)
|
.from(schema.submission)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(schema.submission.id, message.submission_id),
|
eq(schema.submission.id, message.submissionId),
|
||||||
eq(schema.submission.userId, ws.data.userId),
|
eq(schema.submission.userId, ws.data.userId),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -103,17 +103,17 @@ async function handleMessage(
|
|||||||
const [flowchart] = await db
|
const [flowchart] = await db
|
||||||
.select({ id: schema.flowchartSubmission.id, status: schema.flowchartSubmission.status, score: schema.flowchartSubmission.aiScore, grade: schema.flowchartSubmission.aiGrade })
|
.select({ id: schema.flowchartSubmission.id, status: schema.flowchartSubmission.status, score: schema.flowchartSubmission.aiScore, grade: schema.flowchartSubmission.aiGrade })
|
||||||
.from(schema.flowchartSubmission)
|
.from(schema.flowchartSubmission)
|
||||||
.where(and(eq(schema.flowchartSubmission.id, message.submission_id), eq(schema.flowchartSubmission.userId, ws.data.userId)))
|
.where(and(eq(schema.flowchartSubmission.id, message.submissionId), eq(schema.flowchartSubmission.userId, ws.data.userId)))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
if (!flowchart) {
|
if (!flowchart) {
|
||||||
ws.send(JSON.stringify({ type: "error", message: "Submission not found" }))
|
ws.send(JSON.stringify({ type: "error", message: "Submission not found" }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const replay = flowchart.status === 2
|
const replay = flowchart.status === 2
|
||||||
? { type: "flowchart_evaluation_completed", submission_id: flowchart.id, score: flowchart.score ?? undefined, grade: flowchart.grade ?? undefined }
|
? { type: "flowchart_evaluation_completed", submissionId: flowchart.id, score: flowchart.score ?? undefined, grade: flowchart.grade ?? undefined }
|
||||||
: flowchart.status === 3
|
: flowchart.status === 3
|
||||||
? { type: "flowchart_evaluation_failed", submission_id: flowchart.id, error: "Evaluation failed" }
|
? { type: "flowchart_evaluation_failed", submissionId: flowchart.id, error: "Evaluation failed" }
|
||||||
: { type: "flowchart_evaluation_update", submission_id: flowchart.id }
|
: { type: "flowchart_evaluation_update", submissionId: flowchart.id }
|
||||||
ws.send(JSON.stringify(flowchartUpdateSchema.parse(replay)))
|
ws.send(JSON.stringify(flowchartUpdateSchema.parse(replay)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -129,13 +129,10 @@ async function handleMessage(
|
|||||||
: "finished"
|
: "finished"
|
||||||
const parsed = submissionUpdateSchema.safeParse({
|
const parsed = submissionUpdateSchema.safeParse({
|
||||||
type: "submission_update",
|
type: "submission_update",
|
||||||
submission_id: submission.id,
|
submissionId: submission.id,
|
||||||
result: submission.result,
|
result: submission.result,
|
||||||
status,
|
status,
|
||||||
time_cost: statistics.time_cost,
|
|
||||||
memory_cost: statistics.memory_cost,
|
|
||||||
score: statistics.score,
|
score: statistics.score,
|
||||||
err_info: statistics.err_info,
|
|
||||||
})
|
})
|
||||||
if (parsed.success) ws.send(JSON.stringify(parsed.data))
|
if (parsed.success) ws.send(JSON.stringify(parsed.data))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function useSubmissionMonitor() {
|
|||||||
|
|
||||||
console.log("[SubmissionMonitor] 收到WebSocket更新:", data)
|
console.log("[SubmissionMonitor] 收到WebSocket更新:", data)
|
||||||
|
|
||||||
if (data.submission_id !== submissionId.value) {
|
if (data.submissionId !== submissionId.value) {
|
||||||
console.log("[SubmissionMonitor] 提交ID不匹配,忽略")
|
console.log("[SubmissionMonitor] 提交ID不匹配,忽略")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,13 +287,10 @@ export class BaseWebSocket<T extends WebSocketMessage = WebSocketMessage> {
|
|||||||
*/
|
*/
|
||||||
export interface SubmissionUpdate extends WebSocketMessage {
|
export interface SubmissionUpdate extends WebSocketMessage {
|
||||||
type: "submission_update"
|
type: "submission_update"
|
||||||
submission_id: string
|
submissionId: string
|
||||||
result: number
|
result: number
|
||||||
status: "pending" | "judging" | "finished" | "error"
|
status: "pending" | "judging" | "finished" | "error"
|
||||||
time_cost?: number
|
|
||||||
memory_cost?: number
|
|
||||||
score?: number
|
score?: number
|
||||||
err_info?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -314,7 +311,7 @@ class SubmissionWebSocket extends BaseWebSocket<SubmissionUpdate> {
|
|||||||
this.pendingSubmissionId = submissionId
|
this.pendingSubmissionId = submissionId
|
||||||
const success = this.send({
|
const success = this.send({
|
||||||
type: "subscribe",
|
type: "subscribe",
|
||||||
submission_id: submissionId,
|
submissionId,
|
||||||
})
|
})
|
||||||
if (success) this.pendingSubmissionId = ""
|
if (success) this.pendingSubmissionId = ""
|
||||||
}
|
}
|
||||||
@@ -325,7 +322,7 @@ class SubmissionWebSocket extends BaseWebSocket<SubmissionUpdate> {
|
|||||||
if (
|
if (
|
||||||
this.send({
|
this.send({
|
||||||
type: "subscribe",
|
type: "subscribe",
|
||||||
submission_id: submissionId,
|
submissionId,
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
this.pendingSubmissionId = ""
|
this.pendingSubmissionId = ""
|
||||||
@@ -429,7 +426,7 @@ export interface FlowchartEvaluationUpdate extends WebSocketMessage {
|
|||||||
| "flowchart_evaluation_completed"
|
| "flowchart_evaluation_completed"
|
||||||
| "flowchart_evaluation_failed"
|
| "flowchart_evaluation_failed"
|
||||||
| "flowchart_evaluation_update"
|
| "flowchart_evaluation_update"
|
||||||
submission_id: string
|
submissionId: string
|
||||||
score?: number
|
score?: number
|
||||||
grade?: string
|
grade?: string
|
||||||
feedback?: string
|
feedback?: string
|
||||||
@@ -453,7 +450,7 @@ class FlowchartWebSocket extends BaseWebSocket<FlowchartEvaluationUpdate> {
|
|||||||
subscribe(submissionId: string) {
|
subscribe(submissionId: string) {
|
||||||
const success = this.send({
|
const success = this.send({
|
||||||
type: "subscribe",
|
type: "subscribe",
|
||||||
submission_id: submissionId,
|
submissionId,
|
||||||
})
|
})
|
||||||
if (!success) {
|
if (!success) {
|
||||||
console.error("[Flowchart WebSocket] 订阅失败: 连接未就绪")
|
console.error("[Flowchart WebSocket] 订阅失败: 连接未就绪")
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const flowchartUpdateSchema = z.object({
|
|||||||
"flowchart_evaluation_failed",
|
"flowchart_evaluation_failed",
|
||||||
"flowchart_evaluation_update",
|
"flowchart_evaluation_update",
|
||||||
]),
|
]),
|
||||||
submission_id: z.string(),
|
submissionId: z.string(),
|
||||||
score: z.number().optional(),
|
score: z.number().optional(),
|
||||||
grade: z.string().optional(),
|
grade: z.string().optional(),
|
||||||
feedback: z.string().optional(),
|
feedback: z.string().optional(),
|
||||||
|
|||||||
@@ -68,15 +68,20 @@ export const embeddedSubmissionSchema = submissionDetailSchema
|
|||||||
// 站内信页面拿它拼 `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。
|
// 站内信页面拿它拼 `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。
|
||||||
.extend({ problem: z.string() })
|
.extend({ problem: z.string() })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判题进度推送。**只带前端真正要用的东西**:靠 submissionId 认领、靠 result /
|
||||||
|
* status 决定是继续等还是去拉详情。
|
||||||
|
*
|
||||||
|
* 这里曾经还带着 time_cost / memory_cost / err_info —— 从 statistic_info 原样
|
||||||
|
* 抄一份出来,前端一处都没读过。耗时和错误信息在提交详情里本来就有,判完了去
|
||||||
|
* 拉一次就是了,不必让推送顺带背一份 JSONB 的形状。
|
||||||
|
*/
|
||||||
export const submissionUpdateSchema = z.object({
|
export const submissionUpdateSchema = z.object({
|
||||||
type: z.literal("submission_update"),
|
type: z.literal("submission_update"),
|
||||||
submission_id: z.string(),
|
submissionId: z.string(),
|
||||||
result: judgeStatusSchema,
|
result: judgeStatusSchema,
|
||||||
status: z.enum(["pending", "judging", "finished", "error"]),
|
status: z.enum(["pending", "judging", "finished", "error"]),
|
||||||
time_cost: z.number().optional(),
|
|
||||||
memory_cost: z.number().optional(),
|
|
||||||
score: z.number().optional(),
|
score: z.number().optional(),
|
||||||
err_info: z.string().optional(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const submissionListItemSchema = z.object({
|
export const submissionListItemSchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user