Files

97 lines
3.3 KiB
Python

"""随身助手后端入口。"""
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from .api import agents, auth, chat, config, files, projects, tasks, ws
from .config import settings
from .core.migrate import migrate
from .core.response import fail
from .database import Base, SessionLocal, engine
from .services.seed import seed_admin_user, seed_builtin_agents, seed_default_config
STATIC_DIR = Path(__file__).resolve().parent.parent / "static"
STATIC_DIR.mkdir(exist_ok=True)
@asynccontextmanager
async def lifespan(app: FastAPI):
settings.ensure_dirs()
Base.metadata.create_all(bind=engine)
db = SessionLocal()
try:
migrate(db)
seed_builtin_agents(db)
seed_admin_user(db)
seed_default_config(db)
finally:
db.close()
yield
app = FastAPI(
title=settings.APP_NAME,
version="0.1.0",
description="随身助手:对话 / 智能体 / 多类型项目开发 / 视频分析",
lifespan=lifespan,
)
# CORS:开发阶段放开,生产环境按需收紧
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
return JSONResponse(status_code=500, content={"code": 500, "data": None, "message": f"服务器错误:{exc}"})
# 路由注册(统一 /api 前缀)
app.include_router(auth.router, prefix=settings.API_PREFIX)
app.include_router(chat.router, prefix=settings.API_PREFIX)
app.include_router(agents.router, prefix=settings.API_PREFIX)
app.include_router(projects.router, prefix=settings.API_PREFIX)
app.include_router(files.router, prefix=settings.API_PREFIX)
app.include_router(tasks.router, prefix=settings.API_PREFIX)
app.include_router(config.router, prefix=settings.API_PREFIX)
app.include_router(ws.router, prefix=settings.API_PREFIX)
# 静态:上传文件访问(视频抽帧 URL 等)
settings.ensure_dirs()
app.mount("/uploads", StaticFiles(directory=str(settings.UPLOAD_DIR)), name="uploads")
@app.exception_handler(404)
async def spa_fallback(request: Request, exc: Exception):
"""SPA 兜底:非 API 路径一律返回 index.html(前端 hash 路由,实际无需,但稳妥)。"""
if request.url.path.startswith("/api") or request.url.path.startswith("/uploads"):
return JSONResponse(status_code=404, content={"code": 404, "data": None, "message": "接口不存在"})
index = STATIC_DIR / "index.html"
if index.exists():
return FileResponse(index)
return JSONResponse(status_code=404, content={"code": 404, "data": None, "message": "前端未构建"})
@app.get("/api/health")
def health():
return {"code": 0, "data": {"status": "ok", "app": settings.APP_NAME}, "message": "ok"}
# 前端静态托管(最后挂载,避免抢占 API 路由)
app.mount("/", StaticFiles(directory=str(STATIC_DIR), html=True), name="static")
if __name__ == "__main__":
import uvicorn
uvicorn.run("app.main:app", host=settings.HOST, port=settings.PORT, reload=settings.DEBUG)