第一次提交
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""应用配置:从环境变量 / .env 读取。"""
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env", env_file_encoding="utf-8", extra="ignore"
|
||||
)
|
||||
|
||||
# 基础
|
||||
app_name: str = "item-bank"
|
||||
debug: bool = False
|
||||
|
||||
# 数据库:SQLite(aiosqlite 异步驱动)
|
||||
# 生产环境(k3s)里指向 PVC 挂载路径,如 /data/item_bank.db
|
||||
database_url: str = "sqlite+aiosqlite:///./data/item_bank.db"
|
||||
|
||||
# 认证
|
||||
jwt_secret: str = "dev-only-change-me" # 生产由 k8s Secret 注入
|
||||
jwt_algorithm: str = "HS256"
|
||||
access_token_expire_minutes: int = 60 * 24 # 24h
|
||||
|
||||
# 用户 AI 配置加密密钥(Fernet),生产由 Secret 注入
|
||||
# 生成:python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
||||
fernet_key: str = ""
|
||||
|
||||
# 对象存储(S3 兼容),存上传图片
|
||||
s3_endpoint_url: str | None = None # 如 https://s3.amazonaws.com 或自建 endpoint
|
||||
s3_region: str | None = None
|
||||
s3_access_key: str | None = None
|
||||
s3_secret_key: str | None = None
|
||||
s3_bucket: str | None = None
|
||||
|
||||
# 上传限制
|
||||
max_upload_bytes: int = 10 * 1024 * 1024 # 10 MB
|
||||
allowed_image_mimes: tuple[str, ...] = (
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
)
|
||||
|
||||
# OCR 服务(Pix2Text 独立微服务,可选;未配置则只走多模态 VLM)
|
||||
ocr_service_url: str | None = None
|
||||
|
||||
# CORS:允许的前端来源(逗号分隔),生产填实际域名
|
||||
cors_origins: str = "http://localhost:5173"
|
||||
|
||||
# 限流(slowapi 语法,如 "5/minute")。测试时可用 RATE_LIMIT_ENABLED=false 关掉
|
||||
rate_limit_enabled: bool = True
|
||||
rate_limit_auth: str = "10/minute" # 登录/注册:防爆破,按 IP
|
||||
rate_limit_ai: str = "20/minute" # AI 端点:防刷额度,按用户
|
||||
rate_limit_upload: str = "30/minute" # 图片上传
|
||||
|
||||
@property
|
||||
def cors_origin_list(self) -> list[str]:
|
||||
return [o.strip() for o in self.cors_origins.split(",") if o.strip()]
|
||||
|
||||
def check_production_secrets(self) -> list[str]:
|
||||
"""返回生产环境下的配置问题清单(debug 模式不检查)。
|
||||
|
||||
这个服务会暴露到公网,弱密钥或忘改的默认值是实际风险,
|
||||
启动时报出来比事后发现好。
|
||||
"""
|
||||
problems: list[str] = []
|
||||
if self.debug:
|
||||
return problems
|
||||
if self.jwt_secret == "dev-only-change-me":
|
||||
problems.append("JWT_SECRET 仍是默认值,必须改为强随机值")
|
||||
elif len(self.jwt_secret.encode()) < 32:
|
||||
# HS256 的密钥短于 32 字节会降低签名强度(RFC 7518 §3.2)
|
||||
problems.append("JWT_SECRET 少于 32 字节,请用更长的随机值")
|
||||
if not self.fernet_key:
|
||||
problems.append("FERNET_KEY 未配置,用户无法保存 AI api_key")
|
||||
if "*" in self.cors_origins:
|
||||
problems.append("CORS_ORIGINS 不应含通配符")
|
||||
return problems
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
Reference in New Issue
Block a user