import os import logging from contextlib import asynccontextmanager from fastapi import FastAPI from tortoise import Tortoise from redis.asyncio import Redis from fastapi.staticfiles import StaticFiles from aiomysql import create_pool, OperationalError as AiomysqlOperationalError from app.core.exceptions import SettingNotFound from app.core.init_app import ( init_data, make_middlewares, register_exceptions, register_routers, ) try: from app.settings.config import settings except ImportError: raise SettingNotFound("Can not import settings") # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): # 初始化资源 app.state.mysql_pool = None app.state.redis_client = None try: # 初始化 MySQL 连接池 app.state.mysql_pool = await create_pool( host=settings.FLOW_MYSQL_HOST, port=settings.FLOW_MYSQL_PORT, user=settings.FLOW_MYSQL_USER, password=settings.FLOW_MYSQL_PASSWORD, db=settings.FLOW_MYSQL_DB, # 核心并发参数 minsize=15, # 初始保持20个连接 maxsize=50, # 最大连接数 # 连接管理优化 pool_recycle=100, # 180秒回收连接 connect_timeout=15, # 连接超时时间 # 其他优化 charset='utf8mb4', echo=False, ) # 验证连接有效性 async with app.state.mysql_pool.acquire() as conn: await conn.ping() logger.info("✅ MySQL 连接池初始化成功") except AiomysqlOperationalError as e: logger.error(f"❌ MySQL 连接池初始化失败: 数据库操作错误 - {str(e)}") raise except Exception as e: logger.error(f"❌ MySQL 连接池初始化失败: 未知错误 - {str(e)}") raise try: # 初始化 Redis 连接 app.state.redis_client = Redis( host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DB, decode_responses=True, password=settings.REDIS_PASSWORD, socket_connect_timeout=5, socket_keepalive=True, retry_on_timeout=True, # 连接池参数 max_connections=1024, # Redis连接池最大连接数 health_check_interval=30, retry_on_error=[ConnectionError, TimeoutError], # 重试错误类型 single_connection_client=False, # 不使用单连接客户端 auto_close_connection_pool=True # 自动关闭连接池 ) await app.state.redis_client.ping() logger.info("✅ Redis 连接成功") except Exception as e: logger.error(f"❌ Redis 连接失败: {str(e)}") try: await init_data() except Exception as e: logger.error(f"❌ 初始化数据失败: {str(e)}") raise yield # 资源清理 if app.state.redis_client: try: await app.state.redis_client.close() logger.info("🛑 Redis 连接已关闭") except Exception as e: logger.warning(f"⚠️ Redis 关闭失败: {str(e)}") if app.state.mysql_pool: try: app.state.mysql_pool.close() await app.state.mysql_pool.wait_closed() logger.info("🛑 MySQL 连接池已关闭") except Exception as e: logger.warning(f"⚠️ MySQL 连接池关闭失败: {str(e)}") try: await Tortoise.close_connections() logger.info("🛑 Tortoise 连接已关闭") except Exception as e: logger.warning(f"⚠️ Tortoise 连接关闭失败: {str(e)}") def create_app() -> FastAPI: app = FastAPI( title=settings.APP_TITLE, description=settings.APP_DESCRIPTION, version=settings.VERSION, openapi_url="/openapi.json", middleware=make_middlewares(), lifespan=lifespan, ) static_dir = os.path.join(settings.BASE_DIR, 'web', 'public', 'resource') if os.path.exists(static_dir): app.mount("/resource", StaticFiles(directory=static_dir), name="resource") else: logger.warning(f"⚠️ 静态文件目录不存在: {static_dir}") register_exceptions(app) register_routers(app, prefix="/api") return app app = create_app()