config.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. from pydantic import field_validator
  2. from pydantic_settings import BaseSettings, SettingsConfigDict
  3. from typing import Any, Dict, List
  4. from dotenv import load_dotenv
  5. from urllib.parse import quote
  6. import os
  7. # Docker/Jenkins 挂载 /app/.env.produ;本地开发可同路径或环境变量覆盖
  8. load_dotenv(".env.produ")
  9. def _strip_env_quotes(value: str | None) -> str | None:
  10. if value is None:
  11. return None
  12. v = value.strip()
  13. if len(v) >= 2 and v[0] == v[-1] and v[0] in ('"', "'"):
  14. return v[1:-1].strip()
  15. return v
  16. class Settings(BaseSettings):
  17. # 基础配置
  18. PROJECT_NAME: str = "Alien Cloud Python"
  19. API_V1_STR: str = "/api/v1"
  20. # 鉴权配置 (原 alien-gateway 职责)
  21. SECRET_KEY: str = ""
  22. ALGORITHM: str = "HS256"
  23. ACCESS_TOKEN_EXPIRE_MINUTES: int = 10080
  24. # 数据库配置(生产须与 Java alien_produ 一致)
  25. DB_USER: str = "root"
  26. DB_PASSWORD: str = ""
  27. DB_HOST: str = ""
  28. DB_PORT: int = 3306
  29. DB_NAME: str = "alien_produ"
  30. # Redis Sentinel 高可用配置
  31. REDIS_SENTINELS: str = ""
  32. REDIS_MASTER_NAME: str = "mymaster"
  33. REDIS_PASSWORD: str = ""
  34. REDIS_DB: int = 0
  35. REDIS_SENTINEL_USERNAME: str = ""
  36. REDIS_SENTINEL_PASSWORD: str = ""
  37. REDIS_SOCKET_TIMEOUT: float = 0.5
  38. REDIS_CONNECT_TIMEOUT: float = 1.0
  39. STORE_BASE_URL: str = ""
  40. ALIYUN_SMS_SIGN_NAME_CONTRACT: str = ""
  41. ALIYUN_SMS_TEMPLATE_CODE_CONTRACT: str = ""
  42. ALIYUN_ACCESS_KEY_ID: str = ""
  43. ALIYUN_ACCESS_KEY_SECRET: str = ""
  44. @field_validator(
  45. "DB_HOST",
  46. "DB_USER",
  47. "DB_NAME",
  48. "DB_PASSWORD",
  49. "SECRET_KEY",
  50. "STORE_BASE_URL",
  51. mode="before",
  52. )
  53. @classmethod
  54. def strip_quoted_env(cls, value: Any) -> Any:
  55. if isinstance(value, str):
  56. return _strip_env_quotes(value) or ""
  57. return value
  58. @property
  59. def SQLALCHEMY_DATABASE_URI(self) -> str:
  60. return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
  61. @property
  62. def REDIS_SENTINEL_NODES(self) -> List[tuple[str, int]]:
  63. nodes: List[tuple[str, int]] = []
  64. for item in self.REDIS_SENTINELS.split(","):
  65. entry = item.strip()
  66. if not entry:
  67. continue
  68. if ":" not in entry:
  69. raise ValueError(f"Invalid REDIS_SENTINELS entry: {entry}")
  70. host, port = entry.split(":", 1)
  71. nodes.append((host.strip(), int(port.strip())))
  72. return nodes
  73. @property
  74. def REDIS_SENTINEL_KWARGS(self) -> Dict[str, Any]:
  75. kwargs: Dict[str, Any] = {}
  76. if self.REDIS_SENTINEL_USERNAME:
  77. kwargs["username"] = self.REDIS_SENTINEL_USERNAME
  78. if self.REDIS_SENTINEL_PASSWORD:
  79. kwargs["password"] = self.REDIS_SENTINEL_PASSWORD
  80. return kwargs
  81. @property
  82. def REDIS_SENTINEL_URL(self) -> str:
  83. # 标准 Sentinel URL:
  84. # redis+sentinel://[username[:password]@]host1:port1,host2:port2/db?sentinel_master=mymaster
  85. if self.REDIS_SENTINEL_USERNAME:
  86. user = quote(self.REDIS_SENTINEL_USERNAME, safe="")
  87. pwd = quote(self.REDIS_PASSWORD, safe="")
  88. auth = f"{user}:{pwd}@"
  89. elif self.REDIS_PASSWORD:
  90. pwd = quote(self.REDIS_PASSWORD, safe="")
  91. auth = f":{pwd}@"
  92. else:
  93. auth = ""
  94. hosts = ",".join(f"{host}:{port}" for host, port in self.REDIS_SENTINEL_NODES)
  95. master = quote(self.REDIS_MASTER_NAME, safe="")
  96. return f"redis+sentinel://{auth}{hosts}/{self.REDIS_DB}?sentinel_master={master}"
  97. @property
  98. def REDIS_CELERY_SENTINEL_URL(self) -> str:
  99. # Celery/Kombu Sentinel URL 格式:
  100. # sentinel://:redis_password@sentinel_host:port/db;...
  101. # URL 中的 auth 密码 = Redis 主从节点密码
  102. # (Kombu 通过 Sentinel 发现主节点地址后,用此密码向 Redis 主节点认证)
  103. # Sentinel 自身的密码(如有)通过 broker_transport_options["sentinel_kwargs"]["password"] 传递
  104. auth = f":{quote(self.REDIS_PASSWORD, safe='')}@" if self.REDIS_PASSWORD else ""
  105. return ";".join(
  106. f"sentinel://{auth}{host}:{port}/{self.REDIS_DB}"
  107. for host, port in self.REDIS_SENTINEL_NODES
  108. )
  109. @property
  110. def REDIS_SENTINEL_TRANSPORT_OPTIONS(self) -> Dict[str, Any]:
  111. return {
  112. "master_name": self.REDIS_MASTER_NAME,
  113. "sentinel_kwargs": self.REDIS_SENTINEL_KWARGS,
  114. "password": self.REDIS_PASSWORD,
  115. "db": self.REDIS_DB,
  116. }
  117. model_config = SettingsConfigDict(
  118. case_sensitive=True,
  119. env_file=".env.produ",
  120. extra="ignore",
  121. )
  122. settings = Settings()