feat:完成大部分数据文件上传解析功能

This commit is contained in:
2026-10-08 08:23:01 -07:00
parent 05c050d2ba
commit bb6a9405db
27 changed files with 2675 additions and 18 deletions
+95
View File
@@ -0,0 +1,95 @@
"""数据文件接口:上传限制、上传解析、列出、查看、删除,见 docs/screen-agent/data-files.md 第 5 节"""
from typing import Annotated, get_args
from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile
from app.common.response import ApiResponse, success_response
from app.core.auth import get_current_user
from app.datafiles.service import MAX_FILES_PER_CONVERSATION, DataFileService
from app.schemas.base import NonBlankString
from app.schemas.data_file import (
DataFileDetail,
DataFileInfo,
DataFileLimits,
DataFileList,
DataFileRecord,
DataFormat,
)
from app.schemas.user import CurrentUser
# 上传限制不需要登录,单独一个路由;要先于 /files/{file_id} 注册
limits_router = APIRouter(prefix="/files", tags=["DataFile"])
router = APIRouter(prefix="/files", tags=["DataFile"])
# 文件选择框的过滤条件;后端按文件内容识别格式,不看扩展名
ACCEPT = ".csv,.tsv,.txt,.xlsx,.xls,.xlsb,.json,.jsonl,.ndjson,.parquet"
def _service(request: Request) -> DataFileService:
return request.app.state.data_file_service
def _detail(record: DataFileRecord) -> DataFileDetail:
# 按接口模型重新校验,去掉 owner_id、sha256、列概况等只在后端用的字段
return DataFileDetail.model_validate(record.model_dump())
@limits_router.get("/limits")
async def get_limits(request: Request) -> ApiResponse[DataFileLimits]:
return success_response(
DataFileLimits(
max_size_mb=_service(request).max_mb,
max_files_per_conversation=MAX_FILES_PER_CONVERSATION,
formats=list(get_args(DataFormat)),
accept=ACCEPT,
)
)
@router.post("")
async def upload_file(
request: Request,
conversation_id: Annotated[NonBlankString, Form()],
file: Annotated[UploadFile, File()],
user: Annotated[CurrentUser, Depends(get_current_user)],
) -> ApiResponse[DataFileDetail]:
record = await _service(request).upload(
user.id, conversation_id, file.filename, file.file
)
return success_response(_detail(record))
@router.get("")
async def list_files(
request: Request,
conversation_id: Annotated[NonBlankString, Query()],
user: Annotated[CurrentUser, Depends(get_current_user)],
) -> ApiResponse[DataFileList]:
records = await _service(request).list_files(user.id, conversation_id)
return success_response(
DataFileList(
files=[
DataFileInfo.model_validate(record.model_dump()) for record in records
]
)
)
@router.get("/{file_id}")
async def get_file(
file_id: str,
request: Request,
user: Annotated[CurrentUser, Depends(get_current_user)],
) -> ApiResponse[DataFileDetail]:
return success_response(_detail(await _service(request).get_file(user.id, file_id)))
@router.delete("/{file_id}")
async def delete_file(
file_id: str,
request: Request,
user: Annotated[CurrentUser, Depends(get_current_user)],
) -> ApiResponse[dict]:
await _service(request).delete_file(user.id, file_id)
return success_response()