2026-01-07 17:32:58 +08:00
|
|
|
"""
|
|
|
|
|
配置管理模块 - 负责配置的读取、验证和持久化
|
|
|
|
|
"""
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
from typing import Any, Dict
|
|
|
|
|
from pathlib import Path
|
2026-01-12 09:19:38 +08:00
|
|
|
from loguru import logger
|
2026-01-07 17:32:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigManager:
|
|
|
|
|
"""配置管理器"""
|
|
|
|
|
|
|
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
|
"llm_api": {
|
2026-01-12 09:19:38 +08:00
|
|
|
"base_url": "https://integrate.api.nvidia.com/v1",
|
2026-01-07 17:32:58 +08:00
|
|
|
"api_key": "",
|
2026-01-12 09:19:38 +08:00
|
|
|
"model": "deepseek-ai/deepseek-r1",
|
|
|
|
|
"timeout": 120,
|
2026-01-07 17:32:58 +08:00
|
|
|
"retry_times": 3
|
|
|
|
|
},
|
|
|
|
|
"spider": {
|
|
|
|
|
"target_url": "https://example.com",
|
|
|
|
|
"xpath": "//a[contains(@class, 'linkblack')]",
|
|
|
|
|
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
|
|
|
"fetch_interval": 60,
|
|
|
|
|
"retry_times": 3,
|
2026-01-21 11:54:21 +08:00
|
|
|
"retry_interval": 5,
|
|
|
|
|
"playwright_dir": ""
|
2026-01-07 17:32:58 +08:00
|
|
|
},
|
|
|
|
|
"ui": {
|
|
|
|
|
"opacity": 0.9,
|
|
|
|
|
"is_on_top": True,
|
|
|
|
|
"thresholds": {
|
|
|
|
|
"cold": 30,
|
|
|
|
|
"warm": 70
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"database": {
|
|
|
|
|
"path": "guba.db"
|
|
|
|
|
},
|
|
|
|
|
"logging": {
|
|
|
|
|
"level": "INFO",
|
|
|
|
|
"path": "guba.log"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def __init__(self, config_path: str = "config.json"):
|
|
|
|
|
self.config_path = Path(config_path)
|
|
|
|
|
self.config = self._load_config()
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.info(f"配置管理器初始化完成,配置文件: {config_path}")
|
2026-01-07 17:32:58 +08:00
|
|
|
|
|
|
|
|
def _load_config(self) -> Dict[str, Any]:
|
|
|
|
|
"""加载配置文件"""
|
|
|
|
|
if self.config_path.exists():
|
|
|
|
|
try:
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.info(f"从文件加载配置: {self.config_path}")
|
2026-01-07 17:32:58 +08:00
|
|
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
loaded_config = json.load(f)
|
|
|
|
|
# 合并默认配置,确保所有键都存在
|
2026-01-12 09:19:38 +08:00
|
|
|
merged = self._merge_config(self.DEFAULT_CONFIG, loaded_config)
|
|
|
|
|
logger.info("配置加载成功")
|
|
|
|
|
return merged
|
2026-01-07 17:32:58 +08:00
|
|
|
except (json.JSONDecodeError, IOError) as e:
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.error(f"配置文件加载失败,使用默认配置: {e}")
|
2026-01-07 17:32:58 +08:00
|
|
|
return self.DEFAULT_CONFIG.copy()
|
|
|
|
|
else:
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.warning(f"配置文件不存在: {self.config_path},使用默认配置")
|
2026-01-07 17:32:58 +08:00
|
|
|
return self.DEFAULT_CONFIG.copy()
|
|
|
|
|
|
|
|
|
|
def _merge_config(self, default: Dict, loaded: Dict) -> Dict:
|
|
|
|
|
"""递归合并配置"""
|
|
|
|
|
result = default.copy()
|
|
|
|
|
for key, value in loaded.items():
|
|
|
|
|
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
|
|
|
result[key] = self._merge_config(result[key], value)
|
|
|
|
|
else:
|
|
|
|
|
result[key] = value
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
def save_config(self) -> bool:
|
|
|
|
|
"""保存配置到文件"""
|
|
|
|
|
try:
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.debug(f"保存配置到文件: {self.config_path}")
|
2026-01-07 17:32:58 +08:00
|
|
|
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
json.dump(self.config, f, ensure_ascii=False, indent=4)
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.info("配置保存成功")
|
2026-01-07 17:32:58 +08:00
|
|
|
return True
|
|
|
|
|
except IOError as e:
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.error(f"配置保存失败: {e}")
|
2026-01-07 17:32:58 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def get(self, *keys: str, default: Any = None) -> Any:
|
|
|
|
|
"""获取嵌套配置值"""
|
|
|
|
|
value = self.config
|
|
|
|
|
for key in keys:
|
|
|
|
|
if isinstance(value, dict) and key in value:
|
|
|
|
|
value = value[key]
|
|
|
|
|
else:
|
|
|
|
|
return default
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
def set(self, value: Any, *keys: str) -> bool:
|
|
|
|
|
"""设置嵌套配置值"""
|
|
|
|
|
if len(keys) < 1:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
current = self.config
|
|
|
|
|
for key in keys[:-1]:
|
|
|
|
|
if key not in current:
|
|
|
|
|
current[key] = {}
|
|
|
|
|
current = current[key]
|
|
|
|
|
|
|
|
|
|
current[keys[-1]] = value
|
|
|
|
|
return self.save_config()
|
|
|
|
|
|
|
|
|
|
def update_llm_api(self, base_url: str = None, api_key: str = None,
|
|
|
|
|
model: str = None, timeout: int = None, retry_times: int = None):
|
|
|
|
|
"""更新LLM API配置"""
|
|
|
|
|
if base_url:
|
|
|
|
|
self.config["llm_api"]["base_url"] = base_url
|
|
|
|
|
if api_key:
|
|
|
|
|
self.config["llm_api"]["api_key"] = api_key
|
|
|
|
|
if model:
|
|
|
|
|
self.config["llm_api"]["model"] = model
|
|
|
|
|
if timeout:
|
|
|
|
|
self.config["llm_api"]["timeout"] = timeout
|
|
|
|
|
if retry_times:
|
|
|
|
|
self.config["llm_api"]["retry_times"] = retry_times
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.info("LLM API配置已更新")
|
2026-01-07 17:32:58 +08:00
|
|
|
self.save_config()
|
|
|
|
|
|
|
|
|
|
def update_spider(self, target_url: str = None, xpath: str = None,
|
|
|
|
|
user_agent: str = None, fetch_interval: int = None,
|
2026-01-21 11:54:21 +08:00
|
|
|
retry_times: int = None, retry_interval: int = None,
|
|
|
|
|
playwright_dir: str = None):
|
2026-01-07 17:32:58 +08:00
|
|
|
"""更新爬虫配置"""
|
|
|
|
|
if target_url:
|
|
|
|
|
self.config["spider"]["target_url"] = target_url
|
|
|
|
|
if xpath:
|
|
|
|
|
self.config["spider"]["xpath"] = xpath
|
|
|
|
|
if user_agent:
|
|
|
|
|
self.config["spider"]["user_agent"] = user_agent
|
|
|
|
|
if fetch_interval:
|
|
|
|
|
self.config["spider"]["fetch_interval"] = fetch_interval
|
|
|
|
|
if retry_times:
|
|
|
|
|
self.config["spider"]["retry_times"] = retry_times
|
|
|
|
|
if retry_interval:
|
|
|
|
|
self.config["spider"]["retry_interval"] = retry_interval
|
2026-01-21 11:54:21 +08:00
|
|
|
if playwright_dir:
|
|
|
|
|
self.config["spider"]["playwright_dir"] = playwright_dir
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.info("爬虫配置已更新")
|
2026-01-07 17:32:58 +08:00
|
|
|
self.save_config()
|
|
|
|
|
|
|
|
|
|
def update_ui(self, opacity: float = None, is_on_top: bool = None,
|
|
|
|
|
cold_threshold: int = None, warm_threshold: int = None):
|
|
|
|
|
"""更新UI配置"""
|
|
|
|
|
if opacity is not None:
|
|
|
|
|
self.config["ui"]["opacity"] = max(0.3, min(1.0, opacity))
|
|
|
|
|
if is_on_top is not None:
|
|
|
|
|
self.config["ui"]["is_on_top"] = is_on_top
|
|
|
|
|
if cold_threshold is not None:
|
|
|
|
|
self.config["ui"]["thresholds"]["cold"] = cold_threshold
|
|
|
|
|
if warm_threshold is not None:
|
|
|
|
|
self.config["ui"]["thresholds"]["warm"] = warm_threshold
|
2026-01-12 09:19:38 +08:00
|
|
|
logger.info("UI配置已更新")
|
2026-01-07 17:32:58 +08:00
|
|
|
self.save_config()
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def llm_api_config(self) -> Dict:
|
|
|
|
|
return self.config["llm_api"]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def spider_config(self) -> Dict:
|
|
|
|
|
return self.config["spider"]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def ui_config(self) -> Dict:
|
|
|
|
|
return self.config["ui"]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def database_config(self) -> Dict:
|
|
|
|
|
return self.config["database"]
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def logging_config(self) -> Dict:
|
|
|
|
|
return self.config["logging"]
|