diff --git a/backend/routers/config.py b/backend/routers/config.py index 515e3e5..5abc917 100644 --- a/backend/routers/config.py +++ b/backend/routers/config.py @@ -300,8 +300,14 @@ def _ensure_upload_keys(config: dict) -> bool: def _migrate_old_themes(config: dict) -> bool: - """将旧式目录皮肤迁移为 ZIP 存入 data/skins/,返回是否有变更""" - changed = False + """将旧式目录皮肤迁移为 ZIP 存入 data/skins/,返回是否有变更。 + + 仅在首次执行(用旧配置升级时)生效,之后写入标记跳过:否则每次 load_config() + 都会重新执行——一旦某个皮肤被删除,只要对应证书的 theme 仍引用它、且旧版 + themes 目录还在,就会被立刻重新生成,导致皮肤包无法真正删除。 + """ + if config.get("_legacy_themes_migrated"): + return False themes_dir = AUTOMATION_DIR / "themes" for app_id, app in config.get("apps", {}).items(): upload_key = app.get("upload_key", "") @@ -336,8 +342,8 @@ def _migrate_old_themes(config: dict) -> bool: app["skins"] = skins if theme != skin_name: cert["theme"] = skin_name - changed = True - return changed + config["_legacy_themes_migrated"] = True + return True def load_config() -> dict: diff --git a/backend/routers/tasks.py b/backend/routers/tasks.py index b0a94f9..2982b6f 100644 --- a/backend/routers/tasks.py +++ b/backend/routers/tasks.py @@ -3,6 +3,7 @@ import os import json import shutil import tempfile +import time import uuid from datetime import datetime from typing import List @@ -18,6 +19,10 @@ from ..deps import get_current_user router = APIRouter(prefix="/api/tasks", tags=["tasks"], dependencies=[Depends(get_current_user)]) +# 同一用户对同一任务连击下载按钮时,短时间内只记一次操作日志,避免刷屏 +_DOWNLOAD_LOG_DEBOUNCE_SECONDS = 5 +_recent_download_logs: dict[str, float] = {} + @router.post("", response_model=TaskResponse) async def create_task(task: TaskCreate, request: Request, user: dict = Depends(get_current_user), db: Session = Depends(get_db)): @@ -276,11 +281,22 @@ async def get_download_link(task_id: str, request: Request, user: dict = Depends ) except (json.JSONDecodeError, DistributionError) as exc: raise HTTPException(status_code=502, detail=f"生成下载链接失败:{exc}") from exc - from ..services.audit_log import record_operation - record_operation(db, user, "ipa_download_requested", request=request, task=task, - resource_type="ipa", resource_name=task.oss_url, - detail={"controlled": True}) - db.commit() + + debounce_key = f"{user.get('username')}:{task_id}" + now = time.monotonic() + last = _recent_download_logs.get(debounce_key) + if last is None or now - last > _DOWNLOAD_LOG_DEBOUNCE_SECONDS: + if len(_recent_download_logs) > 500: + cutoff = now - _DOWNLOAD_LOG_DEBOUNCE_SECONDS + for key, ts in list(_recent_download_logs.items()): + if ts < cutoff: + del _recent_download_logs[key] + _recent_download_logs[debounce_key] = now + from ..services.audit_log import record_operation + record_operation(db, user, "ipa_download_requested", request=request, task=task, + resource_type="ipa", resource_name=task.oss_url, + detail={"controlled": True}) + db.commit() return {"url": download_url, "expires_in": DOWNLOAD_URL_EXPIRE_SECONDS} diff --git a/frontend/src/views/HistoryView.vue b/frontend/src/views/HistoryView.vue index 95ab8a0..26806d9 100644 --- a/frontend/src/views/HistoryView.vue +++ b/frontend/src/views/HistoryView.vue @@ -56,7 +56,7 @@ QR - + - @@ -159,7 +159,7 @@