Files
meijiaka-zy/python-api/app/config.py
T
2026-05-27 15:38:50 +08:00

235 lines
8.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
配置管理 - Pydantic Settings
==========================
所有配置项通过环境变量或 .env 文件注入。
"""
from functools import lru_cache
from typing import Literal
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""应用配置"""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
arbitrary_types_allowed=True,
)
# 应用基础配置
APP_NAME: str = Field(default="美家卡智影 API", description="应用名称")
APP_VERSION: str = Field(default="1.6.6", description="应用版本")
DEBUG: bool = Field(default=False, description="调试模式")
ENV: Literal["development", "staging", "production"] = Field(
default="development", description="运行环境"
)
# 服务器配置
HOST: str = Field(default="0.0.0.0", description="监听地址") # nosec: B104
PORT: int = Field(default=8000, description="监听端口")
APP_BASE_URL: str = Field(
default="",
description="应用公网地址(用于第三方回调,如 https://dev.tapi.meijiaka.cn",
)
WORKERS: int = Field(default=1, description="工作进程数(生产环境建议 > 1)")
# 数据库配置(统一使用 PostgreSQL)
DATABASE_URL: str = Field(
default="postgresql+asyncpg://postgres:postgres@localhost:5432/meijiaka_zy",
description="数据库连接字符串(PostgreSQL)",
)
DATABASE_POOL_SIZE: int = Field(default=10, description="数据库连接池常驻连接数")
DATABASE_MAX_OVERFLOW: int = Field(default=10, description="连接池临时溢出上限(建议 ≤ pool_size)")
DATABASE_POOL_RECYCLE: int = Field(
default=1800, description="连接回收时间(秒),防止长连接被数据库静默断开"
)
DATABASE_POOL_TIMEOUT: int = Field(
default=30, description="获取连接超时(秒),防止连接池耗尽时请求无限等待"
)
# Redis 配置
REDIS_HOST: str = Field(
default="localhost",
description="Redis 主机地址",
)
REDIS_PORT: int = Field(
default=6379,
description="Redis 端口",
)
REDIS_DB: int = Field(
default=0,
description="Redis 数据库编号",
)
REDIS_PASSWORD: str | None = Field(
default=None,
description="Redis 密码(无密码请留空)",
)
# 安全配置
SECRET_KEY: str = Field(
...,
description="JWT 签名密钥(生产环境必须修改)",
)
ACCESS_TOKEN_EXPIRE_MINUTES: int = Field(
default=30,
description="Access Token 过期时间(分钟)",
)
REFRESH_TOKEN_EXPIRE_DAYS: int = Field(
default=30,
description="Refresh Token 过期时间(天)",
)
ALGORITHM: str = Field(default="HS256", description="JWT 算法")
# CORS 配置
CORS_ORIGINS: str = Field(
default="http://localhost:1420,http://127.0.0.1:1420,http://localhost:8080,http://127.0.0.1:8080,tauri://localhost,http://tauri.localhost,https://tauri.localhost",
description="允许的跨域来源(逗号分隔)",
)
# AI 模型配置
# 字节跳动 - 火山方舟
# 文档:https://www.volcengine.com/docs/82379/1399009
# base_url 已从 Settings 移除,改用 config/platform-config.yaml 配置
VOLCENGINE_API_KEY: str | None = Field(default=None, description="火山方舟 API Key")
VOLCENGINE_MODEL: str = Field(
default="doubao-seed-2-0-pro-260215",
description="火山方舟默认模型(Model ID",
)
# 火山引擎音视频字幕服务
VOLCENGINE_CAPTION_APPID: str | None = Field(default=None, description="火山字幕 AppID")
VOLCENGINE_CAPTION_TOKEN: str | None = Field(default=None, description="火山字幕 Token")
# 火山引擎 MediaKit 服务(背景移除等多媒体处理)
VOLCENGINE_MEDIAKIT_TOKEN: str | None = Field(default=None, description="火山引擎 MediaKit Token")
# Vidu 密钥(base_url 已从 Settings 移除,改用 config/platform-config.yaml 配置)
VIDU_API_KEY: str | None = Field(default=None, description="Vidu API Key")
# 七牛云存储配置
QINIU_ACCESS_KEY: str | None = Field(default=None, description="七牛云 Access Key")
QINIU_SECRET_KEY: str | None = Field(default=None, description="七牛云 Secret Key")
QINIU_VIDEO_BUCKET: str = Field(default="media-liche", description="视频存储 Bucket")
QINIU_VIDEO_DOMAIN: str = Field(default="media.liche.cn", description="视频存储域名")
QINIU_IMAGE_BUCKET: str = Field(default="img-liche", description="图片存储 Bucket")
QINIU_IMAGE_DOMAIN: str = Field(default="img.liche.cn", description="图片存储域名")
# 微信支付配置(APIv2
WXPAY_MCHID: str | None = Field(default=None, description="微信支付商户号")
WXPAY_APPID: str | None = Field(default=None, description="微信支付 AppID")
WXPAY_API_KEY: str | None = Field(default=None, description="微信支付 APIv2 密钥")
WXPAY_NOTIFY_URL: str | None = Field(
default=None, description="微信支付回调地址(完整 URL"
)
# B2M 短信平台配置
SMS_APP_ID: str | None = Field(default=None, description="B2M 短信平台 AppID")
SMS_SECRET_KEY: str | None = Field(default=None, description="B2M 短信平台 AES 密钥")
SMS_BASE_URL: str | None = Field(
default=None, description="B2M 短信平台接口地址(如 http://sms.b2m.cn:8080"
)
SMS_EXTENDED_CODE: str | None = Field(
default=None, description="B2M 短信平台扩展码(选填)"
)
SMS_CODE_WHITELIST: str = Field(
default="",
description="免验证码登录白名单(逗号分隔的手机号,如 13800138000,13900139000",
)
# 文件上传限制(字节)
UPLOAD_MAX_VIDEO_SIZE: int = Field(
default=500 * 1024 * 1024, description="视频最大上传大小(字节)"
)
UPLOAD_MAX_IMAGE_SIZE: int = Field(
default=20 * 1024 * 1024, description="图片最大上传大小(字节)"
)
UPLOAD_MAX_AUDIO_SIZE: int = Field(
default=100 * 1024 * 1024, description="音频最大上传大小(字节)"
)
# 日志配置
LOG_LEVEL: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = Field(
default="DEBUG",
description="日志级别",
)
@property
def cors_origins_list(self) -> list[str]:
"""将 CORS_ORIGINS 字符串解析为列表"""
return [origin.strip() for origin in self.CORS_ORIGINS.split(",")]
@property
def app_base_url(self) -> str:
"""应用公网地址(用于第三方回调)"""
if self.APP_BASE_URL:
return self.APP_BASE_URL.rstrip("/")
if self.ENV == "production":
return "https://tapi.meijiaka.cn"
if self.ENV == "staging":
return "https://dev.tapi.meijiaka.cn"
return f"http://{self.HOST}:{self.PORT}"
@property
def use_redis(self) -> bool:
"""是否使用 Redis"""
return bool(self.REDIS_HOST)
@property
def sms_code_whitelist_set(self) -> set[str]:
"""免验证码登录白名单(去重、去空格)"""
if not self.SMS_CODE_WHITELIST:
return set()
return {
mobile.strip()
for mobile in self.SMS_CODE_WHITELIST.split(",")
if mobile.strip()
}
@lru_cache
def get_settings() -> Settings:
"""获取配置单例(带缓存)"""
settings = Settings()
# 生产环境安全检查
if settings.ENV == "production":
if not settings.SECRET_KEY:
raise ValueError(
"生产环境必须设置强随机 SECRET_KEY!"
"请在 .env 文件中设置一个随机字符串(至少 32 位)。"
)
# 检查 CORS 配置
if settings.CORS_ORIGINS and "localhost" in settings.CORS_ORIGINS.lower():
import warnings
warnings.warn(
"生产环境 CORS 配置中包含 localhost,建议限制为实际域名",
RuntimeWarning,
stacklevel=2,
)
return settings
def reload_settings() -> Settings:
"""
重新加载配置(仅用于开发调试)。
⚠️ 生产环境推荐滚动重启,不要依赖此方法。
清除 lru_cache 后重新读取 .env,返回新的配置实例。
注意:已导入模块的模块级变量(如 `settings = get_settings()`)不会自动更新,
仅后续新的 `get_settings()` 调用会返回新实例。
"""
get_settings.cache_clear()
return get_settings()