Files
fastapi-best-architecture/backend/core/registrar.py
T
Dylan 16bcaf963d Add distributed deployment support for snowflake ID (#927)
* feat: Add support distributed deployment for Snowflake

* Update the algorithm implementation

* Remove duplicate codes and update error messages
2025-11-19 12:48:24 +08:00

221 lines
6.1 KiB
Python

import os
from asyncio import create_task
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
import socketio
from fastapi import Depends, FastAPI
from fastapi_limiter import FastAPILimiter
from fastapi_pagination import add_pagination
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.middleware.cors import CORSMiddleware
from starlette.staticfiles import StaticFiles
from starlette.types import ASGIApp
from starlette_context.middleware import ContextMiddleware
from starlette_context.plugins import RequestIdPlugin
from backend import __version__
from backend.common.exception.exception_handler import register_exception
from backend.common.log import set_custom_logfile, setup_logging
from backend.common.response.response_code import StandardResponseCode
from backend.core.conf import settings
from backend.core.path_conf import STATIC_DIR, UPLOAD_DIR
from backend.database.db import create_tables
from backend.database.redis import redis_client
from backend.middleware.access_middleware import AccessMiddleware
from backend.middleware.i18n_middleware import I18nMiddleware
from backend.middleware.jwt_auth_middleware import JwtAuthMiddleware
from backend.middleware.opera_log_middleware import OperaLogMiddleware
from backend.middleware.state_middleware import StateMiddleware
from backend.plugin.tools import build_final_router
from backend.utils.demo_site import demo_site
from backend.utils.health_check import ensure_unique_route_names, http_limit_callback
from backend.utils.openapi import simplify_operation_ids
from backend.utils.serializers import MsgSpecJSONResponse
from backend.utils.snowflake import snowflake
@asynccontextmanager
async def register_init(app: FastAPI) -> AsyncGenerator[None, None]:
"""
启动初始化
:param app: FastAPI 应用实例
:return:
"""
# 创建数据库表
await create_tables()
# 初始化 redis
await redis_client.open()
# 初始化 limiter
await FastAPILimiter.init(
redis=redis_client,
prefix=settings.REQUEST_LIMITER_REDIS_PREFIX,
http_callback=http_limit_callback,
)
# 初始化 snowflake 节点
await snowflake.init()
# 创建操作日志任务
create_task(OperaLogMiddleware.consumer())
yield
# 释放 snowflake 节点
await snowflake.shutdown()
# 关闭 redis 连接
await redis_client.aclose()
def register_app() -> FastAPI:
"""注册 FastAPI 应用"""
class MyFastAPI(FastAPI):
if settings.MIDDLEWARE_CORS:
# Related issues
# https://github.com/fastapi/fastapi/discussions/7847
# https://github.com/fastapi/fastapi/discussions/8027
def build_middleware_stack(self) -> ASGIApp:
return CORSMiddleware(
super().build_middleware_stack(),
allow_origins=settings.CORS_ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
expose_headers=settings.CORS_EXPOSE_HEADERS,
)
app = MyFastAPI(
title=settings.FASTAPI_TITLE,
version=__version__,
description=settings.FASTAPI_DESCRIPTION,
docs_url=settings.FASTAPI_DOCS_URL,
redoc_url=settings.FASTAPI_REDOC_URL,
openapi_url=settings.FASTAPI_OPENAPI_URL,
default_response_class=MsgSpecJSONResponse,
lifespan=register_init,
)
# 注册组件
register_logger()
register_socket_app(app)
register_static_file(app)
register_middleware(app)
register_router(app)
register_page(app)
register_exception(app)
return app
def register_logger() -> None:
"""注册日志"""
setup_logging()
set_custom_logfile()
def register_static_file(app: FastAPI) -> None:
"""
注册静态资源服务
:param app: FastAPI 应用实例
:return:
"""
# 上传静态资源
if not os.path.exists(UPLOAD_DIR):
os.makedirs(UPLOAD_DIR)
app.mount('/static/upload', StaticFiles(directory=UPLOAD_DIR), name='upload')
# 固有静态资源
if settings.FASTAPI_STATIC_FILES:
app.mount('/static', StaticFiles(directory=STATIC_DIR), name='static')
def register_middleware(app: FastAPI) -> None:
"""
注册中间件(执行顺序从下往上)
:param app: FastAPI 应用实例
:return:
"""
# Opera log
app.add_middleware(OperaLogMiddleware)
# State
app.add_middleware(StateMiddleware)
# JWT auth
app.add_middleware(
AuthenticationMiddleware,
backend=JwtAuthMiddleware(),
on_error=JwtAuthMiddleware.auth_exception_handler,
)
# I18n
app.add_middleware(I18nMiddleware)
# Access log
app.add_middleware(AccessMiddleware)
# ContextVar
app.add_middleware(
ContextMiddleware,
plugins=[RequestIdPlugin(validate=True)],
default_error_response=MsgSpecJSONResponse(
content={'code': StandardResponseCode.HTTP_400, 'msg': 'BAD_REQUEST', 'data': None},
status_code=StandardResponseCode.HTTP_400,
),
)
def register_router(app: FastAPI) -> None:
"""
注册路由
:param app: FastAPI 应用实例
:return:
"""
dependencies = [Depends(demo_site)] if settings.DEMO_MODE else None
# API
router = build_final_router()
app.include_router(router, dependencies=dependencies)
# Extra
ensure_unique_route_names(app)
simplify_operation_ids(app)
def register_page(app: FastAPI) -> None:
"""
注册分页查询功能
:param app: FastAPI 应用实例
:return:
"""
add_pagination(app)
def register_socket_app(app: FastAPI) -> None:
"""
注册 Socket.IO 应用
:param app: FastAPI 应用实例
:return:
"""
from backend.common.socketio.server import sio
socket_app = socketio.ASGIApp(
socketio_server=sio,
other_asgi_app=app,
# 切勿删除此配置:https://github.com/pyropy/fastapi-socketio/issues/51
socketio_path='/ws/socket.io',
)
app.mount('/ws', socket_app)