refactor(proxy): remove global auto-start flag

- Remove global proxy auto-start flag from config and UI.
- Simplify per-app takeover start/stop and stop server when the last takeover is disabled.
- Restore live takeover detection used for crash recovery.
- Keep proxy_config.enabled column but always write 0 for compatibility.
- Tests: not run (not requested).
This commit is contained in:
Jason
2025-12-20 08:48:59 +08:00
parent b6ff721d67
commit ba59483b33
7 changed files with 60 additions and 142 deletions

View File

@@ -11,7 +11,7 @@ use crate::store::AppState;
pub async fn start_proxy_server(
state: tauri::State<'_, AppState>,
) -> Result<ProxyServerInfo, String> {
state.proxy_service.start(true).await
state.proxy_service.start().await
}
/// 启动代理服务器(带 Live 配置接管)

View File

@@ -16,19 +16,18 @@ impl Database {
let result = {
let conn = lock_conn!(self.conn);
conn.query_row(
"SELECT enabled, listen_address, listen_port, max_retries,
"SELECT listen_address, listen_port, max_retries,
request_timeout, enable_logging, live_takeover_active
FROM proxy_config WHERE id = 1",
[],
|row| {
Ok(ProxyConfig {
enabled: row.get::<_, i32>(0)? != 0,
listen_address: row.get(1)?,
listen_port: row.get::<_, i32>(2)? as u16,
max_retries: row.get::<_, i32>(3)? as u8,
request_timeout: row.get::<_, i32>(4)? as u64,
enable_logging: row.get::<_, i32>(5)? != 0,
live_takeover_active: row.get::<_, i32>(6).unwrap_or(0) != 0,
listen_address: row.get(0)?,
listen_port: row.get::<_, i32>(1)? as u16,
max_retries: row.get::<_, i32>(2)? as u8,
request_timeout: row.get::<_, i32>(3)? as u64,
enable_logging: row.get::<_, i32>(4)? != 0,
live_takeover_active: row.get::<_, i32>(5).unwrap_or(0) != 0,
})
},
)
@@ -57,7 +56,7 @@ impl Database {
COALESCE((SELECT created_at FROM proxy_config WHERE id = 1), datetime('now')),
datetime('now'))",
rusqlite::params![
if config.enabled { 1 } else { 0 },
0, // 已移除自动启用逻辑,保留列但固定为 0
config.listen_address,
config.listen_port as i32,
config.max_retries as i32,

View File

@@ -524,12 +524,12 @@ pub fn run() {
}
}
// 异常退出恢复 + 自动启动代理服务器
// 异常退出恢复
let app_handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
let state = app_handle.state::<AppState>();
// 检查是否有 Live 备份(表示上次接管状态)
// 检查是否有 Live 备份(表示上次异常退出时可能处于接管状态)
let has_backups = match state.db.has_any_live_backup().await {
Ok(v) => v,
Err(e) => {
@@ -537,55 +537,15 @@ pub fn run() {
false
}
};
// 检查 Live 配置是否仍处于被接管状态(包含占位符)
let live_taken_over = state.proxy_service.detect_takeover_in_live_configs();
// 获取代理配置
let proxy_config = match state.db.get_proxy_config().await {
Ok(config) => Some(config),
Err(e) => {
log::error!("启动时获取代理配置失败: {e}");
None
}
};
if has_backups {
// 有备份说明上次退出时有接管状态
// 检查 Live 配置是否仍处于被接管状态(包含占位符)
let live_taken_over = state.proxy_service.detect_takeover_in_live_configs();
if live_taken_over {
// Live 配置仍是接管状态,尝试重新启动代理服务以恢复接管
log::info!("检测到上次接管状态,正在重新启动代理服务...");
match state.proxy_service.start(false).await {
Ok(info) => {
log::info!("代理服务器已恢复启动: {}:{}", info.address, info.port);
}
Err(e) => {
// 启动失败,恢复 Live 配置
log::error!("恢复代理服务失败: {e},正在恢复 Live 配置...");
if let Err(e) = state.proxy_service.recover_from_crash().await {
log::error!("恢复 Live 配置失败: {e}");
} else {
log::info!("Live 配置已恢复");
}
}
}
if has_backups || live_taken_over {
log::warn!("检测到上次异常退出(存在接管残留),正在恢复 Live 配置...");
if let Err(e) = state.proxy_service.recover_from_crash().await {
log::error!("恢复 Live 配置失败: {e}");
} else {
// Live 配置已经是正常状态,清理残留备份
log::info!("Live 配置已是正常状态,清理残留备份...");
if let Err(e) = state.db.delete_all_live_backups().await {
log::warn!("清理残留 Live 备份失败: {e}");
}
}
} else if let Some(config) = proxy_config {
// 没有备份,检查是否需要自动启动代理服务器(总开关)
if config.enabled {
log::info!("代理服务配置为启用,正在启动...");
match state.proxy_service.start(true).await {
Ok(info) => {
log::info!("代理服务器自动启动成功: {}:{}", info.address, info.port)
}
Err(e) => log::error!("代理服务器自动启动失败: {e}"),
}
log::info!("Live 配置已恢复");
}
}
});
@@ -855,20 +815,26 @@ pub async fn cleanup_before_exit(app_handle: &tauri::AppHandle) {
if proxy_service.is_running().await {
log::info!("检测到代理服务器正在运行,开始清理...");
// 检查是否处于 Live 接管模式
if let Ok(is_takeover) = state.db.is_live_takeover_active().await {
if is_takeover {
// 接管模式:停止并恢复配置
if let Err(e) = proxy_service.stop_with_restore().await {
log::error!("退出时恢复 Live 配置失败: {e}");
} else {
log::info!("已恢复 Live 配置");
}
// 检查是否存在 Live 备份(有则视为接管)
let has_backups = match state.db.has_any_live_backup().await {
Ok(v) => v,
Err(e) => {
log::error!("退出时检查 Live 备份失败: {e}");
false
}
};
if has_backups {
// 接管模式:停止并恢复配置
if let Err(e) = proxy_service.stop_with_restore().await {
log::error!("退出时恢复 Live 配置失败: {e}");
} else {
// 非接管模式:仅停止代理
if let Err(e) = proxy_service.stop().await {
log::error!("退出时停止代理失败: {e}");
}
log::info!("已恢复 Live 配置");
}
} else {
// 非接管模式:仅停止代理
if let Err(e) = proxy_service.stop().await {
log::error!("退出时停止代理失败: {e}");
}
}

View File

@@ -3,8 +3,6 @@ use serde::{Deserialize, Serialize};
/// 代理服务器配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyConfig {
/// 是否启用代理服务
pub enabled: bool,
/// 监听地址
pub listen_address: String,
/// 监听端口
@@ -23,7 +21,6 @@ pub struct ProxyConfig {
impl Default for ProxyConfig {
fn default() -> Self {
Self {
enabled: false,
listen_address: "127.0.0.1".to_string(),
listen_port: 15721, // 使用较少占用的高位端口
max_retries: 3,

View File

@@ -41,31 +41,16 @@ impl ProxyService {
}
/// 启动代理服务器
///
/// - `persist_enabled = true`:将 `proxy_config.enabled` 持久化为启用(用于“总开关”)
/// - `persist_enabled = false`:仅在当前进程启动代理服务(用于“按 App 接管”自动启动)
pub async fn start(&self, persist_enabled: bool) -> Result<ProxyServerInfo, String> {
pub async fn start(&self) -> Result<ProxyServerInfo, String> {
// 1. 获取配置
let mut config = self
let config = self
.db
.get_proxy_config()
.await
.map_err(|e| format!("获取代理配置失败: {e}"))?;
// 2. 仅在需要时持久化 enabled避免“按 App 接管”自动启动时误打开总开关)
if persist_enabled {
config.enabled = true;
}
// 3. 若已在运行:确保持久化状态(如需要)并返回当前信息
if let Some(server) = self.server.read().await.as_ref() {
if persist_enabled {
self.db
.update_proxy_config(config)
.await
.map_err(|e| format!("保存代理配置失败: {e}"))?;
}
let status = server.get_status().await;
return Ok(ProxyServerInfo {
address: status.address,
@@ -86,14 +71,6 @@ impl ProxyService {
// 5. 保存服务器实例
*self.server.write().await = Some(server);
// 6. 持久化 enabled 状态(仅总开关)
if persist_enabled {
self.db
.update_proxy_config(config)
.await
.map_err(|e| format!("保存代理配置失败: {e}"))?;
}
log::info!("代理服务器已启动: {}:{}", info.address, info.port);
Ok(info)
}
@@ -138,7 +115,7 @@ impl ProxyService {
}
// 5. 启动代理服务器
match self.start(true).await {
match self.start().await {
Ok(info) => Ok(info),
Err(e) => {
// 启动失败,恢复原始配置
@@ -187,16 +164,16 @@ impl ProxyService {
/// 为指定应用开启/关闭 Live 接管
///
/// - 开启:自动启动代理服务(不影响总开关持久化),仅接管当前 app 的 Live 配置
/// - 关闭:仅恢复当前 app 的 Live 配置;若总开关未开启且无其它接管,则自动停止代理服务
/// - 开启:自动启动代理服务,仅接管当前 app 的 Live 配置
/// - 关闭:仅恢复当前 app 的 Live 配置;若无其它接管,则自动停止代理服务
pub async fn set_takeover_for_app(&self, app_type: &str, enabled: bool) -> Result<(), String> {
let app = AppType::from_str(app_type).map_err(|e| format!("无效的应用类型: {e}"))?;
let app_type_str = app.as_str();
if enabled {
// 1) 代理服务未运行则自动启动(不持久化总开关)
// 1) 代理服务未运行则自动启动
if !self.is_running().await {
self.start(false).await?;
self.start().await?;
}
// 2) 已接管则直接返回(幂等)
@@ -252,7 +229,7 @@ impl ProxyService {
.await
.map_err(|e| format!("删除 {app_type_str} Live 备份失败: {e}"))?;
// 3) 若无其它接管,更新旧标志,并在总开关未开启时停止代理服务
// 3) 若无其它接管,更新旧标志,并停止代理服务
let has_any_backup = self
.db
.has_any_live_backup()
@@ -261,13 +238,7 @@ impl ProxyService {
if !has_any_backup {
let _ = self.db.set_live_takeover_active(false).await;
let master_enabled = self
.db
.get_proxy_config()
.await
.map_err(|e| format!("获取代理配置失败: {e}"))?
.enabled;
if !master_enabled && self.is_running().await {
if self.is_running().await {
// 此时没有任何 app 处于接管状态,停止服务即可
let _ = self.stop().await;
}
@@ -495,12 +466,6 @@ impl ProxyService {
.await
.map_err(|e| format!("停止代理服务器失败: {e}"))?;
// 将 enabled 设为 false避免下次启动时自动开启
if let Ok(mut config) = self.db.get_proxy_config().await {
config.enabled = false;
let _ = self.db.update_proxy_config(config).await;
}
log::info!("代理服务器已停止");
Ok(())
} else {
@@ -513,15 +478,6 @@ impl ProxyService {
// 1. 停止代理服务器(即使未运行也继续执行恢复逻辑)
if let Err(e) = self.stop().await {
log::warn!("停止代理服务器失败(将继续恢复 Live 配置): {e}");
// stop() 只有在 server 实例存在时才会把 enabled 设为 false
// 这里兜底确保“总开关关闭”能落盘关闭状态。
if let Ok(mut config) = self.db.get_proxy_config().await {
if config.enabled {
config.enabled = false;
let _ = self.db.update_proxy_config(config).await;
}
}
}
// 2. 恢复原始 Live 配置
@@ -946,7 +902,7 @@ impl ProxyService {
/// 从异常退出中恢复(启动时调用)
///
/// 检测到 live_takeover_active=true 但代理未运行时调用此方法。
/// 检测到 Live 备份残留时调用此方法。
/// 会恢复 Live 配置、清除接管标志、删除备份。
pub async fn recover_from_crash(&self) -> Result<(), String> {
// 1. 恢复 Live 配置
@@ -970,7 +926,7 @@ impl ProxyService {
/// 检测 Live 配置是否处于“被接管”的残留状态
///
/// 用于兜底处理:当数据库标志未写入成功(或旧版本遗留)但 Live 文件已经写成代理占位符时,
/// 用于兜底处理:当数据库备份缺失但 Live 文件已经写成代理占位符时,
/// 启动流程可以据此触发恢复逻辑。
pub fn detect_takeover_in_live_configs(&self) -> bool {
if let Ok(config) = self.read_claude_live() {
@@ -1255,9 +1211,8 @@ impl ProxyService {
.await
.map_err(|e| format!("获取代理配置失败: {e}"))?;
// 保存到数据库(保持 enabled 和 live_takeover_active 状态不变)
// 保存到数据库(保持 live_takeover_active 状态不变)
let mut new_config = config.clone();
new_config.enabled = previous.enabled;
new_config.live_takeover_active = previous.live_takeover_active;
self.db

View File

@@ -26,8 +26,11 @@ import { useTranslation } from "react-i18next";
import type { TFunction } from "i18next";
import type { ProxyConfig } from "@/types/proxy";
// 表单数据类型(包含 enabled 字段,该字段由后端自动管理
type ProxyConfigForm = Omit<ProxyConfig, "enabled">;
// 表单数据类型(包含可编辑字段
type ProxyConfigForm = Pick<
ProxyConfig,
"listen_address" | "listen_port" | "max_retries" | "request_timeout" | "enable_logging"
>;
const createProxyConfigSchema = (t: TFunction) => {
const requestTimeoutSchema = z
@@ -120,19 +123,18 @@ export function ProxySettingsDialog({
useEffect(() => {
if (config) {
form.reset({
...config,
listen_address: config.listen_address,
listen_port: config.listen_port,
max_retries: config.max_retries,
request_timeout: config.request_timeout,
enable_logging: config.enable_logging,
});
}
}, [config, form]);
const onSubmit = async (data: ProxyConfigForm) => {
try {
// 添加 enabled 字段(从当前配置中获取,保持不变)
const configToSave: ProxyConfig = {
...data,
enabled: config?.enabled ?? true,
};
await updateConfig(configToSave);
await updateConfig(data);
closePanel();
} catch (error) {
console.error("Save config failed:", error);

View File

@@ -1,5 +1,4 @@
export interface ProxyConfig {
enabled: boolean;
listen_address: string;
listen_port: number;
max_retries: number;