feat:新增大屏存取发布接口

This commit is contained in:
2026-10-11 08:27:30 -07:00
parent dadd797391
commit afc07ba695
23 changed files with 2672 additions and 36 deletions
+58
View File
@@ -1,6 +1,7 @@
"""FastAPI 应用入口"""
import asyncio
import re
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI
@@ -8,10 +9,15 @@ from loguru import logger
from app.api.agent import router
from app.api.conversations import router as conversation_router
from app.api.datasource import router as datasource_router
from app.api.files import UploadSizeLimit
from app.api.files import limits_router as file_limits_router
from app.api.files import router as file_router
from app.api.materials import router as material_router
from app.api.public import router as public_router
from app.api.screens import router as screen_router
from app.config import settings
from app.core.body_limit import BodyLimit, BodyLimitRule
from app.core.auth import get_current_user
from app.core.exceptions import register_exception_handlers
from app.core.logging import setup_logging
@@ -32,12 +38,16 @@ async def lifespan(application: FastAPI):
from app.services.agent import AgentService
from app.services.material_mongo import warm_material_cache
from app.services.persistence import open_persistence
from app.services.screen_mongo import MongoScreenStore
from app.services.screens import ScreenService
from app.services.thumbnails import ThumbnailStorage
# 按配置选择 MongoDB 或进程内存,会话记录与图检查点使用同一种存储
persistence = await open_persistence()
application.state.conversation_store = persistence.store
mcp_processes = []
sweeper = None
trash_sweeper = None
data_files = None
try:
# 数据文件的元数据和会话记录用同一种存储;启动时先清理一遍过期目录,之后每小时一次
@@ -48,6 +58,21 @@ async def lifespan(application: FastAPI):
)
application.state.data_file_service = data_files
sweeper = asyncio.create_task(data_files.run_sweeper())
# 大屏存取必须落 MongoDB:没配时不创建服务,screens 相关接口返回 503,而不是存在内存里重启就丢
screen_service = None
if persistence.db is not None:
screen_store = MongoScreenStore(persistence.db)
await screen_store.ensure_indexes()
screen_service = ScreenService(
screen_store,
ThumbnailStorage(settings.thumbnail_dir),
data_files,
settings.trash_retention_days,
)
trash_sweeper = asyncio.create_task(screen_service.run_trash_sweeper())
else:
logger.warning("未配置 MONGODB_URI,大屏存取接口不可用")
application.state.screen_service = screen_service
# 物料目录启动时载入内存,动作校验与前端接口共用,避免重复查询数据库
await warm_material_cache(persistence.db)
# 拉起 mcp_servers.json 中由应用管理的服务;单个服务失败只记录,用到它的请求执行时报错
@@ -64,6 +89,8 @@ async def lifespan(application: FastAPI):
finally:
if sweeper is not None:
sweeper.cancel()
if trash_sweeper is not None:
trash_sweeper.cancel()
if data_files is not None:
await data_files.aclose()
# 共享客户端只在应用退出时关闭,追踪刷新放到线程避免阻塞事件循环
@@ -102,6 +129,37 @@ def create_app() -> FastAPI:
# 上传在读请求体之前先按 Content-Length 检查大小
application.add_middleware(UploadSizeLimit, path=f"/api{file_router.prefix}")
application.include_router(material_router, prefix="/api")
application.include_router(screen_router, prefix="/api", dependencies=authenticated)
application.include_router(
datasource_router, prefix="/api", dependencies=authenticated
)
# 匿名接口统一放 /api/public 下,不挂鉴权
application.include_router(public_router, prefix="/api")
# 保存 / 发布的请求体比普通接口大得多,单独放宽;封面图 1 MB
application.add_middleware(
BodyLimit,
rules=[
BodyLimitRule(
frozenset({"POST", "PUT"}),
re.compile(r"/api/screens(/[^/]+)?"),
settings.screen_max_mb * 1024 * 1024,
f"大屏数据不能超过 {settings.screen_max_mb} MB",
),
BodyLimitRule(
frozenset({"PUT"}),
re.compile(r"/api/screens/[^/]+/thumbnail"),
1024 * 1024 + 64 * 1024,
"封面图不能超过 1 MB",
),
# 其余带请求体的接口(发布、复制、改名、数据源取数、会话)都只有几个字段
BodyLimitRule(
frozenset({"POST", "PUT", "PATCH"}),
re.compile(r"/api/(screens|datasource|conversations)(/.*)?"),
64 * 1024,
"请求体过大",
),
],
)
return application