- 21 个模块目录(auth/system/dictionary/configuration/cms/member/region/link/notice/job/log/upload/index) - 每个模块 router.py(合并模块导出多个 router)/schemas.py/service.py/repository.py/models.py - routers/v1.py 与 routers/__init__.py 改为直接引用 modules,前缀与权限串原样保留 - core/middleware.py 懒加载指向 modules.system.menu / modules.log;core/redis_client.py 指向 modules.job.scheduler - apps/views|services|forms 全部改为 re-export 垫片 - 迁移后 grep 守门通过:modules/core 无 apps/extensions 残留引用 - 实测登录、27+ 模块接口、写操作 commit、操作日志落库均正常 Co-Authored-By: Claude <noreply@anthropic.com>
80 lines
2.4 KiB
Python
80 lines
2.4 KiB
Python
"""
|
|
Redis客户端核心模块(由 utils/redis_ctx.py 迁移而来)
|
|
|
|
封装 Redis 连接管理和请求级上下文注入:
|
|
- lifespan 中创建连接池,关闭时断开
|
|
- 每个请求通过中间件将 Redis 客户端注入 contextvars
|
|
- 业务代码通过 get_redis() 获取当前请求的 Redis 客户端
|
|
"""
|
|
|
|
import logging
|
|
from contextvars import ContextVar
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from redis.asyncio import Redis
|
|
from redis import asyncio as aioredis
|
|
|
|
from config import REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_INDEX, REDIS_AUTH
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 请求级 Redis 客户端上下文变量
|
|
_redis_ctx: ContextVar[Redis] = ContextVar('_redis_ctx')
|
|
|
|
|
|
def get_redis() -> Redis:
|
|
"""获取当前请求的 Redis 客户端"""
|
|
return _redis_ctx.get()
|
|
|
|
|
|
def _build_redis_url() -> str:
|
|
"""构建 Redis 连接 URL"""
|
|
if REDIS_AUTH and REDIS_PASSWORD:
|
|
return f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_INDEX}"
|
|
return f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_INDEX}"
|
|
|
|
|
|
async def create_redis_pool() -> Redis:
|
|
"""创建 Redis 异步连接池"""
|
|
redis = await aioredis.from_url(
|
|
_build_redis_url(),
|
|
encoding="utf-8",
|
|
decode_responses=True,
|
|
protocol=2
|
|
)
|
|
return redis
|
|
|
|
|
|
@asynccontextmanager
|
|
async def redis_lifespan(app: FastAPI):
|
|
"""Redis 生命周期管理,替代已废弃的 on_event("startup"/"shutdown")"""
|
|
# startup: 创建连接池
|
|
redis = await create_redis_pool()
|
|
await redis.ping()
|
|
app.state.redis = redis
|
|
print(f"Redis 连接成功 --> {REDIS_HOST}:{REDIS_PORT}/{REDIS_INDEX}")
|
|
logger.info(f"Redis 连接成功: {REDIS_HOST}:{REDIS_PORT}/{REDIS_INDEX}")
|
|
|
|
# startup: 初始化定时任务调度器
|
|
from modules.job.scheduler import init_scheduler, shutdown_scheduler
|
|
job_count = init_scheduler()
|
|
logger.info(f"定时任务调度器已启动,加载 {job_count} 个任务")
|
|
|
|
yield
|
|
# shutdown: 关闭调度器
|
|
shutdown_scheduler()
|
|
|
|
# shutdown: 关闭连接
|
|
await redis.aclose()
|
|
app.state.redis = None
|
|
logger.info("Redis 连接已关闭")
|
|
|
|
|
|
async def redis_middleware(request, call_next):
|
|
"""Redis 上下文注入中间件,每个请求开始时将 Redis 客户端注入 contextvars"""
|
|
redis = getattr(request.app.state, 'redis', None)
|
|
if redis is not None:
|
|
_redis_ctx.set(redis)
|
|
return await call_next(request)
|