gen_deploy_sql.sh
12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#!/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 <<HDR
-- ============================================================================
-- parking-cloud · 存量表**抽样数据**(可选,非启动必需)
-- ⚠️ 本文件由 sql/tools/gen_deploy_sql.sh --with-data 生成,**不要手改**。
-- ⚠️ 内含真实生产数据的抽样(手机号 / 车牌 / 姓名),**不要提交、不要上生产**。
--
-- 执行前提:先跑 00 → 01 → 02 → 03
-- 执行: mysql -uroot parking_cloud < 04_legacy_sample.sql
--
-- 本库抽样上限 500 行/表(生成时的本地镜像口径)。表间**无外键**(实测 0 个),
-- 所以导入顺序无所谓;但也意味着**没有一致性保证**——仅够"页面有数据"。
-- ============================================================================
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
HDR
# --no-create-info:只要 INSERT,结构已由 03 建好
# --extended-insert(默认开):一张表一条 INSERT,文件小、导入快
dump_base --no-create-info --no-create-db --skip-triggers "$DEPLOY_DB" "${TABLES[@]}" | normalize
cat <<'FTR'
SET FOREIGN_KEY_CHECKS = 1;
FTR
} > "$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"