一张总图:请求从浏览器走到数据库 · znzryb
## 一张总图
先放全貌,后面几节再一块块拆开:
```text
浏览器(React 页面)
│ HTTPS
▼
nginx ──────────────── 生产机(腾讯云,上海)────────────────
├─ /assets/…、图片 → 直接读硬盘上的前端构建产物
└─ /api/… → 转给 127.0.0.1:8080
│
▼
oj-server(后端,Go + Gin)
│ SQL
▼
PostgreSQL ◀───────── SQL(抢任务、写结果)
▲ │
│ oj-judge(判题机)
│ │ HTTP
│ ▼
│ go-judge(沙箱,真正跑选手代码)
│
反向 SSH 隧道
│
家里那台评测机(在 NAT 后面)
也跑 oj-judge + go-judge,连的是同一个 PostgreSQL
```
## 四个进程,各干什么
生产机上用 systemd 跑着这几个服务:
| 服务 | 是什么 | 代码入口 |
|---|---|---|
| `oj-server` | 后端:所有 `/api/…` 接口 | `backend/cmd/server/main.go` |
| `oj-judge` | 判题机:从数据库里抢待判的提交、编译运行、写结果 | `backend/cmd/judge/main.go` |
| `go-judge` | 沙箱:真正去执行选手代码、限制时间和内存 | 第三方程序,`oj-judge` 通过 HTTP 调它 |
| `go-judge-prepare` | 给出题工作台用的另一个沙箱 | 同上 |
**最关键的一点:`oj-server` 和 `oj-judge` 之间不直接说话。** 它们只通过数据库交流 —— 后端把提交写进 `submissions` 表(状态 `pending`),判题机自己去表里抢。这样判题机可以随便加:家里那台机器只要连得上数据库,就能一起分担。
## 后端进程启动时做了什么
`backend/cmd/server/main.go` 的 `main` 函数,去掉日志和可选功能,骨架就这几步:
```go
s := &state.App{Config: state.ConfigFromEnv()} // 读环境变量
db.Migrate(url) // 把还没跑过的数据库迁移跑掉
pool, _ := db.Connect(ctx, url) // 建连接池
s.DB = pool
r := app.BuildRouter(s) // 装配所有路由
r.Run("0.0.0.0:8080") // 开始监听
```
`state.App` 是整个后端唯一的「全局对象」,里面最重要的就是 `DB *pgxpool.Pool`(连接池)和配置。每个接口都从它手里拿连接池 —— 下一节会看到。