feat(core): 初始化 Key-IP Sentinel 服务与部署骨架
- 搭建 FastAPI、Redis、PostgreSQL、Nginx 与 Docker Compose 基础结构 - 实现反向代理、首用绑定、拦截告警、归档任务和管理接口 - 提供 Vue3 管理后台初版,以及 uv/requirements 双依赖配置
This commit is contained in:
77
app/api/settings.py
Normal file
77
app/api/settings.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from pydantic import BaseModel, Field
|
||||
from redis.asyncio import Redis
|
||||
|
||||
from app.config import RUNTIME_SETTINGS_REDIS_KEY, RuntimeSettings, Settings
|
||||
from app.core.ip_utils import extract_client_ip
|
||||
from app.dependencies import get_redis, get_runtime_settings, get_settings, require_admin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/admin/api/settings", tags=["settings"], dependencies=[Depends(require_admin)])
|
||||
|
||||
|
||||
class SettingsResponse(BaseModel):
|
||||
alert_webhook_url: str | None = None
|
||||
alert_threshold_count: int = Field(ge=1)
|
||||
alert_threshold_seconds: int = Field(ge=1)
|
||||
archive_days: int = Field(ge=1)
|
||||
failsafe_mode: Literal["open", "closed"]
|
||||
|
||||
|
||||
class SettingsUpdateRequest(SettingsResponse):
|
||||
pass
|
||||
|
||||
|
||||
def serialize_runtime_settings(runtime_settings: RuntimeSettings) -> dict[str, str]:
|
||||
return {
|
||||
"alert_webhook_url": runtime_settings.alert_webhook_url or "",
|
||||
"alert_threshold_count": str(runtime_settings.alert_threshold_count),
|
||||
"alert_threshold_seconds": str(runtime_settings.alert_threshold_seconds),
|
||||
"archive_days": str(runtime_settings.archive_days),
|
||||
"failsafe_mode": runtime_settings.failsafe_mode,
|
||||
}
|
||||
|
||||
|
||||
@router.get("", response_model=SettingsResponse)
|
||||
async def get_runtime_config(
|
||||
runtime_settings: RuntimeSettings = Depends(get_runtime_settings),
|
||||
) -> SettingsResponse:
|
||||
return SettingsResponse(**runtime_settings.model_dump())
|
||||
|
||||
|
||||
@router.put("", response_model=SettingsResponse)
|
||||
async def update_runtime_config(
|
||||
payload: SettingsUpdateRequest,
|
||||
request: Request,
|
||||
settings: Settings = Depends(get_settings),
|
||||
redis: Redis | None = Depends(get_redis),
|
||||
):
|
||||
if redis is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Settings persistence is unavailable because Redis is offline.",
|
||||
)
|
||||
|
||||
updated = RuntimeSettings(**payload.model_dump())
|
||||
try:
|
||||
await redis.hset(RUNTIME_SETTINGS_REDIS_KEY, mapping=serialize_runtime_settings(updated))
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Failed to persist runtime settings.",
|
||||
) from exc
|
||||
|
||||
async with request.app.state.runtime_settings_lock:
|
||||
request.app.state.runtime_settings = updated
|
||||
|
||||
logger.info(
|
||||
"Runtime settings updated.",
|
||||
extra={"client_ip": extract_client_ip(request, settings)},
|
||||
)
|
||||
return SettingsResponse(**updated.model_dump())
|
||||
Reference in New Issue
Block a user