Files
order_site/docs/部署启动与数据库重建.md
yml2213 08318ef8d9 feat(deploy): 财务上线数据阶段编排与运维加固
- 新增 deploy/post-deploy.sh:回填 → 业务配置 → 同步 → 验收四阶段编排,支持 --only/--dry-run/窗口参数
- 新增 tools 镜像与 scripts/verify-finance-launch.sql(15 项上线验收)
- 验收口径:成本闸门二选一、要求已过账凭证、备用金支出关联、成本覆盖 WARN
- 加固:参数校验、工具镜像每次刷新、docker run 兜底超时、迁移前缀探测、验收失败退出码 2
- 工具镜像预建 /app/data,消除日志 EACCES
- 回填后与 restore-db 恢复后自动 VACUUM (ANALYZE),修复订单列表分页 COUNT 全表扫描(41 万单实测 27s → 0.17s)
- 文档:41 万单同步用 7 天窗 + 180s 超时;回填/恢复后刷新统计
2026-10-28 11:52:42 +08:00

12 KiB
Raw Permalink Blame History

部署启动与数据库重建

本文记录本地开发、数据库迁移、重建库和 Ubuntu 服务器部署的常用命令。

数据库迁移策略

项目使用 node-pg-migrate,迁移目录:

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;也可用命令手动执行。

开发常用命令

# 查看本地文件与库内已应用记录
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 网络内有效):

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

本地开发启动

首次:

cp .env.mac-docker.example .env
docker compose -f docker-compose.dev.yml up -d --build

日常:

docker compose -f docker-compose.dev.yml up -d

状态与日志:

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:

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 名称不确定时:

docker volume ls | grep postgres_dev_data

删除当前 dev compose 全部 volume(会重装 node_modules volume):

docker compose -f docker-compose.dev.yml down -v
docker compose -f docker-compose.dev.yml up -d --build

确认迁移记录:

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 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();
}
'

本地验证

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。

cp .env.server.example .env

至少配置:

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:

DATABASE_URL=postgres://postgres:你的数据库密码@postgres:5432/order_site

不要写成容器内的 127.0.0.1(那会指向后端容器自己)。

部署:

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,避免顺手把数据库镜像静默升级)。

docker compose ps
docker compose logs -f backend

财务/成本上线后的初始化(迁移 103–105)

代码与迁移只负责建表;存量订单回填、备用金账户、积分余额、商品目录、人工 SKU 不会自动创建。 部署成功后按「回填 → 商品初始化 → 财务同步 → 验收」四步走:

1. 数据回填

bash deploy/post-deploy.sh --dry-run            # 先看计划与待回填量
bash deploy/post-deploy.sh --only=backfill      # ① 收入回填 + ② 履约方式回填

不要在商品目录为空时直接跑 --only=sync:财务同步会为每个未归一订单项写一条 product_mapping_missing 成本异常(生产规模 90 天实测约 36 万条),后台异常队列会被淹没。

脚本包含 revenue:backfill、fulfillment:backfill、finance:sync 与只读验收四段;任何一步都能重跑, --only=backfill|sync|verify 可单独执行。

生产 backend 镜像没有 tsx 与 scripts/,脚本通过 tools 镜像执行 (deploy/docker/tools.Dockerfile,docker compose --profile tools)。

2. 业务配置:后台 UI

  1. 钱包管理 → 补建客服备用金账户 → 按需拨付;
  2. 商品与成本 → 积分钱包 → 给客服发放三平台积分;
  3. 商品与成本 → 标准商品 → 从接单模板初始化 → 按工单回填商品 → 归一人工商品;
  4. 商品与成本 → 人工 SKU 配置 → 扫描并逐项配置(保存后自动回填订单项)。

第 3 步的商品目录/工单回填必须先于下一节 --only=sync。商品目录为空时同步会为每个 未归一订单项写一条 product_mapping_missing 异常(实测 90 天约 36 万条)。

3. 财务同步

商品归一完成后执行:

bash deploy/post-deploy.sh --only=sync          # ③ 成本快照 / 销售事实

工具镜像每次运行都会重建(层缓存下只需几秒),确保 git pull 后执行的是当前脚本。

4. 总账与验收

  1. 财务 → 总账与对账 → 同步销售事实到总账 → 检查试算平衡;按需关账/导入平台账单。
bash deploy/post-deploy.sh --only=verify        # ④ 只读验收

退出码:0 = 完成且验收无 FAIL、1 = 执行失败、2 = 验收未通过。 15 项检查全部 PASS 才算上线完成。若 15 号「近 7 天非人工销售事实成本覆盖」显示 WARN, 说明自动发货/接单平台仍有成本缺口,需要先归一商品并配置成本版本;WARN 不阻断验收, 但会直接影响毛利口径。

存量初始化完成后,日常重跑可直接 bash deploy/post-deploy.sh(回填 + 同步 + 验收一条龙)。

历史人工成本不补录;人工成本监控从第一次创建人工履约配置的时间开始。 定时任务 cost-finance-sync 每 5 分钟运行,但只覆盖最近 7 天;更早历史靠 post-deploy.sh 的分窗同步。


服务器日常更新

cd /home/order_site && git pull
bash deploy/ubuntu-deploy.sh            # 想看这次会做什么:加 --dry-run

后端启动时自动执行未应用的增量迁移,一般不必单独跑迁移。

确认:

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();
}
'

服务器重建数据库

会删除生产数据。仅适合新环境、测试机或确认可丢数据的场景。

docker compose down
docker volume rm order_site_postgres_data
bash deploy/ubuntu-deploy.sh

或:

bash deploy/ubuntu-deploy.sh --reset-db

volume 名不确定时:

docker volume ls | grep postgres_data

不要随手:

docker compose down -v

这会连同 caddy_data / caddy_config 一起删掉,可能触发证书重签。

常见问题

后端一直未 ready

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 的方式对齐。

如何新增字段 / 索引

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 并映射端口。