#!/usr/bin/env bash # ============================================================================ # parking-cloud · 部署包生成器 # # 产出两个文件(都落在 sql/ 下,与 00/01/02 同目录,编号接续): # # 03_legacy_schema.sql 679 张存量表的**表结构**(无数据,约 1.3 MB) # 04_legacy_sample.sql 679 张存量表的**抽样数据**(少量,可选,见 §PII) # # 和 00/01/02 的分工: # 00_create_database.sql 建库 # 01_system_schema.sql 13 张 system_* 表结构(P1 手写,权威) # 02_system_seed.sql 13 张 system_* 表种子(admin/admin123 等) # 03_legacy_schema.sql ← 本脚本产出:679 张存量表结构 # 04_legacy_sample.sql ← 本脚本产出:679 张存量表抽样数据 # 所以 03/04 都**排除 system_***,绝不与 01/02 重叠。 # # ⚠️ 为什么要生成本文件,而不是直接用 10_dump_legacy.sh 的产物: # 10_dump_legacy.sh 面向**本地镜像**(生产全量/抽样导入本机), # 它的 dump 里带着本地 MySQL 的默认排序规则;本脚本面向 # **拿到别的服务器上执行**,必须与目标服务端版本无关。差异见 §normalize。 # # 用法: # bash sql/tools/gen_deploy_sql.sh # 只出 03 # bash sql/tools/gen_deploy_sql.sh --with-data # 03 + 04 # DEPLOY_DB_HOST=127.0.0.1 DEPLOY_DB_USER=root bash sql/tools/gen_deploy_sql.sh # # 退出码:0 = 生成且自检全过;非 0 = 有硬约束不满足(绝不产出坏文件) # ============================================================================ set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" SQL_DIR="$(cd "$HERE/.." && pwd)" DEPLOY_DB_HOST="${DEPLOY_DB_HOST:-127.0.0.1}" DEPLOY_DB_PORT="${DEPLOY_DB_PORT:-3306}" DEPLOY_DB_USER="${DEPLOY_DB_USER:-root}" DEPLOY_DB="${DEPLOY_DB:-parking_cloud}" # 密码走 MYSQL_PWD(所有 MySQL 官方客户端都认),**不上 argv**——否则会出现在 ps 里 [[ -n "${DEPLOY_DB_PASSWORD:-}" ]] && export MYSQL_PWD="$DEPLOY_DB_PASSWORD" MYSQL_BIN="${MYSQL_BIN:-$(command -v mysql || echo /opt/homebrew/opt/mysql-client/bin/mysql)}" DUMP_BIN="${DUMP_BIN:-$(command -v mysqldump || echo /opt/homebrew/opt/mysql-client/bin/mysqldump)}" OUT_SCHEMA="$SQL_DIR/03_legacy_schema.sql" OUT_DATA="$SQL_DIR/04_legacy_sample.sql" WITH_DATA=0 [[ "${1:-}" == "--with-data" ]] && WITH_DATA=1 die() { echo "✘ $*" >&2; exit 1; } say() { printf '%s\n' "$*"; } # --------------------------------------------------------------------------- # 存量表清单:库里除了 13 张 system_* 之外的全部 # 顺序固定(ORDER BY TABLE_NAME)→ 产出**可复现**(重跑 md5 一致) # --------------------------------------------------------------------------- list_tables() { "$MYSQL_BIN" -h"$DEPLOY_DB_HOST" -P"$DEPLOY_DB_PORT" -u"$DEPLOY_DB_USER" \ -N -B -e "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$DEPLOY_DB' AND TABLE_TYPE='BASE TABLE' AND TABLE_NAME NOT LIKE 'system\\_%' ORDER BY TABLE_NAME;" } TABLES=() while IFS= read -r t; do [[ -n "$t" ]] && TABLES+=("$t"); done < <(list_tables) [[ ${#TABLES[@]} -gt 0 ]] || die "从 $DEPLOY_DB 里一张存量表都没查到——库名或连接参数不对?" for t in "${TABLES[@]}"; do [[ "$t" == system_* ]] && die "system_* 漏进了存量表清单($t)——排除规则失效,停" done SYSTEM_CNT=$("$MYSQL_BIN" -h"$DEPLOY_DB_HOST" -P"$DEPLOY_DB_PORT" -u"$DEPLOY_DB_USER" \ -N -B -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$DEPLOY_DB' AND TABLE_NAME LIKE 'system\\_%';") TOTAL_CNT=$("$MYSQL_BIN" -h"$DEPLOY_DB_HOST" -P"$DEPLOY_DB_PORT" -u"$DEPLOY_DB_USER" \ -N -B -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$DEPLOY_DB' AND TABLE_TYPE='BASE TABLE';") say "存量表 ${#TABLES[@]} 张 / system_* ${SYSTEM_CNT} 张 / 库内合计 ${TOTAL_CNT} 张" [[ $(( ${#TABLES[@]} + SYSTEM_CNT )) -eq "$TOTAL_CNT" ]] \ || die "679+13 对不上库内总数(${#TABLES[@]}+$SYSTEM_CNT != $TOTAL_CNT)" # --------------------------------------------------------------------------- # 统一 dump 参数 # --set-gtid-purged=OFF 不往输出里写 GTID(目标库多半没开 GTID,写了反而报错) # --no-tablespaces 不给普通账号添 PROCESS 权限要求 # --skip-comments/date 去掉 "Dump completed on ..." 之类**每次都变**的行 # ⚠️ 若换用 8.x 客户端去 dump 5.7 服务端,还需加 --column-statistics=0 # --------------------------------------------------------------------------- dump_base() { "$DUMP_BIN" -h"$DEPLOY_DB_HOST" -P"$DEPLOY_DB_PORT" -u"$DEPLOY_DB_USER" \ --skip-comments --skip-dump-date --no-tablespaces --set-gtid-purged=OFF \ --single-transaction --default-character-set=utf8mb4 "$@" } # --------------------------------------------------------------------------- # normalize:把「本地镜像的产物」修成「与目标服务端版本无关的 DDL」 # # ① COLLATE=utf8mb4_0900_ai_ci → utf8mb4_general_ci # **这是本脚本存在的首要理由。** 0900_* 是 MySQL 8.0 专有排序规则, # 生产库是 5.7.28,拿到 5.7 上执行会直接 `Unknown collation` 失败。 # 它怎么来的:本地镜像库建表时没写 COLLATE,落到**本地 MySQL 26.7 的 # utf8mb4 默认**(= 0900_ai_ci)。源库 5.7 的真身是 utf8_general_ci, # 所以这不是"改",是**改回去**。实测命中 697 处。 # 为什么是**替换**而不是**删除** COLLATE:删了就退回目标服务端默认值, # 于是同一份脚本在 5.7 和 8.0 上建出两种排序规则——文件就不再可复现了。 # 写死 general_ci(= 00_create_database.sql 定的目标形态)才是确定的。 # ⚠️ `utf8mb4_bin`(294 处)**必须原样保留**:它对应源库那 30 张 utf8_bin 表, # 是 10_dump_legacy.sh 特意映射过来的,换成 general_ci 会静默改变 # `parking_lot.pl_no` 这类字段的**等值比较语义**。 # ② 抹掉 AUTO_INCREMENT=N(仅表选项行) # 它随本地抽样数据量变化(实测 585 处),留着则重跑产出不一致。 # 抹掉是安全的:04 的数据带显式主键,InnoDB 插入后会把计数器顶到 max(id)+1。 # --------------------------------------------------------------------------- normalize() { sed -E \ -e 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' \ -e '/^\) ENGINE/ s/ AUTO_INCREMENT=[0-9]+//' } # --------------------------------------------------------------------------- # 生成 03:表结构 # --------------------------------------------------------------------------- gen_schema() { local tmp; tmp="$(mktemp)" { cat <<'HDR' -- ============================================================================ -- parking-cloud · 存量表结构(679 张) -- ⚠️ 本文件由 sql/tools/gen_deploy_sql.sh 生成,**不要手改**——改了下次重跑就没了。 -- -- 执行前提:先跑 00_create_database.sql 建好 parking_cloud 库 -- 执行: mysql -uroot parking_cloud < 03_legacy_schema.sql -- -- 内容:zt_sys / zt_park / zt_pay / zt_order / zt_bill / zt_charge 六库合并后的 -- 全部存量表**结构**,不含数据(数据见 04_legacy_sample.sql,可选)。 -- **不含 system_* 13 张表**——那些在 01_system_schema.sql。 -- -- 幂等:每条都是 DROP TABLE IF EXISTS + CREATE TABLE,可重复执行 -- (⚠️ 重跑会清掉这些表里的数据,对着有数据的库执行前先想清楚) -- -- 兼容:MySQL 5.7 与 8.0 都能建(不含 8.0 专有排序规则,不含 8.0 专有语法)。 -- 外键 0 个、触发器/视图/存储过程/事件 均 0 个,5 张分区表的分区定义原样保留。 -- ============================================================================ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; HDR dump_base --no-data "$DEPLOY_DB" "${TABLES[@]}" | normalize cat <<'FTR' SET FOREIGN_KEY_CHECKS = 1; FTR } > "$tmp" # ---- 自检:任何一条不成立就不落盘(宁可不出文件,不出坏文件)---- local n n=$(grep -c '^CREATE TABLE' "$tmp" || true) [[ "$n" -eq "${#TABLES[@]}" ]] || die "03 产出 $n 张表,期望 ${#TABLES[@]} 张" n=$(grep -c '^CREATE TABLE `system_' "$tmp" || true) [[ "$n" -eq 0 ]] || die "03 里混进了 $n 张 system_* 表(会和 01 撞车)" n=$(grep -c '0900' "$tmp" || true) [[ "$n" -eq 0 ]] || die "03 里还剩 $n 处 0900_* 排序规则,5.7 上会 Unknown collation" n=$(grep -c 'AUTO_INCREMENT=' "$tmp" || true) [[ "$n" -eq 0 ]] || die "03 里还剩 $n 处 AUTO_INCREMENT=N(产出不可复现)" n=$(grep -cE 'mb4mb4|mb4mb3|utf8mb3|utf8_general_ci|utf8_bin|utf8_unicode_ci|latin1|CHARSET=utf8([^m]|$)' "$tmp" || true) [[ "$n" -eq 0 ]] || die "03 里还剩 $n 处未归一的字符集/排序规则声明" n=$(grep -c 'PARTITION BY' "$tmp" || true) [[ "$n" -eq 5 ]] || die "03 里分区表是 $n 张,期望 5 张(分区定义不能丢)" mv "$tmp" "$OUT_SCHEMA" say "✔ 03_legacy_schema.sql ${#TABLES[@]} 张表结构,$(wc -c < "$OUT_SCHEMA" | tr -d ' ') 字节" } # --------------------------------------------------------------------------- # 生成 04:抽样数据 # # ⚠️ PII 警告:这里面是**真实生产库抽出来的行**——含客户手机号、车牌、姓名。 # · 不随仓库提交(.gitignore 已排除本文件) # · 只该放到开发/测试机,**不要往生产或对外环境灌** # · 目标库有 system_* 种子(02)但**存量表是空的**也完全能跑起来—— # 04 是"让列表页有东西可看"用的,不是启动必需 # · 21 张密钥类表(struct_only_tables.txt)在本地镜像里本来就是空的, # 所以 dump 出来自然 0 行,不会泄漏密钥 # --------------------------------------------------------------------------- gen_data() { local tmp; tmp="$(mktemp)" { cat < "$tmp" local rows # 行数用"插入了多少行"直接数:INSERT 语句里的 ( 计数过于脆弱,改数 VALUES 组 # mysqldump 的 --extended-insert 形如 INSERT INTO `t` VALUES (..),(..),(..); # 用 INSERT 条数与总字节作为自检口径更稳(行数用库侧再核一次) rows=$(grep -c '^INSERT INTO' "$tmp" || true) [[ "$rows" -gt 0 ]] || die "04 里一条 INSERT 都没有——抽样数据没导进本地镜像?" local bad bad=$(grep -cE '^INSERT INTO `system_' "$tmp" || true) [[ "$bad" -eq 0 ]] || die "04 里混进了 $bad 条 system_* 插入(会和 02 的种子撞主键)" mv "$tmp" "$OUT_DATA" say "✔ 04_legacy_sample.sql 涉及 $rows 张表,$(wc -c < "$OUT_DATA" | tr -d ' ') 字节" } gen_schema [[ "$WITH_DATA" == "1" ]] && gen_data # --------------------------------------------------------------------------- # 复现性自检:schema 连生成两次必须字节一致 # --------------------------------------------------------------------------- if [[ "${DEPLOY_SKIP_REPRO:-0}" != "1" ]]; then a=$(md5 -q "$OUT_SCHEMA" 2>/dev/null || md5sum "$OUT_SCHEMA" | cut -d' ' -f1) gen_schema >/dev/null 2>&1 || true b=$(md5 -q "$OUT_SCHEMA" 2>/dev/null || md5sum "$OUT_SCHEMA" | cut -d' ' -f1) # ⚠️ 变量一律写成 ${a}:macOS 自带 bash 3.2 下 `$a)` 会把全角括号 # 当成变量名的一部分,报 "a): unbound variable"(实测踩中) [[ "$a" == "$b" ]] || die "03 两次生成 md5 不一致(${a} vs ${b})——产出不可复现" say "✔ 复现性:两次生成 md5 一致(${a})" fi say "" say "部署顺序: 00_create_database → 01_system_schema → 02_system_seed → 03_legacy_schema [→ 04_legacy_sample]" say "详细步骤见 sql/DEPLOY.md"