Commit Graph
59 Commits
Author SHA1 Message Date
xuyueandClaude Opus 5 7793a2fe4c 清理旧后端下线后的残留
Deploy / deploy (push) Has been cancelled
旧 Django 后端 2026-08-26 下线、回滚路径作废,代码里还留着几处以它为前提的
死代码和过期注释。

- PUBLIC_WS_URL / PUBLIC_OJ_URL 全部删除。BaseWebSocket 的
  `${PUBLIC_WS_URL}/${path}/` fallback 是 Channels 时代的写法,而三个子类
  早就各自传 url,等于永不执行;config.vue 里 PUBLIC_OJ_URL 只是个会被
  getWebsiteConfig() 立刻覆盖、且指着 :8000 的假初值。WebSocketConfig.path
  随之去掉,url 改成必填。
- vite.config:删掉 /api2、/ws2 的迁移期注释,换成还成立的约束(代理这三段
  要和 docker/Caddyfile 同步);newBackend 改名 backend。
- 六处 “回滚时旧后端还要读” 换成真实理由:snake_case 保留的结论不变,但因为
  判题机按这套键名写、存量 JSONB 就是这形状。
- CLAUDE.md:数据库一节开头「不写迁移 + 先想清楚回滚」与下一节的 drizzle
  migration 自相矛盾,改掉;判题状态码不再要求同步到 OnlineJudge,理由换成
  历史 submission.result 和沙箱用的就是这套编码。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 09:09:26 -06:00
xuyueandClaude Opus 5 1198895b54 refactor(前端): vue-tsc 清零,type-check 可以当 CI 门禁了
接上 vue-tsc 时有 143 条既有错误,上一批迁移带走 89 条,这里把剩下的清完。

大部分是噪音(未使用的回调参数、死变量、少数 unknown),但里面躺着两个真问题:

- 个人主页查不存在的用户会抛:getProfile 返回 null 时后面照样取
  .acmProblemsStatus,靠 `!` 压着。加了早返回。
- getProblemSetProgress 我上一批标错了类型:后台这个接口返回的是裸数组,
  不是分页信封(和 oj 侧的 /user-progress 不一样)。已改正并加注释区分。

其余处理:

- 未使用的回调参数按 TS 约定加 `_` 前缀(v-for 的项、供子类重写的空钩子、
  路由守卫的 from);确认无用的局部变量直接删(clickX/clickY 算了点击位置
  但飞出方向用的是随机角度,hasToday 下面已经用 lastDateOnly 判过,
  prefix 算了周/月但标签里没用上)。
- App.vue 的 highlight.js 注册:Promise.all([...]).then(m => m.map(x => x.default))
  会把元组塌成 (HLJSApi | LanguageFn)[],hljs 上就找不到 registerLanguage。
  改成逐个取 .default。
- 给一批 api 补上契约类型(getMetrics / getHitokoto / getTutorials /
  formatCode / getProblemBeatRate / getClassUsernames 等),契约相应补了
  8 个 z.infer 导出。
- ContestRank.submissionInfo 和 AcmHelperItem.acInfo 在 api 边界窄化成
  SubmissionInfo —— 契约里是 Record<string, unknown>(JSONB 原文)。
- FlowchartEditor 的 TS2589:vue-flow 的 Node 嵌套太深,ref<Node[]>([]) 的
  UnwrapRef 推导撞上实例化层级上限,改成 ref([]) as Ref<Node[]>。
- api2.ts 的响应拦截器**故意**不返回 AxiosResponse(要把 { data } 信封剥掉),
  类型上确实说不通,没硬掰,写注释说明为什么用断言。
- 题目列表的「随机」按钮在模板里一直是注释状态,对应的 getRandom 和
  getRandomProblemID 一并删除(后端 /problems/random 保留不动)。

验证:vue-tsc 0 条,apps/api tsc、check:routes、web build 全通过;
修过 key 的列都打接口确认过字段真实存在。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:04:10 -06:00
xuyueandClaude Opus 5 c2a81209d7 refactor(前端): 拆掉 camelCase→snake_case 转换层,契约成为唯一真相
utils/legacy.ts 是迁移期的临时层:新后端一律 camelCase,而组件读的还是
旧 Django 的 snake_case,于是在 api 层做一次递归键名重写。它自己的注释就
写了「迁移完成后这一层应当整体拆掉」。现在拆了。

代价不只是那 96 处包装:每个响应都要递归遍历整个对象重写一遍键名,而且
utils/types.ts 和 packages/contract 是两份真相 —— 手抄的那份还抄歪了好几处。

做法是按域推进,每域都用 vue-tsc 相对基线做差,确认零新增错误后再往下走。
前端的类型现在一律以契约为准,只在必要处窄化(比如 languages/template 的键
窄化成 LANGUAGE),删掉的重复定义包括 WebsiteConfig、LoginSummary、
AchievementSummary、ProblemSet、Contest、User、Profile、AdminTag、
StuckProblem 等等,其中 ClassComparison 有两个组件各手抄了一份。

## 顺带修掉的真 bug

- 管理端公告列表的「可见」开关每次都 400:列表响应被契约 omit 掉了 content,
  而更新接口要求 content 必填,toggleVisible 把列表行原样回传。而且是乐观
  翻转、不 await 不 catch,管理员看到开关动了、实际没存也没有提示。
  改成先 GET 整条再 PUT,加失败提示。

- 删有提交的题时只显示笼统的「删除失败」:前端还在 match 旧 Django 的英文
  文案,而后端返回的是 problem-has-submissions + 中文。连同另外 8 处同类
  匹配一起改成判错误码 —— 文案是后端随时能改的,match 文案改一个字就静默失效。

- SubmissionStatus.time_limit_exceeded 写成 `1 | 2`,TS 按位或算成 3,和
  memory_limit_exceeded 撞了同一个值。后端 judge/status.ts 里这是分开的
  两个码,按后端拆成 cpu_/real_ 两项。当前没有代码读这两个成员,但
  CLAUDE.md 明确要求判题状态码三处同步。

- 流程图历史翻到没有提交的那一页会直接抛:契约里 submission 是 nullable,
  被 any 掩盖成看起来非空。补了 null 分支。

## 契约里被逼出来的三处不诚实

- grade 写成 z.string(),但 averageGrade() 在没有可用数据时返回空串,
  前端三张图表拿它查 Record<Grade,...> 会查出 undefined。按实际收紧成
  z.enum([...,""]),四个查表点都补了「无评级」分支。

- difficulty 写成 z.string()。核对过生产库 dump:956 道题只有
  Low/Mid/High 三个值(761/149/46)。收紧成枚举。

- topReaction 写成 z.string(),既对不上前端渲染的 {type,count},也对不上
  旧后端 get_top_reactions 下发的形状。改成正确形状并注明当前恒传 null。

## 明确保留 snake_case 的 54 处

判题沙箱原始输出(cpu_time/exit_code/output_md5/compile_output)、
statistic_info 内容(err_info/time_cost/ast_results)、submission_info
JSONB(is_ac/ac_time/error_number,回滚时旧后端还要读)、SQL 判题引擎的
total_rows/order_sensitive/changed_tables、WebSocket 的 submission_id、
以及数据库选项键 enable_maxkb。每一处都在类型定义旁写了为什么不能改。

language 没有跟着收紧契约 —— 它是配置项、随时可能加语言,收紧会让新语言
在后端 parse 时直接抛。改在 api 边界一处窄化。

## 另外

- utils/http.ts 整个模块已是死代码(四处引用全是 import type),删除。
- profile 的 blog/github/school/major/language 五个字段全链路空转,没有
  任何组件读,从契约到类型一并摘除(数据库列不动)。
- admin/account.ts 往 user_profile 塞的 totalScore 是 OI 模式遗留,表里
  没这一列。Drizzle 按表定义拼列名会把它静默丢弃,所以没出过错,是死代码。

验证:vue-tsc 143 → 54 条且无新增,apps/api tsc、check:routes、web build
全通过;各域响应形状逐条打接口核对过。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:44:24 -06:00
xuyueandClaude Opus 5 ea521e7b0a feat(阶段5): 镜像、三套 compose,前端切回 /api 与 /ws
## 镜像

一个 Dockerfile 两个 target:api(单二进制)和 web(Caddy + 前端产物)。
旧后端是一个容器里用 supervisord 跑 caddy+gunicorn+dramatiq,这里拆成
oj-web / oj-api / oj-worker 三个容器 —— Docker 本身就是进程管理器,
拆开之后 worker 挂了能单独重启、日志也分得开,少一层 supervisord 要维护。

同一个镜像换个子命令就是 worker,镜像里只有一份运行时。
新增 healthcheck 子命令:运行镜像是 debian-slim,没有 curl/wget,
让二进制自己打 /health(只打 /health 不碰库 —— 库挂了该由库的 healthcheck 报,
不该让 api 跟着被判不健康然后被重启)。

**数据目录照抄旧后端**(test_case、public/upload、public/avatar)。
不是审美问题:切换那天不用搬动任何文件,回滚时旧后端立刻能找到自己的数据。
少一次几十 GB 的 mv,就少一个在停机窗口里出错的机会。

构建路上踩到三个坑,都是「本地能过、容器里过不了」那一类:

- 构建上下文吸进了 data/,judge_server/run 是判题沙箱用别的 uid 建的,
  docker 连 stat 都做不了,构建直接失败 → 补 .dockerignore
- mermaid@9.4.3(机房老 Chrome 的 legacy 依赖,不能砍)从容器里连
  registry.npmjs.com 稳定失败,主机上没问题 → 换 npmmirror,并重试两次
- 容器里 bun 用 isolated 布局,本地是扁平的。靠「提升」才能解析到的包
  在容器里一律解析不到:@node-rs/jieba-linux-x64-gnu(编译要 import 它的 .node)、
  以及前端的 @codemirror/{language,state,view} 和 @lezer/highlight。
  这些本来就是代码直接 import 的,补成直接依赖。顺手写了个脚本扫全仓,
  确认只有这 4 个。

## 前端切回 /api、/ws

迁移期用 /api2、/ws2 指新后端,/api、/ws 还指着 Django。端点已全部搬完,
临时前缀去掉。改动只有三处(api2.ts 的 baseURL、websocket.ts 的两个 URL),
`api2` 那些 import 是模块名不是路径,不动。vite 代理同步收敛成三条。

## compose

- debian:全套(含 postgres,对外开 5445 给机房连)
- school:**没有 postgres**,连服务器的库;本地 Redis + 本地判题沙箱
- 两边共用一个库但各有各的队列和 WS 推送,和旧后端的 Dramatiq/Channels 拓扑一致

密钥一律走 env 且带 `:?`,没设置就直接报错退出,不静默用弱默认值。
COOKIE_SECURE 在机房必须是 false(http 直连 IP,带 Secure 的 Cookie 发不回来,
表现是「登录成功但立刻又变未登录」)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 23:41:20 -06:00
xuyueandClaude Opus 5 bc54fbff98 feat(阶段4): 站点配置 / 判题机 / 孤儿用例 / 概览 / 图片上传
GET/POST          admin/website
  GET               admin/judge-servers
  PUT               admin/judge-servers/:id
  DELETE            admin/judge-servers/:hostname
  GET/DELETE        admin/orphan-test-cases
  GET               admin/dashboard
  GET               admin/random-usernames
  POST              admin/upload-image

顺带补上配置广播:旧后端改配置会经 WebSocket 推给所有开着页面的人,改完立刻生效。
新后端只服务 /ws/submissions,前端的 ConfigWebSocket 还连着旧 Django Channels。
现在加了 /ws/config 通道(同一个 Bun.serve 只能挂一个 handler,用 socket data 上的
kind 区分),前端 ConfigWebSocket 改走 /ws2/config。

几处判断:

- **判活不能比字符串**。库里 timestamptz 形如 `2026-08-07 13:42:50+00`(空格分隔),
  toISOString() 是 `...T13:42:44.000Z`(T 分隔),字典序空格 < 'T',同一天的心跳永远
  小于阈值 —— 所有判题机都会显示离线。实测确实复现(dashboard 说 1 台在线、列表却
  两台全 abnormal),已改为 Date.parse 后比较。
- 删指定的孤儿用例时**先确认它确实是孤儿**。旧后端不校验,一个手抖的 id 就能删掉在用
  题目的测试数据,而测试数据没有别处备份。
- 图片上传的文件名完全由服务端生成,不带用户提供的任何一段;另加 10MB 上限 ——
  旧后端靠 nginx 兜,但机房那台机器盘写满之后判题也会一起挂。
- 停用判题机后不再 process_pending_task():任务在 BullMQ 里排着,worker 恢复自己接着
  消费,不存在旧自研分发器那种「没有新提交就一直 waiting」的问题。
- dashboard 不再下发 env.FORCE_HTTPS / STATIC_CDN_HOST,前端从未读过。

实测:学生 403;配置读写回读 + oj 侧 /site 同步生效 + 还原;判题机列表带 token、
状态判定正确(一台 normal 一台 abnormal,与 dashboard 计数一致);删不存在 404;
删非孤儿用例 404;随机点名缺班级号 400。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 07:43:38 -06:00
xuyueandClaude Opus 5 ced8f3ee1b docs(阶段3): 记录出口标准达成;补跑 prettier
覆盖率对账表更新:admin 侧 3/45 已实现,缺口 42。达成判据是 apps/web 的
oj/ 与 shared/ 两个目录已无指向旧 Django 的运行时调用,残留的 utils/http
引用只剩 ApiResponse 这一个类型。

顺带补跑 npm fmt,几个此前未格式化的文件随之改动,均为纯格式。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 06:48:20 -06:00
xuyueandClaude Opus 5 8c00cdc947 feat(阶段3): oj 侧端点铺开(基线提交,未经评审)
由外部 agent (Codex) 在本会话额度中断期间完成。原样提交作为基线,
后续修复单独成 commit,便于区分与回退。

覆盖 oj 侧 65 个端点,新增 9 组路由(account/achievement/ai/classroom/
content/contest/flowchart/problemset/site)与对应 Zod 契约。

已核验:tsc --noEmit 退出码 0;API 可启动;/api/problems 返回真实数据;
judge 与 flowchart worker 均 ready。
未核验:权限边界与数据泄露,评审进行中。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 01:25:36 -06:00
xuyue ec274419c3 Build Phase 2 judge vertical slice 2026-08-06 22:42:39 -06:00
xuyue ae1fb329b5 feat(阶段1): 搬入 ojnext 为 apps/web,未改业务代码 2026-08-06 21:18:16 -06:00