#!/bin/bash # ============================================================ # 表结构对齐脚本 - 准生产 → VPS # ============================================================ # 用法:./sync_tables.sh [target_database] # 示例:./sync_tables.sh dmp_serp # ./sync_tables.sh dmp_smdm dmp_smdm # ============================================================ # 去掉 set -e,允许错误继续执行 # 加载配置 CONFIG_FILE="/home/yangxuan/.openclaw/workspace-sql/.db-sync-config.yaml" SCRIPT_DIR="/home/yangxuan/codes/xuan-sql/scripts" MEMORY_DIR="/home/yangxuan/.openclaw/workspace-sql/memory" # 解析参数 SOURCE_DB="${1:-}" TARGET_DB="${2:-$SOURCE_DB}" if [ -z "$SOURCE_DB" ]; then echo "用法:$0 [target_database]" echo "示例:$0 dmp_serp" exit 1 fi # 从配置文件读取连接信息(简单 YAML 解析) SOURCE_HOST=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "host:" | head -1 | awk '{print $2}') SOURCE_PORT=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "port:" | head -1 | awk '{print $2}') SOURCE_USER=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "user:" | head -1 | awk '{print $2}') SOURCE_PWD_ENV=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "password_env:" | head -1 | awk '{print $2}') TARGET_HOST=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "host:" | head -1 | awk '{print $2}') TARGET_PORT=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "port:" | head -1 | awk '{print $2}') TARGET_USER=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "user:" | head -1 | awk '{print $2}') TARGET_PWD_ENV=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "password_env:" | head -1 | awk '{print $2}') # 获取密码 SOURCE_PWD="${!SOURCE_PWD_ENV}" TARGET_PWD="${!TARGET_PWD_ENV}" if [ -z "$SOURCE_PWD" ] || [ -z "$TARGET_PWD" ]; then echo "错误:密码环境变量未设置" echo "请确保已设置:$SOURCE_PWD_ENV 和 $TARGET_PWD_ENV" exit 1 fi # 生成输出目录 DATE_STR=$(date +%Y%m%d) OUTPUT_DIR="$SCRIPT_DIR/$DATE_STR" mkdir -p "$OUTPUT_DIR" echo "============================================================" echo "表结构对齐 - 准生产 → VPS" echo "============================================================" echo "源数据库:$SOURCE_HOST:$SOURCE_PORT / $SOURCE_DB" echo "目标数据库:$TARGET_HOST:$TARGET_PORT / $TARGET_DB" echo "输出目录:$OUTPUT_DIR" echo "============================================================" # 验证连接 echo "" echo "[1/5] 验证数据库连接..." # 验证源库 if ! mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -e "SELECT 1;" > /dev/null 2>&1; then echo "错误:无法连接到源数据库(准生产)" exit 1 fi echo "✓ 源数据库连接成功" # 验证目标库 if ! mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -e "SELECT 1;" > /dev/null 2>&1; then echo "错误:无法连接到目标数据库(VPS)" exit 1 fi echo "✓ 目标数据库连接成功" # 获取表列表 echo "" echo "[2/5] 获取源数据库表列表..." TABLES=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "SHOW TABLES;" 2>/dev/null) TABLE_COUNT=$(echo "$TABLES" | wc -l) echo "找到 $TABLE_COUNT 张表" if [ "$TABLE_COUNT" -gt 50 ]; then read -p "表数量超过 50 张(当前:$TABLE_COUNT),是否继续?[y/N] " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "已取消" exit 0 fi fi # 初始化统计 SUCCESS_COUNT=0 FAIL_COUNT=0 declare -a TABLE_STATUS=() # 处理每个表 echo "" echo "[3/5] 对比并生成脚本..." for TABLE in $TABLES; do echo "" echo "处理表:$TABLE" SCRIPT_FILE="$OUTPUT_DIR/${TARGET_DB}__${TABLE}__struct.sql" # 获取源表结构 SOURCE_DDL=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "SHOW CREATE TABLE $TABLE\G" 2>/dev/null) # 检查目标表是否存在 TARGET_EXISTS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "SHOW TABLES LIKE '$TABLE';" 2>/dev/null | wc -l) if [ "$TARGET_EXISTS" -eq 0 ]; then # 目标表不存在,创建表 echo " → 目标表不存在,执行 CREATE TABLE" # 提取 CREATE TABLE 语句 CREATE_STMT=$(echo "$SOURCE_DDL" | grep -A1000 "Create Table:" | tail -n +2 | sed 's/^ //') # 生成脚本 cat > "$SCRIPT_FILE" << EOF -- ============================================================ -- 表结构同步脚本:$TABLE -- 源:准生产 ($SOURCE_HOST:$SOURCE_PORT) -- 目标:VPS ($TARGET_HOST:$TARGET_PORT) $TARGET_DB -- 生成时间:$(date '+%Y-%m-%d %H:%M:%S') -- 操作:CREATE TABLE(目标表不存在) -- ============================================================ USE $TARGET_DB; $CREATE_STMT -- ============================================================ -- 验证 -- ============================================================ -- SHOW CREATE TABLE $TABLE\\G EOF # 执行脚本 if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" < "$SCRIPT_FILE" 2>/dev/null; then echo " ✓ CREATE TABLE 成功" ((SUCCESS_COUNT++)) TABLE_STATUS+=("| $TABLE | CREATE | ✅ | 新建表 |") else echo " ✗ CREATE TABLE 失败" ((FAIL_COUNT++)) TABLE_STATUS+=("| $TABLE | CREATE | ❌ | 创建失败 |") fi else # 目标表已存在,对比结构 echo " → 目标表已存在,对比结构差异..." TARGET_DDL=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "SHOW CREATE TABLE $TABLE\G" 2>/dev/null) # 获取字段列表对比 SOURCE_FIELDS=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e " SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), IFNULL(COLUMN_COMMENT,'') FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$SOURCE_DB' AND TABLE_NAME = '$TABLE' ORDER BY ORDINAL_POSITION; " 2>/dev/null) TARGET_FIELDS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e " SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), IFNULL(COLUMN_COMMENT,'') FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$TARGET_DB' AND TABLE_NAME = '$TABLE' ORDER BY ORDINAL_POSITION; " 2>/dev/null) # 生成 ALTER 语句(简化版:只处理字段增删改) ALTER_STMT="" CHANGES="" # 检查源有目标无的字段(添加) while IFS='|' read -r COL_NAME COL_TYPE IS_NULL COL_DEF COL_COMMENT; do if ! echo "$TARGET_FIELDS" | grep -q "^$COL_NAME|"; then # 字段不存在,添加 NULLABLE="$([ "$IS_NULL" = "YES" ] && echo "NULL" || echo "NOT NULL")" DEFAULT="$([ "$COL_DEF" != "NULL" ] && echo "DEFAULT $COL_DEF" || echo "")" COMMENT="$([ -n "$COL_COMMENT" ] && echo "COMMENT '$COL_COMMENT'" || echo "")" ALTER_STMT="$ALTER_STMT ALTER TABLE $TABLE ADD COLUMN $COL_NAME $COL_TYPE $NULLABLE $DEFAULT $COMMENT;" CHANGES="$CHANGES\n - 添加字段:$COL_NAME" fi done <<< "$SOURCE_FIELDS" # 检查目标有源无的字段(删除) while IFS='|' read -r COL_NAME COL_TYPE IS_NULL COL_DEF COL_COMMENT; do if ! echo "$SOURCE_FIELDS" | grep -q "^$COL_NAME|"; then # 源不存在,删除 ALTER_STMT="$ALTER_STMT ALTER TABLE $TABLE DROP COLUMN $COL_NAME;" CHANGES="$CHANGES\n - 删除字段:$COL_NAME" fi done <<< "$TARGET_FIELDS" if [ -n "$ALTER_STMT" ]; then echo " → 发现结构差异,生成 ALTER 语句" # 生成脚本 cat > "$SCRIPT_FILE" << EOF -- ============================================================ -- 表结构同步脚本:$TABLE -- 源:准生产 ($SOURCE_HOST:$SOURCE_PORT) -- 目标:VPS ($TARGET_HOST:$TARGET_PORT) $TARGET_DB -- 生成时间:$(date '+%Y-%m-%d %H:%M:%S') -- 操作:ALTER TABLE(结构差异) -- ============================================================ USE $TARGET_DB; -- 结构差异:$(echo -e "$CHANGES") $ALTER_STMT -- ============================================================ -- 验证 -- ============================================================ -- SHOW CREATE TABLE $TABLE\\G EOF # 执行脚本(失败继续) if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" < "$SCRIPT_FILE" 2>/dev/null; then echo " ✓ ALTER TABLE 成功" ((SUCCESS_COUNT++)) TABLE_STATUS+=("| $TABLE | ALTER | ✅ | $(echo -e "$CHANGES" | wc -l) 处变更 |") else echo " ✗ ALTER TABLE 失败(继续执行下一个表)" ((FAIL_COUNT++)) TABLE_STATUS+=("| $TABLE | ALTER | ❌ | 执行失败 |") fi else echo " ✓ 结构一致,无需修改" ((SUCCESS_COUNT++)) TABLE_STATUS+=("| $TABLE | SKIP | ✅ | 结构一致 |") rm -f "$SCRIPT_FILE" # 删除空脚本 fi fi done # 生成 README echo "" echo "[4/5] 生成 README.md..." cat > "$OUTPUT_DIR/README.md" << EOF # 表结构同步脚本 - $DATE_STR **源:** 准生产 \`$SOURCE_HOST:$SOURCE_PORT\` \`$SOURCE_DB\` **目标:** VPS \`$TARGET_HOST:$TARGET_PORT\` \`$TARGET_DB\` **生成时间:** $(date '+%Y-%m-%d %H:%M:%S') **执行状态:** $([ $FAIL_COUNT -eq 0 ] && echo "✅ 已完成" || echo "⚠️ 部分失败") ## 脚本清单 | 序号 | 脚本文件 | 操作类型 | 状态 | 说明 | |------|---------|---------|------|------| EOF i=1 for STATUS in "${TABLE_STATUS[@]}"; do TABLE_NAME=$(echo "$STATUS" | cut -d'|' -f2 | tr -d ' ') SCRIPT_NAME="${TARGET_DB}__${TABLE_NAME}__struct.sql" echo "| $i | \`$SCRIPT_NAME\` $STATUS" >> "$OUTPUT_DIR/README.md" ((i++)) done cat >> "$OUTPUT_DIR/README.md" << EOF ## 执行结果 - 总表数:$TABLE_COUNT - 成功:$SUCCESS_COUNT - 失败:$FAIL_COUNT ## 验证命令 \`\`\`sql -- 检查表列表 SHOW TABLES LIKE '$TARGET_DB%'; -- 检查表结构 SHOW CREATE TABLE \\G \`\`\` EOF # 记录到记忆文件 echo "" echo "[5/5] 记录到记忆文件..." MEMORY_FILE="$MEMORY_DIR/$(date +%Y-%m-%d).md" if [ ! -f "$MEMORY_FILE" ]; then echo "# $(date +%Y-%m-%d) 工作记录" > "$MEMORY_FILE" echo "" >> "$MEMORY_FILE" fi cat >> "$MEMORY_FILE" << EOF ## 表结构同步 - $(date '+%H:%M') **源:** 准生产 \`$SOURCE_DB\` **目标:** VPS \`$TARGET_DB\` **处理表数:** $TABLE_COUNT **成功:** $SUCCESS_COUNT **失败:** $FAIL_COUNT ### 处理的表 | 表名 | 操作 | 状态 | 说明 | |------|------|------|------| EOF for STATUS in "${TABLE_STATUS[@]}"; do echo "$STATUS" >> "$MEMORY_FILE" done cat >> "$MEMORY_FILE" << EOF ### 脚本位置 \`\`\` $OUTPUT_DIR/ ├── README.md └── ${TARGET_DB}__*_struct.sql \`\`\` EOF echo "" echo "============================================================" echo "执行完成!" echo "============================================================" echo "总表数:$TABLE_COUNT" echo "成功:$SUCCESS_COUNT" echo "失败:$FAIL_COUNT" echo "脚本目录:$OUTPUT_DIR" echo "记忆文件:$MEMORY_FILE" echo "============================================================"