3 Commits

Author SHA1 Message Date
f00c941ede perf(构建): builder 分开拷源码,只改后端不再陪编一次前端
Some checks failed
Deploy / deploy (push) Has been cancelled
builder 阶段是 `COPY . .` 一把梭,而 docker 的层缓存是链式的:那一层的校验和
覆盖整个仓库,改任何一个文件都会把它作废,后面 vite build 和 bun compile 全部
重跑。前端那 160s 一次都躲不掉,改一篇 markdown 也要陪着编。

按各自的输入分开拷,慢的那条放前面(缓存链上越靠前越不容易被碰到):

  只改 apps/api           前端两层命中缓存,只重编后端       ~1s
  只改 apps/web           前端重编,后端被链条带着            ~160s
  改 packages/contract    两边都重编(本来就都依赖它,是对的)
  改 docs / 根上的杂项    builder 根本看不见                  0

代价是 builder 只看得见这里点名拷进来的四样,新加顶层目录要同步加 COPY。

顺带堵掉两个同源的洞:

· apps/web/docs、tests、**/CLAUDE.md 进了 .dockerignore。它们在 apps/web 里面,
  整个目录拷进去的话,改一篇文档也会让 vite build 那层失效。

· .dockerignore 末尾为 --prebuilt 开的例外(!dist/oj2-api、!apps/web/dist)让本地
  产物照样进构建上下文 —— 这是比 `COPY . .` 更隐蔽的一条:在本机编一次后端,
  80MB 的 dist/oj2-api 变了,rsync 上去,服务器就重编一次前端,源码一行没动。
  dist/oj2-api 现在不再被 builder 拷贝;apps/web/dist 仍在 COPY apps/web 里面,
  所以 deploy.sh 头上那条手动 rsync 补了 --exclude dist。CI 走 --prebuilt,
  builder 整个不进构建图,不受影响。

验证:用老 Dockerfile 和新 Dockerfile 各构建一次 --target artifacts 导出对比,
后端二进制 sha256 相同,前端 dist 500 个文件逐个 sha256 一致。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 10:25:15 -06:00
6356643cc9 ci(部署): 部署走 GitHub Actions,产物挪到 runner 上编
Some checks failed
Deploy / deploy (push) Has been cancelled
服务器性能差,镜像 builder 阶段(1238 个包的 bun install + bun compile +
vite build)首次约 5 分钟,光前端就 160s。改成在 runner 上编好 dist/oj2-api
和 apps/web/dist,rsync 过去,服务器那边只剩几条 COPY。

服务器自己编的能力没有砍掉。Dockerfile 顶部加了全局 ARG ARTIFACTS:

  build(默认)    builder 阶段自己编,只要有 docker 就能手动部署
  prebuilt         用构建上下文里现成的产物,builder 不进构建图

实现是两个 scratch 阶段归一成 /artifacts/ 布局,再用变量阶段名
FROM artifacts-${ARTIFACTS} 选一个,运行时阶段不关心产物是谁编的。

deploy.sh 加 --prebuilt:产物缺失在自检就 die;顺带检测 buildx —— 没装的话
compose 会退回 classic builder,那个不看依赖图、所有阶段挨个跑,产物白编
(结果正确但不省时间),黄字提醒不阻断。

.dockerignore 末尾放行两个产物路径,必须在 dist/ 那些规则之后,
dockerignore 是最后一条匹配说了算。

顺手删掉 apps/web/.github/ —— 从 ojnext 抄来的残留,路径不对(GitHub 只认
仓库根的 .github/workflows/)从来没跑过,内容也是 ojnext 的。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 18:30:22 -06:00
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