AI 时代 OJ 设计的第 1 步:给「可信 AC」和学情分析攒数据,本身不判任何事。 - 契约:提交请求加可选的 trace(活跃时长、键入/粘贴/删除字符数、切后台次数等, 只有计数、不含按键内容);写成 .optional().catch(undefined),坏了就当没带, 不让附带数据把提交挡成 400 - 迁移 0016 建 submission_trace,与 submission 一对一、CASCADE;since_prev_ms 由服务端在同一条 INSERT 里算(排掉自身),bigint —— 实测已有 44 天的间隔,int4 装不下 - 后端写 trace 失败只记日志,不影响提交 - 前端 oj/problem/utils/editTrace.ts 是模块单例(扩展对象不能过 Pinia 的响应式代理), 只数带 userEvent 的事务:格式化回写 / 载入草稿 / 协作对方的改动天然排除; closeBrackets 越过右括号时是原样替换,按 no-op 跳过。比赛编辑器同样挂上 - 实跑:后端四种请求、前端浏览器里键入/粘贴/删除/setCode 回写/切后台/提交后清零, 计数与预期逐项一致 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,36 @@ export const statisticInfoSchema = z.looseObject({
|
||||
.optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* 编辑过程的聚合信号,落进 `submission_trace`。**只有计数,不含任何按键内容。**
|
||||
*
|
||||
* 口径是「自上次提交以来」的增量(前端每次提交成功后清零,切题也清零),
|
||||
* 所以同一道题连交几次,每条提交各记各的那一段。
|
||||
*
|
||||
* 全部来自客户端,**可以伪造** —— 这是接受了的:它只用来给「可信 AC」加权、
|
||||
* 给老师提示「建议关注」,不单独判任何事。服务端自己算的间隔另见 `since_prev_ms`。
|
||||
*/
|
||||
export const submissionTraceSchema = z.object({
|
||||
/** 活跃编辑时长:相邻两次编辑间隔不超过 60 秒才累加,页面不可见时不计 */
|
||||
activeMs: z.number().int().min(0).max(1e8),
|
||||
/** 打开这道题(或上次提交)到这次提交的墙钟时长 */
|
||||
sinceOpenMs: z.number().int().min(0).max(1e9),
|
||||
/** 键入、输入法上屏、补全插入的字符数 */
|
||||
typedChars: z.number().int().min(0).max(1e7),
|
||||
/** 粘贴、从外部拖入的字符数 */
|
||||
pastedChars: z.number().int().min(0).max(1e7),
|
||||
pasteCount: z.number().int().min(0).max(1e5),
|
||||
/** 单次最大粘贴的字符数 */
|
||||
maxPaste: z.number().int().min(0).max(1e7),
|
||||
deletedChars: z.number().int().min(0).max(1e7),
|
||||
/** 页面切到后台的次数(切标签页、切窗口、最小化)。只作辅助,别单独拿来说事 */
|
||||
blurCount: z.number().int().min(0).max(1e5),
|
||||
/** 这一段开始时编辑器里已有的字符数(本地草稿 / 模板 / 上次提交后的代码) */
|
||||
initialLen: z.number().int().min(0).max(1e7),
|
||||
/** 提交时这道题正在课堂协作中。老师替学生交的那条也会是 true,统计时要排掉 */
|
||||
collab: z.boolean(),
|
||||
})
|
||||
|
||||
export const createSubmissionRequestSchema = z.object({
|
||||
problemId: z.number().int().positive(),
|
||||
/**
|
||||
@@ -102,6 +132,13 @@ export const createSubmissionRequestSchema = z.object({
|
||||
* 所以这里带错了顶多是标记不准,不会影响成绩。
|
||||
*/
|
||||
problemSetId: z.number().int().positive().optional(),
|
||||
/**
|
||||
* 编辑过程信号,见 submissionTraceSchema。**坏了就当没带**(`.catch`):
|
||||
* 整个请求体是一把 safeParse,这里要是能 400,一份附带的统计数据就能挡住
|
||||
* 学生交作业。刷新过页面、老版本前端、脚本提交都会没有它,那是「无数据」,
|
||||
* 不是「可疑」。
|
||||
*/
|
||||
trace: submissionTraceSchema.optional().catch(undefined),
|
||||
})
|
||||
|
||||
export const createSubmissionResponseSchema = z.object({
|
||||
@@ -384,6 +421,7 @@ export const formatCodeRequestSchema = z.object({
|
||||
export const formatCodeResponseSchema = z.object({ code: z.string() })
|
||||
|
||||
export type StatisticInfo = z.infer<typeof statisticInfoSchema>
|
||||
export type SubmissionTrace = z.infer<typeof submissionTraceSchema>
|
||||
export type CreateSubmissionRequest = z.infer<
|
||||
typeof createSubmissionRequestSchema
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user