修复接单模板动态匹配并移除冗余运维文档

This commit is contained in:
yml
2026-11-15 00:00:04 +08:00
parent 572c43ce0e
commit 36f52a7f68
20 changed files with 627 additions and 1330 deletions
+2 -3
View File
@@ -18,11 +18,10 @@
| 文档 | 用途 |
| --- | --- |
| [生产升级操作手册](生产升级操作手册.md) | 含迁移、履约切换、财务重建的生产升级与回滚步骤 |
| [部署启动与数据库重建](部署启动与数据库重建.md) | 本地与生产部署、数据库重建 |
| [数据库备份与恢复](数据库备份与恢复.md) | 备份、恢复、OSS |
| [数据库SQL约束与运维](数据库SQL约束与运维.md) | SQL 护栏与生产观测 |
部署、备份、恢复和生产升级以 `deploy/` 下脚本、Compose 文件和实际迁移为准,不另设文档副本。
## 专项参考
专项参考只解释具体平台、工具或历史问题,不能覆盖唯一权威文档。
@@ -513,4 +513,4 @@ npm run revenue:backfill -- --batch-size=2000 --max-batches=100
- 接口契约: [成本中心API契约.md](成本中心API契约.md)
- 人工支出与备用金: [人工支出与备用金.md](人工支出与备用金.md)
- 影子总账: [财务系统集成方案.md](财务系统集成方案.md)
- 运维: [数据库SQL约束与运维.md](数据库SQL约束与运维.md)、[部署启动与数据库重建.md](部署启动与数据库重建.md)
- 运维: [数据库SQL约束与运维.md](数据库SQL约束与运维.md)
-155
View File
@@ -1,155 +0,0 @@
# 数据库备份与恢复
生产数据库(PostgreSQL,Compose 容器)每日备份到阿里云 OSS,与图片存储共用 OSS 基础设施,但使用独立的 `backups/` 前缀与(建议)独立的 RAM 凭证。
## 备份内容与存放
| 内容 | 方式 | OSS key 前缀 |
| --- | --- | --- |
| PostgreSQL 全库 | `pg_dump -Fc`(custom 格式,自带压缩,MVCC 快照无需停机) | `backups/postgres/` |
| 平台 JSON 配置(`apps/backend/data`,排除日志/缓存) | `tar -czf` | `backups/configs/` |
- 每个备份对象附带 `<key>.manifest.json` 清单:大小、sha256、创建时间、数据库版本、已应用迁移列表。没有有效清单的备份默认不会被 `latest` 选择,恢复也会拒绝;只有明确使用 `--allow-unverified` 才兼容历史备份。
- 不备份的内容:图片(已在 OSS)、Caddy 证书(自动重新签发)、日志(`apps/backend/data/logs`)。
- PostgreSQL dump 是数据库内部一致快照,但数据库与 OSS 不共享事务;备份时刻附近的新图片可能只存在一侧。恢复后应按 `file_assets.object_key` 抽查 OSS 对象,并为图片桶开启版本控制/保留策略。
- 服务器本地在 `backups/` 目录保留最近 `DB_BACKUP_KEEP` 份(默认 7),OSS 端过期由桶生命周期规则管理。
- 涉及脚本:`deploy/backup-db.sh`(备份)、`deploy/restore-db.sh`(恢复/演练)、后端镜像内 `dist/db-backup.js`(OSS 传输与校验)。
## 首次开通
以下步骤在阿里云控制台与服务器上各执行一次。
### 1. RAM 授权(建议独立,可直接复用现有凭证)
现有 `STORAGE_OSS_*` 的 RAM 用户拥有整个桶的权限,也能直接跑备份。但备份包含全部订单与用户数据,是最敏感的资产,建议单独创建一个 RAM 用户 `order-site-backup`,只授予 `backups/` 前缀,自定义权限策略:
```json
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": ["oss:PutObject", "oss:GetObject"],
"Resource": ["acs:oss:*:*:你的桶名/backups/*"]
},
{
"Effect": "Allow",
"Action": ["oss:HeadBucket"],
"Resource": ["acs:oss:*:*:你的桶名"]
},
{
"Effect": "Allow",
"Action": ["oss:ListObjects"],
"Resource": ["acs:oss:*:*:你的桶名"],
"Condition": {
"StringLike": { "oss:Prefix": ["backups/", "backups/*"] }
}
}
]
}
```
为该用户创建 AccessKey,填入服务器 `.env` 的 `DB_BACKUP_OSS_ACCESS_KEY_ID` / `DB_BACKUP_OSS_SECRET_ACCESS_KEY`(不填则回退 `STORAGE_OSS_*`)。若用独立备份桶,同时覆盖 `DB_BACKUP_OSS_ENDPOINT` / `DB_BACKUP_OSS_BUCKET` / `DB_BACKUP_OSS_REGION`。
### 2. 桶生命周期规则(控制存储成本)
在 OSS 控制台为目标桶添加生命周期规则:
- 前缀 `backups/`:最后一次访问 90 天后删除(按需调整,数据库每日一份,90 份足够覆盖一个季度)。
- 建议同时开启版本控制:误删备份时可找回旧版本;给非当前版本配置 7 天过期,避免版本堆积。
### 3. 手动执行一次,验证链路
```bash
bash deploy/backup-db.sh
bash deploy/restore-db.sh --list
bash deploy/restore-db.sh --verify
```
`--verify` 会把最新带有效清单的备份恢复到一个隔离的临时 PostgreSQL 容器(不接入业务网络,不影响线上库),并打印迁移列表与各表行数。建议之后每月执行一次演练——没验证过的备份等于没有备份。
生产升级前的恢复验证是硬门禁,并且必须验证本次生成的指定备份,而不是只验证任意旧备份。
具体步骤和当前恢复阻断项见
[生产升级操作手册](生产升级操作手册.md)。恢复验证未通过时禁止执行生产迁移。
### 4. 配置每日定时备份
```bash
crontab -e
```
```cron
# 每天 03:00 备份数据库与配置到 OSS(目录需先存在,首次开通时脚本已创建)
0 3 * * * cd /srv/order_site && bash deploy/backup-db.sh >> backups/backup.log 2>&1
```
把 `/srv/order_site` 换成实际项目目录。日志追加在 `backups/backup.log`,可用 logrotate 管理;备份失败时 cron 不通知,请定期查看该文件,或后续接入告警 webhook。
## 日常运维
- **部署自动备份**:`bash deploy/ubuntu-deploy.sh` 在每次执行数据库迁移前会自动备份一次;`--reset-db` 会在删除数据卷前抓最后一份旧库备份,并跳过重置后空库备份,避免空库被误选为 latest。备份失败会中止部署,确认为可接受后可用 `--skip-backup` 继续。
- **手动备份**:`bash deploy/backup-db.sh`(可加 `--keep N` 覆盖本地保留份数)。
- **查看备份列表**:`bash deploy/restore-db.sh --list`。
- **恢复演练**:`bash deploy/restore-db.sh --verify`(可加 `--key` 指定备份)。
- **兼容历史备份**:仅在确认对象来源可信时加 `--allow-unverified`,不要作为生产默认参数。
## 恢复
### 场景 A:整库恢复到最新备份(最常用)
```bash
bash deploy/restore-db.sh
```
流程:下载最新备份并强制校验 sha256 → 停 backend/web 并确认已停止 → `pg_restore --clean --if-exists` → 执行迁移补差 → `VACUUM (ANALYZE)` 刷新统计信息与可见性 → 重启并等待健康检查。任一步失败会尝试恢复原先运行的服务,并明确报告数据库可能处于部分恢复状态。执行前需交互输入 `RESTORE` 确认(非交互终端用 `RESTORE_CONFIRM=1` 或 `--yes`)。
> 恢复后必须 `VACUUM (ANALYZE)`:dump 不带 `pg_statistic`,没有统计信息时订单列表的 `COUNT` 会被规划成 `orders` 全表扫描,41 万单冷缓存实测 14–27 秒(默认语句超时 15 秒会直接 500)。`restore-db.sh` 已自动执行;手工 `pg_restore` 请补跑:
>
> ```bash
> docker compose exec -T postgres psql -U postgres -d order_site -c 'VACUUM (ANALYZE)'
> ```
如需同时恢复 `apps/backend/data` 配置:
```bash
bash deploy/restore-db.sh --restore-configs
```
也可以用 `--config-key` 指定配置归档。配置归档会先校验路径,再解包到 data 目录。
### 场景 B:恢复到指定时间点
```bash
bash deploy/restore-db.sh --list
bash deploy/restore-db.sh --key backups/postgres/order_site-20260823-030000.dump
```
### 场景 C:服务器全损(数据卷丢失/换机)
在新服务器完成部署(`docs/部署启动与数据库重建.md`)后,把旧服务器 `.env`(含 OSS 凭证)恢复到新服务器,再执行场景 A。图片不受影响(在 OSS),平台 JSON 配置可从 `backups/configs/order_site-<时间戳>.tar.gz` 解回 `apps/backend/data/`。
### 场景 D:外部数据库(DATABASE_URL 非 postgres 服务)
`backup-db.sh` 与 `restore-db.sh` 会自动识别 `.env` 的 `DATABASE_URL`:指向外部库时,从 postgres 容器经网络直连执行 pg_dump/pg_restore,因此服务器仍需有可用的 postgres 镜像/容器作为客户端。恢复外部库前会额外告警确认目标。
### 恢复后检查
- 打开后台登录、近期订单列表,抽查数据到备份时间点为止。
- `bash deploy/restore-db.sh --list` 确认下一次定时备份已恢复产生新对象。
## 故障排查
| 现象 | 处理 |
| --- | --- |
| `[db-backup] HeadBucket 403` | RAM 策略缺 `oss:HeadBucket`,按上文策略补齐 |
| `ListObjects 403` | 策略缺 `oss:ListObjects` 或 `oss:Prefix` 条件与实际前缀不匹配 |
| `PutObject 403` | `Resource` 里的桶名/前缀与 `.env` 的 `DB_BACKUP_OSS_BUCKET` / `DB_BACKUP_OSS_PREFIX` 不一致 |
| `缺少 OSS 配置` | `.env` 里 `DB_BACKUP_OSS_*` 与 `STORAGE_OSS_*` 都为空;改完后备份脚本用的是镜像内程序,若同时改了代码需重新构建 |
| 下载校验 `sha256 校验失败` | 备份对象被篡改或截断,换更早的备份重试 |
| `pg_restore` 报版本错误 | 不会发生在本编排内(pg_dump/pg_restore 与库同镜像);仅外部库场景需保证客户端 ≥ 服务端大版本 |
## 安全注意
- 备份 RAM 凭证只进服务器 `.env`(已 gitignore),不要提交到仓库。
- 备份桶/前缀保持私有,不要开公共读写;`backups/` 下的对象不要通过前端暴露。
- 若备份桶与图片桶相同,确认图片访问逻辑只读图片前缀,不会枚举 `backups/`。
-717
View File
@@ -1,717 +0,0 @@
---
type: 生产运维操作手册
description: order_site 生产结构升级、数据切换、验收与回滚的唯一执行步骤。
tags: [生产, 部署, 数据库, 迁移, 备份, 回滚, 财务]
status: current
version: 1.3
updated_at: 2026-11-14
---
# 生产升级操作手册
> 本手册适用于包含数据库迁移、领域模型切换或财务重建的生产升级。
> 业务与架构口径仍以
> [统一订单履约与财务模型-权威版](统一订单履约与财务模型-权威版.md) 为唯一权威。
>
> 涉及普通文档修改、无迁移的小版本更新,可继续使用
> [部署启动与数据库重建](部署启动与数据库重建.md)。
> 只要包含迁移、表结构切换或派生数据重建,必须按本手册执行。
> 2026-11-13 增量待发布基线:`dev_11.13` 从生产迁移 `151` 增量到 `161`。
> 该次变更包含 `152_order_source_facts_immutable.sql`、
> `158_consignment_worker_name_sync.sql`、`159_consignment_platform.sql`、
> `160_consignment_auto_match_setting.sql`、`161_semi_auto_fulfillment.sql`。
> 在发布负责人确认目标提交并完成隔离恢复演练前,只作为开发基线,
> 不得按旧的 `147–150` 一条路径直接上线。
>
> 2026-11-14 增量待发布基线:在 `161` 之上依次新增
> `162_fulfillment_queue_buckets.sql`、`163_work_order_hall_price_version.sql`、
> `164_template_product_cost_visibility.sql`、`165_work_order_price_sync_scope.sql`。
> `162` 建立履约待办四桶只读投影、补载体扫描专用索引和新单优先扫描口径;
> `163` 增加接单大厅价格成本版本引用、改价同步候选索引和版本闸门,不重写历史工单金额。
> `164` 增加接单模板到标准商品的显式关联,修复无订单模板商品落入未路由、无法先配置成本的问题。
> `165` 将模板改价同步范围收敛到 `open`,未发布工单在发布时物化最新价,避免批量改价扫描数万行。
> 两项迁移都不改写原始订单、履约尝试或售后事实;仍须完成停写、最终备份、隔离恢复验证、
> 迁移、健康检查、履约审计和大厅价格审计后才可恢复营业。
### 1.1 迁移 `151 → 165` 增量门禁
- 升级前必须确认生产最高迁移仍为 `151`;分支合并后重新核对迁移号,禁止回退或改写 `151` 及以前文件。
- 半自动迁移已收敛为单个 `161_semi_auto_fulfillment.sql`,不得在已应用 main `158–160`
的库上重新引入 `153–156` 或 `162–165`。
- `161` 一次新增账号级 Cookie JSON 凭证表、`semi_auto_tasks.credential_id`、
最终 Connector 来源约束和兼容保留的 `semi_auto.credential.export` 权限记录;
该权限不再参与 CK 导出判定,导出由后台登录、禁止缓存和审计控制;
不迁移或清理订单、退款、资金流水等 A 级事实。
- 迁移完成后至少验证 `semi_auto_credentials` 表、任务外键和索引存在,以及
`v_fulfillment_queue`、补载体部分索引存在;
- `163` 新增 `work_orders.hall_price_version_id`、模板工单进入 `open` 的版本闸门和
改价同步候选索引;正在使用寄卖现货的合同态工单豁免版本闸门。
- `164` 新增 `work_product_rules.canonical_product_id` 及唯一索引,并重建
`v_product_current`,让无订单的模板商品明确归属接单平台成本范围。
- `165` 删除未发布工单的模板改价候选索引;改价 API 只允许扫描并更新 `open` 工单。
- 用测试库完成 `001 → 165` 空库迁移,并在隔离库验证 `151 → 165`。
- 迁移后必须验证新建停用模板能在“接单平台”成本页签看到并配置成本,且不出现在“未路由”。
- 迁移后执行 `npm run audit:work-order-hall-pricing`,`critical` 必须为 0。
- 该增量仍包含迁移,普通停机发布不能使用 `deploy/ubuntu-deploy.sh` 在旧后端运行时直接换库;
必须沿用本手册的停写、最终备份、隔离恢复验证、迁移、新后端健康检查和验收流程。
## 一、本次升级基线
以下信息是 2026-11-08 的首次执行基线。后续每次升级必须由发布负责人更新,
旧值只允许保留在发布记录中,不能继续作为执行依据。
| 项目 | 值 |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 发布日期 | 2026-11-08 |
| 发布分支 | `dev_11.8` |
| 目标提交 | `d76e8265f806342c670d570e8ee72f5e8ab08fdc` |
| 生产起始迁移 | `146` |
| 目标迁移 | `150` |
| 新增迁移 | `147_internal_sku_product_routing.sql`、`148_fulfillment_execution_truth.sql`、`149_finance_derived_scope.sql`、`150_voucher_consume_order_received_scope.sql` |
| 升级类型 | 履约、内部 SKU、商品路由、财务派生数据硬切 |
| 预计维护窗口 | 2–3 小时 |
| 核心停写时间 | 20–40 分钟 |
同生产规模本地演练结果:
- 数据量:订单 `456777`、订单项 `456762`、履约 `456762`。
- `146 → 150` 单事务迁移:约 `61 秒`。
- 90 天财务重建:约 `14–17 秒`。
- 第二次财务重建:`ledger.adjusted = 0`,幂等成立。
- 严格财务审计:`critical: []`。
- 原始订单、订单项、收入、退款和资金流水守恒。
## 二、绝对禁止
1. 禁止使用 `deploy/ubuntu-deploy.sh` 执行本次 `147–150` 升级;它会在旧后端
仍运行时执行迁移。
2. 禁止在生产执行 `deploy/ubuntu-deploy.sh --reset-db`。
3. 禁止使用 `--skip-backup`。
4. 禁止在迁移前跳过 `restore-db.sh --verify`。
5. 禁止在旧后端仍运行时执行 `147–150` 硬切迁移。
6. 禁止迁移后用旧镜像启动后端;旧代码不兼容 `order_items.legacy_*`。
7. 禁止为了赶进度绕过硬门禁、数据守恒检查或财务审计。
8. 禁止自动猜测缺失渠道、商品归属或人工配置。
9. 禁止删除或重写原始订单、退款、真实工单、资金流水和审计日志。
10. 禁止在迁移过程中重启 PostgreSQL、强制杀迁移事务或重复启动第二个迁移进程。
11. 禁止在本轮观察期结束前删除 `legacy_*` 字段和兼容表。
## 三、角色与记录
执行前至少确定以下角色:
| 角色 | 职责 |
| ------------ | ---------------------------------------------- |
| 发布负责人 | 冻结提交、确认窗口、决定继续或回滚 |
| 数据库执行人 | 备份、迁移、统计刷新、数据检查 |
| 验收人 | 独立执行审计、核对输出,不复用执行人的口头结论 |
| 业务负责人 | 确认已知待分类项、财务口径和恢复营业 |
整个过程中必须记录:
- 升级前后 commit。
- 升级前后迁移号。
- 数据库与配置备份 OSS key、sha256、迁移号和文件大小。
- 每个阶段的开始时间、结束时间和退出码。
- 三组审计的完整输出。
- 财务重建 dry-run 与 execute 的完整 JSON。
- 上线验收 SQL 的 PASS、FAIL、WARN 清单。
- 继续、暂停或回滚的决定及确认人。
## 四、上线前门禁
以下条件必须全部满足,否则不得进入维护窗口。
### 4.1 代码门禁
```bash
cd /srv/order_site # 替换为服务器实际目录
git fetch --prune
git switch dev_11.8
git pull --ff-only
git status --short
git rev-parse HEAD
```
要求:
- `git status --short` 无输出。
- `git rev-parse HEAD` 等于本次批准的提交。
- 后端 `npm run check` 通过。
- 前端 `npm run check` 通过。
- 本次发布没有未提交文件。
### 4.2 生产只读基线
先执行只读检查,不写数据库:
```bash
docker compose exec -T postgres \
sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -f -' <<'SQL'
BEGIN READ ONLY;
SELECT COALESCE(filename, name) AS migration
FROM schema_migrations
ORDER BY COALESCE(filename, name) DESC
LIMIT 5;
SELECT
(SELECT count(*) FROM orders) AS orders,
(SELECT count(*) FROM order_items) AS order_items,
(SELECT count(*) FROM order_refunds WHERE status = 'confirmed') AS refunds,
(SELECT COALESCE(sum(amount_fen), 0) FROM order_refunds WHERE status = 'confirmed') AS refund_fen,
(SELECT count(*) FROM work_orders) AS work_orders,
(SELECT count(*) FROM fulfillment_tasks) AS fulfillment_tasks,
(SELECT count(*) FROM fund_entries) AS fund_entries;
COMMIT;
SQL
```
要求:
- 当前最高迁移必须是 `146`。
- 订单、订单项、退款和资金流水数量记录到发布单。
- `docker compose ps`、`/health/ready` 和外网主入口均正常。
### 4.3 恢复验证硬门禁
这是 P0 门禁。备份文件存在不等于可回滚。
```bash
bash deploy/backup-db.sh
bash deploy/restore-db.sh --list
bash deploy/restore-db.sh --verify --key '<本次备份完整 OSS key>'
```
必须确认:
- `backup-db.sh` 退出码为 `0`。
- OSS manifest、sha256 和文件大小有效。
- `restore-db.sh --verify` 退出码为 `0`。
- 临时恢复库最高迁移为升级前迁移。
- 订单、订单项等关键表数量与生产只读基线一致。
> 2026-11-08 审核结论:当前备份恢复到隔离库时,会因
> `idx_order_items_pending_internal_sku` 依赖函数恢复顺序,以及 `livehub_ro`
> 角色不存在而返回非零。恢复脚本因此会判定备份不可用。
>
> 在上述问题修复并重新验证通过前,本次升级状态为 **禁止上线**。
> 不允许使用 `--allow-unverified`、手工忽略 `pg_restore` 错误或跳过此门禁。
### 4.4 调度任务门禁
升级前在后台关闭全部定时任务:
```text
后台 → 平台配置 → 监控任务 → 任务总开关 → 关闭 → 保存任务
```
要求:
- 总开关显示关闭。
- 所有任务不再排下一次执行。
- 升级期间不得重新开启,直到财务重建、业务配置和上线验收全部通过。
该配置保存在数据库中,会被升级前备份一并保存。
## 五、维护窗口执行顺序
严格按照以下顺序执行。任何一步失败都立即停止,不得跳步。
### 5.1 预构建镜像
在停止业务前完成构建,不替换正在运行的容器:
```bash
docker compose build backend web
docker compose --profile tools build tools
```
构建失败时停止,不进入维护窗口。
### 5.2 进入维护状态
先把公网流量切到维护页,或只允许办公网 IP 访问,并保留管理员内部访问通道。
迁移后的业务配置需要通过后台 UI 完成,不能全程只启动 backend。
如果无法在维护期间隔离公网用户和保留管理员入口,暂停升级。
通知停止接单后执行:
```bash
docker compose stop web backend
docker compose ps
```
要求:
- `web`、`backend` 已停止。
- PostgreSQL 保持运行。
- 此时开始计算核心停写时间。
### 5.3 生成最终升级前备份
虽然 4.3 已验证恢复链路,这里仍必须生成停机后的最终备份:
```bash
# 重新执行 4.2 的只读计数 SQL,记录停机后的最终基线
bash deploy/backup-db.sh
bash deploy/restore-db.sh --list
```
记录:
- 完整 OSS key。
- 对应配置备份 OSS key。
- manifest 中的迁移号,必须为 `146`。
- sha256 和文件大小。
最终停机备份也必须做隔离恢复验证:
```bash
bash deploy/restore-db.sh --verify --key '<最终备份完整 OSS key>'
```
要求:
- 命令退出码为 `0`。
- manifest、sha256 和下载校验通过。
- 临时恢复库最高迁移为 `146`。
- 订单、订单项和资金流水数与停机后的最终基线一致。
最终备份恢复验证未通过时,不得执行迁移。
### 5.4 执行数据库迁移
必须先确认旧后端已停止,再执行:
```bash
docker compose run --rm --no-deps backend node dist/db/migrate.js
```
预期执行:
```text
147_internal_sku_product_routing
148_fulfillment_execution_truth
149_finance_derived_scope
150_voucher_consume_order_received_scope
```
要求:
- 命令退出码为 `0`。
- 不允许中断、重复启动或并行启动。
- 迁移失败时保持后端停止,不自动重启。
迁移后检查:
```bash
docker compose exec -T postgres sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "
SELECT COALESCE(filename, name)
FROM schema_migrations
ORDER BY COALESCE(filename, name) DESC
LIMIT 5
"'
```
最高迁移必须为 `150`。
### 5.5 刷新统计信息
迁移后立即执行,否则履约审计可能超时并产生错误计划:
```bash
docker compose exec -T postgres sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -c "
VACUUM (ANALYZE)
orders,
order_items,
fulfillments,
fulfillment_attempts,
fulfillment_issues,
internal_skus,
sku_channel_routes,
manual_fulfillment_configs,
product_catalog,
cost_snapshots,
sales_daily_facts,
finance_journal_entries
"'
```
命令失败时停止上线;不要用放宽超时替代统计刷新。
### 5.6 启动新后端
先只启动后端,不启动 Web:
```bash
docker compose up -d --no-deps --force-recreate backend
docker compose exec -T backend sh -lc \
'curl -fsS http://127.0.0.1:3000/health/ready'
```
要求:
- 健康检查返回成功。
- 后端日志中定时任务系统显示已停用。
- 不允许用旧镜像执行此步骤。
## 六、迁移后审计
### 6.1 数据回填与订单级核销补投
调度任务保持关闭,执行:
```bash
bash deploy/post-deploy.sh --only=backfill
```
要求:
- 命令退出码为 `0`。
- `revenue:backfill` 完成后 `revenue_basis = 'unknown'` 为 `0`。
- `voucher:backfill-order-received` 完成最近 7 天订单级核销补投,剩余待入队为 `0`。
- 新任务来源为 `order_received`,`target_key` 使用 `order:<订单ID>`。
- `VACUUM ANALYZE` 成功。
`post-deploy.sh` 末尾仍可能打印旧上线手册链接和“从接单模板初始化”提示;
以本手册为准,不要按旧提示重复初始化商品。
### 6.2 商品路由审计
```bash
docker compose --profile tools run --rm --no-deps tools \
npm run audit:product-routing -- --strict
```
以下结构性异常必须为 `0`:
- 无主路由且无人工配置的 `man:` 商品。
- 同一内部 SKU 多主路由。
- `manual / third_party_voucher` 商品缺少内部 SKU。
- 非法商品来源。
`fallback_without_primary`、`missing_manual_route` 和未路由商品可以保留为
“待分类”,但必须逐类记录数量,交业务负责人在本轮维护窗口内确认。
严格审计可能因这些已知待分类项返回非零;只有确认失败项全部属于已签字类别、
且上述结构性异常均为 `0` 时,才允许继续。
### 6.3 履约审计
```bash
docker compose --profile tools run --rm --no-deps tools \
npm run audit:fulfillment -- --strict
```
以下完整性指标必须为 `0`:
- 正式履约缺失。
- 正式履约状态差异。
- 执行尝试缺失。
- 开放异常缺失。
- 履约事件未关联正式记录。
- 近 24 小时重复尝试。
以下业务异常不得增加,并必须已经进入结构化待办:
- `missing_internal_sku`
- `missing_channel`
- `manual_config_missing`
- `carrier_missing`
- `state_inconsistent`
- `user_refund`
- `platform_cancelled`
其中 `state_inconsistent`、售后关闭仍进入履约队列、已分配但无有效载体属于
恢复营业前必须处理或由业务负责人明确签字的阻塞项。
2026-11-08 演练基线:
- 待处理履约项 `476`。
- 状态不一致 `7`。
- 售后关闭仍进入履约队列 `24`。
- 已分配但无有效载体 `27`。
### 6.3.1 接单大厅价格审计
迁移 `163`、`164` 后必须执行:
```bash
docker compose --profile tools run --rm --no-deps tools \
npm run audit:work-order-hall-pricing
```
要求:
- `critical` 必须为 `0`。
- `open` 模板工单的 `hall_price_version_id` 必须对应当前有效成本版本。
- 普通工单金额和拼单剩余份额单价必须与当前模板一致。
- 已加入拼单份额的合同金额不得因审计或改价被重写。
- 寄卖合同态工单不参与模板价审计覆盖。
- 新建停用模板必须已出现在“接单平台”标准商品范围,并能先配置成本;不得显示为“未路由”。
审计只扫描已进入大厅的 `open` 工单。`pending_material / unassigned` 保留旧价格是预期行为,
发布入口会强制物化最新成本版本,不应为这些未发布工单批量补写价格。
### 6.4 财务重建 dry-run
先定义本次财务回看区间:
```bash
RECONCILE_FROM=2026-08-11
RECONCILE_TO=2026-11-08
```
执行 dry-run:
```bash
docker compose --profile tools run --rm --no-deps tools \
npm run finance:rebuild-derived -- \
--date-from="$RECONCILE_FROM" \
--date-to="$RECONCILE_TO"
```
检查:
- `closedPeriods` 为空;若包含已关账期间,停止并先走财务确认流程。
- `scopeItems > 0`。
- `protectedTotals` 与生产基线一致。
- `plan` 中的 estimated 快照、销售事实和总账调整数量能够解释。
### 6.5 业务配置
为完成后台操作,启动 Web;此时公网仍必须停留在维护入口,不能对普通用户开放:
```bash
docker compose up -d --no-deps --force-recreate web
```
由业务负责人完成,脚本不能代替:
1. 钱包管理 → 补建客服备用金账户并按需拨付。
2. 商品与成本 → 积分钱包 → 发放三平台积分。
3. 商品与成本 → 标准商品 → 按需执行「按工单回填商品」和「归一人工商品」。
4. 商品与成本 → 人工 SKU 配置 → 扫描并逐项配置。
5. 商品与成本 → 标准商品 → 处理未路由、待分类和人工兜底项。
> 迁移 `122` 已负责接单模板商品初始化。本手册不再要求执行旧的
> “从接单模板初始化”步骤。
完成业务配置后重新执行商品路由审计,并记录前后数量变化。
### 6.6 财务重建
业务配置完成后,重新执行 6.4 的 dry-run,确认 `protectedTotals`、已关账期间和
计划数量仍符合预期。业务负责人确认后再执行:
```bash
docker compose --profile tools run --rm --no-deps tools \
npm run finance:rebuild-derived -- \
--date-from="$RECONCILE_FROM" \
--date-to="$RECONCILE_TO" \
--execute \
--confirm=REBUILD_DERIVED
```
要求:
- `protectedTotals` 前后完全一致。
- 订单、订单项、收入、退款、人工支出和资金流水守恒。
- 已过账总账只追加反向/补差凭证,不删除历史凭证。
- 所有成本缺口都进入结构化异常,不做自动猜测。
再执行一次相同命令验证幂等:
```bash
docker compose --profile tools run --rm --no-deps tools \
npm run finance:rebuild-derived -- \
--date-from="$RECONCILE_FROM" \
--date-to="$RECONCILE_TO" \
--execute \
--confirm=REBUILD_DERIVED
```
第二次必须满足:
- `ledger.adjusted = 0`。
- `protectedTotals` 再次保持完全一致。
然后执行:
```bash
docker compose --profile tools run --rm --no-deps tools \
npm run audit:finance -- --strict
```
要求 `critical` 为空。
## 七、上线验收
停止调度任务期间执行:
```bash
bash deploy/post-deploy.sh \
--only=verify \
--launch-at='YYYY-MM-DD HH:mm:ss+08'
```
要求:
- 退出码为 `0`。
- FAIL 必须为 `0`。
- WARN 必须逐项记录并由业务负责人签字。
- `audit:finance --strict` 必须同时通过。
2026-11-08 演练中,在做完财务重建后仍有:
- `07 客服备用金账户已补建`:需在后台完成初始化后才可通过。
- `15 近 7 天非人工成本覆盖`:演练时为 WARN,原因为 `206`,必须确认成本缺口。
## 八、恢复营业
验收通过后:
```bash
docker compose up -d --no-deps --force-recreate web
```
然后:
1. 后台 → 平台配置 → 监控任务 → 开启任务总开关 → 保存。
2. 确认任务运行状态恢复,且下一次执行时间已生成。
3. 检查主站、打手端、领取页和订单服务链接。
必须完成以下冒烟检查:
- 后台登录。
- 订单列表、详情和筛选。
- 接单工单列表、详情和状态操作。
- 履约渠道页和商品与成本页签数量一致。
- 人工 SKU、自动发货、接单平台、第三方卡券和统一结算分类正确。
- 财务总账试算平衡。
- 打手端登录、订单页和大厅。
- 领取链接 `/h5/<订单号>`。
- 订单服务链接 `/o` 和 `/o/<订单号>`。
## 九、观察期
恢复营业后:
- 前 60 分钟持续观察错误率、慢查询、定时任务和订单入单。
- 前 7 天每天检查商品路由、履约和财务严格审计。
- 一个完整财务期间持续对平后,才允许讨论删除兼容表、`legacy_*`
字段和旧脚本。
观察期内不得删除:
- `product_channel_capabilities`
- `manual_sku_registry` 派生行
- `internal_sku_channels`
- `order_items.legacy_*`
- `fulfillments`、`fulfillment_attempts`、`fulfillment_issues`、`fulfillment_state_events`
## 十、失败与回滚
### 10.1 迁移命令失败
如果迁移尚未提交:
1. 保持 backend/web 停止。
2. 保存完整迁移日志。
3. 确认 PostgreSQL 中不存在半提交状态。
4. 修复迁移或取消本次发布。
5. 取消发布时,先切回升级前 commit 并重新构建 backend/web,再启动旧后端。
6. 不得在未确认迁移事务已回滚时启动任何一版后端。
### 10.2 迁移已提交但新后端失败
硬切后的数据库不兼容旧镜像。选择以下一种:
1. **修复前进**:保持停服,修复新后端,禁止用旧镜像连接迁移 `150`。
2. **整库回滚**:使用已通过验证的升级前备份恢复。
整库回滚:
回滚前必须先切回旧代码并重建旧镜像。否则 `restore-db.sh` 会用当前新镜像补迁移,
把刚恢复的 `146` 再次升级到 `150`。
```bash
git switch --detach '<升级前 commit>'
docker compose build backend web
bash deploy/restore-db.sh \
--key '<升级前最终备份 OSS key>' \
--config-key '<对应配置备份 OSS key>' \
--yes
```
恢复后:
1. 确认最高迁移为 `146`。
2. 启动 backend/web。
3. 执行健康检查、登录和近期订单抽查。
4. 恢复调度任务总开关。
> 当前恢复脚本只有在 4.3 硬门禁通过后才可用于回滚。
> 恢复脚本失败时,数据库可能处于部分恢复状态,必须立即停止业务并人工处理。
### 10.3 财务重建失败
财务重建是幂等的数据阶段,不应因为一次失败立即整库回滚:
1. 保持停服和调度关闭。
2. 保存失败 JSON、日志和当前 protected totals。
3. 修复数据或脚本后重跑。
4. 只有原始订单、退款、资金流水发生不守恒或数据库不可用时才考虑整库回滚。
### 10.4 验收失败
验收 FAIL 或关键审计异常时不得恢复营业:
1. 保持 backend 健康但 Web 不开放。
2. 调度保持关闭。
3. 按失败项修复或走整库回滚。
4. 修复后从对应阶段重新执行,不跳步。
## 十一、发布记录模板
```text
发布日期:
发布负责人:
数据库执行人:
验收人:
业务负责人:
升级前 commit:
目标 commit:
升级前迁移:
目标迁移:
升级前备份 key:
升级前备份 sha256:
升级前配置备份 key:
恢复验证结果:
维护开始时间:
迁移开始时间:
迁移结束时间:
统计刷新结束时间:
财务重建结束时间:
验收结束时间:
恢复营业时间:
商品路由审计:
履约审计:
财务 dry-run:
第一次财务重建:
第二次财务重建:
财务严格审计:
上线验收 FAIL:
上线验收 WARN:
已知待分类项及负责人:
最终结论:继续 / 暂停 / 回滚
```
@@ -3,7 +3,7 @@ type: 权威架构与实施计划
description: 订单、内部 SKU、履约路由、履约执行、商品、成本与财务的唯一权威口径及重构计划。
tags: [订单, 内部SKU, 履约, 商品, 成本, 财务, 迁移]
status: authoritative
version: 2.21
version: 2.22
updated_at: 2026-11-14
---
@@ -11,8 +11,8 @@ updated_at: 2026-11-14
> 状态:**唯一权威文档**。
> 最后校准:2026-11-14。
> 生产基线:迁移 `151`。
> 开发基线:迁移 `165`(仅本地,尚未部署生产)。
> 生产基线:迁移 `165`。
> 开发基线:迁移 `166`(仅本地,尚未部署生产)。
>
> 从本文件生效起,订单、内部 SKU、履约渠道、履约载体、标准商品、成本和财务相关实现,
> 只能以本文件为业务口径。其他旧分析只作为历史资料,不再具备设计或验收效力。
@@ -396,6 +396,20 @@ manual fulfillment
- 已有模板商品在迁移 `164` 中按模板生成的标准 SKU 一次性回填关联;删除仍被模板引用的商品时
必须拒绝并提示改为归档。
### 3.4.5 接单平台动态模板路由
接单平台路由负责声明该内部 SKU 走 `work_order`,不应把内部 SKU 名称伪造成模板 key。
- `sku_channel_routes.carrier_config_json.workRuleKey` 为空时,按真实订单运行统一模板匹配;
支持 `contains`、商品默认绑定、SKU 精确、SKU 数量系列、店铺白名单和订单数量解析。
- `templateSelection = dynamic` 表示动态匹配;可记录最近命中的 `workRuleKey` 供排查,
但下一次订单仍必须重新运行匹配,不能把最近模板固化。
- 显式 `workRuleKey` 只能指向真实存在的 `work_product_rules.rule_key`;迁移 `166` 的触发器
拒绝未来写入不存在的模板 key。
- 统一渠道配置不得为接单平台创建 `tpl:<内部 SKU>` 假草稿商品。真实成本商品来自命中的模板,
工单创建后写入 `fulfillments.canonical_product_id`。
- 错误 key、历史草稿商品和错误履约商品关联由迁移 `166` 清理;历史订单、模板和成本事实不删除。
### 3.5 商品与财务域
标准商品目标结构:
@@ -1025,8 +1039,6 @@ docs/README.md
docs/统一订单履约与财务模型-权威版.md
唯一业务与架构权威
docs/部署启动与数据库重建.md
docs/数据库备份与恢复.md
docs/数据库SQL约束与运维.md
运维参考
@@ -1043,7 +1055,7 @@ docs/archive/
## 十三、当前下一步
截至 2026-11-14,生产基线为 `151`,集成分支开发基线为 `165`。迁移 `152` 保护订单来源快照,
截至 2026-11-14,生产基线为 `165`,集成分支开发基线为 `166`。迁移 `152` 保护订单来源快照,
迁移 `158–160` 收口寄卖登记人同步、寄卖平台与自动匹配设置;
迁移 `161` 一次建立人工渠道半自动任务、内置 Connector 默认值、账号级 Cookie JSON 凭证、
任务凭证引用和最终约束;迁移 `162` 建立履约待办四桶只读投影、补载体专用索引和新单优先扫描口径。
@@ -1053,12 +1065,14 @@ docs/archive/
进入接单平台成本范围。
迁移 `165` 删除覆盖未发布工单的改价候选索引,模板改价只刷新 `open` 工单;待完善和未分配
工单在发布时物化最新价格,避免每次改价扫描数万行。
迁移 `166` 让接单平台路由支持动态模板匹配,清理历史伪造 key 和草稿商品,并拒绝对不存在模板
的非空 `workRuleKey` 写入。
迁移中保留的 `semi_auto.credential.export` 权限仅作兼容记录,
不再作为 CK 导出门禁。
1. 生产升级只按 [生产升级操作手册](生产升级操作手册.md) 执行。
1. 生产升级直接按 `deploy/` 脚本、实际迁移和停写流程执行,不再维护独立升级手册。
2. 升级前备份必须完成隔离恢复验证;恢复验证未通过时禁止迁移。
3. `147 → 165` 按生产升级手册分批执行,迁移后刷新统计并完成商品、履约、
3. `165 → 166` 迁移后刷新统计并完成商品、履约、
财务和大厅价格审计;无法确定的数据进入未路由或待分类,不做猜测。
4. 执行 `finance:rebuild-derived` 的 dry-run,核对 A 级数据守恒后,
再按窗口执行 `--execute --confirm=REBUILD_DERIVED`,并验证第二次重建幂等。
-376
View File
@@ -1,376 +0,0 @@
# 部署启动与数据库重建
本文记录本地开发、数据库迁移、重建库和 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
```
## 财务/成本上线后的初始化(历史基线 150)
代码与迁移只负责建表;**存量订单回填、备用金账户、积分余额、商品目录、人工 SKU 不会自动创建**。
部署成功后按「回填 → 商品初始化 → 财务同步 → 验收」四步走:
### 1. 数据回填
```bash
bash deploy/post-deploy.sh --dry-run # 先看计划与待回填量
bash deploy/post-deploy.sh --only=backfill # ① 收入回填 + ② 订单级收单核销补投
```
> 不要在商品目录为空时直接跑 `--only=sync`:财务同步会为每个未归一订单项写一条
> `product_mapping_missing` 成本异常(生产规模 90 天实测约 36 万条),后台异常队列会被淹没。
脚本包含 `revenue:backfill`、财务派生数据重建(`finance:rebuild-derived`)与只读验收;任何一步都能重跑,
`--only=backfill|sync|verify` 可单独执行。
> 生产 backend 镜像没有 `tsx` 与 `scripts/`,脚本通过 `tools` 镜像执行
> ([deploy/docker/tools.Dockerfile](../deploy/docker/tools.Dockerfile),`docker compose --profile tools`)。
### 2. 业务配置:后台 UI
1. 钱包管理 → 补建客服备用金账户 → 按需拨付;
2. 商品与成本 → 积分钱包 → 给客服发放三平台积分;
3. 商品与成本 → 标准商品:迁移 122 会自动补齐接单模板商品;新模板保存时也会自动建商品。按需执行「按工单回填商品」和「归一人工商品」,不再需要从接单模板初始化;
4. 商品与成本 → 人工 SKU 配置 → 扫描并逐项配置(保存后自动回填订单项)。
> 第 3 步的工单回填必须先于下一节 `--only=sync`。商品目录为空时同步会为每个
> 未归一订单项写一条 `product_mapping_missing` 异常(实测 90 天约 36 万条)。
### 3. 财务同步
商品归一完成后执行:
```bash
bash deploy/post-deploy.sh --only=sync # ③ 成本快照 / 销售事实 / 总账调整
```
> 工具镜像每次运行都会重建(层缓存下只需几秒),确保 `git pull` 后执行的是当前脚本。
### 4. 总账与验收
5. 财务 → 总账与对账 → 检查试算平衡;`post-deploy.sh` 已按日追加销售事实调整凭证,
之后按需关账/导入平台账单。
```bash
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` 的分窗同步。
---
## 服务器日常更新
```bash
cd /home/order_site && git pull
bash deploy/ubuntu-deploy.sh # 想看这次会做什么:加 --dry-run
```
如果本次包含数据库迁移、履约/商品路由切换或财务派生数据重建,不要直接执行上述命令,
必须按照 [生产升级操作手册](生产升级操作手册.md) 的停写、备份验证、迁移、审计、
财务重建和验收顺序执行。
后端启动时自动执行未应用的增量迁移,一般不必单独跑迁移。
确认:
```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` 并映射端口。