mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 01:48:22 +08:00
feat: update backend scripts and migration tools
This commit is contained in:
@@ -1363,6 +1363,7 @@ func (s *Server) handleLogin(c *gin.Context) {
|
||||
|
||||
// 验证密码
|
||||
if !auth.CheckPassword(req.Password, user.PasswordHash) {
|
||||
log.Printf("DEBUG: 密码验证失败")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=1 GOOS=linux \
|
||||
CGO_CFLAGS="-D_LARGEFILE64_SOURCE" \
|
||||
go build -trimpath -ldflags="-s -w" -o nofx .
|
||||
RUN CGO_ENABLED=1 GOOS=linux CGO_CFLAGS="-D_LARGEFILE64_SOURCE" go build -trimpath -ldflags="-s -w" -o nofx .
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Runtime Stage (Minimal Executable Environment)
|
||||
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 将beta_codes.txt刷到PostgreSQL数据库
|
||||
echo "🎟️ 导入beta_codes.txt到数据库"
|
||||
|
||||
# 检查文件
|
||||
if [ ! -f "beta_codes.txt" ]; then
|
||||
echo "❌ 找不到beta_codes.txt文件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检测docker命令
|
||||
if command -v "docker-compose" &> /dev/null; then
|
||||
DOCKER_CMD="docker-compose"
|
||||
else
|
||||
DOCKER_CMD="docker compose"
|
||||
fi
|
||||
|
||||
# 统计数量
|
||||
TOTAL=$(cat beta_codes.txt | wc -l)
|
||||
echo "📊 文件中共有 $TOTAL 个内测码"
|
||||
|
||||
# 生成SQL
|
||||
cat > import.sql << EOF
|
||||
INSERT INTO beta_codes (code) VALUES
|
||||
EOF
|
||||
|
||||
# 读取每行并生成INSERT语句
|
||||
cat beta_codes.txt | while read line; do
|
||||
if [ -n "$line" ]; then
|
||||
echo "('$line')," >> import.sql
|
||||
fi
|
||||
done
|
||||
|
||||
# 移除最后的逗号并添加冲突处理
|
||||
sed -i '$ s/,$//' import.sql
|
||||
echo "ON CONFLICT (code) DO NOTHING;" >> import.sql
|
||||
|
||||
# 执行导入
|
||||
echo "🔄 导入到数据库..."
|
||||
$DOCKER_CMD exec -T postgres psql -U nofx -d nofx < import.sql
|
||||
|
||||
echo "✅ 导入完成(重复的已跳过)"
|
||||
|
||||
# 清理
|
||||
rm import.sql
|
||||
Executable
+150
@@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 从SQLite导入default用户和系统默认数据到PostgreSQL
|
||||
echo "🔧 从SQLite导入default用户和系统默认数据"
|
||||
echo "========================================"
|
||||
|
||||
# 检查SQLite数据库文件
|
||||
SQLITE_DB="config.db"
|
||||
if [ ! -f "$SQLITE_DB" ]; then
|
||||
echo "❌ 错误:找不到 SQLite 数据库文件 $SQLITE_DB"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检测Docker Compose命令
|
||||
DOCKER_COMPOSE_CMD=""
|
||||
if command -v "docker-compose" &> /dev/null; then
|
||||
DOCKER_COMPOSE_CMD="docker-compose"
|
||||
elif command -v "docker" &> /dev/null && docker compose version &> /dev/null; then
|
||||
DOCKER_COMPOSE_CMD="docker compose"
|
||||
else
|
||||
echo "❌ 错误:找不到 docker-compose 或 docker compose 命令"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📋 使用命令: $DOCKER_COMPOSE_CMD"
|
||||
|
||||
# 分析SQLite中的default用户数据
|
||||
echo "📊 分析SQLite中的default用户数据..."
|
||||
AI_MODEL_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM ai_models WHERE user_id = 'default';" 2>/dev/null || echo "0")
|
||||
EXCHANGE_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM exchanges WHERE user_id = 'default';" 2>/dev/null || echo "0")
|
||||
|
||||
echo " 🤖 AI模型: $AI_MODEL_COUNT 个"
|
||||
echo " 🏢 交易所: $EXCHANGE_COUNT 个"
|
||||
|
||||
if [ "$AI_MODEL_COUNT" -eq 0 ] && [ "$EXCHANGE_COUNT" -eq 0 ]; then
|
||||
echo "⚠️ SQLite中没有default用户的数据,将跳过导入"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 生成导入脚本
|
||||
IMPORT_SQL="import_default_data.sql"
|
||||
|
||||
cat > $IMPORT_SQL << EOL
|
||||
-- 从SQLite导入default用户和系统默认数据
|
||||
-- 生成时间: $(date)
|
||||
|
||||
-- 设置时区
|
||||
SET timezone = 'Asia/Shanghai';
|
||||
|
||||
-- 1. 创建default用户(如果不存在)
|
||||
INSERT INTO users (id, email, password_hash, otp_secret, otp_verified, created_at, updated_at)
|
||||
VALUES ('default', 'default@localhost', '', '', true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
EOL
|
||||
|
||||
# 导出AI模型数据
|
||||
if [ "$AI_MODEL_COUNT" -gt 0 ]; then
|
||||
echo "🤖 导出AI模型数据..."
|
||||
echo "-- AI模型数据 ($AI_MODEL_COUNT 条记录)" >> $IMPORT_SQL
|
||||
echo "INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at) VALUES" >> $IMPORT_SQL
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' || quote(name) || ', ' || quote(provider) || ', ' ||
|
||||
CASE WHEN enabled = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(api_key, '')) || ', ' || quote(COALESCE(custom_api_url, '')) || ', ' ||
|
||||
quote(COALESCE(custom_model_name, '')) || ', ' || quote(created_at) || ', ' || quote(updated_at) || '),'
|
||||
FROM ai_models WHERE user_id = 'default';" | sed '$ s/,$//' >> $IMPORT_SQL
|
||||
|
||||
echo "ON CONFLICT (id) DO UPDATE SET user_id = EXCLUDED.user_id, name = EXCLUDED.name, provider = EXCLUDED.provider, enabled = EXCLUDED.enabled, api_key = EXCLUDED.api_key, custom_api_url = EXCLUDED.custom_api_url, custom_model_name = EXCLUDED.custom_model_name, updated_at = EXCLUDED.updated_at;" >> $IMPORT_SQL
|
||||
echo "" >> $IMPORT_SQL
|
||||
fi
|
||||
|
||||
# 导出交易所数据
|
||||
if [ "$EXCHANGE_COUNT" -gt 0 ]; then
|
||||
echo "🏢 导出交易所数据..."
|
||||
echo "-- 交易所数据 ($EXCHANGE_COUNT 条记录)" >> $IMPORT_SQL
|
||||
echo "INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at) VALUES" >> $IMPORT_SQL
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' || quote(name) || ', ' || quote(type) || ', ' ||
|
||||
CASE WHEN enabled = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(api_key, '')) || ', ' || quote(COALESCE(secret_key, '')) || ', ' ||
|
||||
CASE WHEN testnet = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(hyperliquid_wallet_addr, '')) || ', ' ||
|
||||
quote(COALESCE(aster_user, '')) || ', ' || quote(COALESCE(aster_signer, '')) || ', ' || quote(COALESCE(aster_private_key, '')) || ', ' ||
|
||||
quote(created_at) || ', ' || quote(updated_at) || '),'
|
||||
FROM exchanges WHERE user_id = 'default';" | sed '$ s/,$//' >> $IMPORT_SQL
|
||||
|
||||
echo "ON CONFLICT (id, user_id) DO UPDATE SET name = EXCLUDED.name, type = EXCLUDED.type, enabled = EXCLUDED.enabled, api_key = EXCLUDED.api_key, secret_key = EXCLUDED.secret_key, testnet = EXCLUDED.testnet, hyperliquid_wallet_addr = EXCLUDED.hyperliquid_wallet_addr, aster_user = EXCLUDED.aster_user, aster_signer = EXCLUDED.aster_signer, aster_private_key = EXCLUDED.aster_private_key, updated_at = EXCLUDED.updated_at;" >> $IMPORT_SQL
|
||||
echo "" >> $IMPORT_SQL
|
||||
fi
|
||||
|
||||
# 添加验证查询
|
||||
cat >> $IMPORT_SQL << 'EOL'
|
||||
-- 验证导入结果
|
||||
SELECT '=== 导入完成验证 ===' as status;
|
||||
SELECT 'default用户' as item, COUNT(*) as count FROM users WHERE id = 'default'
|
||||
UNION ALL SELECT 'AI模型', COUNT(*) FROM ai_models WHERE user_id = 'default'
|
||||
UNION ALL SELECT '交易所', COUNT(*) FROM exchanges WHERE user_id = 'default';
|
||||
EOL
|
||||
|
||||
echo "✅ 生成导入脚本: $IMPORT_SQL"
|
||||
|
||||
# 确认执行
|
||||
echo
|
||||
echo "⚠️ 准备导入default用户和系统默认数据,包括:"
|
||||
echo " 1. default用户账户"
|
||||
echo " 2. $AI_MODEL_COUNT 个AI模型"
|
||||
echo " 3. $EXCHANGE_COUNT 个交易所"
|
||||
echo
|
||||
read -p "确认执行导入? (y/N): " confirm
|
||||
|
||||
if [[ $confirm != [yY] ]]; then
|
||||
echo "ℹ️ 已取消导入"
|
||||
echo "手动执行命令: $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx -f /tmp/$IMPORT_SQL"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查PostgreSQL连接
|
||||
echo "🔌 检查数据库连接..."
|
||||
if ! $DOCKER_COMPOSE_CMD exec postgres pg_isready -U nofx > /dev/null 2>&1; then
|
||||
echo "❌ PostgreSQL连接失败,请确保服务正在运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 复制SQL脚本到容器
|
||||
echo "📦 复制导入脚本到容器..."
|
||||
POSTGRES_CONTAINER=$($DOCKER_COMPOSE_CMD ps -q postgres)
|
||||
if [ -z "$POSTGRES_CONTAINER" ]; then
|
||||
echo "❌ 找不到PostgreSQL容器"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker cp $IMPORT_SQL ${POSTGRES_CONTAINER}:/tmp/$IMPORT_SQL
|
||||
|
||||
# 执行导入
|
||||
echo "🔄 执行数据导入..."
|
||||
if $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx --pset pager=off -f /tmp/$IMPORT_SQL; then
|
||||
echo
|
||||
echo "✅ default用户和系统默认数据导入成功!"
|
||||
echo
|
||||
echo "📋 现在可以访问以下接口:"
|
||||
echo " - GET /api/supported-models ($AI_MODEL_COUNT 个AI模型)"
|
||||
echo " - GET /api/supported-exchanges ($EXCHANGE_COUNT 个交易所)"
|
||||
echo
|
||||
echo "🧹 清理导入文件..."
|
||||
rm -f $IMPORT_SQL
|
||||
$DOCKER_COMPOSE_CMD exec postgres rm -f /tmp/$IMPORT_SQL
|
||||
else
|
||||
echo "❌ 数据导入失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🎉 导入完成!"
|
||||
+248
-55
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# PostgreSQL数据迁移脚本 - 一键迁移
|
||||
# 用于将SQLite数据迁移到PostgreSQL
|
||||
# 生产环境 SQLite -> PostgreSQL 数据迁移脚本
|
||||
# 真实数据迁移工具 - 支持完整数据导出和迁移
|
||||
|
||||
set -e
|
||||
|
||||
@@ -10,7 +10,20 @@ RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ 🚀 生产环境数据迁移工具 SQLite → PostgreSQL ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo
|
||||
|
||||
# 检查必要文件
|
||||
SQLITE_DB="config.db"
|
||||
if [ ! -f "$SQLITE_DB" ]; then
|
||||
echo -e "${RED}❌ 错误:找不到 SQLite 数据库文件 $SQLITE_DB${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检测Docker Compose命令
|
||||
DOCKER_COMPOSE_CMD=""
|
||||
@@ -20,31 +33,230 @@ elif command -v "docker" &> /dev/null && docker compose version &> /dev/null; th
|
||||
DOCKER_COMPOSE_CMD="docker compose"
|
||||
else
|
||||
echo -e "${RED}❌ 错误:找不到 docker-compose 或 docker compose 命令${NC}"
|
||||
echo "请安装 Docker Compose 或确保 Docker 支持 compose 子命令"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}🔄 开始数据库迁移...${NC}"
|
||||
echo -e "${BLUE}📋 使用命令: ${DOCKER_COMPOSE_CMD}${NC}"
|
||||
echo -e "${CYAN}📋 使用命令: ${DOCKER_COMPOSE_CMD}${NC}"
|
||||
|
||||
# 检查必要文件
|
||||
if [ ! -f "migrate_actual_data.sql" ]; then
|
||||
echo -e "${RED}❌ 错误:找不到 migrate_actual_data.sql 文件${NC}"
|
||||
echo "请确保在项目根目录执行此脚本"
|
||||
exit 1
|
||||
# 分析当前SQLite数据
|
||||
echo -e "\n${BLUE}📊 分析当前SQLite数据库...${NC}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# 获取数据统计
|
||||
USER_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM users;" 2>/dev/null || echo "0")
|
||||
AI_MODEL_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM ai_models;" 2>/dev/null || echo "0")
|
||||
EXCHANGE_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM exchanges;" 2>/dev/null || echo "0")
|
||||
TRADER_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM traders;" 2>/dev/null || echo "0")
|
||||
CONFIG_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM system_config;" 2>/dev/null || echo "0")
|
||||
BETA_CODE_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM beta_codes;" 2>/dev/null || echo "0")
|
||||
|
||||
echo "📈 数据库表统计:"
|
||||
echo " 👥 用户 (users): $USER_COUNT"
|
||||
echo " 🤖 AI模型 (ai_models): $AI_MODEL_COUNT"
|
||||
echo " 🏢 交易所 (exchanges): $EXCHANGE_COUNT"
|
||||
echo " 🔧 交易员 (traders): $TRADER_COUNT"
|
||||
echo " ⚙️ 系统配置 (system_config): $CONFIG_COUNT"
|
||||
echo " 🎟️ 内测码 (beta_codes): $BETA_CODE_COUNT"
|
||||
|
||||
# 检查是否有exchange_secrets表
|
||||
if sqlite3 $SQLITE_DB "SELECT name FROM sqlite_master WHERE type='table' AND name='exchange_secrets';" | grep -q exchange_secrets; then
|
||||
SECRET_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM exchange_secrets;" 2>/dev/null || echo "0")
|
||||
echo " 🔐 交易所密钥 (exchange_secrets): $SECRET_COUNT"
|
||||
fi
|
||||
|
||||
if [ ! -f "docker-compose.yml" ]; then
|
||||
echo -e "${RED}❌ 错误:找不到 docker-compose.yml 文件${NC}"
|
||||
echo "请确保在项目根目录执行此脚本"
|
||||
exit 1
|
||||
# 检查是否有user_signal_sources表
|
||||
if sqlite3 $SQLITE_DB "SELECT name FROM sqlite_master WHERE type='table' AND name='user_signal_sources';" | grep -q user_signal_sources; then
|
||||
SIGNAL_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM user_signal_sources;" 2>/dev/null || echo "0")
|
||||
echo " 📡 用户信号源 (user_signal_sources): $SIGNAL_COUNT"
|
||||
fi
|
||||
|
||||
# 停止现有服务(避免端口冲突)
|
||||
echo
|
||||
|
||||
# 生成迁移时间戳
|
||||
TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S')
|
||||
MIGRATION_FILE="migrate_production_${TIMESTAMP}.sql"
|
||||
|
||||
echo -e "${BLUE}📤 生成数据迁移脚本: $MIGRATION_FILE${NC}"
|
||||
|
||||
# 生成SQL迁移脚本
|
||||
cat > $MIGRATION_FILE << EOL
|
||||
-- 生产环境数据迁移脚本
|
||||
-- 从SQLite自动导出生成
|
||||
-- 执行时间: $(date)
|
||||
|
||||
-- 设置时区
|
||||
SET timezone = 'Asia/Shanghai';
|
||||
|
||||
EOL
|
||||
|
||||
# 导出用户数据
|
||||
if [ "$USER_COUNT" -gt 0 ]; then
|
||||
echo -e "${CYAN}👥 导出用户数据...${NC}"
|
||||
echo "-- 用户数据 ($USER_COUNT 条记录)" >> $MIGRATION_FILE
|
||||
echo "INSERT INTO users (id, email, password_hash, otp_secret, otp_verified, created_at, updated_at) VALUES" >> $MIGRATION_FILE
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(COALESCE(email, '')) || ', ' || quote(COALESCE(password_hash, '')) || ', ' || quote(COALESCE(otp_secret, '')) || ', ' ||
|
||||
CASE WHEN otp_verified = 1 THEN 'true' ELSE 'false' END || ', ' || quote(created_at) || ', ' || quote(updated_at) || '),'
|
||||
FROM users;" | sed '$ s/,$//' >> $MIGRATION_FILE
|
||||
|
||||
echo "ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email, password_hash = EXCLUDED.password_hash, otp_secret = EXCLUDED.otp_secret, otp_verified = EXCLUDED.otp_verified, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
|
||||
echo "" >> $MIGRATION_FILE
|
||||
fi
|
||||
|
||||
# 导出AI模型数据
|
||||
if [ "$AI_MODEL_COUNT" -gt 0 ]; then
|
||||
echo -e "${CYAN}🤖 导出AI模型数据...${NC}"
|
||||
echo "-- AI模型数据 ($AI_MODEL_COUNT 条记录)" >> $MIGRATION_FILE
|
||||
echo "INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at) VALUES" >> $MIGRATION_FILE
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' ||
|
||||
quote(name) || ', ' || quote(provider) || ', ' ||
|
||||
CASE WHEN enabled = 1 THEN 'true' ELSE 'false' END || ', ' || quote(api_key) || ', ' || quote(COALESCE(custom_api_url, '')) || ', ' ||
|
||||
quote(COALESCE(custom_model_name, '')) || ', ' || quote(created_at) || ', ' || quote(updated_at) || '),'
|
||||
FROM ai_models WHERE user_id IS NOT NULL AND user_id != '' AND user_id != 'default'
|
||||
AND user_id IN (SELECT id FROM users);" | sed '$ s/,$//' >> $MIGRATION_FILE
|
||||
|
||||
echo "ON CONFLICT (id) DO UPDATE SET user_id = EXCLUDED.user_id, name = EXCLUDED.name, provider = EXCLUDED.provider, enabled = EXCLUDED.enabled, api_key = EXCLUDED.api_key, custom_api_url = EXCLUDED.custom_api_url, custom_model_name = EXCLUDED.custom_model_name, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
|
||||
echo "" >> $MIGRATION_FILE
|
||||
fi
|
||||
|
||||
# 导出交易所数据
|
||||
if [ "$EXCHANGE_COUNT" -gt 0 ]; then
|
||||
echo -e "${CYAN}🏢 导出交易所数据...${NC}"
|
||||
echo "-- 交易所数据 ($EXCHANGE_COUNT 条记录)" >> $MIGRATION_FILE
|
||||
echo "INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet, hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at) VALUES" >> $MIGRATION_FILE
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' ||
|
||||
quote(name) || ', ' || quote(type) || ', ' ||
|
||||
CASE WHEN enabled = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(api_key, '')) || ', ' || quote(COALESCE(secret_key, '')) || ', ' ||
|
||||
CASE WHEN testnet = 1 THEN 'true' ELSE 'false' END || ', ' || quote(COALESCE(hyperliquid_wallet_addr, '')) || ', ' ||
|
||||
quote(COALESCE(aster_user, '')) || ', ' || quote(COALESCE(aster_signer, '')) || ', ' || quote(COALESCE(aster_private_key, '')) || ', ' ||
|
||||
quote(created_at) || ', ' || quote(updated_at) || '),'
|
||||
FROM exchanges WHERE user_id IS NOT NULL AND user_id != '' AND user_id != 'default'
|
||||
AND user_id IN (SELECT id FROM users);" | sed '$ s/,$//' >> $MIGRATION_FILE
|
||||
|
||||
echo "ON CONFLICT (id, user_id) DO UPDATE SET name = EXCLUDED.name, type = EXCLUDED.type, enabled = EXCLUDED.enabled, api_key = EXCLUDED.api_key, secret_key = EXCLUDED.secret_key, testnet = EXCLUDED.testnet, hyperliquid_wallet_addr = EXCLUDED.hyperliquid_wallet_addr, aster_user = EXCLUDED.aster_user, aster_signer = EXCLUDED.aster_signer, aster_private_key = EXCLUDED.aster_private_key, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
|
||||
echo "" >> $MIGRATION_FILE
|
||||
fi
|
||||
|
||||
# 导出交易员数据
|
||||
if [ "$TRADER_COUNT" -gt 0 ]; then
|
||||
echo -e "${CYAN}🔧 导出交易员数据...${NC}"
|
||||
echo "-- 交易员数据 ($TRADER_COUNT 条记录)" >> $MIGRATION_FILE
|
||||
echo "INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, custom_prompt, override_base_prompt, system_prompt_template, is_cross_margin, created_at, updated_at) VALUES" >> $MIGRATION_FILE
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(id) || ', ' || quote(user_id) || ', ' ||
|
||||
quote(name) || ', ' || quote(ai_model_id) || ', ' ||
|
||||
quote(exchange_id) || ', ' || initial_balance || ', ' || scan_interval_minutes || ', ' ||
|
||||
CASE WHEN is_running = 1 THEN 'true' ELSE 'false' END || ', ' || btc_eth_leverage || ', ' || altcoin_leverage || ', ' ||
|
||||
quote(COALESCE(trading_symbols, '')) || ', ' ||
|
||||
CASE WHEN use_coin_pool = 1 THEN 'true' ELSE 'false' END || ', ' || CASE WHEN use_oi_top = 1 THEN 'true' ELSE 'false' END || ', ' ||
|
||||
quote(COALESCE(custom_prompt, '')) || ', ' || CASE WHEN override_base_prompt = 1 THEN 'true' ELSE 'false' END || ', ' ||
|
||||
quote(COALESCE(system_prompt_template, 'default')) || ', ' || CASE WHEN is_cross_margin = 1 THEN 'true' ELSE 'false' END || ', ' ||
|
||||
quote(created_at) || ', ' || quote(updated_at) || '),'
|
||||
FROM traders WHERE user_id IS NOT NULL AND user_id != '' AND user_id != 'default'
|
||||
AND user_id IN (SELECT id FROM users);" | sed '$ s/,$//' >> $MIGRATION_FILE
|
||||
|
||||
echo "ON CONFLICT (id) DO UPDATE SET user_id = EXCLUDED.user_id, name = EXCLUDED.name, ai_model_id = EXCLUDED.ai_model_id, exchange_id = EXCLUDED.exchange_id, initial_balance = EXCLUDED.initial_balance, scan_interval_minutes = EXCLUDED.scan_interval_minutes, is_running = EXCLUDED.is_running, btc_eth_leverage = EXCLUDED.btc_eth_leverage, altcoin_leverage = EXCLUDED.altcoin_leverage, trading_symbols = EXCLUDED.trading_symbols, use_coin_pool = EXCLUDED.use_coin_pool, use_oi_top = EXCLUDED.use_oi_top, custom_prompt = EXCLUDED.custom_prompt, override_base_prompt = EXCLUDED.override_base_prompt, system_prompt_template = EXCLUDED.system_prompt_template, is_cross_margin = EXCLUDED.is_cross_margin, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
|
||||
echo "" >> $MIGRATION_FILE
|
||||
fi
|
||||
|
||||
# 导出系统配置
|
||||
if [ "$CONFIG_COUNT" -gt 0 ]; then
|
||||
echo -e "${CYAN}⚙️ 导出系统配置...${NC}"
|
||||
echo "-- 系统配置数据 ($CONFIG_COUNT 条记录)" >> $MIGRATION_FILE
|
||||
echo "INSERT INTO system_config (key, value, updated_at) VALUES" >> $MIGRATION_FILE
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(key) || ', ' || quote(value) || ', ' || quote(updated_at) || '),'
|
||||
FROM system_config;" | sed '$ s/,$//' >> $MIGRATION_FILE
|
||||
|
||||
echo "ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
|
||||
echo "" >> $MIGRATION_FILE
|
||||
fi
|
||||
|
||||
# 导出内测码数据
|
||||
if [ "$BETA_CODE_COUNT" -gt 0 ]; then
|
||||
echo -e "${CYAN}🎟️ 导出内测码数据...${NC}"
|
||||
echo "-- 内测码数据 ($BETA_CODE_COUNT 条记录)" >> $MIGRATION_FILE
|
||||
echo "INSERT INTO beta_codes (code, used, used_by, used_at, created_at) VALUES" >> $MIGRATION_FILE
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(code) || ', ' || CASE WHEN used = 1 THEN 'true' ELSE 'false' END || ', ' ||
|
||||
quote(COALESCE(used_by, '')) || ', ' || CASE WHEN used_at IS NULL THEN 'NULL' ELSE quote(used_at) END || ', ' ||
|
||||
quote(created_at) || '),'
|
||||
FROM beta_codes;" | sed '$ s/,$//' >> $MIGRATION_FILE
|
||||
|
||||
echo "ON CONFLICT (code) DO UPDATE SET used = EXCLUDED.used, used_by = EXCLUDED.used_by, used_at = EXCLUDED.used_at;" >> $MIGRATION_FILE
|
||||
echo "" >> $MIGRATION_FILE
|
||||
fi
|
||||
|
||||
# 导出用户信号源(如果存在)
|
||||
if sqlite3 $SQLITE_DB "SELECT name FROM sqlite_master WHERE type='table' AND name='user_signal_sources';" | grep -q user_signal_sources; then
|
||||
SIGNAL_COUNT=$(sqlite3 $SQLITE_DB "SELECT COUNT(*) FROM user_signal_sources;" 2>/dev/null || echo "0")
|
||||
if [ "$SIGNAL_COUNT" -gt 0 ]; then
|
||||
echo -e "${CYAN}📡 导出用户信号源数据...${NC}"
|
||||
echo "-- 用户信号源数据 ($SIGNAL_COUNT 条记录)" >> $MIGRATION_FILE
|
||||
echo "INSERT INTO user_signal_sources (user_id, coin_pool_url, oi_top_url, created_at, updated_at) VALUES" >> $MIGRATION_FILE
|
||||
|
||||
sqlite3 $SQLITE_DB "SELECT '(' || quote(user_id) || ', ' ||
|
||||
quote(COALESCE(coin_pool_url, '')) || ', ' ||
|
||||
quote(COALESCE(oi_top_url, '')) || ', ' || quote(created_at) || ', ' || quote(updated_at) || '),'
|
||||
FROM user_signal_sources WHERE user_id IS NOT NULL AND user_id != '' AND user_id != 'default'
|
||||
AND user_id IN (SELECT id FROM users);" | sed '$ s/,$//' >> $MIGRATION_FILE
|
||||
|
||||
echo "ON CONFLICT (user_id) DO UPDATE SET coin_pool_url = EXCLUDED.coin_pool_url, oi_top_url = EXCLUDED.oi_top_url, updated_at = EXCLUDED.updated_at;" >> $MIGRATION_FILE
|
||||
echo "" >> $MIGRATION_FILE
|
||||
fi
|
||||
fi
|
||||
|
||||
# 添加迁移验证查询
|
||||
cat >> $MIGRATION_FILE << 'EOL'
|
||||
-- 迁移验证查询
|
||||
SELECT '=== 数据迁移完成验证 ===' as status;
|
||||
SELECT 'users' as table_name, COUNT(*) as record_count FROM users
|
||||
UNION ALL SELECT 'ai_models', COUNT(*) FROM ai_models
|
||||
UNION ALL SELECT 'exchanges', COUNT(*) FROM exchanges
|
||||
UNION ALL SELECT 'traders', COUNT(*) FROM traders
|
||||
UNION ALL SELECT 'system_config', COUNT(*) FROM system_config
|
||||
UNION ALL SELECT 'beta_codes', COUNT(*) FROM beta_codes
|
||||
UNION ALL SELECT 'user_signal_sources', COUNT(*) FROM user_signal_sources
|
||||
ORDER BY table_name;
|
||||
|
||||
-- 显示关键配置
|
||||
SELECT '=== 关键系统配置 ===' as info;
|
||||
SELECT key,
|
||||
CASE WHEN LENGTH(value) > 50 THEN LEFT(value, 50) || '...' ELSE value END as value
|
||||
FROM system_config
|
||||
WHERE key IN ('admin_mode', 'beta_mode', 'api_server_port', 'default_coins', 'jwt_secret')
|
||||
ORDER BY key;
|
||||
EOL
|
||||
|
||||
echo -e "${GREEN}✅ 迁移脚本生成完成: $MIGRATION_FILE${NC}"
|
||||
|
||||
# 确认是否执行迁移
|
||||
echo
|
||||
echo -e "${YELLOW}⚠️ 准备执行数据迁移,这将:${NC}"
|
||||
echo " 1. 停止现有服务"
|
||||
echo " 2. 启动PostgreSQL和Redis"
|
||||
echo " 3. 执行数据迁移"
|
||||
echo " 4. 验证迁移结果"
|
||||
echo
|
||||
read -p "确认执行迁移? (y/N): " confirm
|
||||
|
||||
if [[ $confirm != [yY] ]]; then
|
||||
echo -e "${BLUE}ℹ️ 迁移脚本已生成,可稍后手动执行${NC}"
|
||||
echo "手动执行命令: $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx -f /tmp/$MIGRATION_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 执行迁移
|
||||
echo -e "\n${BLUE}🚀 开始执行数据迁移...${NC}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# 停止现有服务
|
||||
echo -e "${YELLOW}🛑 停止现有服务...${NC}"
|
||||
$DOCKER_COMPOSE_CMD down 2>/dev/null || true
|
||||
|
||||
# 启动PostgreSQL和Redis服务
|
||||
# 启动PostgreSQL和Redis
|
||||
echo -e "${YELLOW}🚀 启动PostgreSQL和Redis服务...${NC}"
|
||||
$DOCKER_COMPOSE_CMD up postgres redis -d
|
||||
|
||||
@@ -54,7 +266,7 @@ sleep 15
|
||||
|
||||
# 检查PostgreSQL连接
|
||||
echo -e "${BLUE}🔌 测试数据库连接...${NC}"
|
||||
max_retries=12
|
||||
max_retries=15
|
||||
retry_count=0
|
||||
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
@@ -64,7 +276,7 @@ while [ $retry_count -lt $max_retries ]; do
|
||||
else
|
||||
retry_count=$((retry_count + 1))
|
||||
echo -e "${YELLOW}⏳ 等待PostgreSQL启动... (${retry_count}/${max_retries})${NC}"
|
||||
sleep 5
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -82,56 +294,37 @@ if [ -z "$POSTGRES_CONTAINER" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker cp migrate_actual_data.sql ${POSTGRES_CONTAINER}:/tmp/migrate_actual_data.sql
|
||||
docker cp $MIGRATION_FILE ${POSTGRES_CONTAINER}:/tmp/$MIGRATION_FILE
|
||||
|
||||
# 验证文件复制成功
|
||||
if ! $DOCKER_COMPOSE_CMD exec postgres test -f /tmp/migrate_actual_data.sql; then
|
||||
if ! $DOCKER_COMPOSE_CMD exec postgres test -f /tmp/$MIGRATION_FILE; then
|
||||
echo -e "${RED}❌ 迁移脚本复制失败${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 执行数据迁移
|
||||
echo -e "${BLUE}🔄 执行数据迁移...${NC}"
|
||||
if $DOCKER_COMPOSE_CMD exec postgres env PAGER="" psql -U nofx -d nofx -f /tmp/migrate_actual_data.sql; then
|
||||
if $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx --pset pager=off -f /tmp/$MIGRATION_FILE; then
|
||||
echo -e "${GREEN}✅ 数据迁移成功!${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ 数据迁移失败${NC}"
|
||||
echo "查看错误日志: $DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx -c \"SELECT version();\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证数据
|
||||
echo -e "${BLUE}🔍 验证迁移结果...${NC}"
|
||||
$DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx --pset pager=off -c "
|
||||
SELECT '=== 数据库迁移验证 ===' as info;
|
||||
SELECT
|
||||
relname as \"表名\",
|
||||
n_live_tup as \"记录数\"
|
||||
FROM pg_stat_user_tables
|
||||
WHERE n_live_tup > 0
|
||||
ORDER BY relname;
|
||||
"
|
||||
|
||||
# 显示系统配置(简化版本,避免长文本问题)
|
||||
echo -e "${BLUE}📋 显示关键配置...${NC}"
|
||||
$DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx --pset pager=off -c "
|
||||
SELECT COUNT(*) as \"配置项总数\" FROM system_config;
|
||||
SELECT 'admin_mode: ' || COALESCE((SELECT value FROM system_config WHERE key='admin_mode'), 'N/A') as \"管理员模式\";
|
||||
SELECT 'beta_mode: ' || COALESCE((SELECT value FROM system_config WHERE key='beta_mode'), 'N/A') as \"内测模式\";
|
||||
SELECT 'api_server_port: ' || COALESCE((SELECT value FROM system_config WHERE key='api_server_port'), 'N/A') as \"API端口\";
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}🎉 数据库迁移完成!${NC}"
|
||||
echo ""
|
||||
echo
|
||||
echo -e "${GREEN}🎉 生产环境数据迁移完成!${NC}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo -e "${BLUE}📋 后续步骤:${NC}"
|
||||
echo -e "1. 启动完整应用: ${YELLOW}$DOCKER_COMPOSE_CMD up${NC}"
|
||||
echo -e "2. 验证功能: 访问 ${YELLOW}http://localhost:3000${NC}"
|
||||
echo -e "3. 备份原SQLite: ${YELLOW}mv config.db config.db.backup${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}🔧 如需回滚到SQLite:${NC}"
|
||||
echo -e "1. 备份原SQLite: ${YELLOW}mv config.db config.db.backup.$(date +%Y%m%d)${NC}"
|
||||
echo -e "2. 启动完整应用: ${YELLOW}$DOCKER_COMPOSE_CMD up${NC}"
|
||||
echo -e "3. 验证功能: 访问 ${YELLOW}http://localhost:3000${NC}"
|
||||
echo -e "4. 删除迁移文件: ${YELLOW}rm $MIGRATION_FILE${NC}"
|
||||
echo
|
||||
echo -e "${BLUE}🔧 如需回滚:${NC}"
|
||||
echo -e "1. 停止服务: ${YELLOW}$DOCKER_COMPOSE_CMD down${NC}"
|
||||
echo -e "2. 删除环境变量: ${YELLOW}unset POSTGRES_HOST${NC} 或编辑 .env 文件"
|
||||
echo -e "3. 恢复备份: ${YELLOW}mv config.db.backup config.db${NC}"
|
||||
echo -e "2. 恢复SQLite: ${YELLOW}mv config.db.backup.$(date +%Y%m%d) config.db${NC}"
|
||||
echo -e "3. 删除环境变量或编辑 .env 文件注释掉 POSTGRES_HOST"
|
||||
echo -e "4. 重启: ${YELLOW}$DOCKER_COMPOSE_CMD up${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}🚀 PostgreSQL迁移成功!系统已升级到现代化数据库架构${NC}"
|
||||
echo
|
||||
echo -e "${GREEN}🚀 PostgreSQL生产环境迁移成功!${NC}"
|
||||
|
||||
+9
-1
@@ -59,7 +59,15 @@ GROUP BY used
|
||||
ORDER BY used;
|
||||
"
|
||||
|
||||
echo -e "\n📝 未使用的内测码:"
|
||||
$DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx --pset pager=off -c "
|
||||
SELECT code
|
||||
FROM beta_codes
|
||||
WHERE used = false
|
||||
ORDER BY created_at DESC;
|
||||
"
|
||||
|
||||
echo -e "\n👥 用户信息:"
|
||||
$DOCKER_COMPOSE_CMD exec postgres psql -U nofx -d nofx --pset pager=off -c "
|
||||
SELECT id, email, otp_verified, created_at FROM users ORDER BY created_at;
|
||||
"
|
||||
"
|
||||
|
||||
Reference in New Issue
Block a user