Commit Graph

6 Commits

Author SHA1 Message Date
7b6bfeb6fb fix(标签): /problem-tags 计数漏过滤隐藏题和赛题
Some checks failed
Deploy / deploy (push) Has been cancelled
标签列表按题目数 > 0 过滤,但计数没有像 /problems 那样限制
visible=true 且非赛题,导致标签在首页出现、点进去却一道题都没有。

同时把旧仓库冻结政策收紧写进 CLAUDE.md:从今天起旧仓库零改动,
不再有内部小修的例外,所有后续工作只落在 OJ2。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 00:22:05 -06:00
604857b25e fix(一言): 接回真数据集,之前线上返回的是三条硬编码
/quotes/random 在阶段 3 基线里只铺了个桩(三条写死的句子),读盘逻辑没移植,
所以 oj2.xuyue.cc 上一直是假数据——和 DATA_DIR 无关,哪儿部署都一样。

数据本来就在服务器上、位置也已经对上:旧栈挂 ./data/backend:/data,Django 的
HITOKOTO_DIR 就是它下面的 hitokoto/;OJ2 的 api 挂的是同一个目录。所以
Dockerfile 里跟着 TEST_CASE_DIRECTORY 写死 /data/hitokoto 即可,不用搬文件。

按分类懒加载并常驻,但只留前端用得上的 hitokoto/from 两个字段:原样缓存
7160 条要 2.5MB,裁完 1.4MB,而 api 容器只有 512m。读不到数据集时回落到
内置三条,不影响启动(本机 dev 默认就走这条)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 10:20:00 -06:00
1a059d0b88 feat(切换): 支持并行试跑,新站先挂 oj2.xuyue.cc 跑几天
设计文档写的是「不双跑不灰度」,这次改主意了。理由:「只换前后端」形态下新栈
本来就不碰数据库进程,双跑的增量风险只剩「两个后端同时写同一个库」这一条;
换来的是**正式切换退化成改一行 NPM 上游**,比停机切换更稳,回滚也不用停任何容器。

## 两个新变量

    WEB_PORT          对外端口。8080 还被旧 backend 占着(机房是 81)
    JUDGE_STATE_DIR   判题机运行状态目录

JUDGE_STATE_DIR 是必须的:不设的话新旧两个 judger 会同时往
`data/judge_server/{run,log}` 里写。默认值用嵌套写法跟着 DATA_DIR 走
(`${JUDGE_STATE_DIR:-${DATA_DIR:-../data}/judge_server}`,compose 支持嵌套默认值,
试过),所以一次性切换那条路径完全不受影响。

test_case 和 public/upload 仍然共享 —— 那是故意的,测试点和题面图片两边必须
看到同一份。

## 手册

新增第四节「并行试跑」,后面章节顺移(原四~九 → 五~十),两处交叉引用一并改了。
第五节拆成两条路径:试跑过的只需改 NPM 上游 + 事后停旧栈;没试跑的走原来那套。
第七节回滚同理。

试跑那节写明了三件容易踩的:NPM 的 Websockets Support 必须打开(漏了的话页面
一切正常,唯独「判题中…」永远不动,而刷新一下结果就出来,自测很难发现)、
两边登录态不互通、以及双写的是真实数据不是沙盒(别在 oj2 上办正式比赛)。

## 验证

试跑形态 `config` 解析:判题机目录落到 judge_server_oj2、端口 8090、test_case
仍指向共享的那份;不设新变量时默认值一个没变(judge_server / 8080)。
四份 compose `config -q` 全通过。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 09:04:16 -06:00
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
967e9ef7f7 feat: 路由遮蔽检查脚本;切换前把前后端接口逐条对了一遍
## 切换前核对:前端调的接口后端有没有漏

写脚本把前端 224 个调用点和后端注册的路由逐条比对。**零缺口** —— 唯一报出来的
一条是我的正则被嵌套括号截断了(`users/${encodeURIComponent(...)}/badges`),
后端那条路由是有的。「后端有、前端没调」那 25 条也逐个看过,全是脚本的假阳性
(把 `.get("user")` 这类非路由调用当成了路由)和假阴性(前端用三元表达式拼路径,
正则看不见,比如 `GET /me` 其实在 shared/api.ts 里被调)。

结论是没发现缺口,但这个脚本不够可靠、不足以证明"一定没有",所以没留进仓库。

## 路由遮蔽检查(留成常驻脚本)

比"有没有漏"更值得防的是遮蔽:**Hono 按注册顺序匹配,不是静态优先**。
`/problems/:id` 注册在 `/problems/random` 前面的话,后者永远进不去 ——
不报错、不警告,只是静默走进前一条的 handler。阶段 4 真实发生过一次,
两个教师用的分析端点被吃掉,一直到评审才发现。

全仓 167 条路由按真实注册顺序扫:**零遮蔽**。

这个结论敢下,是因为检测器本身也验了:
- 自检用例里放了阶段 4 那个历史真实案例,能抓到;边界(两边都是参数、
  段数不同、不同前缀)不误报
- 核对了 24 个 router 全在扫描范围内,没有漏扫
- 反向验证:往 problem.ts 末尾加一条注册在 `:displayId` 之后的字面量路由,
  脚本立刻报出来并 exit 1

未经验证的检测器报"没问题"是没有意义的 —— 这个教训今天已经吃过两次
(tree-sitter 那次、SQL 内存那次)。

脚本落在 apps/api/src/scripts/check-route-shadowing.ts,
`bun run --filter '@oj2/api' check:routes`,加完路由跑一下。
CLAUDE.md 里也写了。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 03:42:37 -06:00
f6bc42f534 docs: 给 OJ2 补一份自己的 CLAUDE.md;Dockerfile 拷上 bunfig.toml
## bunfig.toml 没拷进容器,才是那批「本地能过、容器过不了」的根因

bunfig.toml 里设了 `linker = "hoisted"`,但 Dockerfile 只拷了 package.json 和
bun.lock,于是容器里用的是默认的 isolated 布局(包都在 node_modules/.bun/ 下),
本地靠"提升"才解析得到的包在容器里一律 Could not resolve —— 而本地构建始终是好的,
只有镜像构建才炸。之前是一个一个补直接依赖补过去的(那是对的、该保留),
这里让两边布局一致,是第二道保险。

带上之后装 622 个包(isolated 是 1238),镜像重建通过,二进制在容器里
serve + healthcheck 正常。

顺手补了 main.ts 帮助文本里漏掉的 healthcheck 子命令(compose 里把 command
写错时,看到的就是这行)。

## OJ2/CLAUDE.md

OJ2 是独立仓库,之前没有自己的项目指引。写了一份,重点是几条「不知道就会踩」的:

- **本机 Docker 可用**,整套依赖和上线演练都能在本机跑 —— 别沿用上一代
  "本机跑不起来后端"的旧假设
- 单二进制不能依赖 node_modules,`.node` 资源导入 dev 和编译两种形态行为不同,
  **改完两种形态都要跑**
- SQL 判题 spawn 的是二进制自己,入口必须有 argv 分发,那道递归闸不能删
- 判题状态码三处同步、raw_password 要保留、比赛只有 ACM、前端要兼容老 Chrome
- 不写迁移:新旧后端跑同一套表结构,这是回滚能成立的前提

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 03:37:45 -06:00