checkpoint: require_redis 显式 503 守卫——存储类接口(AI配置/在线/缓存/验证码)接线

- dependencies: require_redis 改标准 FastAPI 依赖(Depends(redis_getter)),Redis 不可用显式 503 而非 500/静默空数据
- AI配置 5 端点 / 在线监控 5 端点 / 缓存监控 6 端点切到 require_redis
- 免认证验证码端点(captcha/get、slider/complete)也接线:Redis 挂时返回 503 而非 500
- login/token/oauth 保留 redis_getter(服务层内联 503 守卫语义不变)
This commit is contained in:
34047007@qq.com
2026-08-07 21:47:47 +08:00
parent 0b50e0adfa
commit c1fa752d9b
5 changed files with 27 additions and 27 deletions
@@ -9,7 +9,7 @@ from redis.asyncio import Redis
from app.common.response import ResponseSchema, SuccessResponse from app.common.response import ResponseSchema, SuccessResponse
from app.core.base_schema import AuthSchema, PaginationQueryParam from app.core.base_schema import AuthSchema, PaginationQueryParam
from app.core.database import async_db_session from app.core.database import async_db_session
from app.core.dependencies import AuthPermission, _authenticate, redis_getter from app.core.dependencies import AuthPermission, _authenticate, require_redis
from app.core.exceptions import CustomException from app.core.exceptions import CustomException
from app.core.logger import logger from app.core.logger import logger
from app.core.router_class import OperationLogRoute from app.core.router_class import OperationLogRoute
@@ -110,7 +110,7 @@ async def ai_chat_controller(
@ChatRouter.get("/model", summary="获取 AI 模型配置列表", response_model=ResponseSchema[AiModelConfigListResponse]) @ChatRouter.get("/model", summary="获取 AI 模型配置列表", response_model=ResponseSchema[AiModelConfigListResponse])
async def list_model_config_controller( async def list_model_config_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:query"]))],
) -> JSONResponse: ) -> JSONResponse:
service = AiModelConfigService(auth, redis) service = AiModelConfigService(auth, redis)
@@ -120,7 +120,7 @@ async def list_model_config_controller(
@ChatRouter.post("/model", status_code=status.HTTP_201_CREATED, summary="新增一个 AI 模型配置", response_model=ResponseSchema[dict[str, Any]]) @ChatRouter.post("/model", status_code=status.HTTP_201_CREATED, summary="新增一个 AI 模型配置", response_model=ResponseSchema[dict[str, Any]])
async def create_model_config_controller( async def create_model_config_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:update"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:update"]))],
data: Annotated[AiModelConfigUpdateSchema, Body(description="模型配置参数")], data: Annotated[AiModelConfigUpdateSchema, Body(description="模型配置参数")],
) -> JSONResponse: ) -> JSONResponse:
@@ -132,7 +132,7 @@ async def create_model_config_controller(
@ChatRouter.put("/model/{config_id}", summary="更新指定 ID 的 AI 模型配置", response_model=ResponseSchema[dict[str, Any]]) @ChatRouter.put("/model/{config_id}", summary="更新指定 ID 的 AI 模型配置", response_model=ResponseSchema[dict[str, Any]])
async def update_model_config_controller( async def update_model_config_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:update"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:update"]))],
config_id: Annotated[str, Path(description="配置项 ID")], config_id: Annotated[str, Path(description="配置项 ID")],
data: Annotated[AiModelConfigUpdateSchema, Body(description="模型配置参数")], data: Annotated[AiModelConfigUpdateSchema, Body(description="模型配置参数")],
@@ -145,7 +145,7 @@ async def update_model_config_controller(
@ChatRouter.delete("/model/{config_id}", summary="删除指定 ID 的 AI 模型配置", response_model=ResponseSchema[None]) @ChatRouter.delete("/model/{config_id}", summary="删除指定 ID 的 AI 模型配置", response_model=ResponseSchema[None])
async def delete_model_config_controller( async def delete_model_config_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:update"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:update"]))],
config_id: Annotated[str, Path(description="配置项 ID")], config_id: Annotated[str, Path(description="配置项 ID")],
) -> JSONResponse: ) -> JSONResponse:
@@ -156,7 +156,7 @@ async def delete_model_config_controller(
@ChatRouter.post("/model/{config_id}/activate", summary="切换激活的 AI 模型配置", response_model=ResponseSchema[None]) @ChatRouter.post("/model/{config_id}/activate", summary="切换激活的 AI 模型配置", response_model=ResponseSchema[None])
async def activate_model_config_controller( async def activate_model_config_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:update"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:update"]))],
config_id: Annotated[str, Path(description="配置项 ID;传 __default__ 使用系统默认")], config_id: Annotated[str, Path(description="配置项 ID;传 __default__ 使用系统默认")],
) -> JSONResponse: ) -> JSONResponse:
+7 -7
View File
@@ -6,7 +6,7 @@ from redis.asyncio.client import Redis
from app.api.v1.module_monitor.cache.schema import CacheInfoSchema, CacheMonitorSchema from app.api.v1.module_monitor.cache.schema import CacheInfoSchema, CacheMonitorSchema
from app.common.response import ResponseSchema, SuccessResponse from app.common.response import ResponseSchema, SuccessResponse
from app.core.dependencies import AuthPermission, redis_getter from app.core.dependencies import AuthPermission, require_redis
from app.core.router_class import OperationLogRoute from app.core.router_class import OperationLogRoute
from .service import CacheService from .service import CacheService
@@ -16,7 +16,7 @@ CacheRouter = APIRouter(route_class=OperationLogRoute, prefix="/cache", tags=["
@CacheRouter.get("/info", summary="获取缓存监控信息", response_model=ResponseSchema[CacheMonitorSchema], dependencies=[Security(AuthPermission(["module_monitor:cache:query"]))]) @CacheRouter.get("/info", summary="获取缓存监控信息", response_model=ResponseSchema[CacheMonitorSchema], dependencies=[Security(AuthPermission(["module_monitor:cache:query"]))])
async def get_monitor_cache_info_controller( async def get_monitor_cache_info_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
) -> JSONResponse: ) -> JSONResponse:
result = await CacheService.get_monitor_statistical_info(redis=redis) result = await CacheService.get_monitor_statistical_info(redis=redis)
return SuccessResponse(data=result, msg="获取缓存监控信息成功") return SuccessResponse(data=result, msg="获取缓存监控信息成功")
@@ -29,7 +29,7 @@ async def get_monitor_cache_name_controller() -> JSONResponse:
@CacheRouter.get("/get/keys/{cache_name}", summary="获取缓存键名列表", response_model=ResponseSchema[list[CacheInfoSchema]], dependencies=[Security(AuthPermission(["module_monitor:cache:query"]))]) @CacheRouter.get("/get/keys/{cache_name}", summary="获取缓存键名列表", response_model=ResponseSchema[list[CacheInfoSchema]], dependencies=[Security(AuthPermission(["module_monitor:cache:query"]))])
async def get_monitor_cache_key_controller(cache_name: Annotated[str, Path(description="缓存名称")], redis: Annotated[Redis, Depends(redis_getter)]) -> JSONResponse: async def get_monitor_cache_key_controller(cache_name: Annotated[str, Path(description="缓存名称")], redis: Annotated[Redis, Depends(require_redis)]) -> JSONResponse:
result = await CacheService.get_monitor_cache_keys(redis=redis, cache_name=cache_name) result = await CacheService.get_monitor_cache_keys(redis=redis, cache_name=cache_name)
return SuccessResponse(data=result, msg=f"获取缓存{cache_name}的键名列表成功") return SuccessResponse(data=result, msg=f"获取缓存{cache_name}的键名列表成功")
@@ -38,27 +38,27 @@ async def get_monitor_cache_key_controller(cache_name: Annotated[str, Path(descr
async def get_monitor_cache_value_controller( async def get_monitor_cache_value_controller(
cache_name: Annotated[str, Path(description="缓存名称")], cache_name: Annotated[str, Path(description="缓存名称")],
cache_key: Annotated[str, Path(description="缓存键名")], cache_key: Annotated[str, Path(description="缓存键名")],
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
) -> JSONResponse: ) -> JSONResponse:
result = await CacheService.get_monitor_cache_value(redis=redis, cache_name=cache_name, cache_key=cache_key) result = await CacheService.get_monitor_cache_value(redis=redis, cache_name=cache_name, cache_key=cache_key)
return SuccessResponse(data=result, msg=f"获取缓存{cache_name}:{cache_key}的值成功") return SuccessResponse(data=result, msg=f"获取缓存{cache_name}:{cache_key}的值成功")
@CacheRouter.delete("/delete/name/{cache_name}", summary="清除指定缓存名称的所有缓存", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:cache:delete"]))]) @CacheRouter.delete("/delete/name/{cache_name}", summary="清除指定缓存名称的所有缓存", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:cache:delete"]))])
async def clear_monitor_cache_name_controller(cache_name: Annotated[str, Path(description="缓存名称")], redis: Annotated[Redis, Depends(redis_getter)]) -> JSONResponse: async def clear_monitor_cache_name_controller(cache_name: Annotated[str, Path(description="缓存名称")], redis: Annotated[Redis, Depends(require_redis)]) -> JSONResponse:
result = await CacheService.clear_monitor_cache_by_name(redis=redis, cache_name=cache_name) result = await CacheService.clear_monitor_cache_by_name(redis=redis, cache_name=cache_name)
return SuccessResponse(msg=f"{cache_name}对应键值清除成功", data=result) return SuccessResponse(msg=f"{cache_name}对应键值清除成功", data=result)
@CacheRouter.delete("/delete/key/{cache_key}", summary="清除指定缓存键", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:cache:delete"]))]) @CacheRouter.delete("/delete/key/{cache_key}", summary="清除指定缓存键", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:cache:delete"]))])
async def clear_monitor_cache_key_controller(cache_key: Annotated[str, Path(description="缓存键名")], redis: Annotated[Redis, Depends(redis_getter)]) -> JSONResponse: async def clear_monitor_cache_key_controller(cache_key: Annotated[str, Path(description="缓存键名")], redis: Annotated[Redis, Depends(require_redis)]) -> JSONResponse:
result = await CacheService.clear_monitor_cache_by_key(redis=redis, cache_key=cache_key) result = await CacheService.clear_monitor_cache_by_key(redis=redis, cache_key=cache_key)
return SuccessResponse(msg=f"{cache_key}清除成功", data=result) return SuccessResponse(msg=f"{cache_key}清除成功", data=result)
@CacheRouter.delete("/clear", summary="清除所有缓存", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:cache:delete"]))]) @CacheRouter.delete("/clear", summary="清除所有缓存", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:cache:delete"]))])
async def clear_monitor_cache_all_controller( async def clear_monitor_cache_all_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
) -> JSONResponse: ) -> JSONResponse:
result = await CacheService.clear_monitor_cache_all(redis=redis) result = await CacheService.clear_monitor_cache_all(redis=redis)
return SuccessResponse(msg="所有缓存清除成功", data=result) return SuccessResponse(msg="所有缓存清除成功", data=result)
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.common.request import PaginationService from app.common.request import PaginationService
from app.common.response import ResponseSchema, SuccessResponse from app.common.response import ResponseSchema, SuccessResponse
from app.core.base_schema import AuthSchema, PaginationQueryParam from app.core.base_schema import AuthSchema, PaginationQueryParam
from app.core.dependencies import AuthPermission, db_getter, get_current_user, redis_getter from app.core.dependencies import AuthPermission, db_getter, get_current_user, require_redis
from app.core.router_class import OperationLogRoute from app.core.router_class import OperationLogRoute
from .schema import DashboardStatsSchema, OnlineOutSchema, OnlineQueryParam from .schema import DashboardStatsSchema, OnlineOutSchema, OnlineQueryParam
@@ -19,7 +19,7 @@ OnlineRouter = APIRouter(route_class=OperationLogRoute, prefix="/online", tags=[
@OnlineRouter.get("/list", summary="获取在线用户列表", response_model=ResponseSchema[list[OnlineOutSchema]], dependencies=[Security(AuthPermission(["module_monitor:online:query"]))]) @OnlineRouter.get("/list", summary="获取在线用户列表", response_model=ResponseSchema[list[OnlineOutSchema]], dependencies=[Security(AuthPermission(["module_monitor:online:query"]))])
async def get_online_list_controller( async def get_online_list_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[OnlineQueryParam, Query()], search: Annotated[OnlineQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
@@ -34,7 +34,7 @@ async def get_online_list_controller(
@OnlineRouter.get("/current", summary="获取当前用户的在线会话", response_model=ResponseSchema[list[OnlineOutSchema]], dependencies=[Depends(get_current_user)]) @OnlineRouter.get("/current", summary="获取当前用户的在线会话", response_model=ResponseSchema[list[OnlineOutSchema]], dependencies=[Depends(get_current_user)])
async def get_current_online_controller( async def get_current_online_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
auth: Annotated[AuthSchema, Depends(get_current_user)], auth: Annotated[AuthSchema, Depends(get_current_user)],
) -> JSONResponse: ) -> JSONResponse:
sessions = await OnlineService.get_current_user_sessions(redis=redis, user_id=auth.user.id) sessions = await OnlineService.get_current_user_sessions(redis=redis, user_id=auth.user.id)
@@ -44,7 +44,7 @@ async def get_current_online_controller(
@OnlineRouter.delete("/delete", summary="强制下线", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:online:delete"]))]) @OnlineRouter.delete("/delete", summary="强制下线", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:online:delete"]))])
async def delete_online_controller( async def delete_online_controller(
session_id: Annotated[str, Body(description="会话编号")], session_id: Annotated[str, Body(description="会话编号")],
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
) -> JSONResponse: ) -> JSONResponse:
await OnlineService.delete_online(redis=redis, session_id=session_id) await OnlineService.delete_online(redis=redis, session_id=session_id)
return SuccessResponse(msg="强制下线成功") return SuccessResponse(msg="强制下线成功")
@@ -52,7 +52,7 @@ async def delete_online_controller(
@OnlineRouter.delete("/clear", summary="清除所有在线用户", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:online:delete"]))]) @OnlineRouter.delete("/clear", summary="清除所有在线用户", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_monitor:online:delete"]))])
async def clear_online_controller( async def clear_online_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
) -> JSONResponse: ) -> JSONResponse:
await OnlineService.clear_online(redis=redis) await OnlineService.clear_online(redis=redis)
return SuccessResponse(msg="清除所有在线用户成功") return SuccessResponse(msg="清除所有在线用户成功")
@@ -61,7 +61,7 @@ async def clear_online_controller(
@OnlineRouter.get("/stats", summary="获取仪表盘统计数据", response_model=ResponseSchema[DashboardStatsSchema]) @OnlineRouter.get("/stats", summary="获取仪表盘统计数据", response_model=ResponseSchema[DashboardStatsSchema])
async def get_dashboard_stats_controller( async def get_dashboard_stats_controller(
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
_auth: Annotated[AuthSchema, Security(AuthPermission(["module_monitor:dashboard:query"]))], _auth: Annotated[AuthSchema, Security(AuthPermission(["module_monitor:dashboard:query"]))],
) -> JSONResponse: ) -> JSONResponse:
data = await OnlineService.get_dashboard_stats(db=db, redis=redis) data = await OnlineService.get_dashboard_stats(db=db, redis=redis)
@@ -30,7 +30,7 @@ from app.common.enums import EnvironmentEnum
from app.common.response import ErrorResponse, RedirectContentResponse, ResponseSchema, SuccessResponse from app.common.response import ErrorResponse, RedirectContentResponse, ResponseSchema, SuccessResponse
from app.config.setting import settings from app.config.setting import settings
from app.core.base_schema import JWTOutSchema from app.core.base_schema import JWTOutSchema
from app.core.dependencies import db_getter, get_current_user, redis_getter from app.core.dependencies import db_getter, get_current_user, redis_getter, require_redis
from app.core.exceptions import CustomException from app.core.exceptions import CustomException
from app.core.logger import logger from app.core.logger import logger
from app.core.redis_crud import RedisCURD from app.core.redis_crud import RedisCURD
@@ -90,7 +90,7 @@ async def get_new_token_controller(
@AuthRouter.get("/captcha/get", summary="获取验证码", response_model=ResponseSchema[CaptchaOutSchema]) @AuthRouter.get("/captcha/get", summary="获取验证码", response_model=ResponseSchema[CaptchaOutSchema])
async def get_captcha_for_login_controller( async def get_captcha_for_login_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
) -> JSONResponse: ) -> JSONResponse:
captcha = await CaptchaService.get_captcha(redis=redis) captcha = await CaptchaService.get_captcha(redis=redis)
return SuccessResponse(data=captcha, msg="获取验证码成功") return SuccessResponse(data=captcha, msg="获取验证码成功")
@@ -112,7 +112,7 @@ async def get_login_default_controller() -> JSONResponse:
@AuthRouter.post("/captcha/slider/complete", summary="滑块验证完成", response_model=ResponseSchema[SliderCompleteOutSchema]) @AuthRouter.post("/captcha/slider/complete", summary="滑块验证完成", response_model=ResponseSchema[SliderCompleteOutSchema])
async def slider_complete_controller( async def slider_complete_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(require_redis)],
body: SliderCompleteSchema, body: SliderCompleteSchema,
) -> JSONResponse: ) -> JSONResponse:
result = await CaptchaService.slider_complete(redis=redis, captcha_key=body.captcha_key) result = await CaptchaService.slider_complete(redis=redis, captcha_key=body.captcha_key)
+5 -5
View File
@@ -1,6 +1,6 @@
import json import json
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from typing import Any from typing import Annotated, Any
from fastapi import Depends, Request from fastapi import Depends, Request
from redis.asyncio.client import Redis from redis.asyncio.client import Redis
@@ -39,12 +39,12 @@ async def redis_getter(request: Request) -> Redis:
return request.app.state.redis return request.app.state.redis
def require_redis(redis: Redis | None) -> Redis: async def require_redis(redis: Annotated[Redis | None, Depends(redis_getter)]) -> Redis:
"""Redis 不可用时显式 503(存储类接口守卫)。 """Redis 不可用时显式 503(存储类接口守卫)。
Redis 是认证会话、AI 模型配置、在线监控调度任务 jobstore 的唯一存储 Redis 是认证会话、AI 模型配置、在线监控存储的唯一存储(调度任务 jobstore 已落 PG
启动降级模式下这些接口无法提供服务,应明确返回 503而非误导性的 存储类接口应依赖本守卫(而非裸 redis_getter):降级模式下明确返回 503
401/500 或静默空数据 而不是 500/401 或静默空数据;免认证接口(如验证码)读 Redis 时也由此兜底
""" """
if redis is None: if redis is None:
raise CustomException( raise CustomException(