Files
claudia/src-tauri/src/commands/relay_stations.rs
YoVinchen e4245a6fed
Some checks are pending
Build Linux / Build Linux x86_64 (push) Waiting to run
Build Test / Build Test (Linux) (push) Waiting to run
Build Test / Build Test (Windows) (push) Waiting to run
Build Test / Build Test (macOS) (push) Waiting to run
Build Test / Build Test Summary (push) Blocked by required conditions
配置导入导出
2025-09-06 18:43:52 +08:00

910 lines
30 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tauri::{command, State};
use anyhow::Result;
use chrono::Utc;
use rusqlite::{params, Connection, Row, OptionalExtension};
use uuid::Uuid;
use crate::commands::agents::AgentDb;
use crate::i18n;
use crate::claude_config;
/// 中转站适配器类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RelayStationAdapter {
Packycode, // PackyCode 平台(放在第一位)
Deepseek, // DeepSeek v3.1
Glm, // 智谱GLM
Qwen, // 千问Qwen
Kimi, // Kimi k2
Custom, // 自定义简单配置
}
impl RelayStationAdapter {
pub fn as_str(&self) -> &str {
match self {
RelayStationAdapter::Packycode => "packycode",
RelayStationAdapter::Deepseek => "deepseek",
RelayStationAdapter::Glm => "glm",
RelayStationAdapter::Qwen => "qwen",
RelayStationAdapter::Kimi => "kimi",
RelayStationAdapter::Custom => "custom",
}
}
}
/// 认证方式
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthMethod {
BearerToken, // Bearer Token 认证(推荐)
ApiKey, // API Key 认证
Custom, // 自定义认证方式
}
/// 中转站配置(完整版本)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayStation {
pub id: String, // 唯一标识符
pub name: String, // 显示名称
pub description: Option<String>, // 描述信息
pub api_url: String, // API 基础 URL
pub adapter: RelayStationAdapter, // 适配器类型
pub auth_method: AuthMethod, // 认证方式
pub system_token: String, // 系统令牌
pub user_id: Option<String>, // 用户 ID可选
pub adapter_config: Option<HashMap<String, serde_json::Value>>, // 适配器特定配置
pub enabled: bool, // 启用状态
pub created_at: i64, // 创建时间
pub updated_at: i64, // 更新时间
}
/// 创建中转站请求(无自动生成字段)
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateRelayStationRequest {
pub name: String,
pub description: Option<String>,
pub api_url: String,
pub adapter: RelayStationAdapter,
pub auth_method: AuthMethod,
pub system_token: String,
pub user_id: Option<String>,
pub adapter_config: Option<HashMap<String, serde_json::Value>>,
pub enabled: bool,
}
/// 更新中转站请求
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateRelayStationRequest {
pub id: String,
pub name: String,
pub description: Option<String>,
pub api_url: String,
pub adapter: RelayStationAdapter,
pub auth_method: AuthMethod,
pub system_token: String,
pub user_id: Option<String>,
pub adapter_config: Option<HashMap<String, serde_json::Value>>,
pub enabled: bool,
}
/// 站点信息(统一格式)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StationInfo {
pub name: String, // 站点名称
pub announcement: Option<String>, // 公告信息
pub api_url: String, // API 地址
pub version: Option<String>, // 版本信息
pub metadata: Option<HashMap<String, serde_json::Value>>, // 扩展元数据
pub quota_per_unit: Option<i64>, // 单位配额(用于价格转换)
}
/// 用户信息(统一格式)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
pub user_id: String, // 用户 ID
pub username: Option<String>, // 用户名
pub email: Option<String>, // 邮箱
pub balance_remaining: Option<f64>, // 剩余余额(美元)
pub amount_used: Option<f64>, // 已用金额(美元)
pub request_count: Option<i64>, // 请求次数
pub status: Option<String>, // 账户状态
pub metadata: Option<HashMap<String, serde_json::Value>>, // 原始数据
}
/// 连接测试结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionTestResult {
pub success: bool, // 连接是否成功
pub response_time: Option<u64>, // 响应时间(毫秒)
pub message: String, // 结果消息
pub error: Option<String>, // 错误信息
}
/// Token 信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenInfo {
pub id: String,
pub name: String,
pub token: String,
pub quota: Option<i64>,
pub used_quota: Option<i64>,
pub status: String,
pub created_at: i64,
pub updated_at: i64,
}
/// Token 分页响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenPaginationResponse {
pub tokens: Vec<TokenInfo>,
pub total: i64,
pub page: usize,
pub size: usize,
pub has_more: bool,
}
impl RelayStation {
pub fn from_row(row: &Row) -> Result<Self, rusqlite::Error> {
let adapter_str: String = row.get("adapter")?;
let auth_method_str: String = row.get("auth_method")?;
let adapter_config_str: Option<String> = row.get("adapter_config")?;
let adapter = serde_json::from_str(&format!("\"{}\"", adapter_str))
.map_err(|_| rusqlite::Error::InvalidColumnType(0, "adapter".to_string(), rusqlite::types::Type::Text))?;
let auth_method = serde_json::from_str(&format!("\"{}\"", auth_method_str))
.map_err(|_| rusqlite::Error::InvalidColumnType(0, "auth_method".to_string(), rusqlite::types::Type::Text))?;
let adapter_config = if let Some(config_str) = adapter_config_str {
if config_str.trim().is_empty() {
None
} else {
Some(serde_json::from_str(&config_str)
.map_err(|_| rusqlite::Error::InvalidColumnType(0, "adapter_config".to_string(), rusqlite::types::Type::Text))?)
}
} else {
None
};
Ok(RelayStation {
id: row.get("id")?,
name: row.get("name")?,
description: row.get("description")?,
api_url: row.get("api_url")?,
adapter,
auth_method,
system_token: row.get("system_token")?,
user_id: row.get("user_id")?,
adapter_config,
enabled: row.get::<_, i32>("enabled")? == 1,
created_at: row.get("created_at")?,
updated_at: row.get("updated_at")?,
})
}
}
/// 初始化中转站数据库表
pub fn init_relay_stations_tables(conn: &Connection) -> Result<()> {
// 中转站表
conn.execute(
r#"
CREATE TABLE IF NOT EXISTS relay_stations (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
api_url TEXT NOT NULL,
adapter TEXT NOT NULL,
auth_method TEXT NOT NULL,
system_token TEXT NOT NULL,
user_id TEXT,
adapter_config TEXT,
enabled INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
"#,
[],
)?;
// 中转站使用日志表
conn.execute(
r#"
CREATE TABLE IF NOT EXISTS relay_station_usage_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
station_id TEXT NOT NULL,
request_type TEXT NOT NULL,
response_time INTEGER,
success INTEGER NOT NULL,
error_message TEXT,
created_at INTEGER NOT NULL,
FOREIGN KEY (station_id) REFERENCES relay_stations (id) ON DELETE CASCADE
)
"#,
[],
)?;
log::info!("Relay stations database tables initialized");
Ok(())
}
/// 获取所有中转站
#[command]
pub async fn relay_stations_list(db: State<'_, AgentDb>) -> Result<Vec<RelayStation>, String> {
let conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
// 确保表存在
init_relay_stations_tables(&conn).map_err(|e| {
log::error!("Failed to initialize relay stations tables: {}", e);
i18n::t("database.init_failed")
})?;
let mut stmt = conn.prepare("SELECT * FROM relay_stations ORDER BY created_at DESC")
.map_err(|e| {
log::error!("Failed to prepare statement: {}", e);
i18n::t("database.query_failed")
})?;
let stations = stmt.query_map([], |row| RelayStation::from_row(row))
.map_err(|e| {
log::error!("Failed to query relay stations: {}", e);
i18n::t("database.query_failed")
})?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| {
log::error!("Failed to collect relay stations: {}", e);
i18n::t("database.query_failed")
})?;
log::info!("Retrieved {} relay stations", stations.len());
Ok(stations)
}
/// 获取单个中转站
#[command]
pub async fn relay_station_get(
id: String,
db: State<'_, AgentDb>
) -> Result<RelayStation, String> {
let conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
let mut stmt = conn.prepare("SELECT * FROM relay_stations WHERE id = ?1")
.map_err(|e| {
log::error!("Failed to prepare statement: {}", e);
i18n::t("database.query_failed")
})?;
let station = stmt.query_row(params![id], |row| RelayStation::from_row(row))
.map_err(|e| {
log::error!("Failed to get relay station {}: {}", id, e);
i18n::t("relay_station.not_found")
})?;
log::info!("Retrieved relay station: {}", id);
Ok(station)
}
/// 创建中转站
#[command]
pub async fn relay_station_create(
request: CreateRelayStationRequest,
db: State<'_, AgentDb>
) -> Result<RelayStation, String> {
let conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
// 确保表存在
init_relay_stations_tables(&conn).map_err(|e| {
log::error!("Failed to initialize relay stations tables: {}", e);
i18n::t("database.init_failed")
})?;
// 验证输入
validate_relay_station_request(&request.name, &request.api_url, &request.system_token)?;
let id = Uuid::new_v4().to_string();
let now = Utc::now().timestamp();
let adapter_str = serde_json::to_string(&request.adapter)
.map_err(|_| i18n::t("relay_station.invalid_adapter"))?
.trim_matches('"').to_string();
let auth_method_str = serde_json::to_string(&request.auth_method)
.map_err(|_| i18n::t("relay_station.invalid_auth_method"))?
.trim_matches('"').to_string();
let adapter_config_str = request.adapter_config.as_ref()
.map(|config| serde_json::to_string(config))
.transpose()
.map_err(|_| i18n::t("relay_station.invalid_config"))?;
// 如果要启用这个新中转站,先禁用所有其他中转站
if request.enabled {
conn.execute(
"UPDATE relay_stations SET enabled = 0",
[],
).map_err(|e| {
log::error!("Failed to disable other relay stations: {}", e);
i18n::t("relay_station.create_failed")
})?;
}
conn.execute(
r#"
INSERT INTO relay_stations
(id, name, description, api_url, adapter, auth_method, system_token, user_id, adapter_config, enabled, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)
"#,
params![
id,
request.name,
request.description,
request.api_url,
adapter_str,
auth_method_str,
request.system_token,
request.user_id,
adapter_config_str,
if request.enabled { 1 } else { 0 },
now,
now
],
).map_err(|e| {
log::error!("Failed to create relay station: {}", e);
i18n::t("relay_station.create_failed")
})?;
let station = RelayStation {
id: id.clone(),
name: request.name,
description: request.description,
api_url: request.api_url,
adapter: request.adapter,
auth_method: request.auth_method,
system_token: request.system_token,
user_id: request.user_id,
adapter_config: request.adapter_config,
enabled: request.enabled,
created_at: now,
updated_at: now,
};
log::info!("Created relay station: {} ({})", station.name, id);
Ok(station)
}
/// 更新中转站
#[command]
pub async fn relay_station_update(
request: UpdateRelayStationRequest,
db: State<'_, AgentDb>
) -> Result<RelayStation, String> {
let conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
// 验证输入
validate_relay_station_request(&request.name, &request.api_url, &request.system_token)?;
let now = Utc::now().timestamp();
let adapter_str = serde_json::to_string(&request.adapter)
.map_err(|_| i18n::t("relay_station.invalid_adapter"))?
.trim_matches('"').to_string();
let auth_method_str = serde_json::to_string(&request.auth_method)
.map_err(|_| i18n::t("relay_station.invalid_auth_method"))?
.trim_matches('"').to_string();
let adapter_config_str = request.adapter_config.as_ref()
.map(|config| serde_json::to_string(config))
.transpose()
.map_err(|_| i18n::t("relay_station.invalid_config"))?;
// 如果要启用这个中转站,先禁用所有其他中转站
if request.enabled {
conn.execute(
"UPDATE relay_stations SET enabled = 0 WHERE id != ?1",
params![request.id],
).map_err(|e| {
log::error!("Failed to disable other relay stations: {}", e);
i18n::t("relay_station.update_failed")
})?;
}
let rows_affected = conn.execute(
r#"
UPDATE relay_stations
SET name = ?2, description = ?3, api_url = ?4, adapter = ?5, auth_method = ?6,
system_token = ?7, user_id = ?8, adapter_config = ?9, enabled = ?10, updated_at = ?11
WHERE id = ?1
"#,
params![
request.id,
request.name,
request.description,
request.api_url,
adapter_str,
auth_method_str,
request.system_token,
request.user_id,
adapter_config_str,
if request.enabled { 1 } else { 0 },
now
],
).map_err(|e| {
log::error!("Failed to update relay station: {}", e);
i18n::t("relay_station.update_failed")
})?;
if rows_affected == 0 {
return Err(i18n::t("relay_station.not_found"));
}
let station = RelayStation {
id: request.id.clone(),
name: request.name,
description: request.description,
api_url: request.api_url,
adapter: request.adapter,
auth_method: request.auth_method,
system_token: request.system_token,
user_id: request.user_id,
adapter_config: request.adapter_config,
enabled: request.enabled,
created_at: 0, // 不重要,前端可以重新获取
updated_at: now,
};
log::info!("Updated relay station: {} ({})", station.name, request.id);
Ok(station)
}
/// 删除中转站
#[command]
pub async fn relay_station_delete(
id: String,
db: State<'_, AgentDb>
) -> Result<String, String> {
let conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
let rows_affected = conn.execute("DELETE FROM relay_stations WHERE id = ?1", params![id])
.map_err(|e| {
log::error!("Failed to delete relay station: {}", e);
i18n::t("relay_station.delete_failed")
})?;
if rows_affected == 0 {
return Err(i18n::t("relay_station.not_found"));
}
log::info!("Deleted relay station: {}", id);
Ok(i18n::t("relay_station.delete_success"))
}
/// 切换中转站启用状态(确保只有一个中转站启用)
#[command]
pub async fn relay_station_toggle_enable(
id: String,
enabled: bool,
db: State<'_, AgentDb>
) -> Result<String, String> {
let conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
let now = Utc::now().timestamp();
// 如果要启用这个中转站,先禁用所有其他中转站
if enabled {
conn.execute(
"UPDATE relay_stations SET enabled = 0, updated_at = ?1 WHERE id != ?2",
params![now, id],
).map_err(|e| {
log::error!("Failed to disable other relay stations: {}", e);
i18n::t("relay_station.update_failed")
})?;
// 获取要启用的中转站信息
let station = relay_station_get_internal(&conn, &id)?;
// 将中转站配置应用到 Claude 配置文件
claude_config::apply_relay_station_to_config(&station).map_err(|e| {
log::error!("Failed to apply relay station config: {}", e);
format!("配置文件写入失败: {}", e)
})?;
} else {
// 如果禁用中转站,清除 Claude 配置中的相关设置
if let Err(e) = claude_config::clear_relay_station_from_config() {
log::error!("Failed to clear relay station config: {}", e);
} else {
log::info!("Cleared relay station config from Claude settings");
}
}
// 更新目标中转站的启用状态
let rows_affected = conn.execute(
"UPDATE relay_stations SET enabled = ?1, updated_at = ?2 WHERE id = ?3",
params![if enabled { 1 } else { 0 }, now, id],
).map_err(|e| {
log::error!("Failed to toggle relay station enable status: {}", e);
i18n::t("relay_station.update_failed")
})?;
if rows_affected == 0 {
return Err(i18n::t("relay_station.not_found"));
}
log::info!("Toggled relay station enable status: {} -> {}", id, enabled);
Ok(if enabled {
i18n::t("relay_station.enabled_success")
} else {
i18n::t("relay_station.disabled_success")
})
}
/// 内部方法:获取单个中转站
fn relay_station_get_internal(conn: &Connection, id: &str) -> Result<RelayStation, String> {
let mut stmt = conn.prepare(
"SELECT * FROM relay_stations WHERE id = ?1"
).map_err(|e| {
log::error!("Failed to prepare statement: {}", e);
i18n::t("database.query_failed")
})?;
let station = stmt.query_row(params![id], |row| {
RelayStation::from_row(row)
}).map_err(|e| {
log::error!("Failed to get relay station: {}", e);
i18n::t("relay_station.not_found")
})?;
Ok(station)
}
/// 输入验证
fn validate_relay_station_request(name: &str, api_url: &str, system_token: &str) -> Result<(), String> {
if name.trim().is_empty() {
return Err(i18n::t("relay_station.name_required"));
}
if api_url.trim().is_empty() {
return Err(i18n::t("relay_station.api_url_required"));
}
// 验证 URL 格式
let parsed_url = url::Url::parse(api_url)
.map_err(|_| i18n::t("relay_station.invalid_url"))?;
// 允许本地开发环境使用 HTTP
let is_localhost = parsed_url.host_str()
.map(|host| host == "localhost" || host == "127.0.0.1" || host == "::1" || host.starts_with("192.168.") || host.starts_with("10."))
.unwrap_or(false);
// 非本地环境必须使用 HTTPS
if !is_localhost && !api_url.starts_with("https://") {
return Err(i18n::t("relay_station.https_required"));
}
if system_token.trim().is_empty() {
return Err(i18n::t("relay_station.token_required"));
}
if system_token.len() < 10 {
return Err(i18n::t("relay_station.token_too_short"));
}
// 检查 Token 是否包含特殊字符
if system_token.chars().any(|c| c.is_whitespace() || c.is_control()) {
return Err(i18n::t("relay_station.token_invalid_chars"));
}
Ok(())
}
/// Token 脱敏显示
#[allow(dead_code)]
pub fn mask_token(token: &str) -> String {
if token.len() <= 8 {
"*".repeat(token.len())
} else {
format!("{}...{}", &token[..4], &token[token.len()-4..])
}
}
/// 手动同步中转站配置到 Claude 配置文件
#[command]
pub async fn relay_station_sync_config(
db: State<'_, AgentDb>
) -> Result<String, String> {
let conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
// 查找当前启用的中转站
let mut stmt = conn.prepare(
"SELECT * FROM relay_stations WHERE enabled = 1 LIMIT 1"
).map_err(|e| {
log::error!("Failed to prepare statement: {}", e);
i18n::t("database.query_failed")
})?;
let station_opt = stmt.query_row([], |row| {
RelayStation::from_row(row)
}).optional().map_err(|e| {
log::error!("Failed to query enabled relay station: {}", e);
i18n::t("database.query_failed")
})?;
if let Some(station) = station_opt {
// 应用中转站配置
claude_config::apply_relay_station_to_config(&station)
.map_err(|e| format!("配置同步失败: {}", e))?;
log::info!("Synced relay station {} config to Claude settings", station.name);
Ok(format!("已同步中转站 {} 的配置到 Claude 设置", station.name))
} else {
// 没有启用的中转站,清除配置
claude_config::clear_relay_station_from_config()
.map_err(|e| format!("清除配置失败: {}", e))?;
log::info!("Cleared relay station config from Claude settings");
Ok("已清除 Claude 设置中的中转站配置".to_string())
}
}
/// 恢复 Claude 配置备份
#[command]
pub async fn relay_station_restore_config() -> Result<String, String> {
claude_config::restore_claude_config()
.map_err(|e| format!("恢复配置失败: {}", e))?;
log::info!("Restored Claude config from backup");
Ok("已从备份恢复 Claude 配置".to_string())
}
/// 获取当前 Claude 配置中的 API 信息
#[command]
pub async fn relay_station_get_current_config() -> Result<HashMap<String, Option<String>>, String> {
let mut config = HashMap::new();
config.insert(
"api_url".to_string(),
claude_config::get_current_api_url().unwrap_or(None)
);
config.insert(
"api_token".to_string(),
claude_config::get_current_api_token().unwrap_or(None)
.map(|token: String| {
// 脱敏显示 token
mask_token(&token)
})
);
Ok(config)
}
/// 导出所有中转站配置
#[command]
pub async fn relay_stations_export(db: State<'_, AgentDb>) -> Result<Vec<RelayStation>, String> {
let conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
// 确保表存在
init_relay_stations_tables(&conn).map_err(|e| {
log::error!("Failed to initialize relay stations tables: {}", e);
i18n::t("database.init_failed")
})?;
let mut stmt = conn.prepare("SELECT * FROM relay_stations ORDER BY created_at DESC")
.map_err(|e| {
log::error!("Failed to prepare statement: {}", e);
i18n::t("database.query_failed")
})?;
let stations = stmt.query_map([], |row| RelayStation::from_row(row))
.map_err(|e| {
log::error!("Failed to query relay stations: {}", e);
i18n::t("database.query_failed")
})?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| {
log::error!("Failed to collect relay stations: {}", e);
i18n::t("database.query_failed")
})?;
log::info!("Exported {} relay stations", stations.len());
Ok(stations)
}
/// 导入结果统计
#[derive(Debug, Serialize, Deserialize)]
pub struct ImportResult {
pub total: usize, // 总数
pub imported: usize, // 成功导入数
pub skipped: usize, // 跳过数(重复)
pub failed: usize, // 失败数
pub message: String, // 结果消息
}
/// 导入中转站配置
#[derive(Debug, Serialize, Deserialize)]
pub struct ImportRelayStationsRequest {
pub stations: Vec<CreateRelayStationRequest>,
pub clear_existing: bool, // 是否清除现有配置
}
#[command]
pub async fn relay_stations_import(
request: ImportRelayStationsRequest,
db: State<'_, AgentDb>
) -> Result<ImportResult, String> {
let mut conn = db.0.lock().map_err(|e| {
log::error!("Failed to acquire database lock: {}", e);
i18n::t("database.lock_failed")
})?;
// 确保表存在
init_relay_stations_tables(&conn).map_err(|e| {
log::error!("Failed to initialize relay stations tables: {}", e);
i18n::t("database.init_failed")
})?;
// 开始事务
let tx = conn.transaction().map_err(|e| {
log::error!("Failed to start transaction: {}", e);
i18n::t("database.transaction_failed")
})?;
// 如果需要清除现有配置
if request.clear_existing {
tx.execute("DELETE FROM relay_stations", [])
.map_err(|e| {
log::error!("Failed to clear existing relay stations: {}", e);
i18n::t("relay_station.clear_failed")
})?;
log::info!("Cleared existing relay stations");
}
// 获取现有的中转站列表(用于重复检查)
let existing_stations: Vec<(String, String)> = if !request.clear_existing {
let mut stmt = tx.prepare("SELECT api_url, system_token FROM relay_stations")
.map_err(|e| {
log::error!("Failed to prepare statement: {}", e);
i18n::t("database.query_failed")
})?;
let stations_iter = stmt.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})
.map_err(|e| {
log::error!("Failed to query existing stations: {}", e);
i18n::t("database.query_failed")
})?;
// 立即收集结果,避免生命周期问题
let mut existing = Vec::new();
for station_result in stations_iter {
match station_result {
Ok(station) => existing.push(station),
Err(e) => {
log::error!("Failed to read existing station: {}", e);
return Err(i18n::t("database.query_failed"));
}
}
}
existing
} else {
Vec::new()
};
// 导入新的中转站
let total = request.stations.len();
let mut imported_count = 0;
let mut skipped_count = 0;
let mut failed_count = 0;
let now = Utc::now().timestamp();
for station_request in request.stations {
// 验证输入
if let Err(e) = validate_relay_station_request(&station_request.name, &station_request.api_url, &station_request.system_token) {
log::warn!("Skipping invalid station {}: {}", station_request.name, e);
failed_count += 1;
continue;
}
// 检查是否重复(同时匹配 api_url 和 system_token
let is_duplicate = existing_stations.iter().any(|(url, token)| {
url == &station_request.api_url && token == &station_request.system_token
});
if is_duplicate {
log::info!("Skipping duplicate station: {} ({})", station_request.name, station_request.api_url);
skipped_count += 1;
continue;
}
let id = Uuid::new_v4().to_string();
let adapter_str = serde_json::to_string(&station_request.adapter)
.map_err(|_| i18n::t("relay_station.invalid_adapter"))?
.trim_matches('"').to_string();
let auth_method_str = serde_json::to_string(&station_request.auth_method)
.map_err(|_| i18n::t("relay_station.invalid_auth_method"))?
.trim_matches('"').to_string();
let adapter_config_str = station_request.adapter_config.as_ref()
.map(|config| serde_json::to_string(config))
.transpose()
.map_err(|_| i18n::t("relay_station.invalid_config"))?;
match tx.execute(
r#"
INSERT INTO relay_stations
(id, name, description, api_url, adapter, auth_method, system_token, user_id, adapter_config, enabled, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)
"#,
params![
id,
station_request.name,
station_request.description,
station_request.api_url,
adapter_str,
auth_method_str,
station_request.system_token,
station_request.user_id,
adapter_config_str,
if station_request.enabled { 1 } else { 0 },
now,
now
],
) {
Ok(_) => imported_count += 1,
Err(e) => {
log::error!("Failed to import relay station: {}", e);
failed_count += 1;
}
}
}
// 提交事务
tx.commit().map_err(|e| {
log::error!("Failed to commit transaction: {}", e);
i18n::t("database.transaction_failed")
})?;
let message = format!(
"导入完成:总计 {} 个,成功 {} 个,跳过 {} 个(重复),失败 {}",
total, imported_count, skipped_count, failed_count
);
log::info!("{}", message);
Ok(ImportResult {
total,
imported: imported_count,
skipped: skipped_count,
failed: failed_count,
message,
})
}