From 2d48f178d4b478cd8b620e2703ecc182681d65d4 Mon Sep 17 00:00:00 2001 From: ypshidifu Date: Thu, 17 Sep 2026 17:40:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B9=B6=E5=8F=91=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E9=9A=94=E7=A6=BB=20+=2010=E5=88=86=E9=92=9F=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E6=B8=85=E7=90=86=E4=B8=8A=E4=BC=A0/=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/app.py b/app.py index b88f231..0c4ecaa 100644 --- a/app.py +++ b/app.py @@ -1,14 +1,63 @@ #!/usr/bin/env python3 -"""vipsong Web — 音乐加密格式转码工具 (Web 版)。""" -import os +"""vipsong Web — 音乐加密格式转码工具 (Web 版)。 + +并发安全说明: +- vipsong.convert_file() 会修改模块级全局 stats 字典,因此所有转码调用 + 被互斥锁 (_convert_lock) 串行化,避免多线程/协程并发时统计数据错乱 + 或输出路径冲突。 +- 每隔 10 分钟自动清扫 static/uploads 与 static/outputs 目录,释放磁盘。 +""" import shutil +import threading +from contextlib import asynccontextmanager from pathlib import Path -from fastapi import FastAPI, File, UploadFile, Form, HTTPException, BackgroundTasks -from fastapi.responses import HTMLResponse, FileResponse, JSONResponse +from fastapi import FastAPI, File, UploadFile, Form, HTTPException +from fastapi.responses import HTMLResponse, FileResponse from fastapi.staticfiles import StaticFiles import vipsong -app = FastAPI(title="vipsong Web", description="音乐加密格式转码工具") +# --- 并发隔离 --------------------------------------------- +# vipsong.convert_file() 内部使用模块全局 stats, 需加锁串行化 +_convert_lock = threading.Lock() + +# --- 定期清理 ----------------------------------------------- +_CLEANUP_INTERVAL = 10 * 60 # 10 分钟 +_cleanup_stop = threading.Event() + + +def _purge_directory(directory: Path): + """删除目录内所有文件(保留目录本身)。""" + if not directory.exists(): + return + for entry in directory.iterdir(): + try: + if entry.is_file() or entry.is_symlink(): + entry.unlink(missing_ok=True) + elif entry.is_dir(): + shutil.rmtree(entry, ignore_errors=True) + except OSError: + pass + + +def _cleanup_loop(): + """后台线程:每隔 _CLEANUP_INTERVAL 清扫上传和输出目录。""" + while not _cleanup_stop.wait(_CLEANUP_INTERVAL): + _purge_directory(UPLOAD_DIR) + _purge_directory(OUTPUT_DIR) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + # 启动清理线程 + t = threading.Thread(target=_cleanup_loop, daemon=True, name="vipsong-cleanup") + t.start() + yield + # 关闭时停止清理线程 + _cleanup_stop.set() + t.join(timeout=5) + + +app = FastAPI(title="vipsong Web", description="音乐加密格式转码工具", lifespan=lifespan) BASE_DIR = Path(__file__).parent UPLOAD_DIR = BASE_DIR / "static" / "uploads" @@ -55,12 +104,14 @@ async def convert( output_path = OUTPUT_DIR / output_name try: - vipsong.convert_file( - str(upload_path), - format, - delete_flag=False, - output_path=str(output_path), - ) + # 加锁串行化: vipsong.convert_file() 修改全局 stats, 多线程下需隔离 + with _convert_lock: + vipsong.convert_file( + str(upload_path), + format, + delete_flag=False, + output_path=str(output_path), + ) except Exception as e: raise HTTPException(status_code=500, detail=f"转码失败: {e}") finally: