diff --git a/apps/admin/services.py b/apps/admin/services.py index ca94e73..3f8edba 100644 --- a/apps/admin/services.py +++ b/apps/admin/services.py @@ -3,7 +3,7 @@ import json import os import time from datetime import datetime, timedelta -from typing import Any, Optional +from typing import Any, Dict, List, Optional, Tuple from core.response import APIResponse from core.storage import FileStorageInterface, storages @@ -82,6 +82,18 @@ class FileService: def _file_metadata_key(self, file_id: int) -> str: return f"{self.FILE_METADATA_KEY_PREFIX}{file_id}" + def _dedupe_download_stats(self, stats: List[TransferStats]) -> List[TransferStats]: + deduped: List[TransferStats] = [] + recent_downloads: Dict[Tuple[Optional[int], str, int], datetime] = {} + for stat in stats: + key = (stat.file_code_id, stat.ip or "", int(stat.size or 0)) + previous = recent_downloads.get(key) + if previous and (stat.created_at - previous).total_seconds() <= 5: + continue + recent_downloads[key] = stat.created_at + deduped.append(stat) + return deduped + async def _delete_file_code(self, file_code: FileCodes): try: name = ( @@ -1289,6 +1301,7 @@ class FileService: download_stats = await TransferStats.filter( action="download", file_code_id=file_code.id ).order_by("created_at") + download_stats = self._dedupe_download_stats(download_stats) timeline.append( { "key": "retrieved", diff --git a/apps/admin/views.py b/apps/admin/views.py index cbf3ed6..6af7bdc 100644 --- a/apps/admin/views.py +++ b/apps/admin/views.py @@ -189,10 +189,25 @@ async def analytics(start: str = "", end: str = ""): end_day = parse_day(end, now.replace(hour=0, minute=0, second=0, microsecond=0)) start_day = parse_day(start, end_day - datetime.timedelta(days=29)) end_exclusive = end_day + datetime.timedelta(days=1) - stats = await TransferStats.filter( + raw_stats = await TransferStats.filter( created_at__gte=start_day, created_at__lt=end_exclusive, - ) + ).order_by("created_at", "id") + stats = [] + recent_downloads = {} + for item in raw_stats: + if item.action == "download": + dedupe_key = ( + item.file_code_id or item.code or "", + item.ip or "", + int(item.size or 0), + ) + previous = recent_downloads.get(dedupe_key) + if previous and (item.created_at - previous).total_seconds() <= 5: + continue + recent_downloads[dedupe_key] = item.created_at + stats.append(item) + files = await FileCodes.all() file_map = {item.code: item for item in files} diff --git a/apps/base/views.py b/apps/base/views.py index 60d2498..0b9e499 100644 --- a/apps/base/views.py +++ b/apps/base/views.py @@ -292,8 +292,9 @@ async def select_file(data: SelectFileModel, ip: str = Depends(ip_limit["error"] return APIResponse(code=404, detail=file_code) assert isinstance(file_code, FileCodes) - await update_file_usage(file_code) - await record_transfer_stat("download", file_code, file_code.size, ip) + if file_code.text is not None: + await update_file_usage(file_code) + await record_transfer_stat("download", file_code, file_code.size, ip) return APIResponse(detail=await build_select_detail(file_code, file_storage)) @@ -303,10 +304,11 @@ async def download_file(key: str, code: str, ip: str = Depends(ip_limit["error"] if await get_select_token(code) != key: ip_limit["error"].add_ip(ip) raise HTTPException(status_code=403, detail="下载鉴权失败") - has, file_code = await get_code_file_by_code(code, False) + has, file_code = await get_code_file_by_code(code) if not has: - return APIResponse(code=404, detail="文件不存在") + return APIResponse(code=404, detail=file_code) assert isinstance(file_code, FileCodes) + await update_file_usage(file_code) await record_transfer_stat("download", file_code, file_code.size, ip) return ( APIResponse(detail=file_code.text)