system_config_manager.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. """
  2. 系统配置管理器
  3. 提供全局配置读取功能,支持缓存和默认值回退
  4. 业务代码通过此模块读取数据库中的系统配置
  5. """
  6. import json
  7. import logging
  8. from typing import Any, Optional, Dict
  9. from datetime import datetime, timedelta
  10. from threading import Lock
  11. from sqlalchemy.orm import Session
  12. from app.database import SessionLocal
  13. from app.models.config import SystemConfig
  14. logger = logging.getLogger(__name__)
  15. class SystemConfigManager:
  16. """系统配置管理器(单例模式)"""
  17. _instance = None
  18. _lock = Lock()
  19. # 配置缓存
  20. _cache: Dict[str, Any] = {}
  21. _cache_time: Dict[str, datetime] = {}
  22. _cache_ttl = timedelta(minutes=5) # 缓存5分钟
  23. # 默认值定义
  24. DEFAULT_VALUES = {
  25. # 基础配置
  26. "system_name": "智创空间",
  27. "system_logo": "",
  28. "contact_email": "",
  29. "icp_number": "",
  30. # 功能开关
  31. "enable_registration": True,
  32. "enable_recharge": True,
  33. "enable_search": True,
  34. "enable_local_models": True,
  35. "enable_openclaw": True,
  36. "enable_openclaw_client": True,
  37. "enable_openclaw_web": True,
  38. "openclaw_client_url": "",
  39. "new_user_bonus": 10.0,
  40. # 限制配置
  41. "max_tokens_per_request": 4000,
  42. "max_images_per_request": 4,
  43. "max_audio_chars": 5000,
  44. "max_video_duration": 10,
  45. "min_balance_required": 0.01,
  46. # 第三方配置(敏感,默认为空)
  47. "dashscope_api_key": "",
  48. "alipay_app_id": "",
  49. "oss_bucket": "",
  50. "oss_endpoint": "",
  51. }
  52. def __new__(cls):
  53. if cls._instance is None:
  54. with cls._lock:
  55. if cls._instance is None:
  56. cls._instance = super().__new__(cls)
  57. return cls._instance
  58. def _get_db(self) -> Session:
  59. """获取数据库会话"""
  60. return SessionLocal()
  61. def _parse_value(self, value_str: str, value_type: str) -> Any:
  62. """解析配置值"""
  63. try:
  64. return json.loads(value_str)
  65. except (json.JSONDecodeError, TypeError):
  66. return value_str
  67. def get(self, key: str, default: Any = None) -> Any:
  68. """
  69. 获取配置值
  70. 优先级:缓存 > 数据库 > 默认值 > 传入的default
  71. """
  72. # 检查缓存
  73. if key in self._cache:
  74. cache_time = self._cache_time.get(key)
  75. if cache_time and datetime.now() - cache_time < self._cache_ttl:
  76. return self._cache[key]
  77. # 从数据库读取
  78. db = self._get_db()
  79. try:
  80. config = db.query(SystemConfig).filter(
  81. SystemConfig.config_key == key
  82. ).first()
  83. if config:
  84. value = self._parse_value(config.config_value, config.config_type)
  85. # 更新缓存
  86. self._cache[key] = value
  87. self._cache_time[key] = datetime.now()
  88. return value
  89. except Exception as e:
  90. logger.warning(f"读取配置 {key} 失败: {e}")
  91. finally:
  92. db.close()
  93. # 返回默认值
  94. if default is not None:
  95. return default
  96. return self.DEFAULT_VALUES.get(key)
  97. def get_string(self, key: str, default: str = "") -> str:
  98. """获取字符串配置"""
  99. value = self.get(key, default)
  100. return str(value) if value is not None else default
  101. def get_int(self, key: str, default: int = 0) -> int:
  102. """获取整数配置"""
  103. value = self.get(key, default)
  104. try:
  105. return int(value)
  106. except (ValueError, TypeError):
  107. return default
  108. def get_float(self, key: str, default: float = 0.0) -> float:
  109. """获取浮点数配置"""
  110. value = self.get(key, default)
  111. try:
  112. return float(value)
  113. except (ValueError, TypeError):
  114. return default
  115. def get_bool(self, key: str, default: bool = False) -> bool:
  116. """获取布尔配置"""
  117. value = self.get(key, default)
  118. if isinstance(value, bool):
  119. return value
  120. if isinstance(value, str):
  121. return value.lower() in ('true', '1', 'yes')
  122. return bool(value)
  123. def clear_cache(self, key: Optional[str] = None):
  124. """清除缓存"""
  125. if key:
  126. self._cache.pop(key, None)
  127. self._cache_time.pop(key, None)
  128. else:
  129. self._cache.clear()
  130. self._cache_time.clear()
  131. def refresh(self, key: str) -> Any:
  132. """强制刷新配置"""
  133. self.clear_cache(key)
  134. return self.get(key)
  135. # 全局单例实例
  136. config_manager = SystemConfigManager()
  137. # 便捷函数
  138. def get_config(key: str, default: Any = None) -> Any:
  139. """获取配置值"""
  140. return config_manager.get(key, default)
  141. def get_config_string(key: str, default: str = "") -> str:
  142. """获取字符串配置"""
  143. return config_manager.get_string(key, default)
  144. def get_config_int(key: str, default: int = 0) -> int:
  145. """获取整数配置"""
  146. return config_manager.get_int(key, default)
  147. def get_config_float(key: str, default: float = 0.0) -> float:
  148. """获取浮点数配置"""
  149. return config_manager.get_float(key, default)
  150. def get_config_bool(key: str, default: bool = False) -> bool:
  151. """获取布尔配置"""
  152. return config_manager.get_bool(key, default)
  153. def clear_config_cache(key: Optional[str] = None):
  154. """清除配置缓存"""
  155. config_manager.clear_cache(key)
  156. def get_tenant_config_bool(db, tenant_id: Optional[int], key: str, default: bool = False) -> bool:
  157. """
  158. 读取租户级配置(bool),优先租户自己的配置,没有则 fallback 到全局配置,再没有则用默认值。
  159. """
  160. from app.models.config import SystemConfig
  161. import json as _json
  162. if tenant_id is not None:
  163. row = db.query(SystemConfig).filter(
  164. SystemConfig.tenant_id == tenant_id,
  165. SystemConfig.config_key == key,
  166. ).first()
  167. if row:
  168. try:
  169. v = _json.loads(row.config_value)
  170. if isinstance(v, bool):
  171. return v
  172. return str(v).lower() in ('true', '1', 'yes')
  173. except Exception:
  174. pass
  175. # fallback 到全局配置
  176. return get_config_bool(key, default)
  177. def get_tenant_config_float(db, tenant_id: Optional[int], key: str, default: float = 0.0) -> float:
  178. """
  179. 读取租户级配置(float),优先租户自己的配置,没有则 fallback 到全局配置,再没有则用默认值。
  180. """
  181. from app.models.config import SystemConfig
  182. import json as _json
  183. if tenant_id is not None:
  184. row = db.query(SystemConfig).filter(
  185. SystemConfig.tenant_id == tenant_id,
  186. SystemConfig.config_key == key,
  187. ).first()
  188. if row:
  189. try:
  190. return float(_json.loads(row.config_value))
  191. except Exception:
  192. pass
  193. return get_config_float(key, default)