契约在前端一直只当类型包用:45 处引用里几乎全是 import type,三个 .parse() 后面
还都紧跟一个 as 把校验结果断言回去,等于没校验。这一轮把形状的来源收拢。
## 语言:三份真相并成一份
前端 utils/types.ts 手写了一份 9 值的语言联合,后端 judge/languages.ts 有自己的一套,
生产库又有一套。手抄那份**漏了 SQL**,而生产库 961 道题里有 9 道 SQL 题、
124191 条提交里有 91 条 SQL 提交 —— 这些提交的 language 在前端类型上是 undefined。
现在唯一来源是契约的 problemLanguageSchema,constants.ts 的显示映射以它为键,
契约里加语言而那边没补映射会当场编译不过。
## 判题产物:按生产数据实测收紧
judgeInfoSchema / statisticInfoSchema 的形状来自 124191 条提交的实测,不是手抄:
- info.data 有 12048 条是 null(编译失败等没有逐测试点结果),前端手抄的 Info
却把 data 写成非空数组 —— 这 12048 条在类型上根本不成立;
- info 还允许**空对象**:非管理员看提交详情时后端下发 info: {}(权限投影)。
收紧时必须把它算进去,否则每条非管理员看的提交详情直接 500 —— 本地实测复现过;
- statistic_info 的五个键按出现次数定成全部可选;另有 8916 条 JSONB 原文因内嵌
带转义的 shell 输出不是合法 JSON,被后端 objectValue() 兜成 { value: ... },
所以不能用严格对象,否则这 8916 条会被误判成分歧。
## 运行时闸门
新增 utils/contract.ts:safeParse 失败时记一条分歧(去重、控制台可见、
window.__OJ2_CONTRACT_DRIFT__ 可查)后**放行原始数据**,不白屏 —— 面向学生的
生产站点,字段空着比整页崩掉可接受。接在 7 条高频链路上:/site、/site/online、
/problems、/problems/:id、/submissions、/submissions/:id、/me。
提交详情的 info 是联合类型,调用方不再直接取 .data,统一走
utils/functions.ts 的 submissionCaseResults()。
## 验证
- vue-tsc 与 tsc -p apps/api 均 exit 0;vite build 通过;
- 契约 schema 直跑真实接口:7/7 通过(自包含脚本,从登录到详情全链路);
- 用生产备份复核收紧后的约束:961 题的 languages 无越界值,template 只出现
C/Python3 两个键 —— 不会因为这次收紧在生产上抛错。
This commit is contained in:
1 parent
a475cac128
commit
aab0404ed7
11 files changed
+414
-115
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema, sampleUserSchema } from "./common"
|
||||
import { problemLanguageSchema } from "./language"
|
||||
|
||||
/**
|
||||
* 题目难度。生产库 956 道题只有这三个值(旧 Django 的 Problem.difficulty choices
|
||||
@@ -296,8 +297,16 @@ export const problemDetailSchema = z.object({
|
||||
}),
|
||||
),
|
||||
hint: z.string().nullable(),
|
||||
languages: z.array(z.string()),
|
||||
template: z.record(z.string(), z.string()),
|
||||
languages: z.array(problemLanguageSchema),
|
||||
/**
|
||||
* 语言 → 代码模板。**用 partialRecord 让键受语言联合约束** ——
|
||||
* 原来这里是 `z.record(z.string(), z.string())`,等价于 `Record<string, string>`,
|
||||
* 前端按语言查模板时拿不到任何键名保护(`template["Pytho3"]` 也是合法表达式)。
|
||||
*
|
||||
* partialRecord 而非 record:没配模板的语言不该出现该键(`template: {}` 是常态),
|
||||
* 用 record 会要求每一个语言键都存在。
|
||||
*/
|
||||
template: z.partialRecord(problemLanguageSchema, z.string()),
|
||||
createTime: z.string(),
|
||||
lastUpdateTime: z.string().nullable(),
|
||||
timeLimit: z.number().int(),
|
||||
|
||||
Reference in new issue
Block a user