一小时读码路线 · znzryb
## 一小时读码路线
按这个顺序打开文件,每一步只看点名的那几处:
| 顺序 | 打开 | 看什么 | 时间 |
|---|---|---|---|
| 1 | `backend/cmd/server/main.go` | `main` 函数:读配置 → 迁移 → 连库 → `BuildRouter` → `Run` | 5 分钟 |
| 2 | `backend/internal/server/state/` 里的 `App` | 全局只有这一个对象,看它有哪些字段 | 3 分钟 |
| 3 | `backend/internal/server/app/app.go` | 路由表。挑三个你熟悉的页面,找到它们调的接口 | 10 分钟 |
| 4 | `backend/internal/server/problems/problems.go` 的 `List` | 一个典型 handler:校验、SQL、返回 | 8 分钟 |
| 5 | `backend/internal/gamify/onfirstac.go` | 领域包怎么接过 `tx`、在一个事务里改多张表 | 8 分钟 |
| 6 | `backend/internal/judge/scheduling.go` 的 `claimInitialTx` | `FOR UPDATE SKIP LOCKED` 抢任务 | 6 分钟 |
| 7 | `backend/internal/server/db/migrations/` 最新的三个文件 | 迁移怎么写、Up / Down | 5 分钟 |
| 8 | `frontend/src/api/http.ts` 和 `client.ts` 里随便一个 `api.xxx` | 前端怎么调接口 | 5 分钟 |
| 9 | `frontend/src/App.tsx` | 前端路由表,对照第 3 步 | 5 分钟 |
## 读的时候带着这三个问题
1. **这条 SQL 在不在事务里?** 看它用的是 `s.DB.Query`(不在)还是 `tx.Exec`(在)。要同时改好几张表的地方,一定是事务。
2. **「只做一次」是靠什么保证的?** 找账本表和 `ON CONFLICT DO NOTHING`。
3. **谁能看见?** 带「仅管理员可见 / 草稿」的东西,SQL 里一定有一段可见性条件;漏了就是泄露。
## 小结
| 经典说法 | 我们这里对应的是 |
|---|---|
| Controller | Gin 的 handler(`internal/server/<功能>/`),工厂函数 `func X(s *state.App) gin.HandlerFunc` |
| Service | 大部分写在 handler 里;重的逻辑抽成领域包(`internal/gamify`、`content`、`judge`、`prepare`) |
| DAO | **没有**。SQL 直接写在 handler 和领域包里,用 pgx 执行 |
| Table | PostgreSQL,表结构由 goose 迁移管理 |
| 消息队列 | **没有**。`submissions` 表本身就是队列,判题机用 `FOR UPDATE SKIP LOCKED` 抢 |