Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

179 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""
图片存储方案迁移脚本 - 自动执行版
将图片从旧的层级目录结构迁移到新的扁平结构
旧结构: /{module}/{year}/{month}/{filename}
/{module}/{year}/{month}/thumbnails/thumb_{filename}.jpg
新结构: /{filename}
/thumbnails/{filename_stem}.jpg
使用方法: python migrate_images_auto.py [--dry-run]
"""
import os
import sys
import shutil
import argparse
from pathlib import Path
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
# 添加项目路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app.config import get_settings
settings = get_settings()
# 数据库路径
DB_PATH = "./cms.db"
UPLOAD_DIR = Path(settings.UPLOAD_DIR)
def migrate_images(dry_run=False):
"""迁移图片路径"""
engine = create_engine(f"sqlite:///{DB_PATH}")
SessionLocal = sessionmaker(bind=engine)
db = SessionLocal()
migrated = []
skipped = []
errors = []
try:
# 获取所有图片记录
result = db.execute(text("SELECT id, file_path, thumb_path, filename FROM images"))
images = result.fetchall()
print(f"找到 {len(images)} 条图片记录")
for img in images:
try:
img_id = img[0]
old_file_path = img[1]
old_thumb_path = img[2]
filename = img[3]
# 如果已经是新格式(只有一级路径),跳过
if old_file_path and old_file_path.startswith("/"):
path_parts = old_file_path.lstrip("/").split("/")
if len(path_parts) == 1 and "/thumbnails/" not in old_file_path:
skipped.append({"id": img_id, "path": old_file_path})
continue
# 新的路径格式
new_file_path = f"/{filename}"
new_thumb_path = None
if old_thumb_path:
new_thumb_path = f"/thumbnails/{Path(filename).stem}.jpg"
if dry_run:
print(f"[DRY RUN] ID={img_id}: {old_file_path} -> {new_file_path}")
if old_thumb_path:
print(f"[DRY RUN] 缩略图: {old_thumb_path} -> {new_thumb_path}")
continue
# 物理文件路径
old_file_full = UPLOAD_DIR / old_file_path.lstrip("/") if old_file_path else None
new_file_full = UPLOAD_DIR / filename
# 移动原图文件
if old_file_full and old_file_full.exists() and old_file_full != new_file_full:
try:
shutil.move(str(old_file_full), str(new_file_full))
print(f"移动文件: {old_file_full} -> {new_file_full}")
except Exception as e:
print(f"移动文件失败 {old_file_full}: {e}")
errors.append({"id": img_id, "error": f"移动原图失败: {e}"})
continue
# 处理缩略图
if old_thumb_path:
old_thumb_full = UPLOAD_DIR / old_thumb_path.lstrip("/")
new_thumb_full = UPLOAD_DIR / "thumbnails" / f"{Path(filename).stem}.jpg"
if old_thumb_full.exists() and old_thumb_full != new_thumb_full:
try:
shutil.move(str(old_thumb_full), str(new_thumb_full))
print(f"移动缩略图: {old_thumb_full} -> {new_thumb_full}")
except Exception as e:
print(f"移动缩略图失败 {old_thumb_full}: {e}")
# 更新数据库
db.execute(
text("UPDATE images SET file_path = :new_path, thumb_path = :new_thumb WHERE id = :id"),
{"new_path": new_file_path, "new_thumb": new_thumb_path, "id": img_id}
)
migrated.append({"id": img_id, "new_path": new_file_path})
print(f"迁移成功 ID={img_id}: {old_file_path} -> {new_file_path}")
except Exception as e:
error_msg = f"迁移失败 ID={img_id}: {e}"
print(error_msg)
errors.append({"id": img_id, "error": str(e)})
if not dry_run:
db.commit()
print("\n" + "="*60)
print(f"迁移结果:")
print(f"成功: {len(migrated)}")
print(f"跳过 (已是新格式): {len(skipped)}")
print(f"失败: {len(errors)}")
print("="*60)
if errors:
print("\n错误详情:")
for err in errors:
print(f" ID={err['id']}: {err['error']}")
# 清理空目录(仅在非 dry-run 时)
if not dry_run:
cleanup_empty_dirs(UPLOAD_DIR)
except Exception as e:
print(f"迁移过程出错: {e}")
if not dry_run:
db.rollback()
finally:
db.close()
def cleanup_empty_dirs(root_dir: Path):
"""清理空目录"""
removed_count = 0
for dirpath, dirnames, filenames in os.walk(str(root_dir), topdown=False):
dir_path = Path(dirpath)
# 跳过根目录和 thumbnails 目录
if dir_path == root_dir or dir_path.name == "thumbnails":
continue
# 检查是否为空
try:
if dir_path.exists() and not any(dir_path.iterdir()):
dir_path.rmdir()
print(f"删除空目录: {dir_path}")
removed_count += 1
except Exception as e:
pass
if removed_count > 0:
print(f"清理完成,共删除 {removed_count} 个空目录")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="图片存储方案迁移工具")
parser.add_argument("--dry-run", action="store_true", help="试运行,不实际执行迁移")
args = parser.parse_args()
print("图片存储方案迁移工具")
print("="*60)
print(f"数据库: {DB_PATH}")
print(f"上传目录: {UPLOAD_DIR}")
print(f"模式: {'试运行' if args.dry_run else '实际执行'}")
print("="*60)
migrate_images(dry_run=args.dry_run)