Files
meijiaka-zy/python-api/app/config.py
T
小鱼开发 95e55293c6 security: 全面生产安全加固与部署修复
后端安全:
- DEBUG 默认 True → False
- 彻底移除 AUTH_BYPASS 认证绕过
- 验证码不再明文打印到日志
- 上传接口增加大小限制(500MB/20MB/100MB)与魔数校验
- python-jose → PyJWT, 更新 requirements.lock/uv.lock
- Bandit 恢复关键规则(B104/B301/B305/B314/B324/B603/B607)
- 修复 5 处 try_except_pass, 15 处加 nosec 注释
- 启用 Bandit pre-commit 钩子

前端安全:
- 配置完整 CSP 策略
- 收紧 Capabilities(fs:allow-read-file → $RESOURCE/**)
- 移除硬编码 devToken
- 清理前端 TODO(美家卡智影命名统一)

部署修复:
- docker-compose.prod 增加 alembic 迁移步骤
- api + scheduler 增加 Redis 心跳健康检查
- Nginx 添加安全响应头
- Nginx client_max_body_size 100M → 500M
- .env.example 补充 UPLOAD_MAX_* 配置与安全注释

其他:
- /voice/upload 合并到 /upload/audio
- Rust 上传增加文件大小检查
- 清理 Rust 19 处 println! + 前端 21 处 console.info
- 修复 VideoCompose.tsx toast 未导入(已有bug)
2026-05-10 23:31:34 +08:00

217 lines
7.9 KiB
Python
Raw 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="0.1.0", 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",
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")
# 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 短信平台扩展码(选填)"
)
# 文件上传限制(字节)
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)
@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()