init_app.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. # -*- coding: utf-8 -*-
  2. from starlette.responses import HTMLResponse
  3. from typing import Any, AsyncGenerator
  4. from fastapi import Depends, FastAPI, Request, Response
  5. from fastapi.staticfiles import StaticFiles
  6. from fastapi.concurrency import asynccontextmanager
  7. from fastapi.openapi.docs import (
  8. get_redoc_html,
  9. get_swagger_ui_html,
  10. get_swagger_ui_oauth2_redirect_html
  11. )
  12. from fastapi_limiter import FastAPILimiter
  13. from fastapi_limiter.depends import RateLimiter
  14. from math import ceil
  15. from app.api.v1.module_business.vardict.service import BizVarDictService
  16. from app.config.setting import settings
  17. from app.core.logger import log
  18. from app.core.discover import router
  19. from app.core.exceptions import CustomException, handle_exception
  20. from app.utils.common_util import import_module, import_modules_async
  21. from app.scripts.initialize import InitializeData
  22. from app.api.v1.module_application.job.tools.ap_scheduler import SchedulerUtil
  23. from app.api.v1.module_system.params.service import ParamsService
  24. from app.api.v1.module_system.dict.service import DictDataService
  25. from app.api.v1.module_business.crane.service import BizCraneService
  26. @asynccontextmanager
  27. async def lifespan(app: FastAPI) -> AsyncGenerator[Any, Any]:
  28. """
  29. 自定义 FastAPI 应用生命周期。
  30. 参数:
  31. - app (FastAPI): FastAPI 应用实例。
  32. 返回:
  33. - AsyncGenerator[Any, Any]: 生命周期上下文生成器。
  34. """
  35. try:
  36. await InitializeData().init_db()
  37. log.info(f"✅ {settings.DATABASE_TYPE}数据库初始化完成")
  38. await import_modules_async(modules=settings.EVENT_LIST, desc="全局事件", app=app, status=True)
  39. log.info("✅ 全局事件模块加载完成")
  40. await ParamsService().init_config_service(redis=app.state.redis)
  41. log.info("✅ Redis系统配置初始化完成")
  42. await DictDataService().init_dict_service(redis=app.state.redis)
  43. log.info("✅ Redis数据字典初始化完成")
  44. await BizVarDictService().init_vardict_group_service(redis=app.state.redis)
  45. log.info("✅ Redis天车数据初始化完成")
  46. await SchedulerUtil.init_system_scheduler()
  47. scheduler_jobs_count = len(SchedulerUtil.get_all_jobs())
  48. scheduler_status = SchedulerUtil.get_job_status()
  49. log.info(f"✅ 定时任务调度器初始化完成 ({scheduler_jobs_count} 个任务)")
  50. # 6. 初始化请求限制器
  51. await FastAPILimiter.init(
  52. redis=app.state.redis,
  53. prefix=settings.REQUEST_LIMITER_REDIS_PREFIX,
  54. http_callback=http_limit_callback,
  55. )
  56. log.info("✅ 请求限制器初始化完成")
  57. # 导入并显示最终的启动信息面板
  58. from app.utils.console import run as console_run
  59. from app.common.enums import EnvironmentEnum
  60. console_run(
  61. host=settings.SERVER_HOST,
  62. port=settings.SERVER_PORT,
  63. reload=True if settings.ENVIRONMENT == EnvironmentEnum.DEV else False,
  64. redis_ready=True,
  65. scheduler_jobs=scheduler_jobs_count,
  66. scheduler_status=scheduler_status,
  67. )
  68. except Exception as e:
  69. log.error(f"❌ 应用初始化失败: {str(e)}")
  70. raise
  71. yield
  72. try:
  73. await import_modules_async(modules=settings.EVENT_LIST, desc="全局事件", app=app, status=False)
  74. log.info("✅ 全局事件模块卸载完成")
  75. await SchedulerUtil.close_system_scheduler()
  76. log.info("✅ 定时任务调度器已关闭")
  77. await FastAPILimiter.close()
  78. log.info("✅ 请求限制器已关闭")
  79. except Exception as e:
  80. log.error(f"❌ 应用关闭过程中发生错误: {str(e)}")
  81. def register_middlewares(app: FastAPI) -> None:
  82. """
  83. 注册全局中间件。
  84. 参数:
  85. - app (FastAPI): FastAPI 应用实例。
  86. 返回:
  87. - None
  88. """
  89. for middleware in settings.MIDDLEWARE_LIST[::-1]:
  90. if not middleware:
  91. continue
  92. middleware = import_module(middleware, desc="中间件")
  93. app.add_middleware(middleware)
  94. def register_exceptions(app: FastAPI) -> None:
  95. """
  96. 统一注册异常处理器。
  97. 参数:
  98. - app (FastAPI): FastAPI 应用实例。
  99. 返回:
  100. - None
  101. """
  102. handle_exception(app)
  103. def register_routers(app: FastAPI) -> None:
  104. """
  105. 注册根路由。
  106. 参数:
  107. - app (FastAPI): FastAPI 应用实例。
  108. 返回:
  109. - None
  110. """
  111. app.include_router(router=router, dependencies=[Depends(RateLimiter(times=5, seconds=10))])
  112. def register_files(app: FastAPI) -> None:
  113. """
  114. 注册静态资源挂载和文件相关配置。
  115. 参数:
  116. - app (FastAPI): FastAPI 应用实例。
  117. 返回:
  118. - None
  119. """
  120. # 挂载静态文件目录
  121. if settings.STATIC_ENABLE:
  122. # 确保日志目录存在
  123. settings.STATIC_ROOT.mkdir(parents=True, exist_ok=True)
  124. app.mount(path=settings.STATIC_URL, app=StaticFiles(directory=settings.STATIC_ROOT), name=settings.STATIC_DIR)
  125. def reset_api_docs(app: FastAPI) -> None:
  126. """
  127. 使用本地静态资源自定义 API 文档页面(Swagger UI 与 ReDoc)。
  128. 参数:
  129. - app (FastAPI): FastAPI 应用实例。
  130. 返回:
  131. - None
  132. """
  133. @app.get(settings.DOCS_URL, include_in_schema=False)
  134. async def custom_swagger_ui_html() -> HTMLResponse:
  135. return get_swagger_ui_html(
  136. openapi_url=str(app.root_path) + str(app.openapi_url),
  137. title=app.title + " - Swagger UI",
  138. oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
  139. swagger_js_url=settings.SWAGGER_JS_URL,
  140. swagger_css_url=settings.SWAGGER_CSS_URL,
  141. swagger_favicon_url=settings.FAVICON_URL,
  142. )
  143. @app.get(str(app.swagger_ui_oauth2_redirect_url), include_in_schema=False)
  144. async def swagger_ui_redirect():
  145. return get_swagger_ui_oauth2_redirect_html()
  146. @app.get(settings.REDOC_URL, include_in_schema=False)
  147. async def custom_redoc_html():
  148. return get_redoc_html(
  149. openapi_url=str(app.root_path) + str(app.openapi_url),
  150. title=app.title + " - ReDoc",
  151. redoc_js_url=settings.REDOC_JS_URL,
  152. redoc_favicon_url=settings.FAVICON_URL,
  153. )
  154. async def http_limit_callback(request: Request, response: Response, expire: int):
  155. """
  156. 请求限制时的默认回调函数
  157. :param request: FastAPI 请求对象
  158. :param response: FastAPI 响应对象
  159. :param expire: 剩余毫秒数
  160. :return:
  161. """
  162. expires = ceil(expire / 30)
  163. raise CustomException(
  164. status_code=429,
  165. msg='请求过于频繁,请稍后重试',
  166. data={'Retry-After': str(expires)},
  167. )