Files
claudia/src-tauri/src/commands/packycode_nodes.rs
2025-10-17 17:20:46 +08:00

291 lines
9.6 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 anyhow::Result;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
use tauri::command;
/// PackyCode 节点类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NodeType {
Direct, // 直连节点
Backup, // 备用节点
Emergency, // 紧急节点(非紧急情况不要使用)
}
/// PackyCode 节点信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackycodeNode {
pub name: String,
pub url: String,
pub node_type: NodeType,
pub description: String,
pub response_time: Option<u64>, // 响应时间(毫秒)
pub available: Option<bool>, // 是否可用
}
/// 节点测速结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeSpeedTestResult {
pub node: PackycodeNode,
pub response_time: u64,
pub success: bool,
pub error: Option<String>,
}
/// 获取所有 PackyCode 节点
pub fn get_all_nodes() -> Vec<PackycodeNode> {
vec![
// 公交车节点 (Bus Service)
PackycodeNode {
name: "公交车默认节点".to_string(),
url: "https://api.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "默认公交车直连节点".to_string(),
response_time: None,
available: None,
},
PackycodeNode {
name: "公交车 HK-CN2".to_string(),
url: "https://api-hk-cn2.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "香港 CN2 线路(公交车)".to_string(),
response_time: None,
available: None,
},
PackycodeNode {
name: "公交车 HK-G".to_string(),
url: "https://api-hk-g.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "香港 G 线路(公交车)".to_string(),
response_time: None,
available: None,
},
PackycodeNode {
name: "公交车 CF-Pro".to_string(),
url: "https://api-cf-pro.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "CloudFlare Pro 线路(公交车)".to_string(),
response_time: None,
available: None,
},
PackycodeNode {
name: "公交车 US-CN2".to_string(),
url: "https://api-us-cn2.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "美国 CN2 线路(公交车)".to_string(),
response_time: None,
available: None,
},
// 滴滴车节点 (Taxi Service)
PackycodeNode {
name: "滴滴车默认节点".to_string(),
url: "https://share-api.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "默认滴滴车直连节点".to_string(),
response_time: None,
available: None,
},
PackycodeNode {
name: "滴滴车 HK-CN2".to_string(),
url: "https://share-api-hk-cn2.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "香港 CN2 线路(滴滴车)".to_string(),
response_time: None,
available: None,
},
PackycodeNode {
name: "滴滴车 HK-G".to_string(),
url: "https://share-api-hk-g.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "香港 G 线路(滴滴车)".to_string(),
response_time: None,
available: None,
},
PackycodeNode {
name: "滴滴车 CF-Pro".to_string(),
url: "https://share-api-cf-pro.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "CloudFlare Pro 线路(滴滴车)".to_string(),
response_time: None,
available: None,
},
PackycodeNode {
name: "滴滴车 US-CN2".to_string(),
url: "https://share-api-us-cn2.packycode.com".to_string(),
node_type: NodeType::Direct,
description: "美国 CN2 线路(滴滴车)".to_string(),
response_time: None,
available: None,
},
]
}
/// 测试单个节点速度(仅测试网络延时,不需要认证)
async fn test_node_speed(node: &PackycodeNode) -> NodeSpeedTestResult {
let client = Client::builder()
.timeout(Duration::from_secs(3)) // 减少超时时间
.danger_accept_invalid_certs(true) // 接受自签名证书
.build()
.unwrap_or_else(|_| Client::new());
let start_time = Instant::now();
// 使用 GET 请求到根路径,这是最简单的 ping 测试
// 不需要 token只测试网络延迟
let url = format!("{}/", node.url.trim_end_matches('/'));
match client
.get(&url)
.timeout(Duration::from_secs(3))
.send()
.await
{
Ok(_response) => {
let response_time = start_time.elapsed().as_millis() as u64;
// 只要能连接到服务器就算成功(不管状态码)
// 因为我们只是测试延迟,不是测试 API 功能
let success = response_time < 3000; // 小于 3 秒就算成功
NodeSpeedTestResult {
node: PackycodeNode {
response_time: Some(response_time),
available: Some(success),
..node.clone()
},
response_time,
success,
error: if success {
None
} else {
Some("响应时间过长".to_string())
},
}
}
Err(e) => {
let response_time = start_time.elapsed().as_millis() as u64;
// 如果是超时错误,特别标记
let error_msg = if e.is_timeout() {
"连接超时".to_string()
} else if e.is_connect() {
"无法连接".to_string()
} else {
format!("网络错误: {}", e)
};
NodeSpeedTestResult {
node: PackycodeNode {
response_time: Some(response_time),
available: Some(false),
..node.clone()
},
response_time,
success: false,
error: Some(error_msg),
}
}
}
}
/// 测试所有节点速度(不需要 token只测试延迟
#[command]
pub async fn test_all_packycode_nodes() -> Result<Vec<NodeSpeedTestResult>, String> {
let nodes = get_all_nodes();
let mut results = Vec::new();
// 并发测试所有节点
let futures: Vec<_> = nodes.iter().map(|node| test_node_speed(node)).collect();
// 等待所有测试完成
for (i, future) in futures.into_iter().enumerate() {
let result = future.await;
log::info!(
"节点 {} 测速结果: {}ms, 成功: {}",
nodes[i].name,
result.response_time,
result.success
);
results.push(result);
}
// 按响应时间排序(成功的节点优先,然后按延迟排序)
results.sort_by(|a, b| match (a.success, b.success) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => a.response_time.cmp(&b.response_time),
});
Ok(results)
}
/// 自动选择最快的节点(仅从直连和备用中选择,不需要 token
#[command]
pub async fn auto_select_best_node() -> Result<PackycodeNode, String> {
let nodes = get_all_nodes();
let mut best_node: Option<(PackycodeNode, u64)> = None;
// 只测试直连和备用节点,过滤掉紧急节点
let test_nodes: Vec<_> = nodes
.into_iter()
.filter(|n| matches!(n.node_type, NodeType::Direct | NodeType::Backup))
.collect();
log::info!("开始测试 {} 个节点...", test_nodes.len());
// 并发测试所有节点
let futures: Vec<_> = test_nodes
.iter()
.map(|node| test_node_speed(node))
.collect();
// 收集结果并找出最佳节点
for (i, future) in futures.into_iter().enumerate() {
let result = future.await;
log::info!(
"节点 {} - 延迟: {}ms, 可用: {}",
test_nodes[i].name,
result.response_time,
result.success
);
if result.success {
match &best_node {
None => {
log::info!("初始最佳节点: {}", result.node.name);
best_node = Some((result.node, result.response_time));
}
Some((_, best_time)) if result.response_time < *best_time => {
log::info!(
"发现更快节点: {} ({}ms < {}ms)",
result.node.name,
result.response_time,
best_time
);
best_node = Some((result.node, result.response_time));
}
_ => {}
}
}
}
match best_node {
Some((node, time)) => {
log::info!("最佳节点选择: {} (延迟: {}ms)", node.name, time);
Ok(node)
}
None => {
log::error!("没有找到可用的节点");
Err("没有找到可用的节点".to_string())
}
}
}
/// 获取节点列表(不测速)
#[command]
pub fn get_packycode_nodes() -> Vec<PackycodeNode> {
get_all_nodes()
}