- core/redis_client.py 移除 scheduler 耦合,lifespan 由 core/app.py 组合(Redis→调度器→Redis) - main.py 改从 core.app 导入 create_app - 删除 apps/、extensions/ 目录;utils 删除已迁移垫片与死代码 formatter.py - 新增 deploy/(nginx、supervisor)、scripts/(init_db、seed)、logs/(结构化日志落盘) - core/app.py 接入 core.logger setup_logging - 更新 CLAUDE.md 反映 core/shared/modules 新结构 Co-Authored-By: Claude <noreply@anthropic.com>
122 lines
4.3 KiB
Python
122 lines
4.3 KiB
Python
# +======================================================================
|
||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||
# +----------------------------------------------------------------------
|
||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||
# | 开源协议: Apache-2.0
|
||
# +======================================================================
|
||
# 由 apps/__init__.py 迁移而来。
|
||
|
||
"""应用工厂:create_app 组装中间件、路由、异常处理与 lifespan。"""
|
||
|
||
import logging
|
||
import time
|
||
from contextlib import asynccontextmanager
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.exceptions import RequestValidationError
|
||
from fastapi.logger import logger
|
||
from starlette.requests import Request
|
||
from starlette.responses import JSONResponse
|
||
|
||
from config.app import FASTAPI_DEBUG
|
||
from core.exceptions import AuthorizationException, BusinessException
|
||
from core.logger import setup_logging
|
||
from core.middleware import (
|
||
register_middleware,
|
||
register_cros,
|
||
db_session_middleware,
|
||
operation_log_middleware,
|
||
)
|
||
from core.redis_client import redis_lifespan, redis_middleware
|
||
from routers import register_router
|
||
|
||
|
||
@asynccontextmanager
|
||
async def _combined_lifespan(app: FastAPI):
|
||
"""组合 lifespan:先起 Redis,再启动定时任务调度器;退出时先停调度器再关 Redis。"""
|
||
async with redis_lifespan(app):
|
||
# 懒加载调度器,避免 core → 业务模块顶层依赖
|
||
from modules.job.scheduler import init_scheduler, shutdown_scheduler
|
||
|
||
job_count = init_scheduler()
|
||
logging.getLogger(__name__).info(
|
||
f"定时任务调度器已启动,加载 {job_count} 个任务")
|
||
try:
|
||
yield
|
||
finally:
|
||
shutdown_scheduler()
|
||
|
||
|
||
def create_app() -> FastAPI:
|
||
# 初始化结构化日志(控制台 + logs/app.log)
|
||
setup_logging()
|
||
|
||
app = FastAPI(
|
||
title="FastAPI",
|
||
description="FastAPI",
|
||
version="v1",
|
||
lifespan=_combined_lifespan,
|
||
)
|
||
|
||
# 注册跨域中间件
|
||
register_cros(app)
|
||
# 注册Redis上下文注入中间件(先注册→内层:先注入Redis再开DB会话)
|
||
app.middleware("http")(redis_middleware)
|
||
# 注册数据库会话中间件(后注册→外层:DB会话包裹业务逻辑,finally保证关闭)
|
||
app.middleware("http")(db_session_middleware)
|
||
# 注册操作日志中间件(在DB会话内层,记录写操作日志)
|
||
app.middleware("http")(operation_log_middleware)
|
||
# 注册路由
|
||
register_router(app)
|
||
# 登录检测
|
||
register_middleware(app)
|
||
# 注册全局异常处理
|
||
register_exception(app)
|
||
|
||
# 请求计时(最后注册→最外层,统计完整耗时)
|
||
@app.middleware("http")
|
||
async def add_process_time_header(request: Request, call_next):
|
||
start_time = time.time()
|
||
response = await call_next(request)
|
||
process_time = time.time() - start_time
|
||
response.headers["X-Process-Time"] = str(round(process_time, 5))
|
||
if FASTAPI_DEBUG:
|
||
logger.info(f"访问记录:{request.method} url:{request.url} 耗时:{str(round(process_time, 5))}")
|
||
return response
|
||
|
||
return app
|
||
|
||
|
||
# 注册全局异常处理
|
||
def register_exception(app: FastAPI):
|
||
"""全局异常捕获。"""
|
||
|
||
@app.exception_handler(AuthorizationException)
|
||
async def unicorn_exception_handler(request: Request, exc: AuthorizationException):
|
||
return JSONResponse(
|
||
status_code=200,
|
||
content={"code": exc.code, "data": None, "msg": exc.msg, "ok": False},
|
||
)
|
||
|
||
@app.exception_handler(BusinessException)
|
||
async def business_exception_handler(request: Request, exc: BusinessException):
|
||
return JSONResponse(
|
||
status_code=200,
|
||
content={"code": exc.code, "data": None, "msg": exc.msg, "ok": False},
|
||
)
|
||
|
||
@app.exception_handler(RequestValidationError)
|
||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||
errors = []
|
||
for err in exc.errors():
|
||
field = err.get("loc", [""])[-1]
|
||
msg = err.get("msg", "")
|
||
if field and field != "body":
|
||
errors.append(f"{field}: {msg}")
|
||
else:
|
||
errors.append(msg)
|
||
return JSONResponse(
|
||
status_code=200,
|
||
content={"code": 1, "data": None, "msg": "/".join(errors)},
|
||
)
|