97 lines
3.3 KiB
Python
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)
|