feat(phase1): core/ 基础设施迁移,旧路径改垫片

- core/: database/redis_client/jwt/password/response/exceptions/security/logger/deps/middleware/app
- create_app 迁移到 core/app.py,中间件注册顺序与异常处理器原样保留
- extensions/ utils/ apps/ 旧文件全部改为 re-export 垫片,import 路径保持兼容
- 新增 core 单元测试(jwt/password/response/request 共 12 个)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-07 10:02:01 +08:00
co-authored by Claude
parent fe1d191e55
commit db4ee85db5
22 changed files with 1476 additions and 364 deletions
+79
View File
@@ -0,0 +1,79 @@
"""
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 apps.services.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)