Files
OJ2/CLAUDE.md
yuetsh cbff292551 feat(切换): compose 支持「只换前后端」,接着用旧栈的 postgres/redis
演练把一件事盖住了:当时是把生产 dump 恢复进 `OJ2/data/postgres` 的,
所以没人发现新 compose 在 `OJ2/docker/` 下,`../data` 解析出来是 `OJ2/data`,
而旧栈用的是 `<部署目录>/data`(服务器上是 `/root/OJDeploy/data`)。

照原手册切过去,postgres 会在一个空目录上初始化一个全新的空库 —— 站点起得来,
但没有用户没有题、判题全挂、题面图片 404。旧数据完好,回滚正常,但当天会白吓一场。

## 改法

三组 env 旋钮,默认值保持原样,不影响本机和演练那条路:

    DATA_DIR    所有数据卷的根,默认 ../data
    DB_HOST/PORT      默认 oj-postgres:5432
    REDIS_HOST/PORT   默认 oj-redis:6379

postgres 和 redis 挪进 `profiles: ["local-data"]`,默认不起 —— 否则会跟旧栈
那两个抢 5445 / 5446。配套给 depends_on 加 `required: false`:实测严格的
depends_on 碰上未启用的 profile 会让整个 project 直接 invalid,不是可选项。
代价写进注释了:自带数据形态下 postgres 起不来时 compose 只警告不中止。

给 api / worker 加 host-gateway 映射,DB_HOST 填 host.docker.internal 就行,
不用去猜 docker0 的网段。数据库流量不出本机。

school 那套的 7 个挂载点同样换成 DATA_DIR —— 机房那台也有自己的旧数据目录,
测试点和题面图片都在里面,同一个坑。

## 验证

用 docs/specs/schema.sql 起了个发布在宿主机 5445 的 postgres 冒充旧栈:
正好 4 个容器(没有 postgres/redis)、oj-api healthy、首页与 /api/site
/api/problems 200、未登录进后台 401。读写两个方向都验了 —— 那个库的
pg_stat_activity 里有来自 172.17.0.1 的 postgres.js 连接,judge_server 表里
也出现了新判题机写进去的心跳行。

四份 compose 的 `config -q` 全通过。

手册第三、四、六节按这个形态重写:停旧栈改成只 stop oj-backend / oj-judge
(旧判题机会争 data/judge_server/run,旧 backend 占着 8080),回滚变成把这两个
再 start 起来,数据库进程全程不停。

**DATA_DIR 漏填不会报错**(它有默认值),是切换当天唯一会静默走歪的地方,
手册里给了 `config | grep source:` 的自查和两种症状的区分。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 08:57:25 -06:00

127 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
OJ2 是判题狗Online Judge的后端重写Django 6 → Bun + TypeScript前后端同仓。
上一代在 `../OnlineJudge/`Django`../ojnext/`Vue SPA**两者都已冻结,
是回滚路径,任何情况下都不要改。**
设计文档:`docs/specs/2026-08-06-bun-backend-rewrite-design.md`
切换手册:`docs/specs/phase5-cutover-runbook.md` ← 上线当天照这份走
## 仓库结构
| 目录 | 作用 |
|---|---|
| `apps/api/` | 后端。Hono + Drizzle + BullMQ编译成单二进制 |
| `apps/web/` | 前端。从 ojnext 原样搬来的 Vue 3 SPA |
| `packages/contract/` | 前后端共用的 Zod 契约 |
| `docker/` | Dockerfile + 三套 composedev / debian / school |
| `docs/specs/` | 设计、端点清单、各阶段评审报告与演练报告 |
## 本机环境
**Docker 可用,全套依赖都能在本机跑起来**PostgreSQL、Redis、判题沙箱
镜像也能在本机构建并完整演练上线。这一点和上一代不同,别沿用"本机跑不起来后端"
的旧假设。
```bash
bun install
bun run db:up # 起 postgres(5433) / redis(6380) / 判题沙箱(8081)
bun run dev # api(3000) + worker + web(5173) 一起起
```
首次要先建 `.env`(照 `.env.example`)。判题机 token 两边必须一致:
`.env``JUDGE_SERVER_TOKEN``docker/.env``OJ2_JUDGE_TOKEN`
常用检查:
```bash
bunx tsc --noEmit -p apps/api # 后端类型检查
bun run --filter '@oj2/api' check:routes # 路由遮蔽检查,加完路由跑一下
cd apps/web && bun run build # 前端构建vite 不做类型检查,构建即验证)
```
**不要写测试** —— 沿用上一代的项目约定。验证靠实跑:起服务、打接口、看结果。
## 几件必须知道的事
### 单二进制是有代价的
`apps/api` 编译成 `bun build --compile` 的单二进制,所以**运行时不能依赖
node_modules**。任何 `require.resolve` / `Bun.resolveSync` / `__dirname` 去找文件的
写法,本地都正常、编译后都会炸,而且**只在离开仓库目录后才炸**(在仓库里跑时它顺着
cwd 摸到了 node_modules假装没事
资源要用 `with { type: "file" }` 内嵌。`.node` 原生模块还要额外注意:这个写法
只有打包器认、`bun run` 不认,所以必须按形态分叉 —— 见 `apps/api/src/vendor/jieba.ts`
的注释,那里把坑写全了。
**改完这类代码dev 和编译两种形态都要跑一遍。** 我吃过亏:只验了编译产物,
dev 直接起不来。
### 路径解析看 `runtime.ts`
编译后 `import.meta.dir` 恒为 `/$bunfs/root`,往上三级就是文件系统根。
相对路径一律走 `runtime.ts``pathBase`,别自己拼。
### SQL 判题会 spawn「自己」
`judge/sql/index.ts` 起的子进程是二进制自身 + `sql-child` 子命令(因为编译后磁盘上
没有 child.ts 可以 spawn。所以**入口必须有 argv 分发**,否则「起自己」变成
「把整个程序再跑一遍」→ 指数级 fork。这不是假想开发时炸过一次开发机。
`OJ2_SQL_CHILD` 那道递归闸不要删。
### 加路由要防遮蔽
**Hono 按注册顺序匹配,不是静态优先**(实测确认过,别凭直觉)。`/problems/:id`
注册在 `/problems/random` 前面的话,后者永远进不去 —— 而且不报错、不警告,
只是静默走进前一条的 handler。阶段 4 真实发生过一次,两个教师用的分析端点被吃掉,
一直到评审才发现。
加完路由跑 `bun run --filter '@oj2/api' check:routes`
### 判题状态码要三处同步
`apps/api/src/judge/status.ts``apps/web/src/utils/constants.ts`
以及上一代的 `../OnlineJudge/submission/models.py`(回滚时要对得上)。
题目表情 reaction 的语义 key 同理。
### 比赛只有 ACM 模式
没有 OI。上一代残留的 OI 分支在阶段 0 已经砍掉,不要"顺手补回来"。
### 前端要兼容老 Chrome
机房电脑 Chrome < 94。`mermaid-legacy` 等 fallback 依赖和 vite 的构建 target
不能动,`vite.config.ts` 里有注释说明。
## 数据库
Drizzle schema 是从生产库 `drizzle-kit pull` 出来的,**不写迁移**。
新旧后端跑在同一套表结构上(阶段 5 演练逐列比对过,零差异),这是回滚能成立的前提 ——
所以改 schema 前先想清楚回滚怎么办。
生产库的几个约定:
- `raw_password` 明文列**要保留**,老师用它找学生密码。不要"顺手清理"。
- `judge_server_heartbeat` 保留。
- `problem.prompt` 是给未来 AI 预留的,当前没接线,不要删。
## 部署
三套 compose 在 `docker/``dev`(本机)、`debian`(服务器)、`school`(机房)。
**机房那套没有 postgres连的是服务器的库。** 两个站点共用一个数据库,
但各有各的 Redis 和判题沙箱 —— 所以上线那天**两边必须一起切**。
`compose.debian.yml` 有两种形态,靠 env 切换:
- **只换前后端**(上线用这个):设 `DATA_DIR` / `DB_HOST` / `REDIS_HOST`
沿用旧栈已经在跑的 postgres 和 redis只起 api / worker / web / judge。
- **自带数据**(本机、演练):不设那几个变量,起栈时加 `--profile local-data`
⚠️ `DATA_DIR` 默认值 `../data`**`OJ2/data`**,不是部署目录的 `data/`
沿用旧数据却忘了设它,会静默起一套空数据(空库、没测试点、图片 404
而且**不报错** —— 这是切换当天唯一会静默走歪的地方。
细节和演练结果都在 `docs/specs/phase5-cutover-runbook.md`