Files
guba-indicator/src/config.rs

349 lines
10 KiB
Rust
Raw Normal View History

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Result, Context};
use tracing::{info, warn, error};
/// LLM API配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmApiConfig {
pub api_key: String,
pub base_url: String,
pub model: String,
pub timeout: u64,
pub retry_times: u32,
}
impl Default for LlmApiConfig {
fn default() -> Self {
Self {
api_key: String::new(),
base_url: "https://open.bigmodel.cn/api/paas/v4".to_string(),
model: "glm-4.7-flash".to_string(),
timeout: 120,
retry_times: 3,
}
}
}
/// 爬虫配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpiderConfig {
pub target_url: String,
pub xpath: String,
pub user_agent: String,
pub fetch_interval: u64,
pub retry_times: u32,
pub retry_interval: u64,
pub chrome_path: String,
}
impl Default for SpiderConfig {
fn default() -> Self {
Self {
target_url: "https://example.com".to_string(),
xpath: "//a[contains(@class, 'linkblack')]".to_string(),
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36".to_string(),
fetch_interval: 60,
retry_times: 3,
retry_interval: 5,
chrome_path: String::new(),
}
}
}
/// UI配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UiConfig {
pub opacity: f32,
pub is_on_top: bool,
pub thresholds: Thresholds,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Thresholds {
pub cold: i32,
pub warm: i32,
}
impl Default for Thresholds {
fn default() -> Self {
Self {
cold: 30,
warm: 70,
}
}
}
impl Default for UiConfig {
fn default() -> Self {
Self {
opacity: 0.9,
is_on_top: true,
thresholds: Thresholds::default(),
}
}
}
/// 数据库配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
pub path: String,
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
path: "guba.db".to_string(),
}
}
}
/// 日志配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
pub level: String,
pub path: String,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
level: "INFO".to_string(),
path: "guba.log".to_string(),
}
}
}
/// 应用配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub llm_api: LlmApiConfig,
pub spider: SpiderConfig,
pub ui: UiConfig,
pub database: DatabaseConfig,
pub logging: LoggingConfig,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
llm_api: LlmApiConfig::default(),
spider: SpiderConfig::default(),
ui: UiConfig::default(),
database: DatabaseConfig::default(),
logging: LoggingConfig::default(),
}
}
}
/// 配置管理器
pub struct ConfigManager {
config_path: PathBuf,
config: AppConfig,
}
impl ConfigManager {
/// 创建新的配置管理器
pub fn new(config_path: &str) -> Result<Self> {
let config_path = Self::get_config_path(config_path)?;
info!("配置管理器初始化,配置文件路径: {:?}", config_path);
let config = if config_path.exists() {
Self::load_from_file(&config_path)?
} else {
warn!("配置文件不存在,使用默认配置");
AppConfig::default()
};
Ok(Self {
config_path,
config,
})
}
/// 获取配置文件路径
fn get_config_path(config_path: &str) -> Result<PathBuf> {
// 检查是否是打包后的环境
let exe_path = std::env::current_exe()?;
let exe_dir = exe_path.parent()
.context("无法获取可执行文件目录")?;
// 优先使用可执行文件所在目录
let path = exe_dir.join(config_path);
Ok(path)
}
/// 从文件加载配置
fn load_from_file(path: &Path) -> Result<AppConfig> {
let content = fs::read_to_string(path)
.with_context(|| format!("读取配置文件失败: {:?}", path))?;
let loaded: serde_json::Value = serde_json::from_str(&content)
.with_context(|| "解析配置文件失败")?;
// 合并默认配置和加载的配置
let default_config = serde_json::to_value(AppConfig::default())?;
let merged = Self::merge_config(&default_config, &loaded);
let config: AppConfig = serde_json::from_value(merged)?;
info!("配置加载成功");
Ok(config)
}
/// 递归合并配置
fn merge_config(default: &serde_json::Value, loaded: &serde_json::Value) -> serde_json::Value {
match (default, loaded) {
(serde_json::Value::Object(mut default_map), serde_json::Value::Object(loaded_map)) => {
for (key, value) in loaded_map {
if let Some(default_value) = default_map.get(key) {
default_map.insert(
key.clone(),
Self::merge_config(default_value, value),
);
} else {
default_map.insert(key.clone(), value.clone());
}
}
serde_json::Value::Object(default_map)
}
_ => loaded.clone(),
}
}
/// 保存配置到文件
pub fn save(&self) -> Result<()> {
let content = serde_json::to_string_pretty(&self.config)?;
fs::write(&self.config_path, content)
.with_context(|| format!("保存配置文件失败: {:?}", self.config_path))?;
info!("配置保存成功");
Ok(())
}
/// 获取嵌套配置值
pub fn get(&self, keys: &[&str]) -> Option<serde_json::Value> {
let mut value = serde_json::to_value(&self.config).ok()?;
for key in keys {
value = value.get(key)?.clone();
}
Some(value)
}
/// 设置嵌套配置值
pub fn set(&mut self, keys: &[&str], value: serde_json::Value) -> Result<()> {
let mut config_value = serde_json::to_value(&self.config)?;
let mut current = &mut config_value;
for key in &keys[..keys.len() - 1] {
current = current
.as_object_mut()
.context("配置不是对象")?
.get_mut(*key)
.context("配置键不存在")?;
}
if let Some(last_key) = keys.last() {
current
.as_object_mut()
.context("配置不是对象")?
.insert(last_key.to_string(), value);
}
self.config = serde_json::from_value(config_value)?;
self.save()?;
Ok(())
}
// Getters
pub fn llm_api_config(&self) -> &LlmApiConfig {
&self.config.llm_api
}
pub fn spider_config(&self) -> &SpiderConfig {
&self.config.spider
}
pub fn ui_config(&self) -> &UiConfig {
&self.config.ui
}
pub fn database_config(&self) -> &DatabaseConfig {
&self.config.database
}
pub fn logging_config(&self) -> &LoggingConfig {
&self.config.logging
}
// Update methods
pub fn update_llm_api(&mut self, api_key: Option<String>, base_url: Option<String>,
model: Option<String>, timeout: Option<u64>,
retry_times: Option<u32>) -> Result<()> {
if let Some(api_key) = api_key {
self.config.llm_api.api_key = api_key;
}
if let Some(base_url) = base_url {
self.config.llm_api.base_url = base_url;
}
if let Some(model) = model {
self.config.llm_api.model = model;
}
if let Some(timeout) = timeout {
self.config.llm_api.timeout = timeout;
}
if let Some(retry_times) = retry_times {
self.config.llm_api.retry_times = retry_times;
}
info!("LLM API配置已更新");
self.save()
}
pub fn update_spider(&mut self, target_url: Option<String>, xpath: Option<String>,
user_agent: Option<String>, fetch_interval: Option<u64>,
retry_times: Option<u32>, retry_interval: Option<u64>,
chrome_path: Option<String>) -> Result<()> {
if let Some(target_url) = target_url {
self.config.spider.target_url = target_url;
}
if let Some(xpath) = xpath {
self.config.spider.xpath = xpath;
}
if let Some(user_agent) = user_agent {
self.config.spider.user_agent = user_agent;
}
if let Some(fetch_interval) = fetch_interval {
self.config.spider.fetch_interval = fetch_interval;
}
if let Some(retry_times) = retry_times {
self.config.spider.retry_times = retry_times;
}
if let Some(retry_interval) = retry_interval {
self.config.spider.retry_interval = retry_interval;
}
if let Some(chrome_path) = chrome_path {
self.config.spider.chrome_path = chrome_path;
}
info!("爬虫配置已更新");
self.save()
}
pub fn update_ui(&mut self, opacity: Option<f32>, is_on_top: Option<bool>,
cold_threshold: Option<i32>, warm_threshold: Option<i32>) -> Result<()> {
if let Some(opacity) = opacity {
self.config.ui.opacity = opacity.clamp(0.3, 1.0);
}
if let Some(is_on_top) = is_on_top {
self.config.ui.is_on_top = is_on_top;
}
if let Some(cold) = cold_threshold {
self.config.ui.thresholds.cold = cold;
}
if let Some(warm) = warm_threshold {
self.config.ui.thresholds.warm = warm;
}
info!("UI配置已更新");
self.save()
}
}