feat: migrate configuration to .env environment variables

This commit implements a complete migration from JSON-based configuration
to .env environment variables, providing better security and flexibility.

Key Changes:
- Add python-dotenv dependency for environment variable support
- Create config/env_loader.py with type conversion utilities
- Add from_env() class methods to all config dataclasses
- Update ConfigLoader to prioritize environment variables
- Add save_to_env() method for .env file management
- Implement database connection factory pattern
- Add base DAO and connection classes for better abstraction
- Support both SQL Server and MySQL with unified interface
- Create migration script (scripts/migrate_to_env.py)
- Update GUI to read/write .env files
- Add comprehensive migration documentation

New Files:
- config/env_loader.py - Environment variable loader
- db/base_connection.py - Base database connection interface
- db/base_dao.py - Base DAO with common utilities
- db/connection_factory.py - Factory for creating connections
- db/mysql_connection.py - MySQL-specific connection
- db/sqlserver_connection.py - SQL Server-specific connection
- db/table_name_converter.py - SQL dialect converter
- scripts/migrate_to_env.py - Configuration migration tool
- docs/ENV_MIGRATION.md - Complete migration guide
- .env.example - Environment variable template

Testing:
- Verified MySQL connection (8.0.44)
- Tested all DAO operations
- Confirmed 150 tables accessible
- Validated configuration loading

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-02-09 22:39:14 +08:00
parent 04b99292ad
commit aaa46ef282
22 changed files with 2545 additions and 563 deletions

226
scripts/migrate_to_env.py Normal file
View File

@@ -0,0 +1,226 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
配置迁移脚本
将现有的 JSON 配置文件迁移到 .env 环境变量文件
"""
import os
import sys
import json
import shutil
from pathlib import Path
# 添加项目根目录到 sys.path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from config.schema import AppConfig
def migrate_json_to_env(
json_file: str = "config/user_settings.json",
env_file: str = ".env",
backup: bool = True
) -> bool:
"""
迁移 JSON 配置到 .env 文件
Args:
json_file: JSON 配置文件路径
env_file: .env 文件路径
backup: 是否备份原 JSON 文件
Returns:
迁移是否成功
"""
json_path = project_root / json_file
env_path = project_root / env_file
# 检查 JSON 文件是否存在
if not json_path.exists():
print(f"❌ JSON 配置文件不存在: {json_path}")
print(f"💡 提示: 如果这是首次运行,请复制 .env.example 到 .env 并填入配置")
return False
# 检查 .env 文件是否已存在
if env_path.exists():
response = input(f"⚠️ .env 文件已存在: {env_path}\n是否覆盖? (y/N): ")
if response.lower() != 'y':
print("❌ 迁移已取消")
return False
# 备份现有的 .env 文件
backup_path = env_path.with_suffix(".env.backup")
shutil.copy(env_path, backup_path)
print(f"✅ 已备份现有 .env 文件到: {backup_path}")
try:
# 读取 JSON 配置
print(f"📖 读取 JSON 配置: {json_path}")
with open(json_path, "r", encoding="utf-8") as f:
json_data = json.load(f)
# 使用 ConfigLoader 将字典转换为配置对象
from config.loader import ConfigLoader
config = ConfigLoader._dict_to_config(json_data)
# 保存到 .env 文件
print(f"💾 保存配置到 .env 文件: {env_path}")
success = ConfigLoader.save_to_env(config, env_file)
if not success:
print("❌ 保存 .env 文件失败")
return False
# 备份原 JSON 文件
if backup:
backup_path = json_path.with_suffix(".json.backup")
shutil.copy(json_path, backup_path)
print(f"✅ 已备份 JSON 配置到: {backup_path}")
print("\n✅ 配置迁移成功!")
print(f"\n📝 新配置文件: {env_path}")
print(f"📦 备份文件: {backup_path if backup else ''}")
print("\n💡 提示:")
print(" 1. 请检查 .env 文件中的配置是否正确")
print(" 2. 确保 .env 文件不会被提交到版本控制")
print(" 3. 可以删除原 JSON 配置文件: " + str(json_path))
return True
except json.JSONDecodeError as e:
print(f"❌ JSON 解析失败: {e}")
return False
except Exception as e:
print(f"❌ 迁移失败: {e}")
import traceback
traceback.print_exc()
return False
def create_env_from_example(
example_file: str = ".env.example",
env_file: str = ".env"
) -> bool:
"""
从 .env.example 创建 .env 文件
Args:
example_file: .env.example 文件路径
env_file: .env 文件路径
Returns:
创建是否成功
"""
example_path = project_root / example_file
env_path = project_root / env_file
if not example_path.exists():
print(f"❌ .env.example 文件不存在: {example_path}")
return False
if env_path.exists():
response = input(f"⚠️ .env 文件已存在: {env_path}\n是否覆盖? (y/N): ")
if response.lower() != 'y':
print("❌ 操作已取消")
return False
try:
shutil.copy(example_path, env_path)
print(f"✅ 已从 {example_file} 创建 {env_file}")
print("\n💡 提示:")
print(" 1. 请编辑 .env 文件,填入实际的配置值")
print(" 2. 特别注意敏感信息(密码、密钥等)")
print(" 3. 确保 .env 文件不会被提交到版本控制")
return True
except Exception as e:
print(f"❌ 创建失败: {e}")
return False
def main():
"""主函数"""
print("=" * 60)
print("🔄 配置迁移工具 - JSON → .env")
print("=" * 60)
# 检查命令行参数
if len(sys.argv) > 1:
command = sys.argv[1].lower()
if command == "from-example":
# 从 .env.example 创建
print("\n📋 模式: 从 .env.example 创建配置文件")
example_file = sys.argv[2] if len(sys.argv) > 2 else ".env.example"
env_file = sys.argv[3] if len(sys.argv) > 3 else ".env"
create_env_from_example(example_file, env_file)
return
elif command == "migrate":
# 从 JSON 迁移
print("\n📋 模式: 从 JSON 配置迁移")
json_file = sys.argv[2] if len(sys.argv) > 2 else "config/user_settings.json"
env_file = sys.argv[3] if len(sys.argv) > 3 else ".env"
migrate_json_to_env(json_file, env_file)
return
elif command == "help":
print("""
用法:
python scripts/migrate_to_env.py <命令> [参数]
命令:
migrate [json_file] [env_file] 从 JSON 配置迁移到 .env
from-example [example] [env_file] 从 .env.example 创建配置文件
help 显示此帮助信息
示例:
python scripts/migrate_to_env.py migrate
python scripts/migrate_to_env.py migrate config/user_settings.json .env
python scripts/migrate_to_env.py from-example
python scripts/migrate_to_env.py from-example .env.example .env.local
""")
return
# 交互模式
print("\n请选择操作:")
print(" 1. 从 JSON 配置迁移到 .env")
print(" 2. 从 .env.example 创建配置文件")
print(" 3. 退出")
choice = input("\n请输入选项 (1-3): ").strip()
if choice == "1":
json_file = input("JSON 配置文件路径 (默认: config/user_settings.json): ").strip()
if not json_file:
json_file = "config/user_settings.json"
env_file = input(".env 文件路径 (默认: .env): ").strip()
if not env_file:
env_file = ".env"
backup_choice = input("是否备份原 JSON 文件? (Y/n): ").strip().lower()
backup = backup_choice != 'n'
migrate_json_to_env(json_file, env_file, backup)
elif choice == "2":
example_file = input(".env.example 文件路径 (默认: .env.example): ").strip()
if not example_file:
example_file = ".env.example"
env_file = input(".env 文件路径 (默认: .env): ").strip()
if not env_file:
env_file = ".env"
create_env_from_example(example_file, env_file)
elif choice == "3":
print("👋 再见!")
else:
print("❌ 无效的选项")
if __name__ == "__main__":
main()