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/migrate-data.sh |  242 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 242 insertions(+), 0 deletions(-)

diff --git a/docker/postgres/migration/migrate-data.sh b/docker/postgres/migration/migrate-data.sh
new file mode 100644
index 0000000..06a658a
--- /dev/null
+++ b/docker/postgres/migration/migrate-data.sh
@@ -0,0 +1,242 @@
+#!/usr/bin/env bash
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+# JNPF 鏁版嵁杩佺Щ锛堝仠鏈虹獥鍙d竴娆℃�э級锛氱幇鍦� MySQL 鈫� 涓浆 mirror 鈫� pgloader 鈫� PG
+#
+#   dump    锛氫粠鐜板満 MySQL mysqldump锛堣烦 base_sys_log 鏃ュ織锛夊埌鏈湴 dump/
+#   mirror  锛氳捣涓存椂 mysql:8.0 涓浆瀹瑰櫒锛岀亴鍏� dump锛坧gloader 鐨� qmynd 涓嶆敮鎸� MySQL8 鎻℃墜锛屾晠涓浆锛�
+#   load    锛歱gloader 鐏� jnpf_init/jnpf_flow + blade_visual_map 璧� --tab鈫扖OPY
+#             + 鏁版嵁鎺ュ彛淇 + log_seq setval + 鎶芥牱鏍¢獙
+#   cleanup 锛氬垹闄や复鏃� mirror 瀹瑰櫒涓� dump/
+#   all     锛歞ump 鈫� mirror 鈫� load锛堜笉鍚� cleanup锛�
+#
+# 鐢ㄦ硶锛�
+#   ./migrate-data.sh all         # 鐜板満鍋滄満绐楀彛鍏ㄦ祦绋�
+#   ./migrate-data.sh load        # 鍙窇 PG 渚э紙mirror 宸插氨缁椂锛屾垨闅旂娴嬭瘯锛�
+#   ./migrate-data.sh cleanup     # 杩佺Щ楠屾敹鍚庢竻鐞嗕复鏃惰祫婧�
+#
+# 鍙傛暟锛堢幆澧冨彉閲忥級锛�
+#   婧愬簱   SRC_MYSQL_HOST/PORT/USER/PWD   **鏃犺蒋榛樿**锛欻OST 涓� PWD 蹇呴』鏄惧紡浼犲叆锛坉ump 姝ユ牎楠岋級
+#   涓浆   MIRROR(瀹瑰櫒鍚�) MIRROR_PWD        榛樿 cx-mysql-mirror / mirror_2026锛堜复鏃跺鍣紝闅� cleanup 閿�姣侊級
+#   鐩爣   CONTAINER PGUSER                 榛樿 cx-postgres / postgres
+#         PG_PASSWORD                       榛樿璇讳粨搴撴牴/浜や粯鍖呮牴 .env 鐨� JNPF_PG_PASSWORD锛坙oad 姝ユ牎楠岋紱
+#                                           pgloader 璧� TCP scram 鐪熼獙瀵嗙爜锛屽�煎繀椤绘纭紝L030锛�
+#         PG_INIT_DB PG_FLOW_DB            榛樿 jnpf_init / jnpf_flow锛堥殧绂绘祴璇曞彲鏀� *_t8锛�
+#         PG_INT_PORT                       瀹瑰櫒鍐� PG 绔彛锛岄粯璁� 5432
+#   NET    docker 缃戠粶                      榛樿鑷姩鍙� CONTAINER 鎵�鍦ㄧ綉缁�
+#   DUMP_DIR / PGLOADER_IMAGE / MYSQL_IMAGE
+# 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+set -euo pipefail
+
+# 鈹�鈹� 閫�褰规姢鏍忥紙2026-07-25锛夆攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+# 鏈剼鏈殑鍓嶆彁銆岃吘璁簯 MySQL = 浜嬪疄婧愩�嶈嚜 2026-07-20 璧峰凡娑堝け锛氱幇浜嬪疄婧愭槸鑵捐浜� CVM 鍏变韩 PG
+# 锛圷OUR_DB_HOST:15432锛屽紑鍙戜笌娴嬭瘯鍏辩敤涓�搴擄級锛孧ySQL 鏁版嵁鍋滃湪 07-16锛屼粎浣滃喎澶囧洖閫�搴曠墝淇濈暀銆�
+# 鏈剼鏈細 truncate 閲嶇亴鐩爣搴擄紝璇窇 = 鐢ㄥ簾寮冩棫蹇収瑕嗙洊褰撳墠涓氬姟鏁版嵁锛屾槸鐪熷疄鏁版嵁浜嬫晠銆�
+# 鑳屾櫙涓庡綋鍓嶆嫇鎵戯細README-杩佺Щ鎿嶄綔鎵嬪唽.md 鏂囬 / docs/db-connection-pool-guide.md
+if [ "${I_KNOW_MIGRATION_IS_RETIRED:-}" != "1" ]; then
+  printf '\033[1;31m[宸查��褰孤锋嫆缁濇墽琛宂\033[0m %s\n' "migrate-data.sh 鑷� 2026-07-21 杩佺Щ瀹屾垚鍚庡凡閫�褰广��" >&2
+  printf '  鍓嶆彁宸蹭笉鎴愮珛锛氳吘璁簯 MySQL 涓嶅啀鏄簨瀹炴簮锛堢幇涓� CVM 鍏变韩 PG锛孧ySQL 鍋滃湪 07-16锛夈�俓n' >&2
+  printf '  璇窇鍚庢灉锛氱敤搴熷純蹇収 truncate 閲嶇亴鐩爣搴� 鈥斺�� 浼氭姽鎺夊綋鍓嶄笟鍔℃暟鎹�俓n' >&2
+  printf '  鑻ョ‘鍦ㄥ叏鏂扮幆澧冨仛涓�娆℃�у巻鍙茶縼绉伙紝鏄惧紡澹版槑鍚庨噸璇曪細\n' >&2
+  printf '    I_KNOW_MIGRATION_IS_RETIRED=1 %s %s\n' "$0" "${*:-}" >&2
+  exit 1
+fi
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SQL_DIR="$SCRIPT_DIR/sql"
+
+# 鍑嵁涓嶈杞粯璁わ紙codex review 2026-07-17锛夛紱.env 浠庤剼鏈洰褰曞悜涓婃帰娴嬶紙浠撳簱鏍规垨浜や粯鍖呮牴锛�
+env_file() {
+  local d="$SCRIPT_DIR"
+  while [ "$d" != "/" ]; do
+    if [ -f "$d/.env" ]; then printf '%s\n' "$d/.env"; return 0; fi
+    d="$(dirname "$d")"
+  done
+  return 1
+}
+envval() {
+  local f
+  f="$(env_file)" || return 0
+  grep -E "^$1=" "$f" | tail -1 | cut -d= -f2- || true
+}
+
+SRC_MYSQL_HOST="${SRC_MYSQL_HOST:-}"
+SRC_MYSQL_PORT="${SRC_MYSQL_PORT:-3306}"
+SRC_MYSQL_USER="${SRC_MYSQL_USER:-root}"
+SRC_MYSQL_PWD="${SRC_MYSQL_PWD:-}"
+
+MIRROR="${MIRROR:-cx-mysql-mirror}"
+MIRROR_USER="${MIRROR_USER:-root}"
+MIRROR_PWD="${MIRROR_PWD:-mirror_2026}"
+
+CONTAINER="${CONTAINER:-cx-postgres}"
+PGUSER="${PGUSER:-postgres}"
+PG_PASSWORD="${PG_PASSWORD:-$(envval JNPF_PG_PASSWORD)}"
+PG_INIT_DB="${PG_INIT_DB:-jnpf_init}"
+PG_FLOW_DB="${PG_FLOW_DB:-jnpf_flow}"
+PG_INT_PORT="${PG_INT_PORT:-5432}"
+
+DUMP_DIR="${DUMP_DIR:-$SCRIPT_DIR/dump}"
+PGLOADER_IMAGE="${PGLOADER_IMAGE:-dimitri/pgloader:latest}"
+MYSQL_IMAGE="${MYSQL_IMAGE:-mysql:8.0}"
+STEP="${1:-all}"
+
+log()  { printf '\033[1;34m[migrate]\033[0m %s\n' "$*"; }
+warn() { printf '\033[1;33m[migrate][warn]\033[0m %s\n' "$*"; }
+die()  { printf '\033[1;31m[migrate][err]\033[0m %s\n' "$*" >&2; exit 1; }
+
+discover_net() {
+  docker inspect "$CONTAINER" >/dev/null 2>&1 || die "鐩爣瀹瑰櫒 $CONTAINER 涓嶅湪鈥斺�斿厛 docker compose up -d cx-postgres 鍐嶈窇 install.sh"
+  docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' "$CONTAINER" | awk '{print $1}'
+}
+NET="${NET:-$(discover_net)}"
+
+mirror_sql() { docker exec -i -e MYSQL_PWD="$MIRROR_PWD" "$MIRROR" mysql --default-character-set=utf8mb4 -u"$MIRROR_USER" "$@"; }
+pg_sql()     { docker exec -i "$CONTAINER" psql -U "$PGUSER" "$@"; }
+mirror_count(){ mirror_sql -N -e "SELECT COUNT(*) FROM \`$2\`" "$1" 2>/dev/null; }
+pg_count()   { pg_sql -tAc "SELECT count(*) FROM $2" -d "$1"; }
+
+# 鈹�鈹� dump锛氱幇鍦� MySQL 鈫� dump/ 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+do_dump() {
+  [ -n "$SRC_MYSQL_HOST" ] || die "缂� SRC_MYSQL_HOST锛堟簮 MySQL 鍦板潃椤绘樉寮忎紶鍏ワ紝濡� SRC_MYSQL_HOST=127.0.0.1锛�"
+  [ -n "$SRC_MYSQL_PWD" ]  || die "缂� SRC_MYSQL_PWD锛堟簮 MySQL 瀵嗙爜椤绘樉寮忎紶鍏ワ紝涓嶈杞粯璁わ級"
+  mkdir -p "$DUMP_DIR"
+  local common="--single-transaction --hex-blob --set-gtid-purged=OFF --default-character-set=utf8mb4 --column-statistics=0"
+  log "mysqldump jnpf_init锛堣烦 base_sys_log锛夆�� 婧� $SRC_MYSQL_HOST:$SRC_MYSQL_PORT"
+  docker run --rm -e MYSQL_PWD="$SRC_MYSQL_PWD" -v "$DUMP_DIR":/dump "$MYSQL_IMAGE" \
+    sh -c "mysqldump -h$SRC_MYSQL_HOST -P$SRC_MYSQL_PORT -u$SRC_MYSQL_USER $common --ignore-table=jnpf_init.base_sys_log jnpf_init > /dump/jnpf_init.sql"
+  log "mysqldump jnpf_flow 鈥�"
+  docker run --rm -e MYSQL_PWD="$SRC_MYSQL_PWD" -v "$DUMP_DIR":/dump "$MYSQL_IMAGE" \
+    sh -c "mysqldump -h$SRC_MYSQL_HOST -P$SRC_MYSQL_PORT -u$SRC_MYSQL_USER $common jnpf_flow > /dump/jnpf_flow.sql"
+  log "dump 瀹屾垚锛�$(du -sh "$DUMP_DIR" | cut -f1) @ $DUMP_DIR"
+}
+
+# 鈹�鈹� mirror锛氳捣涓存椂 mysql8 涓浆 + 鐏� dump 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+do_mirror() {
+  [ -f "$DUMP_DIR/jnpf_init.sql" ] || die "缂� $DUMP_DIR/jnpf_init.sql锛屽厛璺� ./migrate-data.sh dump"
+  # 灏辩华鎺㈡祴璧扮湡瀹炶璇侊細mysqladmin ping 鍦�"璁よ瘉琚嫆"鏃朵篃杩斿洖 0锛屼細鍦ㄩ暅鍍忛娆″垵濮嬪寲鐨�
+  # 涓存椂鏈嶅姟鍣ㄧ獥鍙o紙root 瀵嗙爜灏氭湭鐢熸晥锛夊亣閫氳繃锛岄殢鍚庣亴鍏ユ挒 Access denied锛圠038锛�
+  wait_mirror_ready() {
+    log "绛夊緟 mirror 灏辩华锛堢湡瀹炶璇佹帰娴嬶級鈥�"
+    for i in $(seq 1 60); do
+      docker exec -e MYSQL_PWD="$MIRROR_PWD" "$MIRROR" mysql -u"$MIRROR_USER" -e "SELECT 1" >/dev/null 2>&1 && return 0
+      sleep 3
+    done
+    die "mirror 60脳3s 鏈氨缁紙docker logs $MIRROR 鏌ョ湅鍒濆鍖栬繘搴︼級"
+  }
+  if docker inspect "$MIRROR" >/dev/null 2>&1; then
+    if [ "$(docker inspect -f '{{.State.Running}}' "$MIRROR")" = "true" ]; then
+      warn "mirror 瀹瑰櫒 $MIRROR 杩愯涓紝澶嶇敤锛堝闇�閲嶇亴鍏� ./migrate-data.sh cleanup锛�"
+    else
+      log "mirror 瀹瑰櫒 $MIRROR 瀛樺湪浣嗗凡鍋滄锛屽惎鍔紙L035锛氫笉鍐嶇洸鐩鐢ㄥ仠姝㈡�佸鍣級鈥�"
+      docker start "$MIRROR" >/dev/null
+      wait_mirror_ready
+    fi
+  else
+    log "璧蜂复鏃� mirror锛坢ysql_native_password + skip-log-bin + innodb_strict_mode=OFF锛変簬缃戠粶 $NET"
+    docker run -d --name "$MIRROR" --network "$NET" -e MYSQL_ROOT_PASSWORD="$MIRROR_PWD" "$MYSQL_IMAGE" \
+      --default-authentication-plugin=mysql_native_password --skip-log-bin --innodb-strict-mode=OFF >/dev/null
+    wait_mirror_ready
+  fi
+  log "寤哄簱骞剁亴鍏� dump锛堝ぇ琛ㄨ緝鎱級鈥�"
+  mirror_sql -e "CREATE DATABASE IF NOT EXISTS jnpf_init DEFAULT CHARSET utf8mb4; CREATE DATABASE IF NOT EXISTS jnpf_flow DEFAULT CHARSET utf8mb4;"
+  mirror_sql jnpf_init < "$DUMP_DIR/jnpf_init.sql"
+  mirror_sql jnpf_flow < "$DUMP_DIR/jnpf_flow.sql"
+  log "mirror 鐏屽叆瀹屾垚锛歫npf_init blade_visual_map=$(mirror_count jnpf_init blade_visual_map) 琛�"
+}
+
+# 鈹�鈹� pgloader 妯℃澘娓叉煋 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+render_pgloader() {
+  local tpl="$1" out="$2" pgdb="$3"
+  [ -f "$tpl" ] || die "缂烘ā鏉� $tpl"
+  sed -e "s#{{MYSQL_USER}}#$MIRROR_USER#g" \
+      -e "s#{{MYSQL_PASSWORD}}#$MIRROR_PWD#g" \
+      -e "s#{{MYSQL_HOST}}#$MIRROR#g" \
+      -e "s#{{MYSQL_PORT}}#3306#g" \
+      -e "s#{{PG_USER}}#$PGUSER#g" \
+      -e "s#{{PG_PASSWORD}}#$PG_PASSWORD#g" \
+      -e "s#{{PG_HOST}}#$CONTAINER#g" \
+      -e "s#{{PG_PORT}}#$PG_INT_PORT#g" \
+      -e "s#{{PG_DB}}#$pgdb#g" \
+      "$tpl" > "$out"
+}
+run_pgloader() {
+  local loadfile="$1"
+  docker run --rm --network "$NET" -v "$(dirname "$loadfile")":/loads "$PGLOADER_IMAGE" \
+    pgloader "/loads/$(basename "$loadfile")"
+}
+
+# 鈹�鈹� blade_visual_map锛歮ysqldump --tab锛圢ULL=\N锛屽ぉ鐒跺吋瀹癸級鈫� PG COPY 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+load_blade_visual_map() {
+  log "blade_visual_map锛歮ysqldump --tab 鈫� PG COPY锛坧gloader 瀵硅宸ㄨ琛ㄤ細鍫嗙垎锛屾晠璧� COPY锛�"
+  docker exec -e MYSQL_PWD="$MIRROR_PWD" "$MIRROR" sh -c \
+    "rm -f /var/lib/mysql-files/blade_visual_map.* && mysqldump --tab=/var/lib/mysql-files --default-character-set=utf8mb4 -u$MIRROR_USER jnpf_init blade_visual_map"
+  pg_sql -d "$PG_INIT_DB" -c "TRUNCATE blade_visual_map"
+  docker exec "$MIRROR" cat /var/lib/mysql-files/blade_visual_map.txt \
+    | pg_sql -d "$PG_INIT_DB" -c "COPY blade_visual_map FROM STDIN"
+  docker exec "$MIRROR" rm -f /var/lib/mysql-files/blade_visual_map.*
+  log "blade_visual_map 鈫� PG $PG_INIT_DB = $(pg_count "$PG_INIT_DB" blade_visual_map) 琛�"
+}
+
+# 鈹�鈹� load锛歱gloader + blade + 涓氬姟鏁版嵁鍥炲~ + 鏁版嵁鎺ュ彛淇 + setval + 鎶芥牱 鈹�鈹�鈹�鈹�鈹�鈹�
+do_load() {
+  docker inspect "$MIRROR" >/dev/null 2>&1 || die "mirror $MIRROR 涓嶅湪锛屽厛璺� ./migrate-data.sh mirror"
+  [ -n "$PG_PASSWORD" ] || die "缂� PG_PASSWORD锛堟樉寮忎紶鍏ワ紝鎴栧湪浠撳簱鏍�/浜や粯鍖呮牴 .env 璁� JNPF_PG_PASSWORD锛�"
+  # 娓叉煋鐩綍鏀� DUMP_DIR 涓嬭�岄潪 mktemp锛欸it Bash 鐨� /tmp 鏄� MSYS 铏氭嫙璺緞锛�
+  # docker -v 鎸傝浇鏃� daemon 鍙細寤虹┖鐩綍锛圠036/L038 鍚屾棌锛夛紱DUMP_DIR 鐨勭洏绗﹀舰鎬佸凡琚� dump 姝ュ疄璇佸彲鎸傝浇銆�
+  # 娉ㄦ剰 .loads 鍐呭惈娓叉煋鍚庣殑杩炴帴瀵嗙爜鈥斺�斿嚱鏁拌繑鍥炲嵆鍒狅紝cleanup 姝ュ厹搴�
+  local rdir; rdir="$DUMP_DIR/.loads"; rm -rf "$rdir"; mkdir -p "$rdir"; trap 'rm -rf "$rdir"' RETURN
+
+  log "===== pgloader jnpf_init 鈫� PG ${PG_INIT_DB}锛堟帓闄� base_sys_log/blade_visual_map锛�====="
+  render_pgloader "$SQL_DIR/pgloader_jnpf_init.load.tpl" "$rdir/init.load" "$PG_INIT_DB"
+  run_pgloader "$rdir/init.load"
+
+  load_blade_visual_map
+
+  log "===== pgloader jnpf_flow 鈫� PG ${PG_FLOW_DB}锛堝ぇ鍐� ACT/FLW 琛級====="
+  render_pgloader "$SQL_DIR/pgloader_jnpf_flow.load.tpl" "$rdir/flow.load" "$PG_FLOW_DB"
+  run_pgloader "$rdir/flow.load"
+
+  log "===== 姘寸郴缁熻鍒掓祦绋嬬姸鎬佸洖濉紙鍘嗗彶鏁版嵁瀵煎叆鍚庢墽琛岋級====="
+  pg_sql -d "$PG_INIT_DB" -v ON_ERROR_STOP=1 -f - < "$SQL_DIR/update_lims_sxt_flow_biz_status_20260719.sql"
+  pg_sql -d "$PG_INIT_DB" -v ON_ERROR_STOP=1 -f - < "$SQL_DIR/update_lims_sxt_quyang_zhongzhi_shenqing_biz_status_20260723.sql"
+  pg_sql -d "$PG_INIT_DB" -v ON_ERROR_STOP=1 -f - < "$SQL_DIR/update_lims_sxt_quyang_zhongzhi_shenqing_qingyandan_link_20260723.sql"
+  pg_sql -d "$PG_INIT_DB" -v ON_ERROR_STOP=1 -f - < "$SQL_DIR/update_lims_sxt_plan_status_20260720.sql"
+  pg_sql -d "$PG_INIT_DB" -v ON_ERROR_STOP=1 -f - < "$SQL_DIR/create_lims_sxt_quyang_jihua_renwu_20260724.sql"
+  pg_sql -d "$PG_INIT_DB" -v ON_ERROR_STOP=1 -f - < "$SQL_DIR/alter_lims_sxt_quyang_jihua_renwu_jianyan_xiang_kuaizhao_20260724.sql"
+
+  log "===== 鏁版嵁鎺ュ彛淇锛堝惈 4 鏉� UPDATE + 娈嬬暀妫�鏌ワ紝搴� 0 琛岋級====="
+  pg_sql -d "$PG_INIT_DB" -v ON_ERROR_STOP=1 -f - < "$SQL_DIR/data_interface_pg_fixes.sql"
+
+  log "===== lims_biz_log.log_seq setval ====="
+  pg_sql -d "$PG_INIT_DB" -tAc \
+    "SELECT setval(pg_get_serial_sequence('lims_biz_log','log_seq'), COALESCE((SELECT MAX(log_seq) FROM lims_biz_log),1), true)"
+
+  log "===== 鎶芥牱琛屾暟鏍¢獙锛坢irror vs PG锛�====="
+  local bad=0
+  for t in base_user zhiliang_biaozhun_guanli lims_qingyandan lims_jianyan_xiang lims_biz_log blade_visual_map; do
+    local m p; m="$(mirror_count jnpf_init "$t")"; p="$(pg_count "$PG_INIT_DB" "$t")"
+    if [ "$m" = "$p" ]; then printf '   %-30s mirror=%-8s pg=%-8s 鉁揬n' "$t" "$m" "$p"
+    else printf '   %-30s mirror=%-8s pg=%-8s 鉁梊n' "$t" "$m" "$p"; bad=$((bad+1)); fi
+  done
+  [ "$bad" = 0 ] && log "鎶芥牱涓�鑷� 鉁�" || warn "鎶芥牱鏈� $bad 澶勪笉涓�鑷达紝璺� sql/rowcount_check.sh 鍏ㄩ噺鏍稿"
+  log "鏁版嵁杩佺Щ瀹屾垚銆傚叏閲忛�愯〃鏍稿锛歴ql/rowcount_check.sh锛堣瑙� README锛夈��"
+}
+
+# 鈹�鈹� cleanup锛氬垹涓存椂 mirror + dump 鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+do_cleanup() {
+  if docker inspect "$MIRROR" >/dev/null 2>&1; then
+    log "鍒犻櫎涓存椂 mirror 瀹瑰櫒 $MIRROR"; docker rm -f "$MIRROR" >/dev/null
+  fi
+  [ -d "$DUMP_DIR" ] && { log "鍒犻櫎 dump 鐩綍 $DUMP_DIR"; rm -rf "$DUMP_DIR"; }
+  log "cleanup 瀹屾垚 鉁�"
+}
+
+case "$STEP" in
+  dump)    do_dump ;;
+  mirror)  do_mirror ;;
+  load)    do_load ;;
+  cleanup) do_cleanup ;;
+  all)     do_dump; do_mirror; do_load ;;
+  *)       die "鏈煡姝ラ '$STEP'锛堝彲閫� dump|mirror|load|cleanup|all锛�" ;;
+esac
+log "migrate-data.sh [$STEP] 瀹屾垚 鉁�"

--
Gitblit v1.8.0