# 生产 Compose 物料流转增量迁移 ## 状态、授权与适用范围 本交付**只生成文件**,未连接任何数据库,未执行本地或生产迁移,未重启服务、构建镜像或改动 Compose/环境变量。下列命令供维护人员审核后在**生产服务器**执行,不要在本机开发目录运行。生成脚本的确认不等于已完成生产执行审批。 依据用户提供的生产 `shierfeng-fastapi` / MySQL 8.0.46 结构: - `biz_test_batch`、`biz_tunnel` 已存在;隧道外键使用 ORM 自动名称,保留不改。 - `biz_filling_record.production_date/expiry_date` 及日期 CHECK 已存在,保留现有数据。 - 库存、使用、退回、事件表的隧道关联未升级,使用记录 `line_id` 仍非空。 - 收货字段、FIFO 原因/快照不存在;隧道及收货菜单尚不存在。 这是一条**特定起点、单次执行、遇错即停**的升级路线,不是通用幂等修复器。生产结构有变化必须重新评估,不能删掉检查后强行执行。 ## 文件顺序 以下路径相对生产服务器项目根目录,必须带齐五份 SQL: 1. `shierfeng-fastapi-backend/sql/patches/20261008_prod_existing_tunnel_repair_mysql.sql`:新增专用补丁,保留已存在隧道表,补12个关联字段、5个外键、可空产线、入隧事件和4行菜单。 2. `shierfeng-fastapi-backend/sql/patches/20261008_shipment_receipt_mysql.sql`:复用现有补丁,补收货和在途状态;旧确认单新增 `LEGACY` 标识,不改变旧库存、批号、编号和数量。 3. `shierfeng-fastapi-backend/sql/patches/20261008_usage_fifo_mysql.sql`:复用现有补丁,补可空原因及推荐快照。 4. `shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_baseline.sql`:只读,前后原字段快照。 5. `shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_verify.sql`:只读,迁移后验收。 **不要执行**原隧道补丁 `20261005_tunnel_material_flow_mysql.sql`(会重复建表),不要重复日期/测试批次补丁,不要执行全量 `shierfeng-fastapi.sql`。不要把这些补丁挂到 MySQL 初始化目录,也不要删除/更换 MySQL 数据卷来修复。 ## 1. 审核目标与维护窗口(尚不改库) 进入生产服务器上实际部署的 Compose 目录,使用原部署的 `-f`、`--env-file`、`-p` 参数(如有),下文每条 `docker compose` 都必须保持同一套参数。不要改变项目名或数据卷名称。 ```bash docker compose ps docker compose exec shierfeng-backend-my python -c "import os; print({k: os.getenv(k) for k in ['APP_ENV', 'DB_HOST', 'DB_PORT', 'DB_DATABASE']})" docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --execute="SELECT DATABASE(), VERSION(), @@hostname, @@port;"' ``` 要求后端 `DB_HOST=shierfeng-mysql`、`DB_DATABASE=shierfeng-fastapi`,与 MySQL 查询一致。若使用外部数据库、不同服务名、空输出或其他库名,停止并调整方案,不能把命令照搬到另一个库。 命令利用现有 MySQL 容器环境中的密码,不将密码放进命令参数、终端输出或仓库;不要启用 shell `set -x`,也不要贴完整 `.env`/`docker compose config`。缺少 `MYSQL_ROOT_PASSWORD` 时请 DBA 使用受控凭据,不要把密码改写进文档。 获得维护窗口、停写和生产迁移审批,确认备份保留位置、恢复负责人、可接受恢复时间。先备好一致的新代码版本及上一个可用版本;本操作不执行拉取或构建。 ## 2. 停写、备份与原数据基线 以下开始影响生产服务,必须在审批后的维护窗口由维护人员执行。通知用户退出操作,停止所有外部导入、定时任务、独立 worker 和其他写入来源。 ```bash docker compose stop shierfeng-frontend shierfeng-backend-my docker compose ps docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --execute="SELECT ID, USER, HOST, DB, COMMAND, TIME FROM information_schema.PROCESSLIST WHERE DB=DATABASE() AND ID<>CONNECTION_ID(); SELECT EVENT_NAME, STATUS FROM information_schema.EVENTS WHERE EVENT_SCHEMA=DATABASE() AND STATUS=\"ENABLED\"; SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_TYPE=\"BASE TABLE\" AND ENGINE<>\"InnoDB\";"' ``` 不得继续:仍有业务连接/事务或启用的事件,或存在非 InnoDB 表。不要擅自 kill 会话、禁用事件或转换表;由 DBA 查明并停写。MySQL/Redis 保持运行,不执行全栈停止或删除卷。 在同一个 Bash 会话保存备份路径,`/var/backups` 需有经批准的写入权限和足够磁盘空间;权限不足时停止,不自动改权限: ```bash set -euo pipefail umask 077 prod_backup_dir=$(mktemp -d /var/backups/shierfeng-prod-20261008.XXXXXX) printf '生产备份目录:%s\n' "$prod_backup_dir" docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysqldump --user=root --single-transaction --quick --routines --events --triggers --hex-blob --set-gtid-purged=OFF --no-tablespaces --column-statistics=0 --default-character-set=utf8mb4 "$MYSQL_DATABASE"' > "$prod_backup_dir/before.sql" test -s "$prod_backup_dir/before.sql" sha256sum "$prod_backup_dir/before.sql" > "$prod_backup_dir/before.sha256" docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --batch --raw --skip-column-names' < shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_baseline.sql > "$prod_backup_dir/original-fields.before.tsv" ``` 备份不使用 `--databases`,避免把原库名的 `CREATE DATABASE/USE` 写入文件,便于在另一个隔离实例恢复。备份覆盖全部表和触发器、过程、事件;快照只用于比较本次7张关联业务表的全部原字段,不能代替完整备份。空业务库的快照可能为空,但完整备份不能为空。任何命令失败都停止,不能拿半份文件继续。 ## 3. 恢复演练门禁 将备份复制到受控的**独立验收 MySQL 实例**,不是本机开发库,也不是业务生产库。由 DBA 建立空的同名 `shierfeng-fastapi` 库(新补丁核对库名;实例必须隔离),恢复完整备份并核对所有表及原字段快照,再按第4节顺序试跑三份脚本和第5节验收。 不得为演练启用应用写入,需检查恢复的存储过程/事件/触发器及其 DEFINER;演练实例应禁用事件调度。本交付没有该隔离实例的地址及凭据,因此不预填可能误指向生产的恢复命令。需 DBA 留存恢复目标、命令、成功结果、原字段一致性、耗时和脚本 SHA-256。 **没有完成恢复及补丁试跑,就不能进入生产执行。** 文件非空、哈希正确或 SQL 静态检查通过不等于恢复成功。迁移期间持续停写;若中途恢复业务,原备份/基线不再代表执行起点,必须重新安排窗口和备份。 ## 4. 生产逐份执行(仍保持停写) 重新确认当前终端属于生产服务器、Compose 实例与第1节一致、备份已验证可恢复、三个脚本与演练使用版本哈希相同。`prod_backup_dir` 必须仍是第2节打印的目录;重新登录终端时由维护人员明确填入并核对,禁止猜测路径。 ```bash test -d "$prod_backup_dir" sha256sum -c "$prod_backup_dir/before.sha256" sha256sum shierfeng-fastapi-backend/sql/patches/20261008_prod_existing_tunnel_repair_mysql.sql shierfeng-fastapi-backend/sql/patches/20261008_shipment_receipt_mysql.sql shierfeng-fastapi-backend/sql/patches/20261008_usage_fifo_mysql.sql ``` **每次只执行一份,检查退出码和日志后才进入下一份。** 下列块依赖第2节 `set -euo pipefail`,禁止 `--force`、遇错继续的图形客户端及把三份脚本拼起来盲跑。建议在专用维护终端操作,保留退出状态。 ```bash docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --show-warnings --init-command="SET @shierfeng_prod_migration_approved=1"' < shierfeng-fastapi-backend/sql/patches/20261008_prod_existing_tunnel_repair_mysql.sql > "$prod_backup_dir/01-tunnel.log" 2>&1 ``` 第一份应输出12个新增隧道字段和1个 `line_id=YES`,以及4个隧道菜单。检查 `01-tunnel.log`,无错误才执行: ```bash docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --show-warnings' < shierfeng-fastapi-backend/sql/patches/20261008_shipment_receipt_mysql.sql > "$prod_backup_dir/02-receipt.log" 2>&1 ``` 检查 `02-receipt.log`:历史 `CONFIRMED` 单为 `LEGACY`、`DRAFT` 为 `PENDING`,新增1119收货菜单;无错误才执行: ```bash docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --show-warnings' < shierfeng-fastapi-backend/sql/patches/20261008_usage_fifo_mysql.sql > "$prod_backup_dir/03-fifo.log" 2>&1 ``` 第三份成功不一定有结果集,必须确认退出码0且日志无错误,再做完整验收。这三份脚本都不允许重复执行。 ## 5. 验收与恢复服务 在启动应用前执行: ```bash docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --batch --raw --skip-column-names' < shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_baseline.sql > "$prod_backup_dir/original-fields.after.tsv" cmp "$prod_backup_dir/original-fields.before.tsv" "$prod_backup_dir/original-fields.after.tsv" docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --table' < shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_verify.sql > "$prod_backup_dir/verify.log" ``` 全部验收条件: - `cmp` 退出0,七张表全部原字段逐行一致,包含库存、批号、日期和历史事件。 - 验收文件25行字段(含原日期和产线)均为 `OK`,6行外键均为 `OK`;不能只看 mysql 命令退出码,因为 SELECT 会正常返回 `MISSING/MISMATCH`。 - 三个 CHECK 均存在且 `ENFORCED=YES`;库存枚举含 `IN_TRANSIT`,事件枚举含 `TUNNEL_ENTERED`、`SHIPMENT_RECEIVED`;收货唯一索引 `non_unique=0` 且列顺序为 `target_site_id,receipt_idempotency_key`。 - 恰有菜单520/1116/1117/1118/1119,父ID为121/520/520/520/510,权限分别为 `master:tunnel:list/query/add/edit` 和 `business:shipment:receive`。 - `unexpected_*` 和 `remaining_migration_routines` 均为0。历史日期保持原值;新隧道、快照、收货时间等历史字段为空。 任何一项失败都不要启动。确认运行镜像已是这次适配的新版本后,再恢复原容器(不改变卷/配置、不拉取新镜像): ```bash docker compose start shierfeng-backend-my docker compose ps docker compose logs --since=5m --tail=100 shierfeng-backend-my ``` 确认后端健康、没有缺列/约束错误,再执行: ```bash docker compose start shierfeng-frontend docker compose ps ``` 使用超级管理员重新登录检查库存/使用/退回报表及隧道资料页面。新菜单不会自动授予普通角色,需要按既有角色管理配置菜单和现场范围,另行审批;不要直接修改角色关联表或清空 Redis。新流程用明确授权的测试数据验收,不用正式库存随意试扣。 前述 Web Crypto/HTTPS 问题与本迁移无关;此交付不修改加密开关、证书或反向代理。 ## 失败与恢复原则 - 保持后端及其他写入来源停止,保留备份、日志和实际已落库结构。 - MySQL DDL 会隐式提交,`ROLLBACK` 不能撤销前面已完成的 ALTER;不能盲目重跑、跳过错误或用反向删列 SQL“回滚”。 - 存储过程报错后可能保留临时过程名称,这是失败现场的一部分;不要直接 DROP 后重试,先由 DBA 确定失败阶段。 - 由 DBA 依据实际提交状态决定经过审核的续修,或从已验证完整备份恢复,并匹配已验证的应用版本。恢复目标、方式及可能覆盖的数据必须再次确认,文档不提供自动覆盖生产库命令。 - 不删除数据卷,不重跑初始化,不把开发库覆盖到生产;备份和快照需转存到受控备份系统,遵循保留期限,不提交仓库。 ## 本次生成的验证边界 本轮仅执行不连接数据库的离线 SQL 契约检查和文件自检:6项契约检查通过,10段 Shell 命令经 `bash -n` 语法检查;非空历史字段、误写角色表、误删隧道表3种内存变异均被契约检查拒绝,未把变异写入文件。当前 SQLGlot 不能完整解析存储过程及部分 MySQL ALTER 语法,因此检查采用新增列/查询的 AST 加原 ALTER token 契约,不能称为完整 MySQL 语法验证。 没有执行 MySQL 存储过程、DDL、DML、备份恢复或生产验收;静态检查不能证明这些运行操作成功。MySQL/Compose 操作仍需第3节的独立恢复演练及生产审批。