Files
DadaoTask/backend/app/config.py
T
dadao c4868f04cf feat(task): 批量改状态与看板数据接口
- POST /items/batch-status:逐条成败,不做全回滚;拖 10 条里 1 条冲突不该让另 9 条白干
- GET /items/board:四列一次取全,收件箱不进看板
- 修:/items/board 被 /{item_id} 抢先匹配导致 422
- 新增向量库配置项(M3 用,独立实例,不动 fnsvr-01)
- 3 个新测试,全量 46 passed
2026-10-28 04:05:35 +08:00

127 lines
6.0 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.
"""分层配置:dev / prod 全部走环境变量,启动即校验。"""
from __future__ import annotations
import os
from functools import lru_cache
from typing import Literal
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
DEV_JWT_SECRET = "dev-only-change-me" # noqa: S105 - 仅开发默认值,生产启动时会被校验拦下
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env", env_file_encoding="utf-8", extra="ignore", case_sensitive=False
)
# ── 基础 ──────────────────────────────────────────────
app_name: str = "达道Task"
env: Literal["dev", "test", "prod"] = "dev"
api_prefix: str = "/api/v1"
cors_origins: list[str] = Field(
default_factory=lambda: ["http://localhost:5180", "http://127.0.0.1:5180"]
)
# ── 数据库 ────────────────────────────────────────────
# 应用角色:受 RLS 约束,非 owner、非 superuser
database_url: str = "postgresql+asyncpg://dadao_app:dadao_dev_pw@127.0.0.1:5433/dadaotask"
# 迁移角色:owner,可绕过 RLS,仅 alembic / 运维脚本使用
migration_database_url: str = (
"postgresql+asyncpg://dadao_owner:dadao_dev_pw@127.0.0.1:5433/dadaotask"
)
# 测试专用库:测试会 TRUNCATE 所有表,绝不能落到开发库上。
# 留空则退回 database_url(CI 里直接给 DATABASE_URL 即可)。
test_database_url: str | None = None
test_migration_database_url: str | None = None
db_echo: bool = False
db_pool_size: int = 10
# ── Redis ─────────────────────────────────────────────
redis_url: str = "redis://127.0.0.1:6380/0"
# ── 业务 ──────────────────────────────────────────────
# "今天/本周"这类视图按用户时区切日界,不能按 UTC —— 中国用户 23:30 建的任务
# 在 UTC 下已经是"明天"了。M7 多地域时改成按工作区配置。
default_timezone: str = "Asia/Shanghai"
# ── 认证 ──────────────────────────────────────────────
jwt_secret: str = DEV_JWT_SECRET
jwt_algorithm: str = "HS256"
access_token_minutes: int = 30
refresh_token_days: int = 14
sms_code_ttl_seconds: int = 300
# 同一手机号两次发码的最小间隔;测试里设为 0 可关闭节流
sms_throttle_seconds: int = 60
sms_dev_code: str = "123456"
sms_provider: Literal["dev", "aliyun", "tencent"] = "dev"
# ── 幂等 ──────────────────────────────────────────────
idempotency_enabled: bool = True
idempotency_ttl_seconds: int = 86_400
# ── 知识库向量库(独立实例,M3 起用)───────────────────
# 刻意与业务库分开放:fnsvr-01 上的 PG 没有 pgvector,也不该为了装扩展去动它;
# 向量检索吃内存,和任务库挤在同一实例会互相拖累。
# 未配置时所有向量相关能力(M3 才启用)直接不可用,不会静默降级成假结果。
vector_database_url: str | None = None
embedding_dim: int = 1024
embedding_model: str = "BAAI/bge-m3"
# ── 构建标记(部署校验用)──────────────────────────────
build_commit: str = "unknown"
build_time: str = "unknown"
@field_validator("cors_origins", mode="before")
@classmethod
def _split_origins(cls, value: object) -> object:
"""支持 CORS_ORIGINS="a,b" 这种写法。"""
if isinstance(value, str) and not value.startswith("["):
return [item.strip() for item in value.split(",") if item.strip()]
return value
def validate_runtime(self) -> None:
"""生产环境的致命配置错误,必须在启动时炸掉,而不是等待请求。"""
if self.env != "prod":
return
problems: list[str] = []
if self.jwt_secret == DEV_JWT_SECRET or len(self.jwt_secret) < 32:
problems.append("JWT_SECRET 仍是开发默认值或长度不足 32")
if "dadao_dev_pw" in self.database_url:
problems.append("DATABASE_URL 仍在用开发默认口令")
if self.sms_provider == "dev":
problems.append("生产环境不允许 SMS_PROVIDER=dev(任意验证码可登录)")
if problems:
raise RuntimeError("配置校验失败:" + ";".join(problems))
def resolved_database_url(self) -> str:
"""应用实际连的库:测试环境优先用独立测试库(测试会 TRUNCATE)。"""
if self.env == "test" and self.test_database_url:
return self.test_database_url
return self.database_url
def resolved_migration_database_url(self) -> str:
"""迁移/清表实际连的库:必须和应用同一个库,否则清表清到别处去了。"""
if self.env == "test" and self.test_migration_database_url:
return self.test_migration_database_url
return self.migration_database_url
def resolved_vector_database_url(self) -> str | None:
"""向量库连接串;没配就是 None,调用方必须显式处理"没有向量能力"。"""
return self.vector_database_url
@lru_cache
def get_settings() -> Settings:
return Settings()
def build_commit() -> str:
"""优先读环境变量(CI/镜像注入),退回 git。"""
env_value = os.getenv("BUILD_COMMIT")
if env_value:
return env_value
return get_settings().build_commit