🐛 修复下载历史重复计数
This commit is contained in:
+14
-1
@@ -3,7 +3,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
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.response import APIResponse
|
||||||
from core.storage import FileStorageInterface, storages
|
from core.storage import FileStorageInterface, storages
|
||||||
@@ -82,6 +82,18 @@ class FileService:
|
|||||||
def _file_metadata_key(self, file_id: int) -> str:
|
def _file_metadata_key(self, file_id: int) -> str:
|
||||||
return f"{self.FILE_METADATA_KEY_PREFIX}{file_id}"
|
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):
|
async def _delete_file_code(self, file_code: FileCodes):
|
||||||
try:
|
try:
|
||||||
name = (
|
name = (
|
||||||
@@ -1289,6 +1301,7 @@ class FileService:
|
|||||||
download_stats = await TransferStats.filter(
|
download_stats = await TransferStats.filter(
|
||||||
action="download", file_code_id=file_code.id
|
action="download", file_code_id=file_code.id
|
||||||
).order_by("created_at")
|
).order_by("created_at")
|
||||||
|
download_stats = self._dedupe_download_stats(download_stats)
|
||||||
timeline.append(
|
timeline.append(
|
||||||
{
|
{
|
||||||
"key": "retrieved",
|
"key": "retrieved",
|
||||||
|
|||||||
+16
-1
@@ -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))
|
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))
|
start_day = parse_day(start, end_day - datetime.timedelta(days=29))
|
||||||
end_exclusive = end_day + datetime.timedelta(days=1)
|
end_exclusive = end_day + datetime.timedelta(days=1)
|
||||||
stats = await TransferStats.filter(
|
raw_stats = await TransferStats.filter(
|
||||||
created_at__gte=start_day,
|
created_at__gte=start_day,
|
||||||
created_at__lt=end_exclusive,
|
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()
|
files = await FileCodes.all()
|
||||||
file_map = {item.code: item for item in files}
|
file_map = {item.code: item for item in files}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -292,6 +292,7 @@ async def select_file(data: SelectFileModel, ip: str = Depends(ip_limit["error"]
|
|||||||
return APIResponse(code=404, detail=file_code)
|
return APIResponse(code=404, detail=file_code)
|
||||||
|
|
||||||
assert isinstance(file_code, FileCodes)
|
assert isinstance(file_code, FileCodes)
|
||||||
|
if file_code.text is not None:
|
||||||
await update_file_usage(file_code)
|
await update_file_usage(file_code)
|
||||||
await record_transfer_stat("download", file_code, file_code.size, ip)
|
await record_transfer_stat("download", file_code, file_code.size, ip)
|
||||||
return APIResponse(detail=await build_select_detail(file_code, file_storage))
|
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:
|
if await get_select_token(code) != key:
|
||||||
ip_limit["error"].add_ip(ip)
|
ip_limit["error"].add_ip(ip)
|
||||||
raise HTTPException(status_code=403, detail="下载鉴权失败")
|
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:
|
if not has:
|
||||||
return APIResponse(code=404, detail="文件不存在")
|
return APIResponse(code=404, detail=file_code)
|
||||||
assert isinstance(file_code, FileCodes)
|
assert isinstance(file_code, FileCodes)
|
||||||
|
await update_file_usage(file_code)
|
||||||
await record_transfer_stat("download", file_code, file_code.size, ip)
|
await record_transfer_stat("download", file_code, file_code.size, ip)
|
||||||
return (
|
return (
|
||||||
APIResponse(detail=file_code.text)
|
APIResponse(detail=file_code.text)
|
||||||
|
|||||||
Reference in New Issue
Block a user