Use forked frontend builds for test admin UI
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
from tortoise import connections
|
||||
|
||||
|
||||
async def migrate():
|
||||
conn = connections.get("default")
|
||||
await conn.execute_script(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS transfer_stats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
action VARCHAR(16) NOT NULL,
|
||||
file_code_id INT NULL,
|
||||
code VARCHAR(255) NULL,
|
||||
name VARCHAR(512) NOT NULL DEFAULT '',
|
||||
size BIGINT NOT NULL DEFAULT 0,
|
||||
ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
file_path VARCHAR(1024) NOT NULL DEFAULT '',
|
||||
expired_at TIMESTAMP NULL,
|
||||
deleted_at TIMESTAMP NULL,
|
||||
meta TEXT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_transfer_stats_action_created
|
||||
ON transfer_stats(action, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_transfer_stats_file_code_id
|
||||
ON transfer_stats(file_code_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_transfer_stats_code
|
||||
ON transfer_stats(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_transfer_stats_deleted_at
|
||||
ON transfer_stats(deleted_at);
|
||||
"""
|
||||
)
|
||||
@@ -54,6 +54,24 @@ class UploadChunk(models.Model):
|
||||
completed = fields.BooleanField(default=False)
|
||||
|
||||
|
||||
class TransferStats(models.Model):
|
||||
id = fields.IntField(pk=True)
|
||||
action = fields.CharField(max_length=16, index=True)
|
||||
file_code_id = fields.IntField(null=True, index=True)
|
||||
code = fields.CharField(max_length=255, null=True, index=True)
|
||||
name = fields.CharField(max_length=512, default="")
|
||||
size = fields.BigIntField(default=0)
|
||||
ip = fields.CharField(max_length=64, default="")
|
||||
file_path = fields.CharField(max_length=1024, default="")
|
||||
expired_at = fields.DatetimeField(null=True)
|
||||
deleted_at = fields.DatetimeField(null=True, index=True)
|
||||
meta = fields.TextField(null=True)
|
||||
created_at = fields.DatetimeField(auto_now_add=True, index=True)
|
||||
|
||||
class Meta:
|
||||
table = "transfer_stats"
|
||||
|
||||
|
||||
class KeyValue(Model):
|
||||
id: Optional[int] = fields.IntField(pk=True)
|
||||
key: Optional[str] = fields.CharField(
|
||||
|
||||
+45
-5
@@ -12,7 +12,7 @@ from pydantic import BaseModel, ValidationError
|
||||
from starlette import status
|
||||
|
||||
from apps.admin.dependencies import share_required_login
|
||||
from apps.base.models import FileCodes, UploadChunk, PresignUploadSession
|
||||
from apps.base.models import FileCodes, UploadChunk, PresignUploadSession, TransferStats
|
||||
from apps.base.schemas import (
|
||||
SelectFileModel,
|
||||
InitChunkUploadModel,
|
||||
@@ -59,6 +59,7 @@ class FileUploadService:
|
||||
file_path: str,
|
||||
expire_value: int,
|
||||
expire_style: str,
|
||||
ip: str = "",
|
||||
**extra_fields,
|
||||
) -> str:
|
||||
"""统一创建FileCodes记录,返回code"""
|
||||
@@ -67,7 +68,7 @@ class FileUploadService:
|
||||
)
|
||||
prefix, suffix = os.path.splitext(file_name)
|
||||
|
||||
await FileCodes.create(
|
||||
file_code = await FileCodes.create(
|
||||
code=code,
|
||||
prefix=prefix,
|
||||
suffix=suffix,
|
||||
@@ -79,6 +80,7 @@ class FileUploadService:
|
||||
used_count=used_count,
|
||||
**extra_fields,
|
||||
)
|
||||
await record_transfer_stat("upload", file_code, file_size, ip)
|
||||
return code
|
||||
|
||||
|
||||
@@ -100,6 +102,36 @@ async def create_file_code(code, **kwargs):
|
||||
return await FileCodes.create(code=code, **kwargs)
|
||||
|
||||
|
||||
async def record_transfer_stat(
|
||||
action: str,
|
||||
file_code: FileCodes,
|
||||
size: Optional[int] = None,
|
||||
ip: str = "",
|
||||
deleted_at=None,
|
||||
meta: str = "",
|
||||
) -> None:
|
||||
try:
|
||||
name = (
|
||||
f"{file_code.prefix or ''}{file_code.suffix or ''}"
|
||||
or file_code.uuid_file_name
|
||||
or file_code.code
|
||||
)
|
||||
await TransferStats.create(
|
||||
action=action,
|
||||
file_code_id=file_code.id,
|
||||
code=file_code.code,
|
||||
name=name,
|
||||
size=int(size if size is not None else (file_code.size or 0)),
|
||||
ip=ip or "",
|
||||
file_path=file_code.file_path or "",
|
||||
expired_at=file_code.expired_at,
|
||||
deleted_at=deleted_at,
|
||||
meta=meta or "",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@share_api.post("/text/", dependencies=[Depends(share_required_login)])
|
||||
async def share_text(
|
||||
text: str = Form(...),
|
||||
@@ -115,7 +147,7 @@ async def share_text(
|
||||
expired_at, expired_count, used_count, code = await get_expire_info(
|
||||
expire_value, expire_style
|
||||
)
|
||||
await create_file_code(
|
||||
file_code = await create_file_code(
|
||||
code=code,
|
||||
text=text,
|
||||
expired_at=expired_at,
|
||||
@@ -124,6 +156,7 @@ async def share_text(
|
||||
size=len(text),
|
||||
prefix="Text",
|
||||
)
|
||||
await record_transfer_stat("upload", file_code, text_size, ip)
|
||||
ip_limit["upload"].add_ip(ip)
|
||||
return APIResponse(detail={"code": code})
|
||||
|
||||
@@ -144,7 +177,7 @@ async def share_file(
|
||||
path, suffix, prefix, uuid_file_name, save_path = await get_file_path_name(file)
|
||||
file_storage: FileStorageInterface = storages[settings.file_storage]()
|
||||
await file_storage.save_file(file, save_path)
|
||||
await create_file_code(
|
||||
file_code = await create_file_code(
|
||||
code=code,
|
||||
prefix=prefix,
|
||||
suffix=suffix,
|
||||
@@ -155,6 +188,7 @@ async def share_file(
|
||||
expired_count=expired_count,
|
||||
used_count=used_count,
|
||||
)
|
||||
await record_transfer_stat("upload", file_code, file_size, ip)
|
||||
ip_limit["upload"].add_ip(ip)
|
||||
return APIResponse(detail={"code": code, "name": file.filename})
|
||||
|
||||
@@ -245,6 +279,7 @@ async def get_code_file(code: str, ip: str = Depends(ip_limit["error"])):
|
||||
|
||||
assert isinstance(file_code, FileCodes)
|
||||
await update_file_usage(file_code)
|
||||
await record_transfer_stat("download", file_code, file_code.size, ip)
|
||||
return await file_storage.get_file_response(file_code)
|
||||
|
||||
|
||||
@@ -258,6 +293,7 @@ async def select_file(data: SelectFileModel, ip: str = Depends(ip_limit["error"]
|
||||
|
||||
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=await build_select_detail(file_code, file_storage))
|
||||
|
||||
|
||||
@@ -271,6 +307,7 @@ async def download_file(key: str, code: str, ip: str = Depends(ip_limit["error"]
|
||||
if not has:
|
||||
return APIResponse(code=404, detail="文件不存在")
|
||||
assert isinstance(file_code, FileCodes)
|
||||
await record_transfer_stat("download", file_code, file_code.size, ip)
|
||||
return (
|
||||
APIResponse(detail=file_code.text)
|
||||
if file_code.text
|
||||
@@ -557,7 +594,7 @@ async def complete_upload(
|
||||
expired_at, expired_count, used_count, code = await get_expire_info(
|
||||
data.expire_value, data.expire_style
|
||||
)
|
||||
await FileCodes.create(
|
||||
file_code = await FileCodes.create(
|
||||
code=code,
|
||||
file_hash=file_hash, # 使用合并后计算的哈希
|
||||
is_chunked=True,
|
||||
@@ -571,6 +608,7 @@ async def complete_upload(
|
||||
prefix=prefix,
|
||||
suffix=suffix,
|
||||
)
|
||||
await record_transfer_stat("upload", file_code, chunk_info.file_size, ip)
|
||||
# 清理临时文件
|
||||
await storage.clean_chunks(upload_id, save_path)
|
||||
# 清理数据库中的分片记录
|
||||
@@ -697,6 +735,7 @@ async def presign_upload_proxy(
|
||||
os.path.dirname(session.save_path),
|
||||
session.expire_value,
|
||||
session.expire_style,
|
||||
ip=ip,
|
||||
)
|
||||
|
||||
await session.delete()
|
||||
@@ -721,6 +760,7 @@ async def presign_upload_confirm(upload_id: str, ip: str = Depends(ip_limit["upl
|
||||
os.path.dirname(session.save_path),
|
||||
session.expire_value,
|
||||
session.expire_style,
|
||||
ip=ip,
|
||||
)
|
||||
|
||||
await session.delete()
|
||||
|
||||
Reference in New Issue
Block a user