migrate_system_config.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. #!/usr/bin/env python3
  2. """
  3. 系统配置表迁移脚本
  4. 将横表改为纵表,并插入默认配置数据
  5. """
  6. import pymysql
  7. import os
  8. from pathlib import Path
  9. from dotenv import load_dotenv
  10. # 加载环境变量
  11. env_path = Path(__file__).parent / '.env'
  12. load_dotenv(dotenv_path=env_path)
  13. def get_db_connection():
  14. """获取数据库连接"""
  15. return pymysql.connect(
  16. host=os.getenv("DB_HOST", "localhost"),
  17. port=int(os.getenv("DB_PORT", "3306")),
  18. user=os.getenv("DB_USER", "root"),
  19. password=os.getenv("DB_PASSWORD", ""),
  20. database=os.getenv("DB_NAME", "buff"),
  21. charset='utf8mb4'
  22. )
  23. def migrate_system_config():
  24. """迁移system_config表"""
  25. print("开始迁移system_config表...")
  26. try:
  27. conn = get_db_connection()
  28. cursor = conn.cursor()
  29. # 1. 检查是否需要迁移(检查是否存在config_key字段)
  30. cursor.execute("SHOW COLUMNS FROM system_config LIKE 'config_key'")
  31. has_config_key = cursor.fetchone()
  32. if has_config_key:
  33. print("system_config表已经是纵表结构,跳过迁移")
  34. return
  35. # 2. 备份现有数据
  36. print("备份现有数据...")
  37. cursor.execute("SELECT * FROM system_config LIMIT 1")
  38. old_data = cursor.fetchone()
  39. if old_data:
  40. print(f"发现旧数据: {old_data}")
  41. else:
  42. print("没有找到旧数据,将创建新的纵表结构")
  43. # 3. 删除旧表结构
  44. print("删除旧表结构...")
  45. cursor.execute("DROP TABLE system_config")
  46. # 4. 创建新表结构
  47. print("创建新的纵表结构...")
  48. cursor.execute("""
  49. CREATE TABLE IF NOT EXISTS `system_config` (
  50. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  51. `config_key` varchar(100) NOT NULL COMMENT '配置键名',
  52. `config_value` varchar(500) NOT NULL COMMENT '配置值',
  53. `config_type` enum('int','string','bool','json') DEFAULT 'string' COMMENT '配置值类型',
  54. `description` varchar(255) DEFAULT NULL COMMENT '配置描述',
  55. `is_system` tinyint(1) DEFAULT '1' COMMENT '是否为系统配置:1-是,0-否',
  56. `is_editable` tinyint(1) DEFAULT '1' COMMENT '是否可编辑:1-是,0-否',
  57. `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除:0-否,1-是',
  58. `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  59. `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  60. PRIMARY KEY (`id`),
  61. UNIQUE KEY `uk_config_key` (`config_key`, `is_deleted`) COMMENT '配置键唯一约束'
  62. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表(纵表设计)'
  63. """)
  64. # 5. 插入默认配置数据
  65. print("插入默认配置数据...")
  66. config_data = [
  67. ('heartbeat_interval_seconds', '30', 'int', '心跳间隔秒数(默认30秒)', 1, 1),
  68. ('key_overlap_minutes', '30', 'int', '新旧密钥共存分钟数', 1, 1),
  69. ('key_expire_minutes', '60', 'int', '密钥过期分钟数(从创建时间算起)', 1, 1),
  70. ('max_heartbeat_failures', '10', 'int', '最大心跳失败次数,超过后暂停检测', 1, 1),
  71. ('max_network_failures', '15', 'int', '网络异常最大失败次数,超过后登出', 1, 1),
  72. ('prevent_duplicate_login', '1', 'bool', '是否防止重复登录:0-否,1-是', 1, 1),
  73. ('timestamp_tolerance_seconds', '300', 'int', '时间戳容忍秒数(前后5分钟)', 1, 1),
  74. ('session_extend_enabled', '1', 'bool', '是否启用心跳延长session:0-否,1-是', 1, 1),
  75. ('session_extend_threshold_hours', '4', 'int', 'session延长阈值(小时),距离过期小于此值时延长', 1, 1),
  76. ('session_extend_hours', '2', 'int', '每次心跳延长的小时数', 1, 1),
  77. ('api_timeout_seconds', '30', 'int', 'API请求超时秒数', 1, 1),
  78. ('client_heartbeat_interval_seconds', '30', 'int', '客户端心跳间隔秒数', 1, 1)
  79. ]
  80. cursor.executemany("""
  81. INSERT INTO `system_config` (`config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`)
  82. VALUES (%s, %s, %s, %s, %s, %s)
  83. ON DUPLICATE KEY UPDATE
  84. `config_value` = VALUES(`config_value`),
  85. `config_type` = VALUES(`config_type`),
  86. `description` = VALUES(`description`),
  87. `is_system` = VALUES(`is_system`),
  88. `is_editable` = VALUES(`is_editable`),
  89. `updated_at` = NOW()
  90. """, config_data)
  91. conn.commit()
  92. print("✅ 系统配置表迁移完成!")
  93. # 6. 验证迁移结果
  94. cursor.execute("SELECT COUNT(*) as count FROM system_config WHERE is_deleted = 0")
  95. count = cursor.fetchone()[0]
  96. print(f"✅ 成功插入 {count} 条配置记录")
  97. # 7. 显示配置列表
  98. cursor.execute("SELECT config_key, config_value, config_type, description FROM system_config WHERE is_deleted = 0 ORDER BY config_key")
  99. configs = cursor.fetchall()
  100. print("\n📋 配置列表:")
  101. for config in configs:
  102. print(f" {config[0]}: {config[1]} ({config[2]}) - {config[3]}")
  103. except Exception as e:
  104. print(f"❌ 迁移失败: {e}")
  105. if 'conn' in locals():
  106. conn.rollback()
  107. raise
  108. finally:
  109. if 'cursor' in locals():
  110. cursor.close()
  111. if 'conn' in locals():
  112. conn.close()
  113. if __name__ == "__main__":
  114. print("🗄️ 系统配置表迁移工具")
  115. print("=" * 50)
  116. # 检查是否为非交互环境,如果是则自动继续
  117. import sys
  118. is_interactive = sys.stdin.isatty()
  119. if is_interactive:
  120. # 确认操作
  121. confirm = input("⚠️ 此操作将重新创建system_config表,确定继续吗?(y/N): ")
  122. if confirm.lower() not in ['y', 'yes']:
  123. print("❌ 操作已取消")
  124. exit(0)
  125. else:
  126. print("🔄 非交互环境,自动继续迁移...")
  127. migrate_system_config()
  128. print("\n🎉 迁移完成!请重启后端应用以使用新的配置系统。")