config.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. from urllib.parse import quote_plus
  2. from pydantic_settings import BaseSettings, SettingsConfigDict
  3. class Settings(BaseSettings):
  4. app_name: str = "denamd-server"
  5. app_env: str = "dev"
  6. app_host: str = "0.0.0.0"
  7. app_port: int = 8000
  8. cors_allow_origins: str = "*"
  9. scheduler_heartbeat_seconds: int = 3600
  10. mysql_host: str = ""
  11. mysql_port: int = 3306
  12. mysql_user: str = "root"
  13. mysql_password: str = ""
  14. mysql_database: str = ""
  15. # 新热事件来源库(hot_content_* 表),与需求池库分离
  16. hot_content_mysql_host: str = ""
  17. hot_content_mysql_port: int = 3306
  18. hot_content_mysql_user: str = ""
  19. hot_content_mysql_password: str = ""
  20. hot_content_mysql_database: str = ""
  21. hot_content_mysql_charset: str = "utf8mb4"
  22. # 当下供需gap-分词来源库(demand_content 表)
  23. supply_mysql_host: str = ""
  24. supply_mysql_port: int = 3306
  25. supply_mysql_user: str = ""
  26. supply_mysql_password: str = ""
  27. supply_mysql_database: str = ""
  28. supply_mysql_charset: str = "utf8mb4"
  29. supply_demand_content_table: str = "demand_content"
  30. odps_access_id: str = "LTAI9EBa0bd5PrDa"
  31. odps_access_key: str = "vAalxds7YxhfOA2yVv8GziCg3Y87v5"
  32. odps_project: str = "loghubods"
  33. odps_endpoint: str = "http://service.odps.aliyun.com/api"
  34. demand_pool_source_table: str = "dwd_multi_demand_pool_di"
  35. demand_pool_secondary_source_table: str = "dwd_demand_pool_di"
  36. # 次源 dwd_demand_pool_di 同步开关;false 时仅同步主源 dwd_multi_demand_pool_di
  37. demand_pool_secondary_sync_enabled: bool = False
  38. demand_pool_secondary_strategy: str = "近期需求"
  39. demand_pool_secondary_default_ext_info: str = "{}"
  40. demand_pool_target_table: str = "multi_demand_pool_di"
  41. demand_pool_initial_partitions: str = "20260507,20260508,20260509"
  42. demand_pool_hourly_sync_enabled: bool = True
  43. demand_pool_hourly_sync_minute: int = 0
  44. demand_pool_daily_strategy_alert_enabled: bool = True
  45. demand_pool_daily_strategy_alert_hour: int = 10
  46. demand_pool_daily_strategy_alert_minute: int = 0
  47. feishu_webhook_url: str = ""
  48. feishu_webhook_timeout_seconds: int = 30
  49. # 默认不校验 HTTPS 证书,避免公司代理自签链导致发不出消息;生产若需严格校验可设为 true
  50. feishu_webhook_verify_ssl: bool = False
  51. hot_demand_pool_strategy: str = "新热事件"
  52. hot_content_wxindex_threshold: float = 1_000_000.0
  53. strategy_config_table: str = "strategy_config"
  54. strategy_staging_table: str = "strategy_staging"
  55. strategy_staging_hourly_generate_enabled: bool = True
  56. strategy_staging_hourly_generate_start_hour: int = 3
  57. strategy_staging_hourly_generate_end_hour: int = 23
  58. strategy_staging_hourly_generate_minute: int = 0
  59. # 实验系统:strategy_staging → ODPS 临时需求池表
  60. # 北京时间 03:30 首次写入,之后每小时 :30 一次,23:30 为当天最后一次;分区 dt=当天
  61. experiment_demand_pool_write_enabled: bool = True
  62. experiment_demand_pool_target_table: str = "dwd_multi_demand_pool_di_tmp"
  63. experiment_demand_pool_write_start_hour: int = 3
  64. experiment_demand_pool_write_end_hour: int = 23
  65. experiment_demand_pool_write_minute: int = 30
  66. substance_element_base_table: str = "substance_element_base"
  67. substance_element_effect_table: str = "substance_element_effect_di"
  68. substance_element_daily_sync_enabled: bool = True
  69. substance_element_daily_sync_hour: int = 9
  70. substance_element_daily_sync_minute: int = 30
  71. vertical_category_base_table: str = "vertical_category_base"
  72. vertical_category_effect_table: str = "vertical_category_effect_di"
  73. vertical_category_daily_sync_enabled: bool = True
  74. vertical_category_daily_sync_hour: int = 9
  75. vertical_category_daily_sync_minute: int = 30
  76. model_config = SettingsConfigDict(
  77. env_file=".env",
  78. env_file_encoding="utf-8",
  79. extra="ignore",
  80. )
  81. @property
  82. def mysql_dsn(self) -> str:
  83. return (
  84. "mysql+pymysql://"
  85. f"{quote_plus(self.mysql_user)}:{quote_plus(self.mysql_password)}"
  86. f"@{self.mysql_host}:{self.mysql_port}/{quote_plus(self.mysql_database)}"
  87. )
  88. @property
  89. def hot_content_mysql_configured(self) -> bool:
  90. return bool(self.hot_content_mysql_host.strip() and self.hot_content_mysql_database.strip())
  91. @property
  92. def hot_content_mysql_dsn(self) -> str:
  93. charset = quote_plus(self.hot_content_mysql_charset or "utf8mb4")
  94. return (
  95. "mysql+pymysql://"
  96. f"{quote_plus(self.hot_content_mysql_user)}:"
  97. f"{quote_plus(self.hot_content_mysql_password)}"
  98. f"@{self.hot_content_mysql_host}:{self.hot_content_mysql_port}/"
  99. f"{quote_plus(self.hot_content_mysql_database)}?charset={charset}"
  100. )
  101. @property
  102. def supply_mysql_configured(self) -> bool:
  103. return bool(self.supply_mysql_host.strip() and self.supply_mysql_database.strip())
  104. @property
  105. def supply_mysql_dsn(self) -> str:
  106. charset = quote_plus(self.supply_mysql_charset or "utf8mb4")
  107. return (
  108. "mysql+pymysql://"
  109. f"{quote_plus(self.supply_mysql_user)}:"
  110. f"{quote_plus(self.supply_mysql_password)}"
  111. f"@{self.supply_mysql_host}:{self.supply_mysql_port}/"
  112. f"{quote_plus(self.supply_mysql_database)}?charset={charset}"
  113. )
  114. @property
  115. def demand_pool_initial_partition_list(self) -> list[str]:
  116. return [
  117. partition.strip()
  118. for partition in self.demand_pool_initial_partitions.split(",")
  119. if partition.strip()
  120. ]
  121. @property
  122. def strategy_staging_hourly_cron_hours(self) -> str:
  123. """每小时触发,不跨午夜。默认 start=3、end=23 → '3-23'(末次 23:00,0 点不跑)。"""
  124. start = self.strategy_staging_hourly_generate_start_hour
  125. end = self.strategy_staging_hourly_generate_end_hour
  126. if not (0 <= start <= 23 and 0 <= end <= 23):
  127. raise ValueError("strategy staging hourly hours must be 0-23")
  128. if start > end:
  129. raise ValueError("start_hour cannot be greater than end_hour")
  130. if start == end:
  131. return str(start)
  132. return f"{start}-{end}"
  133. @property
  134. def experiment_demand_pool_write_cron_hours(self) -> str:
  135. """北京时间 hourly cron 小时段,配合 write_minute 使用。
  136. 默认 start=3、end=23、minute=30 → 03:30, 04:30, …, 23:30 各执行一次。
  137. """
  138. start = self.experiment_demand_pool_write_start_hour
  139. end = self.experiment_demand_pool_write_end_hour
  140. if not (0 <= start <= 23 and 0 <= end <= 23):
  141. raise ValueError("experiment demand pool write hours must be 0-23")
  142. if start > end:
  143. raise ValueError("start_hour cannot be greater than end_hour")
  144. if start == end:
  145. return str(start)
  146. return f"{start}-{end}"
  147. @property
  148. def cors_allow_origin_list(self) -> list[str]:
  149. if self.cors_allow_origins.strip() == "*":
  150. return ["*"]
  151. return [
  152. origin.strip()
  153. for origin in self.cors_allow_origins.split(",")
  154. if origin.strip()
  155. ]
  156. settings = Settings()