119 lines
4.1 KiB
Python
119 lines
4.1 KiB
Python
"""FastAPI 应用工厂。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import mimetypes
|
|
import sys
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse, Response
|
|
|
|
from fastapi_admin.config import APP_NAME, APP_CORS_ORIGINS
|
|
from fastapi_admin.config._loader import _find_project_root
|
|
from fastapi_common.fastapi_common_storage.oss_client import OssClient
|
|
from fastapi_common.fastapi_common_web.exception.Base.BusinessException import BusinessException
|
|
|
|
# 确保项目根在 sys.path
|
|
_PROJECT_ROOT = _find_project_root()
|
|
if str(_PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(_PROJECT_ROOT))
|
|
|
|
# fastapi_modules 目录加入路径(使 importlib 能找到各模块)
|
|
_FASTMOD = _PROJECT_ROOT / "fastapi_modules"
|
|
_FASTMOD_LEAUDIT = _FASTMOD / "fastapi_leaudit"
|
|
_NATIVE_LEAUDIT_SRC = _PROJECT_ROOT.parent / "leaudit" / "src"
|
|
if str(_FASTMOD) not in sys.path:
|
|
sys.path.insert(0, str(_FASTMOD))
|
|
if str(_FASTMOD_LEAUDIT) not in sys.path:
|
|
sys.path.insert(0, str(_FASTMOD_LEAUDIT))
|
|
if _NATIVE_LEAUDIT_SRC.exists() and str(_NATIVE_LEAUDIT_SRC) not in sys.path:
|
|
sys.path.insert(0, str(_NATIVE_LEAUDIT_SRC))
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""创建并配置 FastAPI 应用。"""
|
|
|
|
async def _warm_rule_config_summary_cache() -> None:
|
|
import logging
|
|
|
|
logger = logging.getLogger("APP")
|
|
try:
|
|
from fastapi_modules.fastapi_leaudit.services.impl.ruleConfigServiceImpl import GetRuleConfigServiceSingleton
|
|
|
|
logger.info("start warming rule-config summary cache")
|
|
await GetRuleConfigServiceSingleton().WarmPackSummaries(force=True)
|
|
logger.info("rule-config summary cache warmed")
|
|
except Exception as exc:
|
|
logger.warning("rule-config summary cache warm failed: %s", exc)
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
import logging
|
|
logging.basicConfig(level=logging.INFO)
|
|
logging.getLogger("APP").info(f"{APP_NAME} starting...")
|
|
warm_task = asyncio.create_task(_warm_rule_config_summary_cache())
|
|
app.state.rule_config_warm_task = warm_task
|
|
yield
|
|
if not warm_task.done():
|
|
warm_task.cancel()
|
|
logging.getLogger("APP").info(f"{APP_NAME} shutting down...")
|
|
|
|
app = FastAPI(
|
|
title=APP_NAME,
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
docs_url="/api/docs",
|
|
redoc_url=None,
|
|
)
|
|
|
|
# CORS
|
|
origins = [o.strip() for o in APP_CORS_ORIGINS.split(",") if o.strip()]
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# 统一业务异常处理
|
|
@app.exception_handler(BusinessException)
|
|
async def handle_business_exception(request: Request, exc: BusinessException):
|
|
return JSONResponse(
|
|
status_code=exc.status.value,
|
|
content={
|
|
"code": exc.status.value,
|
|
"message": exc.message,
|
|
"data": None,
|
|
},
|
|
)
|
|
|
|
# 注册控制器
|
|
from fastapi_admin.bootstrap_parts.controllers import register_controllers
|
|
register_controllers(app)
|
|
|
|
@app.get("/docauditai/{ObjectPath:path}")
|
|
async def GetCompatObject(ObjectPath: str):
|
|
"""兼容旧前端的对象访问路径,优先读取新桶对象。"""
|
|
Client = OssClient()
|
|
CandidateBuckets = [Client.bucket, "docauditai"]
|
|
for BucketName in CandidateBuckets:
|
|
try:
|
|
if not Client.ObjectExists(ObjectPath, Bucket=BucketName):
|
|
continue
|
|
Content = Client.DownloadBytes(ObjectPath, Bucket=BucketName)
|
|
MediaType = mimetypes.guess_type(ObjectPath)[0] or "application/octet-stream"
|
|
return Response(content=Content, media_type=MediaType)
|
|
except Exception:
|
|
continue
|
|
raise HTTPException(status_code=404, detail="Object not found")
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|