From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master
---
docker/postgres/migration/promote-db.sh | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 262 insertions(+), 0 deletions(-)
diff --git a/docker/postgres/migration/promote-db.sh b/docker/postgres/migration/promote-db.sh
new file mode 100644
index 0000000..d2ff307
--- /dev/null
+++ b/docker/postgres/migration/promote-db.sh
@@ -0,0 +1,262 @@
+#!/usr/bin/env bash
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+# JNPF 鎸簱鍙戝竷锛氭妸鍏变韩搴擄紙寮�鍙�+娴嬭瘯鍏辩敤锛夋暣浣撴惉鍒颁笅涓�涓幆澧冿紙濡傛寮忕幆澧冿級
+#
+# 涓轰粈涔堟槸"鎸簱"鑰屼笉鏄�"鐜板満寤� schema"锛�
+# JNPF 鏄綆浠g爜骞冲彴锛岃〃鍗�/鑿滃崟/瀛楀吀/鏁版嵁鎺ュ彛/娴佺▼瀹氫箟鍏ㄩ儴瀛樺湪鏁版嵁搴撹〃閲屻��
+# 鐜板満璺� install.sh 鍙兘寰楀埌绌虹粨鏋勶紝閰嶇疆涓�涓兘涓嶄細鏈夈�傛墍浠ヤ笂绾� = 鎶婃暣涓簱鎼繃鍘伙紝
+# 閰嶇疆鍜屾暟鎹竴娆℃�у甫璧帮紙鐢ㄦ埛 2026-07-21 瀹氱殑绛栫暐锛岃 docs/release-checklist.md 搂1锛夈��
+#
+# 涓ら樁娈靛垎绂伙紝鍚勮嚜鍦ㄤ笉鍚屾満鍣ㄤ笂鎵ц锛�
+# dump 鍦ㄤ换浣曡兘杩炴簮搴撶殑鏈哄櫒涓婅窇锛堟媺鍑哄洓搴撶殑 -Fc 澶囦唤锛�
+# restore 銆愬繀椤诲湪鐩爣鏈烘湰鍦拌窇銆戔�斺�斿缓搴撹瓒呯骇鐢ㄦ埛锛岃�� postgres 鎸夎璁$姝㈣繙绋嬬櫥褰�
+# verify 鍦ㄧ洰鏍囨満涓婅窇锛堥�愬簱琛ㄦ暟/琛屾暟涓� dump 璁板綍鐨勬竻鍗曟瘮瀵癸級
+#
+# 鐢ㄦ硶锛�
+# # 鈶� 鍦ㄨ兘杩炴簮搴撶殑鏈哄櫒涓婏紙鎶� dump 鐩綍鏁翠釜鎷峰埌鐩爣鏈猴級
+# ./promote-db.sh dump
+# # 鈶� 鍦ㄧ洰鏍囨満涓婏紙鍏堢‘淇� PG 瀹瑰櫒宸茶捣銆佷笖宸茶窇杩� create-app-roles.sh锛�
+# DST_CONTAINER=cx-postgres DUMP_DIR=/d/promote-20260725 ./promote-db.sh restore
+# DST_CONTAINER=cx-postgres DUMP_DIR=/d/promote-20260725 ./promote-db.sh verify
+#
+# 鍙傛暟锛堢幆澧冨彉閲忥級锛�
+# SRC_HOST/SRC_PORT/SRC_USER/SRC_PASSWORD 婧愬簱锛堥粯璁よ .env.tencent 鐨� CVM 鍏变韩搴擄級
+# DST_CONTAINER 鐩爣 PG 瀹瑰櫒鍚嶏紙榛樿 cx-postgres锛�
+# DUMP_DIR dump 浜х墿鐩綍锛堥粯璁� ./promote-dump-<鏃ユ湡>锛�
+# DATABASES 榛樿 "jnpf_init jnpf_flow jnpf_xxljob nacos_config"
+# ALLOW_NONEMPTY=1 鍏佽鐩爣搴撻潪绌猴紙榛樿鎷掔粷锛岄槻璇鐩栫敓浜э級
+#
+# 鈿狅笍 鏈剼鏈埢鎰忋�愪笉鍋氥�戠殑浜嬶紙閮芥槸闇�瑕佷汉鍒ゆ柇銆佷笉璇ョ敱鑴氭湰鏇夸綘鍐冲畾鐨勶級锛�
+# - 涓嶆竻娲楁祴璇曚笟鍔℃暟鎹細鍝簺璇烽獙鍗�/娴佺▼瀹炰緥绠�"娴嬭瘯鍨冨溇"鍙湁涓氬姟鑳藉畾锛岃閰嶅鏂囨。銆屾礂鏁版嵁銆嶈妭
+# - 涓嶆敼 Nacos 閰嶇疆閲岀殑鐜鐩稿叧鍊硷紙ApiDomain/FrontDomain 绛夛級锛歳estore 鍚庢寜鏂囨。娓呭崟浜哄伐鏍稿
+# - 涓嶅缓瑙掕壊锛歳estore 鍓嶅繀椤诲厛璺� create-app-roles.sh锛堢敤鐩爣鐜鐨勫瘑鐮侊級锛屽惁鍒� owner 钀戒笉瀵�
+# 閰嶅鏂囨。锛歞ocs/db-promote-guide.md锛堝惈涓嶇‘瀹氱偣涓庢湭鏉ュ彲鑳界殑鍙樺寲锛�
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
+
+# 榛樿鍙尓涓変釜搴撱��**鍒绘剰涓嶅惈 jnpf_xxljob**锛氬畠鐨� 11 寮犺〃 owner 鏄� postgres锛岃�� CVM/鐢熶骇鐨�
+# postgres 鎸夎璁$姝㈣繙绋嬬櫥褰� 鈫� 杩滅▼ dump 涓嶄簡锛涗笖瀹冭鐨勬槸 xxl-job 鐨勪换鍔℃敞鍐屼笌鎵ц鏃ュ織锛�
+# 姝e紡鐜鏈氨璇ユ槸鍏ㄦ柊鐨勶紝涓嶈鎶婃祴璇曠幆澧冪殑浠诲姟鏃ュ織鎼繃鍘汇�傛寮忕幆澧冭窇 install.sh 寤虹┖缁撴瀯鍗冲彲銆�
+# 锛堢‘瑕佽繛瀹冧竴璧锋惉锛屽彧鑳藉湪婧愭満鏈湴鐢ㄨ秴鎴� dump锛岃 docs/db-promote-guide.md銆宩npf_xxljob 鎬庝箞鍔炪�嶏級
+DATABASES="${DATABASES:-jnpf_init jnpf_flow nacos_config}"
+DUMP_DIR="${DUMP_DIR:-$PWD/promote-dump-$(date +%Y%m%d)}"
+DST_CONTAINER="${DST_CONTAINER:-cx-postgres}"
+# 涓変釜涓氬姟瑙掕壊锛歳estore 鍓嶅繀椤诲凡瀛樺湪锛屽惁鍒� dump 閲岀殑 OWNER TO 钀戒笉涓嬪幓
+REQUIRED_ROLES="${REQUIRED_ROLES:-jnpf_app jnpf_flow_app nacos_app}"
+
+# 姣忎釜搴撳綊灞炰笉鍚岃鑹诧紙涓夊簱涓夎鑹查殧绂伙級锛宒ump 蹇呴』鐢ㄥ悇鑷殑 owner 鈥斺�� 鐢ㄥ崟涓� app 瑙掕壊浼氬湪
+# 鍒殑搴撲笂鎾� "permission denied for table"锛堝疄娴� 2026-07-25锛夈�傛牸寮忥細搴�=瑙掕壊:瀵嗙爜閿�
+db_role() {
+ case "$1" in
+ jnpf_init) echo "jnpf_app|JNPF_APP_DB_PASSWORD" ;;
+ jnpf_flow) echo "jnpf_flow_app|JNPF_FLOW_DB_PASSWORD" ;;
+ nacos_config) echo "nacos_app|JNPF_NACOS_DB_PASSWORD" ;;
+ *) echo "" ;;
+ esac
+}
+
+log() { printf '\033[1;34m[promote]\033[0m %s\n' "$*"; }
+warn() { printf '\033[1;33m[promote][warn]\033[0m %s\n' "$*"; }
+die() { printf '\033[1;31m[promote][err]\033[0m %s\n' "$*" >&2; exit 1; }
+step() { printf '\n\033[1;36m鈹�鈹� %s 鈹�鈹�\033[0m\n' "$*"; }
+
+# 婧愬簱杩炴帴锛氶粯璁ゅ彇 CVM 鍏变韩搴擄紙鍚勮鑹插瘑鐮佸湪浠撳簱鏍� .env.tencent锛屽凡 gitignore锛�
+SRC_HOST="${SRC_HOST:-YOUR_DB_HOST}"
+SRC_PORT="${SRC_PORT:-15432}"
+SRC_ENV_FILE="${SRC_ENV_FILE:-$REPO_ROOT/.env.tencent}"
+
+# 浠� env 鏂囦欢鍙栨寚瀹氶敭鐨勫瘑鐮侊紱SRC_PASSWORD_<閿�> 褰㈠紡鐨勭幆澧冨彉閲忓彲瑕嗙洊锛堜緵闈� CVM 婧愪娇鐢級
+src_password_for() { # $1=瀵嗙爜閿悕锛屽 JNPF_APP_DB_PASSWORD
+ local key="$1" ov
+ ov="$(eval "printf '%s' \"\${${key}:-}\"")"
+ if [ -n "$ov" ]; then printf '%s' "$ov"; return 0; fi
+ [ -f "$SRC_ENV_FILE" ] || return 1
+ grep -E "^${key}=" "$SRC_ENV_FILE" | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'"
+}
+
+# pg_dump/pg_restore 鏉ユ簮锛氫紭鍏堝涓昏嚜甯︼紙鐗堟湰椤� 鈮� 鏈嶅姟绔� 18锛夛紝鍚﹀垯鐢ㄦ湰鍦� PG 闀滃儚璺�
+PG_IMAGE="${PG_IMAGE:-cx/postgres:18.4}"
+have_local_pgdump() { command -v pg_dump >/dev/null 2>&1; }
+
+usage() { sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//'; exit 1; }
+
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� dump 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+do_dump() {
+ step "闃舵 1/3锛氫粠婧愬簱瀵煎嚭锛�$SRC_HOST:${SRC_PORT}锛�"
+ mkdir -p "$DUMP_DIR"
+
+ local fail=0
+ for db in $DATABASES; do
+ local map usr key pwd out
+ map="$(db_role "$db")"
+ if [ -z "$map" ]; then
+ warn " 鉁� ${db} 娌℃湁瀵瑰簲鐨勪笟鍔¤鑹诧紙鍏惰〃澶氬崐褰� postgres锛岃�岃秴鎴风杩滅▼锛�"
+ warn " 鈫� 璇ュ簱鍙兘鍦ㄦ簮鏈烘湰鍦扮敤瓒呮埛 dump锛屾垨纭瀹冩槸鍚︾湡闇�瑕佹尓锛涜 docs/db-promote-guide.md"
+ fail=$((fail+1)); continue
+ fi
+ usr="${map%%|*}"; key="${map##*|}"
+ pwd="$(src_password_for "$key" || true)"
+ [ -n "$pwd" ] || { warn " 鉁� ${db} 鍙栦笉鍒板瘑鐮侊紙閿� $key锛屾煡 $SRC_ENV_FILE 鎴栬鍚屽悕鐜鍙橀噺锛�"; fail=$((fail+1)); continue; }
+ out="$DUMP_DIR/${db}.dump"
+ log "pg_dump ${db}锛堣鑹� ${usr}锛夆��"
+ if have_local_pgdump; then
+ PGPASSWORD="$pwd" pg_dump -h "$SRC_HOST" -p "$SRC_PORT" -U "$usr" \
+ -Fc -d "$db" > "$out" 2>"$DUMP_DIR/${db}.dump.err" || { warn " 鉁� ${db} 瀵煎嚭澶辫触锛岃 ${db}.dump.err"; rm -f "$out"; fail=$((fail+1)); continue; }
+ else
+ docker run --rm -e PGPASSWORD="$pwd" "$PG_IMAGE" \
+ pg_dump -h "$SRC_HOST" -p "$SRC_PORT" -U "$usr" -Fc -d "$db" > "$out" 2>"$DUMP_DIR/${db}.dump.err" \
+ || { warn " 鉁� ${db} 瀵煎嚭澶辫触锛岃 ${db}.dump.err"; rm -f "$out"; fail=$((fail+1)); continue; }
+ fi
+ log " 鉁� ${db} $(du -h "$out" | cut -f1)"
+ done
+ [ "$fail" = 0 ] || die "鏈� ${fail} 涓簱瀵煎嚭澶辫触"
+
+ step "璁板綍婧愬簱娓呭崟锛堜緵 verify 闃舵姣斿锛�"
+ local manifest="$DUMP_DIR/MANIFEST.txt"
+ {
+ echo "# JNPF 鎸簱鍙戝竷 dump 娓呭崟"
+ echo "# 婧�: $SRC_HOST:$SRC_PORT 瀵煎嚭鏃堕棿: $(date '+%Y-%m-%d %H:%M:%S')"
+ for db in $DATABASES; do
+ local n
+ n=$(query_src "$db" "select count(*) from information_schema.tables where table_schema='public' and table_type='BASE TABLE'")
+ echo "TABLES|$db|$n"
+ # jnpf_init 閫愯〃琛屾暟锛堜笟鍔′富搴擄紝鍊煎緱绮剧‘璁板綍锛�
+ if [ "$db" = "jnpf_init" ]; then
+ query_src "$db" "select 'ROWS|$db|'||relname||'|'||(xpath('/row/c/text()', query_to_xml(format('select count(*) as c from public.%I', relname), false, true, '')))[1]::text
+ from pg_stat_user_tables order by relname"
+ fi
+ done
+ } > "$manifest" 2>/dev/null || warn "娓呭崟鐢熸垚閮ㄥ垎澶辫触锛堜笉褰卞搷 dump 鏈韩锛�"
+ local nrows; nrows=$(grep -c '^ROWS' "$manifest" 2>/dev/null) || nrows=0
+ log "娓呭崟宸插啓鍏� $(basename "$manifest")锛堝惈 ${nrows} 寮犺〃鐨勭簿纭鏁帮級"
+
+ step "dump 瀹屾垚"
+ log "浜х墿鐩綍锛�$DUMP_DIR"
+ log "涓嬩竴姝ワ細鎶婃暣涓洰褰曟嫹鍒扮洰鏍囨満锛岀劧鍚庡湪銆愮洰鏍囨満涓娿�戞墽琛� restore"
+ warn "璇ョ洰褰曞惈鍏ㄩ儴涓氬姟鏁版嵁锛屼紶杈撲笌鐣欏瓨鎸夋晱鎰熸暟鎹寰咃紱瀵煎叆瀹屾垚鍚庢竻鐞�"
+}
+
+query_src() { # $1=搴� $2=SQL 鈥斺�� 鑷姩鐢ㄨ搴撳搴旂殑 owner 瑙掕壊杩炴帴
+ local map usr key pwd
+ map="$(db_role "$1")"; [ -n "$map" ] || return 1
+ usr="${map%%|*}"; key="${map##*|}"
+ pwd="$(src_password_for "$key" || true)"; [ -n "$pwd" ] || return 1
+ if have_local_pgdump; then
+ PGPASSWORD="$pwd" psql -h "$SRC_HOST" -p "$SRC_PORT" -U "$usr" -d "$1" -tAc "$2" 2>/dev/null
+ else
+ docker run --rm -e PGPASSWORD="$pwd" "$PG_IMAGE" \
+ psql -h "$SRC_HOST" -p "$SRC_PORT" -U "$usr" -d "$1" -tAc "$2" 2>/dev/null
+ fi
+}
+
+dst_psql() { docker exec -i "$DST_CONTAINER" psql -U postgres -tAc "$1" 2>/dev/null; }
+
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� restore 鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+do_restore() {
+ step "闃舵 2/3锛氱亴鍏ョ洰鏍囧簱锛堝鍣� ${DST_CONTAINER}锛�"
+ [ -d "$DUMP_DIR" ] || die "dump 鐩綍涓嶅瓨鍦細${DUMP_DIR}锛堝厛鍦ㄦ簮渚ц窇 dump 骞舵妸鐩綍鎷疯繃鏉ワ級"
+ docker inspect "$DST_CONTAINER" >/dev/null 2>&1 || die "鐩爣瀹瑰櫒 $DST_CONTAINER 涓嶅湪锛堝厛 docker compose up -d cx-postgres锛�"
+
+ step "鍓嶇疆妫�鏌�"
+ # 鈶� 瑙掕壊蹇呴』宸插瓨鍦ㄢ�斺�斿惁鍒� dump 閲岀殑 OWNER TO 钀戒笉涓嬪幓锛屽皢鏉� app 瑙掕壊鏀逛笉浜嗚〃锛圠041锛�
+ for r in $REQUIRED_ROLES; do
+ if [ "$(dst_psql "select 1 from pg_roles where rolname='$r'")" = "1" ]; then
+ log " 鉁� 瑙掕壊 $r 宸插瓨鍦�"
+ else
+ die "瑙掕壊 $r 涓嶅瓨鍦ㄣ�俽estore 鍓嶅繀椤诲厛鐢ㄣ�愮洰鏍囩幆澧冪殑瀵嗙爜銆戣窇 create-app-roles.sh锛�
+ 鍚﹀垯 dump 涓殑 OWNER TO 鏃犲钀藉湴锛屼簨鍚� app 瑙掕壊浼氭敼涓嶅姩琛紙lessons L041锛夈��"
+ fi
+ done
+ # 鈶� 鐩爣搴撳簲涓虹┖鈥斺�旈槻姝㈡妸宸叉湁鐢熶骇鏁版嵁瑕嗙洊鎺�
+ for db in $DATABASES; do
+ if [ "$(dst_psql "select 1 from pg_database where datname='$db'")" = "1" ]; then
+ local n
+ n=$(docker exec -i "$DST_CONTAINER" psql -U postgres -d "$db" -tAc \
+ "select count(*) from information_schema.tables where table_schema='public'" 2>/dev/null || echo 0)
+ if [ "${n:-0}" -gt 0 ]; then
+ if [ "${ALLOW_NONEMPTY:-}" = "1" ]; then
+ warn " ! $db 宸叉湁 $n 寮犺〃锛孉LLOW_NONEMPTY=1 鈫� 灏嗕互 --clean --if-exists 瑕嗙洊"
+ else
+ die "$db 宸插瓨鍦ㄤ笖鏈� $n 寮犺〃銆傞粯璁ゆ嫆缁濊鐩栦互闃叉姽鎺夌敓浜ф暟鎹��
+ 纭瑕佽鐩栬鏄惧紡澹版槑锛欰LLOW_NONEMPTY=1 $0 restore"
+ fi
+ fi
+ fi
+ done
+
+ step "寮�濮嬬亴鍏�"
+ local fail=0
+ for db in $DATABASES; do
+ local f="$DUMP_DIR/${db}.dump"
+ [ -f "$f" ] || { warn " 鉁� 缂� ${db}.dump锛岃烦杩�"; fail=$((fail+1)); continue; }
+ dst_psql "select 1 from pg_database where datname='$db'" >/dev/null 2>&1
+ if [ "$(dst_psql "select 1 from pg_database where datname='$db'")" != "1" ]; then
+ log " 寤哄簱 $db"
+ docker exec -i "$DST_CONTAINER" createdb -U postgres "$db" || { warn " 鉁� 寤哄簱 $db 澶辫触"; fail=$((fail+1)); continue; }
+ fi
+ log "pg_restore ${db} 鈥�"
+ local opts="-U postgres -d $db --no-privileges --exit-on-error"
+ [ "${ALLOW_NONEMPTY:-}" = "1" ] && opts="$opts --clean --if-exists"
+ # shellcheck disable=SC2086
+ if docker exec -i "$DST_CONTAINER" pg_restore $opts < "$f" 2>"$DUMP_DIR/${db}.restore.err"; then
+ log " 鉁� ${db}"
+ else
+ warn " 鉁� ${db} 鐏屽叆鎶ラ敊锛岃 ${db}.restore.err锛堝墠 3 琛岋級锛�"
+ head -3 "$DUMP_DIR/${db}.restore.err" | sed 's/^/ /'
+ fail=$((fail+1))
+ fi
+ done
+ [ "$fail" = 0 ] || die "鏈� ${fail} 涓簱鐏屽叆澶辫触"
+
+ step "restore 瀹屾垚 鈥斺�� 浣嗗彂甯冭繕娌$粨鏉�"
+ cat <<'NEXT'
+ 蹇呴』浜哄伐鎺ョ潃鍋氱殑涓変欢浜嬶紙鑴氭湰鍒绘剰涓嶄唬鍔筹紝璇﹁ docs/db-promote-guide.md锛夛細
+ 1. 鏍稿 Nacos 鐜鐩稿叧閰嶇疆锛欰piDomain / FrontDomain / 鍚勬湇鍔″湴鍧� 鈥斺�� 杩欎簺鍊奸殢鐜鍙橈紝
+ 鎼繃鏉ョ殑鏄簮鐜鐨勫�硷紝涓嶆敼浼氬鑷存枃浠堕瑙� 302 璺冲埌閿欒鍩熷悕锛坙essons L040锛�
+ 2. 鍐冲畾鏄惁娓呮礂娴嬭瘯涓氬姟鏁版嵁锛氶厤缃繀椤诲甫璧帮紝浣嗘祴璇曟湡鐨勮楠屽崟/娴佺▼瀹炰緥閫氬父璇ユ竻
+ 3. 璺� verify 瀛愬懡浠ゅ仛琛屾暟姣斿锛屽啀鎸� docs/release-checklist.md 杩囧彂甯冮棬绂�
+NEXT
+}
+
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� verify 鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+do_verify() {
+ step "闃舵 3/3锛氭牎楠岋紙鐩爣瀹瑰櫒 $DST_CONTAINER vs dump 娓呭崟锛�"
+ local manifest="$DUMP_DIR/MANIFEST.txt"
+ [ -f "$manifest" ] || die "缂烘竻鍗� ${manifest}锛坉ump 闃舵鐢熸垚锛岄渶涓� dump 鐩綍涓�鍚屾嫹鏉ワ級"
+
+ local bad=0
+ step "琛ㄦ暟閲忔瘮瀵�"
+ while IFS='|' read -r tag db n; do
+ [ "$tag" = "TABLES" ] || continue
+ local m
+ m=$(docker exec -i "$DST_CONTAINER" psql -U postgres -d "$db" -tAc \
+ "select count(*) from information_schema.tables where table_schema='public' and table_type='BASE TABLE'" 2>/dev/null || echo "-")
+ if [ "$m" = "$n" ]; then log " 鉁� $db $m 寮犺〃"; else warn " 鉁� $db 婧� $n / 鐩爣 $m"; bad=$((bad+1)); fi
+ done < <(grep '^TABLES' "$manifest")
+
+ step "jnpf_init 閫愯〃琛屾暟姣斿"
+ local diff=0 checked=0
+ while IFS='|' read -r tag db tbl n; do
+ [ "$tag" = "ROWS" ] || continue
+ checked=$((checked+1))
+ local m
+ m=$(docker exec -i "$DST_CONTAINER" psql -U postgres -d "$db" -tAc "select count(*) from public.\"$tbl\"" 2>/dev/null || echo "-")
+ if [ "$m" != "$n" ]; then printf ' 鉁� %-46s 婧�=%-8s 鐩爣=%s\n' "$tbl" "$n" "$m"; diff=$((diff+1)); fi
+ done < <(grep '^ROWS' "$manifest")
+ log "閫愯〃姣斿锛�$checked 寮犺〃锛�$diff 寮犱笉涓�鑷�"
+ [ "$diff" = 0 ] || warn "涓嶄竴鑷寸殑琛ㄨ嫢鍏ㄦ槸鏃ュ織绫伙紙base_sys_log 绛夛級锛屽彲鑳芥槸 dump 鍚庢簮搴撲粛鍦ㄥ啓鍏ユ墍鑷达紝浜哄伐纭"
+
+ [ "$bad" = 0 ] && [ "$diff" = 0 ] && { step "鏍¢獙閫氳繃 鉁�"; return 0; }
+ step "鏍¢獙鏈叏閫氳繃 鈥斺�� 閫愭潯纭鍚庡啀鏀捐"; return 1
+}
+
+case "${1:-}" in
+ dump) do_dump ;;
+ restore) do_restore ;;
+ verify) do_verify ;;
+ all) die "涓嶆彁渚� all锛歞ump 涓� restore 鎸夎璁″湪涓嶅悓鏈哄櫒涓婃墽琛岋紙restore 闇�鐩爣鏈烘湰鍦拌秴鎴凤級锛岃鍒嗗埆鎵ц" ;;
+ *) usage ;;
+esac
--
Gitblit v1.8.0