feat(core): 初始化 Key-IP Sentinel 服务与部署骨架
- 搭建 FastAPI、Redis、PostgreSQL、Nginx 与 Docker Compose 基础结构 - 实现反向代理、首用绑定、拦截告警、归档任务和管理接口 - 提供 Vue3 管理后台初版,以及 uv/requirements 双依赖配置
This commit is contained in:
98
app/config.py
Normal file
98
app/config.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
from ipaddress import ip_network
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
RUNTIME_SETTINGS_REDIS_KEY = "sentinel:settings"
|
||||
|
||||
|
||||
class RuntimeSettings(BaseModel):
|
||||
alert_webhook_url: str | None = None
|
||||
alert_threshold_count: int = Field(default=5, ge=1)
|
||||
alert_threshold_seconds: int = Field(default=300, ge=1)
|
||||
archive_days: int = Field(default=90, ge=1)
|
||||
failsafe_mode: Literal["open", "closed"] = "closed"
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
extra="ignore",
|
||||
case_sensitive=False,
|
||||
)
|
||||
|
||||
downstream_url: str = Field(alias="DOWNSTREAM_URL")
|
||||
redis_addr: str = Field(alias="REDIS_ADDR")
|
||||
redis_password: str = Field(default="", alias="REDIS_PASSWORD")
|
||||
pg_dsn: str = Field(alias="PG_DSN")
|
||||
sentinel_hmac_secret: str = Field(alias="SENTINEL_HMAC_SECRET", min_length=32)
|
||||
admin_password: str = Field(alias="ADMIN_PASSWORD", min_length=8)
|
||||
admin_jwt_secret: str = Field(alias="ADMIN_JWT_SECRET", min_length=16)
|
||||
trusted_proxy_ips: tuple[str, ...] = Field(default_factory=tuple, alias="TRUSTED_PROXY_IPS")
|
||||
sentinel_failsafe_mode: Literal["open", "closed"] = Field(
|
||||
default="closed",
|
||||
alias="SENTINEL_FAILSAFE_MODE",
|
||||
)
|
||||
app_port: int = Field(default=7000, alias="APP_PORT")
|
||||
alert_webhook_url: str | None = Field(default=None, alias="ALERT_WEBHOOK_URL")
|
||||
alert_threshold_count: int = Field(default=5, alias="ALERT_THRESHOLD_COUNT", ge=1)
|
||||
alert_threshold_seconds: int = Field(default=300, alias="ALERT_THRESHOLD_SECONDS", ge=1)
|
||||
archive_days: int = Field(default=90, alias="ARCHIVE_DAYS", ge=1)
|
||||
|
||||
redis_binding_ttl_seconds: int = 604800
|
||||
downstream_max_connections: int = 512
|
||||
downstream_max_keepalive_connections: int = 128
|
||||
last_used_flush_interval_seconds: int = 5
|
||||
last_used_queue_size: int = 10000
|
||||
login_lockout_threshold: int = 5
|
||||
login_lockout_seconds: int = 900
|
||||
admin_jwt_expire_hours: int = 8
|
||||
archive_job_interval_minutes: int = 60
|
||||
archive_batch_size: int = 500
|
||||
metrics_ttl_days: int = 30
|
||||
webhook_timeout_seconds: int = 5
|
||||
|
||||
@field_validator("downstream_url")
|
||||
@classmethod
|
||||
def normalize_downstream_url(cls, value: str) -> str:
|
||||
return value.rstrip("/")
|
||||
|
||||
@field_validator("trusted_proxy_ips", mode="before")
|
||||
@classmethod
|
||||
def split_proxy_ips(cls, value: object) -> tuple[str, ...]:
|
||||
if value is None:
|
||||
return tuple()
|
||||
if isinstance(value, str):
|
||||
parts = [item.strip() for item in value.split(",")]
|
||||
return tuple(item for item in parts if item)
|
||||
if isinstance(value, (list, tuple, set)):
|
||||
return tuple(str(item).strip() for item in value if str(item).strip())
|
||||
return (str(value).strip(),)
|
||||
|
||||
@cached_property
|
||||
def trusted_proxy_networks(self):
|
||||
return tuple(ip_network(item, strict=False) for item in self.trusted_proxy_ips)
|
||||
|
||||
def build_runtime_settings(self) -> RuntimeSettings:
|
||||
return RuntimeSettings(
|
||||
alert_webhook_url=self.alert_webhook_url or None,
|
||||
alert_threshold_count=self.alert_threshold_count,
|
||||
alert_threshold_seconds=self.alert_threshold_seconds,
|
||||
archive_days=self.archive_days,
|
||||
failsafe_mode=self.sentinel_failsafe_mode,
|
||||
)
|
||||
|
||||
|
||||
_settings: Settings | None = None
|
||||
|
||||
|
||||
def get_settings() -> Settings:
|
||||
global _settings
|
||||
if _settings is None:
|
||||
_settings = Settings()
|
||||
return _settings
|
||||
Reference in New Issue
Block a user