config.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. # APP_ENV: dev / uat / produ(produ 分支默认 produ;UAT Jenkins 设 APP_ENV=uat)
  8. _ENV_FILE = f".env.{os.getenv('APP_ENV', 'produ')}"
  9. load_dotenv(_ENV_FILE)
  10. def _strip_env_quotes(value: str | None) -> str | None:
  11. if value is None:
  12. return None
  13. v = value.strip()
  14. if len(v) >= 2 and v[0] == v[-1] and v[0] in ('"', "'"):
  15. return v[1:-1].strip()
  16. return v
  17. class Settings(BaseSettings):
  18. # 基础配置
  19. PROJECT_NAME: str = "Alien Cloud Python"
  20. API_V1_STR: str = "/api/v1"
  21. # 鉴权配置 (原 alien-gateway 职责)
  22. SECRET_KEY: str = ""
  23. ALGORITHM: str = "HS256"
  24. ACCESS_TOKEN_EXPIRE_MINUTES: int = 10080
  25. # 数据库配置(生产须与 Java alien_produ 一致)
  26. DB_USER: str = "root"
  27. DB_PASSWORD: str = ""
  28. DB_HOST: str = ""
  29. DB_PORT: int = 3306
  30. DB_NAME: str = "alien_produ"
  31. # Redis Sentinel 高可用配置
  32. REDIS_SENTINELS: str = ""
  33. REDIS_MASTER_NAME: str = "mymaster"
  34. REDIS_PASSWORD: str = ""
  35. REDIS_DB: int = 0
  36. REDIS_SENTINEL_USERNAME: str = ""
  37. REDIS_SENTINEL_PASSWORD: str = ""
  38. REDIS_SOCKET_TIMEOUT: float = 0.5
  39. REDIS_CONNECT_TIMEOUT: float = 1.0
  40. STORE_BASE_URL: str = ""
  41. CONTRACT_BASE_URL: str = ""
  42. ALIYUN_SMS_SIGN_NAME_CONTRACT: str = ""
  43. ALIYUN_SMS_TEMPLATE_CODE_CONTRACT: str = ""
  44. ALIYUN_ACCESS_KEY_ID: str = ""
  45. ALIYUN_ACCESS_KEY_SECRET: str = ""
  46. @field_validator(
  47. "DB_HOST",
  48. "DB_USER",
  49. "DB_NAME",
  50. "DB_PASSWORD",
  51. "SECRET_KEY",
  52. "STORE_BASE_URL",
  53. "CONTRACT_BASE_URL",
  54. mode="before",
  55. )
  56. @classmethod
  57. def strip_quoted_env(cls, value: Any) -> Any:
  58. if isinstance(value, str):
  59. return _strip_env_quotes(value) or ""
  60. return value
  61. @property
  62. def SQLALCHEMY_DATABASE_URI(self) -> str:
  63. return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
  64. @property
  65. def REDIS_SENTINEL_NODES(self) -> List[tuple[str, int]]:
  66. nodes: List[tuple[str, int]] = []
  67. for item in self.REDIS_SENTINELS.split(","):
  68. entry = item.strip()
  69. if not entry:
  70. continue
  71. if ":" not in entry:
  72. raise ValueError(f"Invalid REDIS_SENTINELS entry: {entry}")
  73. host, port = entry.split(":", 1)
  74. nodes.append((host.strip(), int(port.strip())))
  75. return nodes
  76. @property
  77. def REDIS_SENTINEL_KWARGS(self) -> Dict[str, Any]:
  78. kwargs: Dict[str, Any] = {}
  79. if self.REDIS_SENTINEL_USERNAME:
  80. kwargs["username"] = self.REDIS_SENTINEL_USERNAME
  81. if self.REDIS_SENTINEL_PASSWORD:
  82. kwargs["password"] = self.REDIS_SENTINEL_PASSWORD
  83. return kwargs
  84. @property
  85. def REDIS_SENTINEL_URL(self) -> str:
  86. if self.REDIS_SENTINEL_USERNAME:
  87. user = quote(self.REDIS_SENTINEL_USERNAME, safe="")
  88. pwd = quote(self.REDIS_PASSWORD, safe="")
  89. auth = f"{user}:{pwd}@"
  90. elif self.REDIS_PASSWORD:
  91. pwd = quote(self.REDIS_PASSWORD, safe="")
  92. auth = f":{pwd}@"
  93. else:
  94. auth = ""
  95. hosts = ",".join(f"{host}:{port}" for host, port in self.REDIS_SENTINEL_NODES)
  96. master = quote(self.REDIS_MASTER_NAME, safe="")
  97. return f"redis+sentinel://{auth}{hosts}/{self.REDIS_DB}?sentinel_master={master}"
  98. @property
  99. def REDIS_CELERY_SENTINEL_URL(self) -> str:
  100. auth = f":{quote(self.REDIS_PASSWORD, safe='')}@" if self.REDIS_PASSWORD else ""
  101. return ";".join(
  102. f"sentinel://{auth}{host}:{port}/{self.REDIS_DB}"
  103. for host, port in self.REDIS_SENTINEL_NODES
  104. )
  105. @property
  106. def REDIS_SENTINEL_TRANSPORT_OPTIONS(self) -> Dict[str, Any]:
  107. return {
  108. "master_name": self.REDIS_MASTER_NAME,
  109. "sentinel_kwargs": self.REDIS_SENTINEL_KWARGS,
  110. "password": self.REDIS_PASSWORD,
  111. "db": self.REDIS_DB,
  112. }
  113. model_config = SettingsConfigDict(
  114. case_sensitive=True,
  115. env_file=_ENV_FILE,
  116. extra="ignore",
  117. )
  118. settings = Settings()