91 lines
2.9 KiB
Python
91 lines
2.9 KiB
Python
"""全局配置:pydantic-settings,全部来自环境变量 / .env。
|
||
|
||
规则:
|
||
- 密钥不得硬编码;SECRET_KEY 缺失或仍为模板值时,生产环境拒绝启动。
|
||
- DATABASE_URL 支持 MySQL / SQLite。
|
||
"""
|
||
|
||
from functools import lru_cache
|
||
from pathlib import Path
|
||
|
||
from pydantic import Field
|
||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||
|
||
_TEMPLATE_MARKERS = ("change-me",)
|
||
|
||
|
||
class Settings(BaseSettings):
|
||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||
|
||
# --- 基础 ---
|
||
environment: str = "local" # local / production
|
||
secret_key: str = Field(min_length=16)
|
||
# Cookie Secure 标记:默认跟随 environment(production→Secure)。
|
||
# 站点还是 HTTP 时必须显式设为 false,否则浏览器丢弃 Cookie 导致登录后立即被踢回。
|
||
cookie_secure: bool | None = None
|
||
|
||
# --- 数据库 ---
|
||
database_url: str = "mysql+pymysql://admin:Lzcc6-01@47.109.98.44:33306/amb_rag?charset=utf8mb4"
|
||
|
||
# --- 文件存储 ---
|
||
storage_root: str = "./data"
|
||
|
||
# --- 套餐 ---
|
||
default_storage_quota: int = 104_857_600 # 100 MB
|
||
default_max_file_size: int = 20_971_520 # 20 MB
|
||
|
||
# --- 限流 ---
|
||
rate_limit_per_token_per_min: int = 60
|
||
rate_limit_per_ip_per_min: int = 30
|
||
|
||
# --- 数据保留 ---
|
||
# 访问日志保留天数,超过自动清理(0 = 永久保留,不推荐)
|
||
access_log_retention_days: int = 90
|
||
# 回收站保留天数,超期自动彻底删除(含物理文件)
|
||
recycle_bin_retention_days: int = 3
|
||
|
||
# --- CORS ---
|
||
frontend_origin: str = "http://localhost:5173"
|
||
|
||
@property
|
||
def is_production(self) -> bool:
|
||
return self.environment == "production"
|
||
|
||
@property
|
||
def use_secure_cookie(self) -> bool:
|
||
"""HTTPS 站点才应启用 Secure Cookie;未显式配置时跟随 environment。"""
|
||
if self.cookie_secure is not None:
|
||
return self.cookie_secure
|
||
return self.is_production
|
||
|
||
@property
|
||
def is_mysql(self) -> bool:
|
||
return "mysql" in self.database_url
|
||
|
||
@property
|
||
def is_sqlite(self) -> bool:
|
||
return "sqlite" in self.database_url
|
||
|
||
@property
|
||
def storage_root_path(self) -> Path:
|
||
return Path(self.storage_root).resolve()
|
||
|
||
def validate_secrets(self) -> None:
|
||
"""生产环境禁止携带模板密钥启动。"""
|
||
if not self.is_production:
|
||
return
|
||
for field_name in ("secret_key",):
|
||
value = getattr(self, field_name).lower()
|
||
if any(marker in value for marker in _TEMPLATE_MARKERS):
|
||
raise RuntimeError(
|
||
f"配置错误:{field_name} 仍为模板值,生产环境禁止启动。"
|
||
"请运行: python -c \"import secrets; print(secrets.token_urlsafe(48))\""
|
||
)
|
||
|
||
|
||
@lru_cache
|
||
def get_settings() -> Settings:
|
||
s = Settings() # type: ignore[call-arg]
|
||
s.validate_secrets()
|
||
return s
|