Files
autoclip/scripts/migrate_to_optimized_storage.py
Kris Ka dc0cf66159 完成项目问题修复和优化
主要改进:
1. 清理重复文件和冗余代码
   - 删除重复的API文件 (upload_backup.py, upload_new.py等)
   - 删除旧版本的B站服务文件 (bilibili_upload_v2.py到v6.py)
   - 统一B站服务接口

2. 优化数据存储架构
   - 创建OptimizedStorageService解决双重存储问题
   - 数据库只存储元数据,文件系统存储实际文件
   - 提供数据迁移脚本和一致性检查工具

3. 统一错误处理机制
   - 创建统一错误处理中间件
   - 提供错误处理装饰器和上下文管理器
   - 统一错误响应格式

4. 完善配置管理
   - 创建UnifiedConfig统一配置系统
   - 整合所有配置源(环境变量、配置文件、默认值)
   - 提供配置迁移脚本

5. 增强进度系统
   - 创建EnhancedProgressService
   - 支持Redis缓存、数据库持久化和内存缓存
   - 提供完整的进度跟踪和状态管理

新增文件:
- backend/services/optimized_storage_service.py
- backend/core/error_middleware.py
- backend/core/unified_config.py
- backend/services/enhanced_progress_service.py
- scripts/migrate_to_optimized_storage.py
- scripts/check_data_consistency.py
- scripts/migrate_config.py
- docs/ERROR_HANDLING_GUIDE.md
- docs/PROGRESS_SYSTEM_GUIDE.md

修复文件:
- backend/services/bilibili_service.py (统一上传接口)
- backend/main.py (使用新的错误处理中间件)
2025-09-15 12:07:09 +08:00

247 lines
8.4 KiB
Python

#!/usr/bin/env python3
"""
数据存储优化迁移脚本
将双重存储模式迁移到优化存储模式(数据库存储元数据,文件系统存储实际文件)
"""
import sys
import os
import json
import logging
from pathlib import Path
from datetime import datetime
# 添加项目根目录到Python路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from backend.core.database import SessionLocal
from backend.services.optimized_storage_service import OptimizedStorageService
from backend.models.project import Project
from backend.models.clip import Clip
from backend.models.collection import Collection
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def analyze_current_storage():
"""分析当前存储状况"""
logger.info("🔍 分析当前存储状况...")
data_dir = project_root / "data"
projects_dir = data_dir / "projects"
if not projects_dir.exists():
logger.warning("项目目录不存在")
return {"projects": [], "total_size": 0}
projects_info = []
total_size = 0
for project_dir in projects_dir.iterdir():
if project_dir.is_dir() and not project_dir.name.startswith('.'):
project_id = project_dir.name
project_size = sum(f.stat().st_size for f in project_dir.rglob('*') if f.is_file())
# 检查是否有重复数据
has_duplicate_data = False
if (project_dir / "clips_metadata.json").exists():
has_duplicate_data = True
projects_info.append({
"project_id": project_id,
"size_mb": round(project_size / (1024 * 1024), 2),
"has_duplicate_data": has_duplicate_data,
"files_count": len(list(project_dir.rglob('*')))
})
total_size += project_size
logger.info(f"📊 分析完成: {len(projects_info)} 个项目, 总大小: {round(total_size / (1024 * 1024), 2)} MB")
return {
"projects": projects_info,
"total_size": total_size
}
def migrate_project_to_optimized_storage(db, project_id: str, dry_run: bool = True):
"""迁移单个项目到优化存储模式"""
logger.info(f"🔄 迁移项目: {project_id} (dry_run={dry_run})")
try:
# 获取项目信息
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
logger.warning(f"项目 {project_id} 在数据库中不存在")
return {"success": False, "error": "项目不存在"}
# 检查项目目录
data_dir = project_root / "data"
old_project_dir = data_dir / "projects" / project_id
if not old_project_dir.exists():
logger.warning(f"项目目录不存在: {old_project_dir}")
return {"success": False, "error": "项目目录不存在"}
if dry_run:
logger.info(f"🔍 模拟迁移项目 {project_id}")
return {"success": True, "dry_run": True, "message": "模拟迁移成功"}
# 创建优化存储服务
storage_service = OptimizedStorageService(db, project_id)
# 执行迁移
migration_result = storage_service.migrate_from_old_storage(old_project_dir)
if migration_result["success"]:
logger.info(f"✅ 项目 {project_id} 迁移成功")
# 清理旧的双重存储文件
cleanup_old_duplicate_files(old_project_dir)
return {
"success": True,
"migrated_files": migration_result["migrated_files"],
"migrated_metadata": migration_result["migrated_metadata"]
}
else:
logger.error(f"❌ 项目 {project_id} 迁移失败: {migration_result['error']}")
return migration_result
except Exception as e:
logger.error(f"❌ 迁移项目 {project_id} 时发生错误: {e}")
return {"success": False, "error": str(e)}
def cleanup_old_duplicate_files(project_dir: Path):
"""清理旧的双重存储文件"""
try:
logger.info(f"🧹 清理项目 {project_dir.name} 的重复文件...")
# 删除重复的元数据文件
duplicate_files = [
"clips_metadata.json",
"collections_metadata.json",
"step1_outline.json",
"step2_timeline.json",
"step3_scoring.json",
"step4_titles.json",
"step5_collections.json"
]
cleaned_count = 0
for file_name in duplicate_files:
file_path = project_dir / file_name
if file_path.exists():
# 备份文件
backup_path = project_dir / f"{file_name}.backup"
file_path.rename(backup_path)
cleaned_count += 1
logger.info(f"📦 备份重复文件: {file_name}")
logger.info(f"✅ 清理完成,备份了 {cleaned_count} 个重复文件")
except Exception as e:
logger.error(f"❌ 清理重复文件失败: {e}")
def main():
"""主函数"""
logger.info("🚀 开始数据存储优化迁移...")
# 分析当前存储状况
storage_info = analyze_current_storage()
if not storage_info["projects"]:
logger.info("📭 没有找到需要迁移的项目")
return
# 显示分析结果
print("\n📊 当前存储状况分析:")
print("=" * 60)
for project in storage_info["projects"]:
status = "⚠️ 有重复数据" if project["has_duplicate_data"] else "✅ 正常"
print(f"项目 {project['project_id'][:8]}... | {project['size_mb']:>8.2f} MB | {project['files_count']:>4} 文件 | {status}")
print(f"\n总大小: {round(storage_info['total_size'] / (1024 * 1024), 2)} MB")
# 询问是否继续
print("\n" + "=" * 60)
print("🔧 迁移选项:")
print("1. 模拟迁移 (dry run) - 查看迁移效果但不实际执行")
print("2. 执行迁移 - 实际迁移数据并清理重复文件")
print("3. 退出")
while True:
choice = input("\n请选择操作 (1/2/3): ").strip()
if choice in ['1', '2', '3']:
break
print("❌ 无效选择,请输入 1、2 或 3")
if choice == '3':
logger.info("👋 用户取消迁移")
return
dry_run = (choice == '1')
if dry_run:
logger.info("🔍 开始模拟迁移...")
else:
logger.info("🚀 开始实际迁移...")
# 创建备份
backup_dir = project_root / f"migration_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
backup_dir.mkdir(exist_ok=True)
logger.info(f"📦 创建备份目录: {backup_dir}")
# 执行迁移
db = SessionLocal()
try:
success_count = 0
failed_count = 0
for project_info in storage_info["projects"]:
project_id = project_info["project_id"]
result = migrate_project_to_optimized_storage(db, project_id, dry_run)
if result["success"]:
success_count += 1
if dry_run:
logger.info(f"✅ 模拟迁移成功: {project_id}")
else:
logger.info(f"✅ 迁移成功: {project_id}")
else:
failed_count += 1
logger.error(f"❌ 迁移失败: {project_id} - {result.get('error', '未知错误')}")
# 显示迁移结果
print("\n" + "=" * 60)
print("📊 迁移结果:")
print(f"✅ 成功: {success_count}")
print(f"❌ 失败: {failed_count}")
print(f"📊 总计: {success_count + failed_count}")
if not dry_run and success_count > 0:
print(f"\n💾 备份位置: {backup_dir}")
print("🔧 建议:")
print("1. 测试系统功能是否正常")
print("2. 确认无误后可以删除备份文件")
print("3. 运行数据一致性检查")
except Exception as e:
logger.error(f"❌ 迁移过程中发生错误: {e}")
finally:
db.close()
logger.info("🎉 迁移完成!")
if __name__ == "__main__":
main()