#!/bin/bash # ============================================================ # backup_api.sh # 导出 pricing, llmage, uapi 三个模块的表数据为 SQL 文件 # # 用法: # ./backup_api.sh [options] # # 选项: # -h HOST MySQL 主机 (默认: localhost) # -P PORT MySQL 端口 (默认: 3306) # -u USER MySQL 用户名 (默认: root) # -p PASSWORD MySQL 密码 # -d DATABASE 数据库名 (默认: sage) # -o OUTPUT_DIR 输出目录 (默认: ./sql_dumps) # --no-data 只导出表结构,不导出数据 # --help 显示帮助 # ============================================================ set -euo pipefail # 默认参数 HOST="localhost" PORT="3306" USER="root" PASSWORD="" DATABASE="sage" OUTPUT_DIR="./sql_dumps" NO_DATA="" REPOS_DIR="$(cd "$(dirname "$0")/../.." 2>/dev/null && pwd || echo "/home/hermesai/repos")" # 各模块排除的表 declare -A MODULE_EXCLUDES MODULE_EXCLUDES["llmage"]="llmusage llmusage_accounting_failed llmusage_history" MODULE_EXCLUDES["uapi"]="uapiset uptask" usage() { sed -n '2,/^# ===/p' "$0" | grep '^#' | sed 's/^# \?//' exit 0 } # 解析参数 while [[ $# -gt 0 ]]; do case "$1" in -h) HOST="$2"; shift 2 ;; -P) PORT="$2"; shift 2 ;; -u) USER="$2"; shift 2 ;; -p) PASSWORD="$2"; shift 2 ;; -d) DATABASE="$2"; shift 2 ;; -o) OUTPUT_DIR="$2"; shift 2 ;; --no-data) NO_DATA="--no-data"; shift ;; --help) usage ;; *) echo "未知参数: $1"; exit 1 ;; esac done # 构建 mysqldump 基础命令 MYSQLDUMP_BASE="mysqldump -h${HOST} -P${PORT} -u${USER}" if [[ -n "$PASSWORD" ]]; then MYSQLDUMP_BASE="${MYSQLDUMP_BASE} -p${PASSWORD}" fi # 从 models/*.json 提取表名 get_tables() { local module_dir="$1" shift local excludes=("$@") local tables=() if [[ ! -d "$module_dir/models" ]]; then echo "警告: 目录不存在 $module_dir/models" >&2 return fi for f in "$module_dir/models"/*.json; do [[ -f "$f" ]] || continue # 从 summary[0].name 提取表名 local tbl tbl=$(python3 -c " import json, sys try: d = json.load(open('$f')) s = d.get('summary', []) if isinstance(s, list) and len(s) > 0: print(s[0].get('name', '')) elif isinstance(s, dict): print(s.get('name', '')) except: pass " 2>/dev/null) if [[ -z "$tbl" ]]; then continue fi # 检查是否在排除列表中 local excluded=false for ex in "${excludes[@]}"; do if [[ "$tbl" == "$ex" ]]; then excluded=true break fi done if [[ "$excluded" == "false" ]]; then tables+=("$tbl") fi done echo "${tables[@]}" } # 创建输出目录 mkdir -p "$OUTPUT_DIR" TIMESTAMP=$(date +%Y%m%d_%H%M%S) echo "============================================================" echo " Sage 模块表数据导出" echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')" echo " 数据库: ${DATABASE}@${HOST}:${PORT}" echo " 输出目录: ${OUTPUT_DIR}" echo "============================================================" # 定义模块 declare -A MODULES MODULES=( ["pricing"]="$REPOS_DIR/pricing" ["llmage"]="$REPOS_DIR/llmage" ["uapi"]="$REPOS_DIR/uapi" ) TOTAL_TABLES=0 TOTAL_FILES=0 for module in pricing llmage uapi; do module_dir="${MODULES[$module]}" echo "" echo "--- 模块: $module ---" if [[ ! -d "$module_dir" ]]; then echo " 跳过: 模块目录不存在 $module_dir" continue fi # 获取表名(按模块排除指定表) excludes_str="${MODULE_EXCLUDES[$module]:-}" excludes_arr=() if [[ -n "$excludes_str" ]]; then read -ra excludes_arr <<< "$excludes_str" fi tables=$(get_tables "$module_dir" "${excludes_arr[@]+"${excludes_arr[@]}"}") if [[ -z "$tables" ]]; then echo " 跳过: 未找到表定义" continue fi echo " 表: $tables" # 生成 SQL 文件 outfile="${OUTPUT_DIR}/${module}_${TIMESTAMP}.sql" table_count=0 for tbl in $tables; do echo -n " 导出 $tbl ... " if ${MYSQLDUMP_BASE} \ --single-transaction \ --routines \ --triggers \ --set-gtid-purged=OFF \ --column-names \ --complete-insert \ $NO_DATA \ "$DATABASE" "$tbl" >> "$outfile" 2>/dev/null; then echo "OK" ((table_count++)) else # 如果表不存在,跳过 echo "跳过(表不存在或无权限)" fi done if [[ $table_count -gt 0 ]]; then echo " => $outfile ($table_count 个表)" ((TOTAL_TABLES += table_count)) ((TOTAL_FILES++)) else rm -f "$outfile" echo " => 无有效表数据" fi done echo "" echo "============================================================" echo " 导出完成" echo " 文件数: $TOTAL_FILES" echo " 表总数: $TOTAL_TABLES" echo " 输出目录: $(cd "$OUTPUT_DIR" && pwd)" echo "============================================================"