config.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import os
  2. from functools import lru_cache
  3. from pathlib import Path
  4. from pydantic_settings import BaseSettings, SettingsConfigDict
  5. _ROOT = Path(__file__).resolve().parents[2]
  6. # 根据环境变量或系统环境确定要加载的 .env 文件
  7. # 优先级:
  8. # 1. 系统环境变量 ENV 指定的 .env.{env} 文件
  9. # 2. 存在的 .env.production/.env.test/.env.dev 文件(按优先级)
  10. # 3. .env 文件
  11. # 4. env.example 文件
  12. def _get_env_file() -> Path:
  13. # 首先检查系统环境变量 ENV
  14. env = os.getenv("ENV", "").lower()
  15. # 如果系统环境变量已设置,优先使用对应的环境文件
  16. if env:
  17. if env == "prod" or env == "production":
  18. env_file = _ROOT / ".env.production"
  19. if env_file.exists():
  20. return env_file
  21. elif env == "test" or env == "testing":
  22. env_file = _ROOT / ".env.test"
  23. if env_file.exists():
  24. return env_file
  25. elif env == "dev" or env == "development":
  26. env_file = _ROOT / ".env.dev"
  27. if env_file.exists():
  28. return env_file
  29. # 如果系统环境变量未设置,按优先级检查环境文件是否存在
  30. # 优先检查生产环境文件(常见于生产服务器)
  31. env_file = _ROOT / ".env.production"
  32. if env_file.exists():
  33. return env_file
  34. env_file = _ROOT / ".env.test"
  35. if env_file.exists():
  36. return env_file
  37. env_file = _ROOT / ".env.dev"
  38. if env_file.exists():
  39. return env_file
  40. # 回退到 .env
  41. env_file = _ROOT / ".env"
  42. if env_file.exists():
  43. return env_file
  44. # 最后回退到 env.example
  45. return _ROOT / "env.example"
  46. _ENV_FILE = _get_env_file()
  47. class Settings(BaseSettings):
  48. model_config = SettingsConfigDict(
  49. env_file=_ENV_FILE,
  50. env_file_encoding="utf-8",
  51. extra="ignore",
  52. )
  53. # 环境配置
  54. env: str = "dev" # dev, test, prod
  55. # 数据库配置
  56. database_url: str = "mysql+asyncmy://root:password@localhost:3306/dibang?charset=utf8mb4" # MySQL 数据库连接字符串,格式: mysql+asyncmy://user:password@host:port/database?charset=utf8mb4
  57. # JWT 配置
  58. jwt_secret: str = "dev-secret-key-change-in-production" # JWT 密钥,生产环境必须设置强密钥
  59. access_token_expires_minutes: int = 60 # 访问令牌过期时间(分钟)
  60. refresh_token_expires_days: int = 14 # 刷新令牌过期时间(天)
  61. # 服务器配置
  62. host: str = "0.0.0.0" # 服务器监听地址
  63. port: int = 8000 # 服务器端口
  64. # CORS 配置
  65. cors_origins: str = "*" # CORS 允许的源,多个用逗号分隔,如: http://localhost:3000,http://localhost:8080
  66. cors_allow_credentials: bool = True # 是否允许携带凭证
  67. cors_allow_methods: str = "*" # 允许的 HTTP 方法,多个用逗号分隔
  68. cors_allow_headers: str = "*" # 允许的 HTTP 头,多个用逗号分隔
  69. # 数据库连接池配置
  70. db_pool_size: int = 10 # 连接池中保持的连接数
  71. db_max_overflow: int = 20 # 连接池外可以额外创建的连接数
  72. db_pool_timeout: int = 30 # 从连接池获取连接的超时时间(秒)
  73. db_pool_recycle: int = 3600 # 连接回收时间(秒),避免 MySQL wait_timeout 导致的连接过期
  74. db_pool_pre_ping: bool = True # 每次使用连接前先 ping,确保连接有效
  75. @property
  76. def cors_origins_list(self) -> list[str]:
  77. """将 CORS origins 字符串转换为列表"""
  78. if self.cors_origins == "*":
  79. return ["*"]
  80. return [origin.strip() for origin in self.cors_origins.split(",")]
  81. @property
  82. def cors_allow_methods_list(self) -> list[str]:
  83. """将 CORS methods 字符串转换为列表"""
  84. if self.cors_allow_methods == "*":
  85. return ["*"]
  86. return [method.strip() for method in self.cors_allow_methods.split(",")]
  87. @property
  88. def cors_allow_headers_list(self) -> list[str]:
  89. """将 CORS headers 字符串转换为列表"""
  90. if self.cors_allow_headers == "*":
  91. return ["*"]
  92. return [header.strip() for header in self.cors_allow_headers.split(",")]
  93. @lru_cache(maxsize=1)
  94. def get_settings() -> Settings:
  95. return Settings()