feat: 并发安全隔离 + 10分钟定时清理上传/输出目录

This commit is contained in:
ypshidifu
2026-09-17 17:40:40 +08:00
parent 271612bbef
commit 2d48f178d4
+62 -11
View File
@@ -1,14 +1,63 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""vipsong Web — 音乐加密格式转码工具 (Web 版)。""" """vipsong Web — 音乐加密格式转码工具 (Web 版)。
import os
并发安全说明:
- vipsong.convert_file() 会修改模块级全局 stats 字典,因此所有转码调用
被互斥锁 (_convert_lock) 串行化,避免多线程/协程并发时统计数据错乱
或输出路径冲突。
- 每隔 10 分钟自动清扫 static/uploads 与 static/outputs 目录,释放磁盘。
"""
import shutil import shutil
import threading
from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, BackgroundTasks from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.responses import HTMLResponse, FileResponse, JSONResponse from fastapi.responses import HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import vipsong 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 BASE_DIR = Path(__file__).parent
UPLOAD_DIR = BASE_DIR / "static" / "uploads" UPLOAD_DIR = BASE_DIR / "static" / "uploads"
@@ -55,12 +104,14 @@ async def convert(
output_path = OUTPUT_DIR / output_name output_path = OUTPUT_DIR / output_name
try: try:
vipsong.convert_file( # 加锁串行化: vipsong.convert_file() 修改全局 stats, 多线程下需隔离
str(upload_path), with _convert_lock:
format, vipsong.convert_file(
delete_flag=False, str(upload_path),
output_path=str(output_path), format,
) delete_flag=False,
output_path=str(output_path),
)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"转码失败: {e}") raise HTTPException(status_code=500, detail=f"转码失败: {e}")
finally: finally: