Use forked frontend builds for test admin UI

This commit is contained in:
Orion
2026-06-05 11:14:13 +08:00
parent 1460b51163
commit 9d978ca68e
32 changed files with 307 additions and 1303 deletions
@@ -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);
"""
)
+18
View File
@@ -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
View File
@@ -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()