Files
2026-08-31 14:37:28 +08:00

134 lines
5.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.
from functools import lru_cache
from pathlib import Path
from dotenv import load_dotenv
from pydantic_settings import BaseSettings, SettingsConfigDict
# .env 在仓库根(server/ 的上一级),供各部分共用
_ROOT_DIR = Path(__file__).resolve().parents[2]
load_dotenv(_ROOT_DIR / ".env")
class Settings(BaseSettings):
"""运行时与密钥。模型清单见 config/models.yaml。"""
model_config = SettingsConfigDict(
env_file=str(_ROOT_DIR / ".env"),
env_file_encoding="utf-8",
extra="ignore",
)
# ── AI 密钥 ──
deepseek_api_key: str = ""
openai_api_key: str = ""
dashscope_api_key: str = ""
# 华北2(北京)业务空间时需填:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1
dashscope_base_http_api_url: str = ""
# ── 运行 ──
host: str = "127.0.0.1"
port: int = 8800
cors_origins: str = ""
# ── V2:数据层 ──
# 本地过渡用 SQLite;上线切 PostgreSQL:postgresql+asyncpg://user:pass@host:5432/ozon_seller
database_url: str = "sqlite+aiosqlite:///./data/app.db"
# 店铺凭证 AES-GCM 加密密钥(shops/categories/publish 冻结链路使用;
# 鉴权/账户体系已按 V2.1 决策移除,后续引入账户时一并重做)
secret_key: str = ""
# ── V2:七牛(图片存储)──
qiniu_access_key: str = ""
qiniu_secret_key: str = ""
qiniu_bucket: str = ""
qiniu_domain: str = "" # 绑定域名,如 https://cdn.example.com
# 为空时用本地文件系统兜底(开发期),不为空时走七牛
storage_backend: str = "local" # local | qiniu
# ── V2:对外地址(插件/前端回写、生成图回调)──
app_base_url: str = "http://127.0.0.1:8800"
# ── 图片与导出资源上限(字节)──
# 单张图片在后端需要解码/加水印,限制单文件可避免一次请求耗尽内存。
image_upload_max_bytes: int = 20 * 1024 * 1024
image_download_max_bytes: int = 20 * 1024 * 1024
# 图片解码前依据真实宽高限制像素,避免超大图耗尽 Pillow/水印处理内存。
image_max_pixels: int = 40_000_000
# 远程图片下载防护:连接/读写/总耗时分别受控;重定向逐跳校验目标地址。
image_download_connect_timeout: float = 10.0
image_download_read_timeout: float = 30.0
image_download_total_timeout: float = 60.0
image_download_max_redirects: int = 3
collection_max_images: int = 100
export_max_images: int = 50
export_zip_max_bytes: int = 200 * 1024 * 1024
# ZIP 超过该值自动落临时磁盘,不继续占用进程内存。
export_zip_memory_threshold_bytes: int = 2 * 1024 * 1024
# ── 商品轮播视频(FFmpeg)──
# Ubuntu 默认安装包会提供同名命令;容器或特殊路径可在 .env 覆盖。
video_ffmpeg_path: str = "ffmpeg"
video_ffprobe_path: str = "ffprobe"
video_width: int = 810
video_height: int = 1080
video_fps: int = 30
video_transition_seconds: float = 0.35
video_crf: int = 22
video_max_rate: str = "3M"
video_buffer_size: str = "6M"
video_max_bytes: int = 45 * 1024 * 1024
video_max_duration_seconds: int = 180
video_render_timeout_seconds: int = 120
background_job_lease_seconds: int = 300
# ── V2.1 套图生图(provider 路由与 image-suite-studio 一致)──
image_provider: str = "doubao" # 未知模型时的兜底 provider:doubao | tongyi | rightapi
request_timeout: int = 300 # 单张生图请求超时(秒)
poll_max_wait: int = 600 # 异步任务轮询上限(秒)
# 水印徽章源图(图片水印用,可 .env 覆盖)
watermark_image_path: str = str(Path(__file__).resolve().parents[1] / "assets" / "watermark.jpg")
# 豆包 / 火山方舟 Seedream
ark_api_key: str = ""
ark_base_url: str = "https://ark.cn-beijing.volces.com/api/v3/images/generations"
ark_image_model: str = "doubao-seedream-4-5-251128"
# 通义 / DashScope(key 复用上方 dashscope_api_key;dashscope_base_http_api_url 是业务空间专用,与此处无关)
dashscope_model: str = "wan2.7-image-pro"
dashscope_base_url: str = "" # 留空按模型自动选择万象异步/千问同步端点
# RightAPI(OpenAI 兼容中转:gpt-image / nano-banana 系列)
rightapi_api_key: str = ""
rightapi_base_url: str = "https://rightapi.ai/draw"
rightapi_image_model: str = "gpt-image-2"
rightapi_max_retries: int = 3 # 429/5xx/超时的重试次数(1 = 不重试)
rightapi_retry_wait: int = 60 # 重试基础等待秒数,按 60→120→240 递增
# Pcore(OpenAI 兼容中转:pcore.ai,异步图片任务;模型值带 @pcore 后缀路由到此)
pcore_api_key: str = ""
pcore_base_url: str = "https://pcore.ai"
pcore_max_retries: int = 3 # 429/5xx/超时的重试次数(1 = 不重试)
pcore_retry_wait: int = 60 # 重试基础等待秒数,按 60→120→240 递增
# DeepSeek 出图方案规划器(key 复用上方 deepseek_api_key;文案生成的模型走 config/models.yaml,互不影响)
deepseek_base_url: str = "https://api.deepseek.com/v1"
deepseek_model: str = "deepseek-v4-flash"
@property
def cors_origin_list(self) -> list[str]:
if not self.cors_origins.strip():
return []
return [o.strip() for o in self.cors_origins.split(",") if o.strip()]
@property
def use_qiniu(self) -> bool:
return self.storage_backend == "qiniu" and bool(
self.qiniu_access_key and self.qiniu_secret_key and self.qiniu_bucket
)
@lru_cache
def get_settings() -> Settings:
return Settings()