102 lines
2.6 KiB
Python
102 lines
2.6 KiB
Python
"""
|
||
main.py
|
||
报告模板管理模块 FastAPI 应用入口。
|
||
|
||
启动:
|
||
uvicorn main:app --host 0.0.0.0 --port 8100
|
||
或:
|
||
python main.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import time
|
||
import uuid
|
||
from contextlib import asynccontextmanager
|
||
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
|
||
from config import settings
|
||
from database import init_database
|
||
from log import configure_logging, get_logger
|
||
from routers import template
|
||
|
||
# 在创建应用前完成日志配置
|
||
configure_logging()
|
||
logger = get_logger("app")
|
||
access_logger = get_logger("app.access")
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(_app: FastAPI):
|
||
logger.info("应用启动 | %s v%s", settings.APP_TITLE, settings.APP_VERSION)
|
||
if settings.DB_AUTO_CREATE_TABLES:
|
||
try:
|
||
init_database()
|
||
except Exception as e: # noqa: BLE001
|
||
logger.warning("启动建表失败(不影响已存在表的使用): %s", e)
|
||
yield
|
||
logger.info("应用关闭")
|
||
|
||
|
||
app = FastAPI(
|
||
title=settings.APP_TITLE,
|
||
version=settings.APP_VERSION,
|
||
description=settings.APP_DESCRIPTION,
|
||
lifespan=lifespan,
|
||
)
|
||
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=settings.CORS_ORIGINS,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
|
||
@app.middleware("http")
|
||
async def log_requests(request: Request, call_next):
|
||
if not settings.LOG_HTTP_ACCESS:
|
||
return await call_next(request)
|
||
|
||
req_id = uuid.uuid4().hex[:8]
|
||
start = time.perf_counter()
|
||
client = request.client.host if request.client else "-"
|
||
access_logger.info("→ [%s] %s %s | client=%s", req_id, request.method, request.url.path, client)
|
||
try:
|
||
response = await call_next(request)
|
||
except Exception:
|
||
cost = (time.perf_counter() - start) * 1000
|
||
access_logger.exception("✗ [%s] %s %s | %.1fms | 未处理异常", req_id, request.method, request.url.path, cost)
|
||
raise
|
||
cost = (time.perf_counter() - start) * 1000
|
||
access_logger.info(
|
||
"← [%s] %s %s | %s | %.1fms",
|
||
req_id, request.method, request.url.path, response.status_code, cost,
|
||
)
|
||
response.headers["X-Request-ID"] = req_id
|
||
return response
|
||
|
||
|
||
app.include_router(template.router)
|
||
|
||
|
||
@app.get("/health", tags=["健康检查"])
|
||
def health() -> dict:
|
||
return {"status": "ok", "version": settings.APP_VERSION}
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import uvicorn
|
||
|
||
# log_config=None:沿用本模块 configure_logging() 的配置,避免被 uvicorn 覆盖
|
||
uvicorn.run(
|
||
"main:app",
|
||
host=settings.HOST,
|
||
port=settings.PORT,
|
||
reload=settings.RELOAD,
|
||
log_config=None,
|
||
)
|