Skip to main content

cc_switch_lib/database/dao/
proxy.rs

1//! 代理功能数据访问层
2//!
3//! 处理代理配置、Provider健康状态和使用统计的数据库操作
4
5use crate::error::AppError;
6use crate::proxy::types::*;
7use rust_decimal::Decimal;
8
9use super::super::{lock_conn, Database};
10
11impl Database {
12    // ==================== Global Proxy Config ====================
13
14    /// 获取全局代理配置(统一字段)
15    ///
16    /// 从 claude 行读取(三行镜像一致)
17    pub async fn get_global_proxy_config(&self) -> Result<GlobalProxyConfig, AppError> {
18        // 使用 block 限制 conn 的作用域,避免跨 await 持有锁
19        let result = {
20            let conn = lock_conn!(self.conn);
21            conn.query_row(
22                "SELECT proxy_enabled, listen_address, listen_port, enable_logging
23                 FROM proxy_config WHERE app_type = 'claude'",
24                [],
25                |row| {
26                    Ok(GlobalProxyConfig {
27                        proxy_enabled: row.get::<_, i32>(0)? != 0,
28                        listen_address: row.get(1)?,
29                        listen_port: row.get::<_, i32>(2)? as u16,
30                        enable_logging: row.get::<_, i32>(3)? != 0,
31                    })
32                },
33            )
34        };
35        // conn 已在 block 结束时释放
36
37        match result {
38            Ok(config) => Ok(config),
39            Err(rusqlite::Error::QueryReturnedNoRows) => {
40                // 如果不存在,创建默认配置
41                self.init_proxy_config_rows().await?;
42                Ok(GlobalProxyConfig {
43                    proxy_enabled: false,
44                    listen_address: "127.0.0.1".to_string(),
45                    listen_port: 15721,
46                    enable_logging: true,
47                })
48            }
49            Err(e) => Err(AppError::Database(e.to_string())),
50        }
51    }
52
53    /// 更新全局代理配置(镜像写三行)
54    pub async fn update_global_proxy_config(
55        &self,
56        config: GlobalProxyConfig,
57    ) -> Result<(), AppError> {
58        let conn = lock_conn!(self.conn);
59
60        conn.execute(
61            "UPDATE proxy_config SET
62                proxy_enabled = ?1,
63                listen_address = ?2,
64                listen_port = ?3,
65                enable_logging = ?4,
66                updated_at = datetime('now')",
67            rusqlite::params![
68                if config.proxy_enabled { 1 } else { 0 },
69                config.listen_address,
70                config.listen_port as i32,
71                if config.enable_logging { 1 } else { 0 },
72            ],
73        )
74        .map_err(|e| AppError::Database(e.to_string()))?;
75
76        Ok(())
77    }
78
79    /// 获取默认成本倍率
80    pub async fn get_default_cost_multiplier(&self, app_type: &str) -> Result<String, AppError> {
81        let result = {
82            let conn = lock_conn!(self.conn);
83            conn.query_row(
84                "SELECT default_cost_multiplier FROM proxy_config WHERE app_type = ?1",
85                [app_type],
86                |row| row.get(0),
87            )
88        };
89
90        match result {
91            Ok(value) => Ok(value),
92            Err(rusqlite::Error::QueryReturnedNoRows) => {
93                self.init_proxy_config_rows().await?;
94                Ok("1".to_string())
95            }
96            Err(e) => Err(AppError::Database(e.to_string())),
97        }
98    }
99
100    /// 设置默认成本倍率
101    pub async fn set_default_cost_multiplier(
102        &self,
103        app_type: &str,
104        value: &str,
105    ) -> Result<(), AppError> {
106        let trimmed = value.trim();
107        if trimmed.is_empty() {
108            return Err(AppError::localized(
109                "error.multiplierEmpty",
110                "倍率不能为空",
111                "Multiplier cannot be empty",
112            ));
113        }
114        trimmed.parse::<Decimal>().map_err(|e| {
115            AppError::localized(
116                "error.invalidMultiplier",
117                format!("无效倍率: {value} - {e}"),
118                format!("Invalid multiplier: {value} - {e}"),
119            )
120        })?;
121
122        // 确保行存在
123        self.ensure_proxy_config_row_exists(app_type)?;
124
125        let conn = lock_conn!(self.conn);
126        conn.execute(
127            "UPDATE proxy_config SET
128                default_cost_multiplier = ?2,
129                updated_at = datetime('now')
130             WHERE app_type = ?1",
131            rusqlite::params![app_type, trimmed],
132        )
133        .map_err(|e| AppError::Database(e.to_string()))?;
134
135        Ok(())
136    }
137
138    /// 获取计费模式来源
139    pub async fn get_pricing_model_source(&self, app_type: &str) -> Result<String, AppError> {
140        let result = {
141            let conn = lock_conn!(self.conn);
142            conn.query_row(
143                "SELECT pricing_model_source FROM proxy_config WHERE app_type = ?1",
144                [app_type],
145                |row| row.get(0),
146            )
147        };
148
149        match result {
150            Ok(value) => Ok(value),
151            Err(rusqlite::Error::QueryReturnedNoRows) => {
152                self.init_proxy_config_rows().await?;
153                Ok("response".to_string())
154            }
155            Err(e) => Err(AppError::Database(e.to_string())),
156        }
157    }
158
159    /// 设置计费模式来源
160    pub async fn set_pricing_model_source(
161        &self,
162        app_type: &str,
163        value: &str,
164    ) -> Result<(), AppError> {
165        let trimmed = value.trim();
166        if !matches!(trimmed, "response" | "request") {
167            return Err(AppError::localized(
168                "error.invalidPricingMode",
169                format!("无效计费模式: {value}"),
170                format!("Invalid pricing mode: {value}"),
171            ));
172        }
173
174        // 确保行存在
175        self.ensure_proxy_config_row_exists(app_type)?;
176
177        let conn = lock_conn!(self.conn);
178        conn.execute(
179            "UPDATE proxy_config SET
180                pricing_model_source = ?2,
181                updated_at = datetime('now')
182             WHERE app_type = ?1",
183            rusqlite::params![app_type, trimmed],
184        )
185        .map_err(|e| AppError::Database(e.to_string()))?;
186
187        Ok(())
188    }
189
190    /// 获取应用级代理配置
191    pub async fn get_proxy_config_for_app(
192        &self,
193        app_type: &str,
194    ) -> Result<AppProxyConfig, AppError> {
195        // 使用 block 限制 conn 的作用域,避免跨 await 持有锁
196        let app_type_owned = app_type.to_string();
197        let result = {
198            let conn = lock_conn!(self.conn);
199            conn.query_row(
200                "SELECT app_type, enabled, auto_failover_enabled,
201                        max_retries, streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
202                        circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
203                        circuit_error_rate_threshold, circuit_min_requests
204                 FROM proxy_config WHERE app_type = ?1",
205                [app_type],
206                |row| {
207                    Ok(AppProxyConfig {
208                        app_type: row.get(0)?,
209                        enabled: row.get::<_, i32>(1)? != 0,
210                        auto_failover_enabled: row.get::<_, i32>(2)? != 0,
211                        max_retries: row.get::<_, i32>(3)? as u32,
212                        streaming_first_byte_timeout: row.get::<_, i32>(4)? as u32,
213                        streaming_idle_timeout: row.get::<_, i32>(5)? as u32,
214                        non_streaming_timeout: row.get::<_, i32>(6)? as u32,
215                        circuit_failure_threshold: row.get::<_, i32>(7)? as u32,
216                        circuit_success_threshold: row.get::<_, i32>(8)? as u32,
217                        circuit_timeout_seconds: row.get::<_, i32>(9)? as u32,
218                        circuit_error_rate_threshold: row.get(10)?,
219                        circuit_min_requests: row.get::<_, i32>(11)? as u32,
220                    })
221                },
222            )
223        };
224        // conn 已在 block 结束时释放
225
226        match result {
227            Ok(config) => Ok(config),
228            Err(rusqlite::Error::QueryReturnedNoRows) => {
229                // 如果不存在,创建默认配置
230                self.init_proxy_config_rows().await?;
231                Ok(AppProxyConfig {
232                    app_type: app_type_owned,
233                    enabled: false,
234                    auto_failover_enabled: false,
235                    max_retries: 3,
236                    streaming_first_byte_timeout: 60,
237                    streaming_idle_timeout: 120,
238                    non_streaming_timeout: 600,
239                    circuit_failure_threshold: 4,
240                    circuit_success_threshold: 2,
241                    circuit_timeout_seconds: 60,
242                    circuit_error_rate_threshold: 0.6,
243                    circuit_min_requests: 10,
244                })
245            }
246            Err(e) => Err(AppError::Database(e.to_string())),
247        }
248    }
249
250    /// 更新应用级代理配置
251    pub async fn update_proxy_config_for_app(
252        &self,
253        config: AppProxyConfig,
254    ) -> Result<(), AppError> {
255        let conn = lock_conn!(self.conn);
256
257        conn.execute(
258            "UPDATE proxy_config SET
259                enabled = ?2,
260                auto_failover_enabled = ?3,
261                max_retries = ?4,
262                streaming_first_byte_timeout = ?5,
263                streaming_idle_timeout = ?6,
264                non_streaming_timeout = ?7,
265                circuit_failure_threshold = ?8,
266                circuit_success_threshold = ?9,
267                circuit_timeout_seconds = ?10,
268                circuit_error_rate_threshold = ?11,
269                circuit_min_requests = ?12,
270                updated_at = datetime('now')
271             WHERE app_type = ?1",
272            rusqlite::params![
273                config.app_type,
274                if config.enabled { 1 } else { 0 },
275                if config.auto_failover_enabled { 1 } else { 0 },
276                config.max_retries as i32,
277                config.streaming_first_byte_timeout as i32,
278                config.streaming_idle_timeout as i32,
279                config.non_streaming_timeout as i32,
280                config.circuit_failure_threshold as i32,
281                config.circuit_success_threshold as i32,
282                config.circuit_timeout_seconds as i32,
283                config.circuit_error_rate_threshold,
284                config.circuit_min_requests as i32,
285            ],
286        )
287        .map_err(|e| AppError::Database(e.to_string()))?;
288
289        Ok(())
290    }
291
292    /// 确保指定 app_type 的 proxy_config 行存在(同步版本,用于 set_* 函数)
293    ///
294    /// 使用与 schema.rs seed 相同的 per-app 默认值
295    fn ensure_proxy_config_row_exists(&self, app_type: &str) -> Result<(), AppError> {
296        let conn = self
297            .conn
298            .lock()
299            .map_err(|e| AppError::Lock(e.to_string()))?;
300
301        // 根据 app_type 使用不同的默认值(与 schema.rs seed 保持一致)
302        let (retries, fb_timeout, idle_timeout, cb_fail, cb_succ, cb_timeout, cb_rate, cb_min) =
303            match app_type {
304                "claude" => (6, 90, 180, 8, 3, 90, 0.7, 15),
305                "codex" => (3, 60, 120, 4, 2, 60, 0.6, 10),
306                "gemini" => (5, 60, 120, 4, 2, 60, 0.6, 10),
307                _ => (3, 60, 120, 4, 2, 60, 0.6, 10), // 默认值
308            };
309
310        conn.execute(
311            "INSERT OR IGNORE INTO proxy_config (
312                app_type, max_retries,
313                streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
314                circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
315                circuit_error_rate_threshold, circuit_min_requests
316            ) VALUES (?1, ?2, ?3, ?4, 600, ?5, ?6, ?7, ?8, ?9)",
317            rusqlite::params![
318                app_type,
319                retries,
320                fb_timeout,
321                idle_timeout,
322                cb_fail,
323                cb_succ,
324                cb_timeout,
325                cb_rate,
326                cb_min
327            ],
328        )
329        .map_err(|e| AppError::Database(e.to_string()))?;
330
331        Ok(())
332    }
333
334    /// 初始化 proxy_config 表的三行数据
335    ///
336    /// 使用与 schema.rs seed 相同的 per-app 默认值
337    async fn init_proxy_config_rows(&self) -> Result<(), AppError> {
338        let conn = lock_conn!(self.conn);
339
340        // 使用与 schema.rs seed 相同的 per-app 默认值
341        // claude: 更激进的重试和超时配置
342        conn.execute(
343            "INSERT OR IGNORE INTO proxy_config (
344                app_type, max_retries,
345                streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
346                circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
347                circuit_error_rate_threshold, circuit_min_requests
348            ) VALUES ('claude', 6, 90, 180, 600, 8, 3, 90, 0.7, 15)",
349            [],
350        )
351        .map_err(|e| AppError::Database(e.to_string()))?;
352
353        // codex: 默认配置
354        conn.execute(
355            "INSERT OR IGNORE INTO proxy_config (
356                app_type, max_retries,
357                streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
358                circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
359                circuit_error_rate_threshold, circuit_min_requests
360            ) VALUES ('codex', 3, 60, 120, 600, 4, 2, 60, 0.6, 10)",
361            [],
362        )
363        .map_err(|e| AppError::Database(e.to_string()))?;
364
365        // gemini: 稍高的重试次数
366        conn.execute(
367            "INSERT OR IGNORE INTO proxy_config (
368                app_type, max_retries,
369                streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
370                circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
371                circuit_error_rate_threshold, circuit_min_requests
372            ) VALUES ('gemini', 5, 60, 120, 600, 4, 2, 60, 0.6, 10)",
373            [],
374        )
375        .map_err(|e| AppError::Database(e.to_string()))?;
376
377        Ok(())
378    }
379
380    // ==================== Legacy Proxy Config (兼容旧代码) ====================
381
382    /// 获取代理配置(兼容旧接口,返回 claude 行的配置)
383    pub async fn get_proxy_config(&self) -> Result<ProxyConfig, AppError> {
384        // 使用 block 限制 conn 的作用域,避免跨 await 持有锁
385        let result = {
386            let conn = lock_conn!(self.conn);
387            conn.query_row(
388                "SELECT listen_address, listen_port, max_retries,
389                        enable_logging,
390                        streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout
391                 FROM proxy_config WHERE app_type = 'claude'",
392                [],
393                |row| {
394                    Ok(ProxyConfig {
395                        listen_address: row.get(0)?,
396                        listen_port: row.get::<_, i32>(1)? as u16,
397                        max_retries: row.get::<_, i32>(2)? as u8,
398                        request_timeout: 600, // 废弃字段,返回默认值
399                        enable_logging: row.get::<_, i32>(3)? != 0,
400                        live_takeover_active: false, // 废弃字段
401                        streaming_first_byte_timeout: row.get::<_, i32>(4).unwrap_or(60) as u64,
402                        streaming_idle_timeout: row.get::<_, i32>(5).unwrap_or(120) as u64,
403                        non_streaming_timeout: row.get::<_, i32>(6).unwrap_or(600) as u64,
404                    })
405                },
406            )
407        };
408        // conn 已在 block 结束时释放
409
410        match result {
411            Ok(config) => Ok(config),
412            Err(rusqlite::Error::QueryReturnedNoRows) => {
413                // 如果不存在,初始化默认配置
414                self.init_proxy_config_rows().await?;
415                Ok(ProxyConfig::default())
416            }
417            Err(e) => Err(AppError::Database(e.to_string())),
418        }
419    }
420
421    /// 更新代理配置(兼容旧接口,更新所有三行的公共字段)
422    pub async fn update_proxy_config(&self, config: ProxyConfig) -> Result<(), AppError> {
423        let conn = lock_conn!(self.conn);
424
425        // 更新所有三行的公共字段
426        conn.execute(
427            "UPDATE proxy_config SET
428                listen_address = ?1,
429                listen_port = ?2,
430                max_retries = ?3,
431                enable_logging = ?4,
432                streaming_first_byte_timeout = ?5,
433                streaming_idle_timeout = ?6,
434                non_streaming_timeout = ?7,
435                updated_at = datetime('now')",
436            rusqlite::params![
437                config.listen_address,
438                config.listen_port as i32,
439                config.max_retries as i32,
440                if config.enable_logging { 1 } else { 0 },
441                config.streaming_first_byte_timeout as i32,
442                config.streaming_idle_timeout as i32,
443                config.non_streaming_timeout as i32,
444            ],
445        )
446        .map_err(|e| AppError::Database(e.to_string()))?;
447
448        Ok(())
449    }
450
451    /// 设置 Live 接管状态(兼容旧版本,更新 enabled 字段)
452    pub async fn set_live_takeover_active(&self, _active: bool) -> Result<(), AppError> {
453        // 不再使用此字段,由 enabled 字段替代
454        // 保留空实现以兼容旧代码
455        Ok(())
456    }
457
458    /// 检查是否处于 Live 接管模式
459    ///
460    /// 检查是否有任一 app 的 enabled = true
461    pub async fn is_live_takeover_active(&self) -> Result<bool, AppError> {
462        let conn = lock_conn!(self.conn);
463        let count: i64 = conn
464            .query_row(
465                "SELECT COUNT(*) FROM proxy_config WHERE enabled = 1",
466                [],
467                |row| row.get(0),
468            )
469            .map_err(|e| AppError::Database(e.to_string()))?;
470        Ok(count > 0)
471    }
472
473    // ==================== Provider Health ====================
474
475    /// 获取Provider健康状态
476    pub async fn get_provider_health(
477        &self,
478        provider_id: &str,
479        app_type: &str,
480    ) -> Result<ProviderHealth, AppError> {
481        let result = {
482            let conn = lock_conn!(self.conn);
483
484            conn.query_row(
485                "SELECT provider_id, app_type, is_healthy, consecutive_failures,
486                        last_success_at, last_failure_at, last_error, updated_at
487                 FROM provider_health
488                 WHERE provider_id = ?1 AND app_type = ?2",
489                rusqlite::params![provider_id, app_type],
490                |row| {
491                    Ok(ProviderHealth {
492                        provider_id: row.get(0)?,
493                        app_type: row.get(1)?,
494                        is_healthy: row.get::<_, i64>(2)? != 0,
495                        consecutive_failures: row.get::<_, i64>(3)? as u32,
496                        last_success_at: row.get(4)?,
497                        last_failure_at: row.get(5)?,
498                        last_error: row.get(6)?,
499                        updated_at: row.get(7)?,
500                    })
501                },
502            )
503        };
504
505        match result {
506            Ok(health) => Ok(health),
507            // 缺少记录时视为健康(关闭后清空状态,再次打开时默认正常)
508            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(ProviderHealth {
509                provider_id: provider_id.to_string(),
510                app_type: app_type.to_string(),
511                is_healthy: true,
512                consecutive_failures: 0,
513                last_success_at: None,
514                last_failure_at: None,
515                last_error: None,
516                updated_at: chrono::Utc::now().to_rfc3339(),
517            }),
518            Err(e) => Err(AppError::Database(e.to_string())),
519        }
520    }
521
522    /// 更新Provider健康状态
523    ///
524    /// 使用默认阈值(5)判断是否健康,建议使用 `update_provider_health_with_threshold` 传入配置的阈值
525    pub async fn update_provider_health(
526        &self,
527        provider_id: &str,
528        app_type: &str,
529        success: bool,
530        error_msg: Option<String>,
531    ) -> Result<(), AppError> {
532        // 默认阈值与 CircuitBreakerConfig::default() 保持一致
533        self.update_provider_health_with_threshold(provider_id, app_type, success, error_msg, 5)
534            .await
535    }
536
537    /// 更新Provider健康状态(带阈值参数)
538    ///
539    /// # Arguments
540    /// * `failure_threshold` - 连续失败多少次后标记为不健康
541    pub async fn update_provider_health_with_threshold(
542        &self,
543        provider_id: &str,
544        app_type: &str,
545        success: bool,
546        error_msg: Option<String>,
547        failure_threshold: u32,
548    ) -> Result<(), AppError> {
549        let conn = lock_conn!(self.conn);
550
551        let now = chrono::Utc::now().to_rfc3339();
552
553        // 先查询当前状态
554        let current = conn.query_row(
555            "SELECT consecutive_failures FROM provider_health
556             WHERE provider_id = ?1 AND app_type = ?2",
557            rusqlite::params![provider_id, app_type],
558            |row| Ok(row.get::<_, i64>(0)? as u32),
559        );
560
561        let (is_healthy, consecutive_failures) = if success {
562            // 成功:重置失败计数
563            (1, 0)
564        } else {
565            // 失败:增加失败计数
566            let failures = current.unwrap_or(0) + 1;
567            // 使用传入的阈值而非硬编码
568            let healthy = if failures >= failure_threshold { 0 } else { 1 };
569            (healthy, failures)
570        };
571
572        let (last_success_at, last_failure_at) = if success {
573            (Some(now.clone()), None)
574        } else {
575            (None, Some(now.clone()))
576        };
577
578        // UPSERT
579        conn.execute(
580            "INSERT OR REPLACE INTO provider_health
581             (provider_id, app_type, is_healthy, consecutive_failures,
582              last_success_at, last_failure_at, last_error, updated_at)
583             VALUES (?1, ?2, ?3, ?4,
584                     COALESCE(?5, (SELECT last_success_at FROM provider_health
585                                   WHERE provider_id = ?1 AND app_type = ?2)),
586                     COALESCE(?6, (SELECT last_failure_at FROM provider_health
587                                   WHERE provider_id = ?1 AND app_type = ?2)),
588                     ?7, ?8)",
589            rusqlite::params![
590                provider_id,
591                app_type,
592                is_healthy,
593                consecutive_failures as i64,
594                last_success_at,
595                last_failure_at,
596                error_msg,
597                &now,
598            ],
599        )
600        .map_err(|e| AppError::Database(e.to_string()))?;
601
602        Ok(())
603    }
604
605    /// 重置Provider健康状态
606    pub async fn reset_provider_health(
607        &self,
608        provider_id: &str,
609        app_type: &str,
610    ) -> Result<(), AppError> {
611        let conn = lock_conn!(self.conn);
612
613        conn.execute(
614            "DELETE FROM provider_health WHERE provider_id = ?1 AND app_type = ?2",
615            rusqlite::params![provider_id, app_type],
616        )
617        .map_err(|e| AppError::Database(e.to_string()))?;
618
619        log::debug!("Reset health status for provider {provider_id} (app: {app_type})");
620
621        Ok(())
622    }
623
624    /// 清空指定应用的健康状态(关闭单个代理时使用)
625    pub async fn clear_provider_health_for_app(&self, app_type: &str) -> Result<(), AppError> {
626        let conn = lock_conn!(self.conn);
627
628        conn.execute(
629            "DELETE FROM provider_health WHERE app_type = ?1",
630            [app_type],
631        )
632        .map_err(|e| AppError::Database(e.to_string()))?;
633
634        log::debug!("Cleared provider health records for app {app_type}");
635        Ok(())
636    }
637
638    /// 清空所有Provider健康状态(代理停止时调用)
639    pub async fn clear_all_provider_health(&self) -> Result<(), AppError> {
640        let conn = lock_conn!(self.conn);
641
642        conn.execute("DELETE FROM provider_health", [])
643            .map_err(|e| AppError::Database(e.to_string()))?;
644
645        log::debug!("Cleared all provider health records");
646        Ok(())
647    }
648
649    // ==================== Circuit Breaker Config (Legacy Compatibility) ====================
650
651    /// 获取熔断器配置(兼容旧接口,从 claude 行读取)
652    ///
653    /// 熔断器配置已合并到 proxy_config 表,每 app 独立
654    /// 此方法保留用于兼容旧代码,建议使用 get_proxy_config_for_app
655    pub async fn get_circuit_breaker_config(
656        &self,
657    ) -> Result<crate::proxy::circuit_breaker::CircuitBreakerConfig, AppError> {
658        // 使用 block 限制 conn 的作用域,避免跨 await 持有锁
659        let result = {
660            let conn = lock_conn!(self.conn);
661            conn.query_row(
662                "SELECT circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
663                        circuit_error_rate_threshold, circuit_min_requests
664                 FROM proxy_config WHERE app_type = 'claude'",
665                [],
666                |row| {
667                    Ok(crate::proxy::circuit_breaker::CircuitBreakerConfig {
668                        failure_threshold: row.get::<_, i32>(0)? as u32,
669                        success_threshold: row.get::<_, i32>(1)? as u32,
670                        timeout_seconds: row.get::<_, i64>(2)? as u64,
671                        error_rate_threshold: row.get(3)?,
672                        min_requests: row.get::<_, i32>(4)? as u32,
673                    })
674                },
675            )
676        };
677        // conn 已在 block 结束时释放
678
679        match result {
680            Ok(config) => Ok(config),
681            Err(rusqlite::Error::QueryReturnedNoRows) => {
682                // 如果不存在,初始化默认配置
683                self.init_proxy_config_rows().await?;
684                Ok(crate::proxy::circuit_breaker::CircuitBreakerConfig::default())
685            }
686            Err(e) => Err(AppError::Database(e.to_string())),
687        }
688    }
689
690    /// 更新熔断器配置(兼容旧接口,更新所有三行)
691    ///
692    /// 熔断器配置已合并到 proxy_config 表
693    /// 此方法保留用于兼容旧代码,建议使用 update_proxy_config_for_app
694    pub async fn update_circuit_breaker_config(
695        &self,
696        config: &crate::proxy::circuit_breaker::CircuitBreakerConfig,
697    ) -> Result<(), AppError> {
698        let conn = lock_conn!(self.conn);
699
700        // 更新所有三行的熔断器配置
701        conn.execute(
702            "UPDATE proxy_config SET
703                circuit_failure_threshold = ?1,
704                circuit_success_threshold = ?2,
705                circuit_timeout_seconds = ?3,
706                circuit_error_rate_threshold = ?4,
707                circuit_min_requests = ?5,
708                updated_at = datetime('now')",
709            rusqlite::params![
710                config.failure_threshold as i32,
711                config.success_threshold as i32,
712                config.timeout_seconds as i64,
713                config.error_rate_threshold,
714                config.min_requests as i32,
715            ],
716        )
717        .map_err(|e| AppError::Database(e.to_string()))?;
718
719        Ok(())
720    }
721
722    // ==================== Live Backup ====================
723
724    /// 保存 Live 配置备份
725    pub async fn save_live_backup(
726        &self,
727        app_type: &str,
728        config_json: &str,
729    ) -> Result<(), AppError> {
730        let conn = lock_conn!(self.conn);
731        let now = chrono::Utc::now().to_rfc3339();
732
733        conn.execute(
734            "INSERT OR REPLACE INTO proxy_live_backup (app_type, original_config, backed_up_at)
735             VALUES (?1, ?2, ?3)",
736            rusqlite::params![app_type, config_json, now],
737        )
738        .map_err(|e| AppError::Database(e.to_string()))?;
739
740        log::info!("已备份 {app_type} Live 配置");
741        Ok(())
742    }
743
744    /// 检查是否存在任意 Live 配置备份
745    pub async fn has_any_live_backup(&self) -> Result<bool, AppError> {
746        let conn = lock_conn!(self.conn);
747        let count: i64 = conn
748            .query_row("SELECT COUNT(*) FROM proxy_live_backup", [], |row| {
749                row.get(0)
750            })
751            .map_err(|e| AppError::Database(e.to_string()))?;
752        Ok(count > 0)
753    }
754
755    /// 获取 Live 配置备份
756    pub async fn get_live_backup(&self, app_type: &str) -> Result<Option<LiveBackup>, AppError> {
757        let conn = lock_conn!(self.conn);
758
759        let result = conn.query_row(
760            "SELECT app_type, original_config, backed_up_at FROM proxy_live_backup WHERE app_type = ?1",
761            rusqlite::params![app_type],
762            |row| {
763                Ok(LiveBackup {
764                    app_type: row.get(0)?,
765                    original_config: row.get(1)?,
766                    backed_up_at: row.get(2)?,
767                })
768            },
769        );
770
771        match result {
772            Ok(backup) => Ok(Some(backup)),
773            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
774            Err(e) => Err(AppError::Database(e.to_string())),
775        }
776    }
777
778    /// 删除 Live 配置备份
779    pub async fn delete_live_backup(&self, app_type: &str) -> Result<(), AppError> {
780        let conn = lock_conn!(self.conn);
781
782        conn.execute(
783            "DELETE FROM proxy_live_backup WHERE app_type = ?1",
784            rusqlite::params![app_type],
785        )
786        .map_err(|e| AppError::Database(e.to_string()))?;
787
788        log::info!("已删除 {app_type} Live 配置备份");
789        Ok(())
790    }
791
792    /// 删除所有 Live 配置备份
793    pub async fn delete_all_live_backups(&self) -> Result<(), AppError> {
794        let conn = lock_conn!(self.conn);
795
796        conn.execute("DELETE FROM proxy_live_backup", [])
797            .map_err(|e| AppError::Database(e.to_string()))?;
798
799        log::info!("已删除所有 Live 配置备份");
800        Ok(())
801    }
802
803    // ==================== Sync Methods for Tray Menu ====================
804
805    /// 同步获取应用的 proxy 启用状态和自动故障转移状态
806    ///
807    /// 用于托盘菜单构建等同步场景
808    /// 返回 (enabled, auto_failover_enabled)
809    pub fn get_proxy_flags_sync(&self, app_type: &str) -> (bool, bool) {
810        let conn = match self.conn.lock() {
811            Ok(c) => c,
812            Err(_) => return (false, false),
813        };
814
815        conn.query_row(
816            "SELECT enabled, auto_failover_enabled FROM proxy_config WHERE app_type = ?1",
817            [app_type],
818            |row| Ok((row.get::<_, i32>(0)? != 0, row.get::<_, i32>(1)? != 0)),
819        )
820        .unwrap_or((false, false))
821    }
822
823    /// 同步设置应用的 proxy 启用状态和自动故障转移状态
824    ///
825    /// 用于托盘菜单点击等同步场景
826    pub fn set_proxy_flags_sync(
827        &self,
828        app_type: &str,
829        enabled: bool,
830        auto_failover_enabled: bool,
831    ) -> Result<(), AppError> {
832        let conn = self
833            .conn
834            .lock()
835            .map_err(|e| AppError::Database(format!("Mutex lock failed: {e}")))?;
836
837        conn.execute(
838            "UPDATE proxy_config SET enabled = ?2, auto_failover_enabled = ?3, updated_at = datetime('now') WHERE app_type = ?1",
839            rusqlite::params![
840                app_type,
841                if enabled { 1 } else { 0 },
842                if auto_failover_enabled { 1 } else { 0 },
843            ],
844        )
845        .map_err(|e| AppError::Database(e.to_string()))?;
846
847        Ok(())
848    }
849}
850
851#[cfg(test)]
852mod tests {
853    use crate::database::Database;
854    use crate::error::AppError;
855
856    #[tokio::test]
857    async fn test_default_cost_multiplier_round_trip() -> Result<(), AppError> {
858        let db = Database::memory()?;
859
860        let default = db.get_default_cost_multiplier("claude").await?;
861        assert_eq!(default, "1");
862
863        db.set_default_cost_multiplier("claude", "1.5").await?;
864        let updated = db.get_default_cost_multiplier("claude").await?;
865        assert_eq!(updated, "1.5");
866
867        Ok(())
868    }
869
870    #[tokio::test]
871    async fn test_default_cost_multiplier_validation() -> Result<(), AppError> {
872        let db = Database::memory()?;
873
874        let err = db
875            .set_default_cost_multiplier("claude", "not-a-number")
876            .await
877            .unwrap_err();
878        // AppError::localized returns AppError::Localized variant
879        assert!(matches!(
880            err,
881            AppError::Localized {
882                key: "error.invalidMultiplier",
883                ..
884            }
885        ));
886
887        Ok(())
888    }
889
890    #[tokio::test]
891    async fn test_pricing_model_source_round_trip_and_validation() -> Result<(), AppError> {
892        let db = Database::memory()?;
893
894        let default = db.get_pricing_model_source("claude").await?;
895        assert_eq!(default, "response");
896
897        db.set_pricing_model_source("claude", "request").await?;
898        let updated = db.get_pricing_model_source("claude").await?;
899        assert_eq!(updated, "request");
900
901        let err = db
902            .set_pricing_model_source("claude", "invalid")
903            .await
904            .unwrap_err();
905        // AppError::localized returns AppError::Localized variant
906        assert!(matches!(
907            err,
908            AppError::Localized {
909                key: "error.invalidPricingMode",
910                ..
911            }
912        ));
913
914        Ok(())
915    }
916}