Files
order_site/docs/部署启动与数据库重建.md
T
yml2213 0b48313617 feat(deploy): 部署改成按需重建,无 schema 变更自动跳过备份
每次部署都无条件重建 backend + web 两个镜像(实测后端 3m35s、前端 2m54s),
并且每次都在迁移前 pg_dump + 传 OSS(生产 492MB dump 那一步约 12 分钟);
只改了后端也要把前端整条链重跑一遍。

改动:
- 新增 deploy/lib/classify-changes.sh:把改动文件映射成"要重建哪一端",
  规则保守——apps/backend/** → backend、apps/frontend/**|deploy/caddy/**|前端 Dockerfile → web、
  文档/部署脚本/测试文件 → 不重建,compose 文件与未知路径 → 两端都重建。
- ubuntu-deploy.sh 用 .deploy-state(上次成功部署的 commit + .env 指纹)算 git diff,
  未变化的一端不 build、也不 --force-recreate,容器保持运行;若它没在跑则用现有镜像拉起。
  两端都要建时合并为一次 compose build(bake 并行)。
- 备份:改动不含 apps/backend/src/db/migrations/** 时自动跳过(--backup 强制、
  --skip-backup 无条件跳过);算不出改动范围(首次使用、force push)时不自动跳过。
- compose pull 改 --policy missing:不再每次重新校验 postgres tag,避免顺手静默升级数据库镜像。
- 新增 --dry-run(打印计划后退出,含基础镜像 digest)、--all、--backend-only/--web-only;
  部署结束打印各阶段耗时,回答"这次怎么比上次慢"不用靠推算。
- .deploy-state 进 .gitignore;README 与 docs/部署启动与数据库重建.md 同步。

验证:新增 7 个用例直接执行分类脚本断言"改动→重建范围",并守住"状态只在健康检查后才落盘"
等关键顺序;全量 595 个单测、typecheck、lint、check:sql 全通过。
2026-10-03 16:29:05 +08:00

311 lines
9.1 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.
# 部署启动与数据库重建
本文记录本地开发、数据库迁移、重建库和 Ubuntu 服务器部署的常用命令。
## 数据库迁移策略
项目使用 `node-pg-migrate`,迁移目录:
```text
apps/backend/src/db/migrations/
001_init.sql 空库基线(完整结构)
002_xxx.sql 后续增量(示例命名)
003_xxx.sql
...
```
记录表:`schema_migrations`。
| 场景 | 行为 |
| --- | --- |
| 全新空库 | 按序号执行 `001_init` → 全部增量 |
| 已有库升级 | 只执行尚未记录的增量文件 |
| 改历史迁移文件内容 | **不会**自动重跑;已应用文件禁止修改 |
约定:
1. `001_init.sql` 只作为基线;上线后结构变更不要改它。
2. 每次 schema 变更新增 `NNN_name.sql`(三位序号)。
3. 迁移与业务代码同一变更一起提交。
4. 后端启动会自动 `up`;也可用命令手动执行。
### 开发常用命令
```bash
# 查看本地文件与库内已应用记录
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate:status
# 手动执行未应用迁移
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate
# 创建下一份增量迁移模板
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate:create -- add_column_example
```
宿主机直连(`postgres` 主机名仅在 Docker 网络内有效):
```bash
cd apps/backend
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/order_site npm run db:migrate:status
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/order_site npm run db:migrate
```
## 本地开发启动
首次:
```bash
cp .env.mac-docker.example .env
docker compose -f docker-compose.dev.yml up -d --build
```
日常:
```bash
docker compose -f docker-compose.dev.yml up -d
```
状态与日志:
```bash
docker compose -f docker-compose.dev.yml ps
docker compose -f docker-compose.dev.yml logs -f backend
docker compose -f docker-compose.dev.yml logs -f frontend
```
本地入口:
- 前端、后台、领取页:`http://localhost`
- 健康检查:`http://localhost/health`
- 就绪检查:`http://localhost/health/ready`
## 本地重建数据库
仅在需要清空开发数据,或本地库与迁移历史严重不一致时使用。
只重建开发库 volume:
```bash
docker compose -f docker-compose.dev.yml down
docker volume rm order_site_postgres_dev_data
docker compose -f docker-compose.dev.yml up -d --build
```
volume 名称不确定时:
```bash
docker volume ls | grep postgres_dev_data
```
删除当前 dev compose 全部 volume(会重装 node_modules volume):
```bash
docker compose -f docker-compose.dev.yml down -v
docker compose -f docker-compose.dev.yml up -d --build
```
确认迁移记录:
```bash
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate:status
```
或:
```bash
docker compose -f docker-compose.dev.yml exec -T backend node --input-type=module -e '
const { query, closeDb } = await import("./src/db/client.ts");
try {
const result = await query("SELECT COALESCE(filename, name) AS name FROM schema_migrations ORDER BY COALESCE(filename, name)");
console.log(result.rows.map((row) => row.name).join("\n"));
} finally {
await closeDb();
}
'
```
## 本地验证
```bash
docker compose -f docker-compose.dev.yml exec -T backend npm test
docker compose -f docker-compose.dev.yml exec -T backend npm run typecheck
docker compose -f docker-compose.dev.yml exec -T backend npm run build
docker compose -f docker-compose.dev.yml exec -T frontend npm run typecheck
docker compose -f docker-compose.dev.yml exec -T frontend npm run build
```
## 服务器首次部署
建议 Ubuntu + Docker Compose。
```bash
cp .env.server.example .env
```
至少配置:
```text
APP_DOMAIN
CADDY_SITE_ADDR
WORKER_SITE_ADDR
CLAIM_BASE_URL
POSTGRES_PASSWORD
DATABASE_URL
ADMIN_SESSION_SECRET
ADMIN_DEFAULT_USERS_JSON
CORS_ALLOWED_ORIGINS
STORAGE_MODE
STORAGE_OSS_ENDPOINT
STORAGE_OSS_BUCKET
STORAGE_OSS_ACCESS_KEY_ID
STORAGE_OSS_SECRET_ACCESS_KEY
STORAGE_OSS_REGION
```
其中 `APP_DOMAIN`、`CORS_ALLOWED_ORIGINS` 与 `STORAGE_MODE=oss` / `STORAGE_OSS_*` 为生产必需:`docker-compose.yml:95` 的 `STORAGE_MODE` 生产默认即 `oss`,`apps/backend/src/config/runtime-validation.ts:107-114` 在生产环境强制校验 `STORAGE_OSS_*` 凭据并使用显式 CORS 域名白名单;`.env.server.example` 已给出这些变量的模板值,其中带 `change-me` 的占位会被 `deploy/ubuntu-deploy.sh` 的占位值检查拦住,需替换为真实值。
容器内连接 Compose PostgreSQL 时,主机名用 `postgres`:
```text
DATABASE_URL=postgres://postgres:你的数据库密码@postgres:5432/order_site
```
不要写成容器内的 `127.0.0.1`(那会指向后端容器自己)。
部署:
```bash
bash deploy/ubuntu-deploy.sh
```
脚本会:检查 Docker、检查 `.env` 占位值、检查 `DATABASE_URL`、修正 `apps/backend/data` 权限、按改动决定重建哪一端、启动、等待健康检查、打印已应用迁移与各阶段耗时。
### 按需重建(默认行为)
脚本把「上次成功部署的 commit + `.env` 指纹」记在项目根的 `.deploy-state`(已 gitignore),
再按 `git diff --name-only 上次commit..HEAD` 判断本次要重建哪一端:
| 改动 | 结果 |
| --- | --- |
| `apps/backend/**`(含 migrations) | 只重建 backend 镜像与容器 |
| `apps/frontend/**`、`deploy/caddy/**`、前端 Dockerfile | 只重建 web 镜像与容器 |
| `README.md`、`docs/**`、`deploy/*.sh`、`deploy/lib/**`、`scripts/**` | 不重建任何镜像,容器保持运行(只剩迁移 + 健康检查) |
| `docker-compose.yml`、`.env` 变化、未知路径、上次 commit 不在仓库里(force push/回滚) | 两端都重建(保守兜底) |
不需要重建的一侧既不跑 Docker 构建、也不 `--force-recreate`;如果它恰好没在运行(服务器重启过),
脚本用现有镜像把它拉起来。首次在有 `--skip-backup` 之前用本脚本时没有 `.deploy-state`,
会按"全量重建一次"处理,之后即进入增量。
备份策略同理:本次改动不含 `apps/backend/src/db/migrations/**` 时自动跳过迁移前备份
(生产上 `pg_dump` + 传 OSS 接近 12 分钟);`--backup` 可强制备份,`--skip-backup` 可无条件跳过,
只有"能算出改动范围"时才会自动跳过。
常用参数:
| 参数 | 说明 |
| --- | --- |
| `--dry-run` | 只打印计划后退出:上次部署的 commit、改动文件、要重建哪一端、是否备份、基础镜像 digest |
| `--all` | 强制两端都重建 |
| `--backend-only` / `--web-only` | 只重建指定一端 |
| `--backup` | 强制迁移前备份 |
| `--skip-backup` | 跳过迁移前备份 |
| `--reset-db` / `--yes` | 见下文「服务器重建数据库」 |
基础镜像也只补缺:`docker compose pull --ignore-buildable --policy missing`
(不再每次部署重新校验 postgres tag,避免顺手把数据库镜像静默升级)。
```bash
docker compose ps
docker compose logs -f backend
```
## 服务器日常更新
```bash
cd /home/order_site && git pull
bash deploy/ubuntu-deploy.sh # 想看这次会做什么:加 --dry-run
```
后端启动时自动执行未应用的增量迁移,一般不必单独跑迁移。
确认:
```bash
docker compose exec -T backend node --input-type=module -e '
const { query, closeDb } = await import("./dist/db/client.js");
try {
const result = await query("SELECT COALESCE(filename, name) AS name FROM schema_migrations ORDER BY COALESCE(filename, name)");
console.log(result.rows.map((row) => row.name).join("\n"));
} finally {
await closeDb();
}
'
```
## 服务器重建数据库
**会删除生产数据**。仅适合新环境、测试机或确认可丢数据的场景。
```bash
docker compose down
docker volume rm order_site_postgres_data
bash deploy/ubuntu-deploy.sh
```
或:
```bash
bash deploy/ubuntu-deploy.sh --reset-db
```
volume 名不确定时:
```bash
docker volume ls | grep postgres_data
```
不要随手:
```bash
docker compose down -v
```
这会连同 `caddy_data` / `caddy_config` 一起删掉,可能触发证书重签。
## 常见问题
### 后端一直未 ready
```bash
docker compose ps
docker compose logs --tail=120 backend
```
常见原因:
- `.env` 仍有 `change-me` 占位值
- `DATABASE_URL` 密码与已有 PostgreSQL volume 初始化密码不一致
- 容器部署里 `DATABASE_URL` 误写 `127.0.0.1`
- 某次增量迁移 SQL 失败
### 改了 `001_init.sql` 但旧库没变
已应用过的迁移不会因文件内容变更而重跑。正确做法是 **新增** `002_xxx.sql` 写增量。
仅本地可丢数据时,才用重建 volume 的方式对齐。
### 如何新增字段 / 索引
```bash
docker compose -f docker-compose.dev.yml exec -T backend \
npm run db:migrate:create -- add_orders_note
# 编辑生成的 apps/backend/src/db/migrations/00N_add_orders_note.sql
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate
docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate:status
```
### 宿主机手动跑迁移连不上 `postgres`
`.env` 里的 `postgres` 只在 Docker 网络有效。宿主机请临时改为 `127.0.0.1` 并映射端口。