#!/usr/bin/env python3 """ 系统配置表迁移脚本 将横表改为纵表,并插入默认配置数据 """ import pymysql import os from pathlib import Path from dotenv import load_dotenv # 加载环境变量 env_path = Path(__file__).parent / '.env' load_dotenv(dotenv_path=env_path) def get_db_connection(): """获取数据库连接""" return pymysql.connect( host=os.getenv("DB_HOST", "localhost"), port=int(os.getenv("DB_PORT", "3306")), user=os.getenv("DB_USER", "root"), password=os.getenv("DB_PASSWORD", ""), database=os.getenv("DB_NAME", "buff"), charset='utf8mb4' ) def migrate_system_config(): """迁移system_config表""" print("开始迁移system_config表...") try: conn = get_db_connection() cursor = conn.cursor() # 1. 检查是否需要迁移(检查是否存在config_key字段) cursor.execute("SHOW COLUMNS FROM system_config LIKE 'config_key'") has_config_key = cursor.fetchone() if has_config_key: print("system_config表已经是纵表结构,跳过迁移") return # 2. 备份现有数据 print("备份现有数据...") cursor.execute("SELECT * FROM system_config LIMIT 1") old_data = cursor.fetchone() if old_data: print(f"发现旧数据: {old_data}") else: print("没有找到旧数据,将创建新的纵表结构") # 3. 删除旧表结构 print("删除旧表结构...") cursor.execute("DROP TABLE system_config") # 4. 创建新表结构 print("创建新的纵表结构...") cursor.execute(""" CREATE TABLE IF NOT EXISTS `system_config` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `config_key` varchar(100) NOT NULL COMMENT '配置键名', `config_value` varchar(500) NOT NULL COMMENT '配置值', `config_type` enum('int','string','bool','json') DEFAULT 'string' COMMENT '配置值类型', `description` varchar(255) DEFAULT NULL COMMENT '配置描述', `is_system` tinyint(1) DEFAULT '1' COMMENT '是否为系统配置:1-是,0-否', `is_editable` tinyint(1) DEFAULT '1' COMMENT '是否可编辑:1-是,0-否', `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除:0-否,1-是', `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_config_key` (`config_key`, `is_deleted`) COMMENT '配置键唯一约束' ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表(纵表设计)' """) # 5. 插入默认配置数据 print("插入默认配置数据...") config_data = [ ('heartbeat_interval_seconds', '30', 'int', '心跳间隔秒数(默认30秒)', 1, 1), ('key_overlap_minutes', '30', 'int', '新旧密钥共存分钟数', 1, 1), ('key_expire_minutes', '60', 'int', '密钥过期分钟数(从创建时间算起)', 1, 1), ('max_heartbeat_failures', '10', 'int', '最大心跳失败次数,超过后暂停检测', 1, 1), ('max_network_failures', '15', 'int', '网络异常最大失败次数,超过后登出', 1, 1), ('prevent_duplicate_login', '1', 'bool', '是否防止重复登录:0-否,1-是', 1, 1), ('timestamp_tolerance_seconds', '300', 'int', '时间戳容忍秒数(前后5分钟)', 1, 1), ('session_extend_enabled', '1', 'bool', '是否启用心跳延长session:0-否,1-是', 1, 1), ('session_extend_threshold_hours', '4', 'int', 'session延长阈值(小时),距离过期小于此值时延长', 1, 1), ('session_extend_hours', '2', 'int', '每次心跳延长的小时数', 1, 1), ('api_timeout_seconds', '30', 'int', 'API请求超时秒数', 1, 1), ('client_heartbeat_interval_seconds', '30', 'int', '客户端心跳间隔秒数', 1, 1) ] cursor.executemany(""" INSERT INTO `system_config` (`config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`) VALUES (%s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE `config_value` = VALUES(`config_value`), `config_type` = VALUES(`config_type`), `description` = VALUES(`description`), `is_system` = VALUES(`is_system`), `is_editable` = VALUES(`is_editable`), `updated_at` = NOW() """, config_data) conn.commit() print("✅ 系统配置表迁移完成!") # 6. 验证迁移结果 cursor.execute("SELECT COUNT(*) as count FROM system_config WHERE is_deleted = 0") count = cursor.fetchone()[0] print(f"✅ 成功插入 {count} 条配置记录") # 7. 显示配置列表 cursor.execute("SELECT config_key, config_value, config_type, description FROM system_config WHERE is_deleted = 0 ORDER BY config_key") configs = cursor.fetchall() print("\n📋 配置列表:") for config in configs: print(f" {config[0]}: {config[1]} ({config[2]}) - {config[3]}") except Exception as e: print(f"❌ 迁移失败: {e}") if 'conn' in locals(): conn.rollback() raise finally: if 'cursor' in locals(): cursor.close() if 'conn' in locals(): conn.close() if __name__ == "__main__": print("🗄️ 系统配置表迁移工具") print("=" * 50) # 检查是否为非交互环境,如果是则自动继续 import sys is_interactive = sys.stdin.isatty() if is_interactive: # 确认操作 confirm = input("⚠️ 此操作将重新创建system_config表,确定继续吗?(y/N): ") if confirm.lower() not in ['y', 'yes']: print("❌ 操作已取消") exit(0) else: print("🔄 非交互环境,自动继续迁移...") migrate_system_config() print("\n🎉 迁移完成!请重启后端应用以使用新的配置系统。")