hi-client/lib/app/model/business/kr_outbound_item.dart
Rust 54b0cc44ff 修正vless协议配置问题
(cherry picked from commit 5cbb1654e22841b5078829e926946def1a10201e)
2025-11-01 23:15:52 -07:00

572 lines
22 KiB
Dart
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

import 'dart:convert';
import 'package:get/get.dart';
import '../response/kr_node_list.dart';
/// 表示出站项的模型类
class KROutboundItem {
int selected = 0; // 是否选中0=未选中1=选中)
String id = ""; // 标签
String tag = ""; // 标签
String serverAddr = ""; // 服务器地址
/// 初始化配置
Map<String, dynamic> config = {}; // 配置项
String city = ""; // 城市
String country = ""; // 国家
double latitude = 0.0; // 节点纬度
double latitudeCountry = 0.0; // 国家中心纬度
double longitude = 0.0; // 节点经度
double longitudeCountry = 0.0; // 国家中心经度
String protocol = "";
/// 延迟
RxInt urlTestDelay = 0.obs;
/// URL
String url = "";
@override
String toString() {
return 'KROutboundItem(tag: $tag, country: $country, delay: ${urlTestDelay.value}ms)';
}
/// 服务器类型
/// 构造函数,接受 KrNodeListItem 对象并初始化 KROutboundItem
KROutboundItem(KrNodeListItem nodeListItem) {
id = nodeListItem.id.toString();
protocol = nodeListItem.protocol;
latitude = nodeListItem.latitude;
latitudeCountry = nodeListItem.latitudeCountry;
longitude = nodeListItem.longitude;
longitudeCountry = nodeListItem.longitudeCountry;
tag = nodeListItem.name; // 设置标签
serverAddr = nodeListItem.serverAddr; // 设置服务器地址
city = nodeListItem.city; // 设置城市
country = nodeListItem.country; // 设置国家
// 🔧 优先使用直接字段构建配置新API格式
// 判断条件:如果有 port 和 serverAddr说明是新格式
if (nodeListItem.port > 0 && nodeListItem.serverAddr.isNotEmpty) {
print(' 节点 ${nodeListItem.name} 使用直接字段构建配置');
_buildConfigFromFields(nodeListItem);
return;
}
// 兜底:尝试解析 config 字段旧API格式
if (nodeListItem.config.isEmpty) {
print('❌ 节点 ${nodeListItem.name} 缺少配置信息无port或config');
config = {};
return;
}
late Map<String, dynamic> json;
try {
json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
} catch (e) {
print('❌ 节点 ${nodeListItem.name} 的 config 解析失败: $e,尝试使用直接字段');
print('📄 Config 内容: ${nodeListItem.config}');
_buildConfigFromFields(nodeListItem);
return;
}
switch (nodeListItem.protocol) {
case "vless":
final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {};
// 智能设置 server_name
String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) {
serverName = nodeListItem.serverAddr;
}
config = {
"type": "vless",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"uuid": nodeListItem.uuid,
if (json["flow"] != null && json["flow"] != "none")
"flow": json["flow"],
if (json["transport"] != null && json["transport"] != "tcp")
"transport": _buildTransport(json),
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {
"enabled": true,
"fingerprint": securityConfig["fingerprint"] ?? "chrome"
}
}
};
break;
case "vmess":
final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {};
// 智能设置 server_name
String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) {
serverName = nodeListItem.serverAddr;
}
config = {
"type": "vmess",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"uuid": nodeListItem.uuid,
"alter_id": 0,
"security": "auto",
if (json["transport"] != null && json["transport"] != "tcp")
"transport": _buildTransport(json),
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
break;
case "shadowsocks":
config = {
"type": "shadowsocks",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"method": json["method"],
"password": nodeListItem.uuid
};
break;
case "hysteria":
case "hysteria2":
// 后端的 "hysteria" 实际上是 Hysteria2 协议
final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {};
config = {
"type": "hysteria2",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"password": nodeListItem.uuid,
"up_mbps": 100,
"down_mbps": 100,
"obfs": {
"type": "salamander",
"password": json["obfs_password"] ?? nodeListItem.uuid
},
"tls": {
"enabled": true,
"server_name": securityConfig["sni"] ?? "",
"insecure": securityConfig["allow_insecure"] ?? true
}
};
break;
case "trojan":
final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {};
// 智能设置 server_name
String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) {
// 如果没有配置 SNI使用服务器地址
serverName = nodeListItem.serverAddr;
}
config = {
"type": "trojan",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"password": nodeListItem.uuid,
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
break;
}
// 检查 relayNode 是否为 JSON 字符串并解析
if (nodeListItem.relayNode.isNotEmpty && nodeListItem.relayMode != "none") {
final relayNodeJson = jsonDecode(nodeListItem.relayNode);
if (relayNodeJson is List && nodeListItem.relayMode != "none") {
// 随机选择一个元素
final randomNode = (relayNodeJson..shuffle()).first;
config["server"] = randomNode["host"]; // 提取 host
config["server_port"] = randomNode["port"]; // 提取 port
}
}
// 解析配置
}
/// 构建传输配置
Map<String, dynamic> _buildTransport(Map<String, dynamic> json) {
final transportType = json["transport"] as String?;
final transportConfig =
json["transport_config"] as Map<String, dynamic>? ?? {};
switch (transportType) {
case "ws":
return {
"type": "ws",
"path": transportConfig["path"] ?? "/",
if (transportConfig["host"] != null)
"headers": {"Host": transportConfig["host"]}
};
case "grpc":
return {
"type": "grpc",
"service_name": transportConfig["service_name"] ?? ""
};
case "http":
return {
"type": "http",
"host": [transportConfig["host"] ?? ""],
"path": transportConfig["path"] ?? "/"
};
default:
return {};
}
}
/// 直接从节点字段构建配置新API格式
void _buildConfigFromFields(KrNodeListItem nodeListItem) {
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
print('📋 节点详细信息:');
print(' - serverAddr: ${nodeListItem.serverAddr}');
print(' - port: ${nodeListItem.port}');
print(' - uuid: ${nodeListItem.uuid}');
print(' - method: ${nodeListItem.method}');
print(' - config: ${nodeListItem.config}');
print(' - protocols: ${nodeListItem.protocols}');
// 🔧 尝试从 config 字段解析 transport 配置
Map<String, dynamic>? transportConfig;
Map<String, dynamic>? securityConfig;
// 🔧 关键修复:优先从 protocols 字段解析配置
print('🔍 检查 protocols 字段: isEmpty=${nodeListItem.protocols.isEmpty}, length=${nodeListItem.protocols.length}');
if (nodeListItem.protocols.isNotEmpty) {
print('✅ protocols 字段不为空,开始解析...');
try {
final protocolsList = jsonDecode(nodeListItem.protocols) as List;
print('📄 解析到 protocols 数组,共 ${protocolsList.length} 个协议');
// 查找匹配当前协议类型的配置
Map<String, dynamic>? matchedProtocol;
for (var protocol in protocolsList) {
final protocolMap = protocol as Map<String, dynamic>;
final type = protocolMap['type']?.toString().toLowerCase() ?? '';
final enable = protocolMap['enable'] ?? true;
print(' 📋 协议: type=$type, enable=$enable');
// 匹配协议类型(注意 hysteria 和 hysteria2 都匹配 hysteria
if (type == nodeListItem.protocol.toLowerCase() ||
(nodeListItem.protocol == 'hysteria' && type == 'hysteria2') ||
(nodeListItem.protocol == 'hysteria2' && type == 'hysteria')) {
matchedProtocol = protocolMap;
print(' ✅ 找到匹配的协议配置: $type');
break;
}
}
if (matchedProtocol != null) {
// 提取 transport 配置
if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) {
final network = matchedProtocol['network'] ?? matchedProtocol['transport'];
print(' 📡 传输协议: $network');
if (network == 'ws' || network == 'websocket') {
transportConfig = {
'type': 'ws',
'path': matchedProtocol['ws_path'] ?? matchedProtocol['path'] ?? '/',
};
final host = matchedProtocol['ws_host'] ?? matchedProtocol['host'];
if (host != null && host.toString().isNotEmpty) {
transportConfig['headers'] = {'Host': host.toString()};
}
print(' ✅ WebSocket transport: $transportConfig');
} else if (network == 'grpc') {
transportConfig = {
'type': 'grpc',
'service_name': matchedProtocol['grpc_service_name'] ?? matchedProtocol['service_name'] ?? '',
};
print(' ✅ gRPC transport: $transportConfig');
} else if (network == 'http' || network == 'h2') {
transportConfig = {
'type': 'http',
'host': [matchedProtocol['http_host'] ?? matchedProtocol['host'] ?? ''],
'path': matchedProtocol['http_path'] ?? matchedProtocol['path'] ?? '/',
};
print(' ✅ HTTP transport: $transportConfig');
}
}
// 提取 security 配置
if (matchedProtocol['tls'] != null || matchedProtocol['security'] != null) {
// 🔧 关键修复:读取 security 字段判断是否启用 TLS
final security = matchedProtocol['security']?.toString().toLowerCase() ?? '';
final tlsEnabled = security == 'tls' || security == 'reality';
securityConfig = {
'tls_enabled': tlsEnabled,
'security_type': security, // 🔧 新增记录安全类型tls/reality
'sni': matchedProtocol['sni'] ?? matchedProtocol['server_name'],
'allow_insecure': matchedProtocol['allow_insecure'] ?? matchedProtocol['insecure'] ?? true,
'fingerprint': matchedProtocol['fingerprint'] ?? 'chrome',
};
// 🔧 新增:如果是 Reality提取 Reality 特定配置
if (security == 'reality') {
securityConfig['reality_enabled'] = true;
securityConfig['reality_public_key'] = matchedProtocol['reality_public_key'] ?? '';
securityConfig['reality_short_id'] = matchedProtocol['reality_short_id'] ?? '';
securityConfig['reality_server_name'] = matchedProtocol['reality_server_addr'] ?? matchedProtocol['reality_server_name'] ?? '';
print(' 🔒 Reality 配置:');
print(' - public_key: ${securityConfig['reality_public_key']}');
print(' - short_id: ${securityConfig['reality_short_id']}');
print(' - server_name: ${securityConfig['reality_server_name']}');
}
print(' ✅ Security config: security=$security, tls_enabled=$tlsEnabled, config=$securityConfig');
}
// 🔧 新增:提取 flow 字段VLESS xtls-rprx-vision 等需要)
if (matchedProtocol['flow'] != null && matchedProtocol['flow'].toString().isNotEmpty) {
securityConfig ??= {};
securityConfig['flow'] = matchedProtocol['flow'].toString();
print(' 📊 Flow: ${securityConfig['flow']}');
}
// 🔧 新增:提取 encryption 字段VLESS 需要)
if (matchedProtocol['encryption'] != null) {
securityConfig ??= {};
securityConfig['encryption'] = matchedProtocol['encryption'].toString();
print(' 🔐 Encryption: ${securityConfig['encryption']}');
}
}
} catch (e, stackTrace) {
print('⚠️ 解析 protocols 字段失败: $e');
print('📍 堆栈跟踪: $stackTrace');
}
} else {
print('❌ protocols 字段为空,跳过解析');
}
// 兜底:尝试从 config 字段解析旧API格式
if (transportConfig == null && nodeListItem.config.isNotEmpty) {
try {
final configJson = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
print('📄 解析到 config JSON: $configJson');
// 提取 transport 配置
if (configJson['transport'] != null && configJson['transport'] != 'tcp') {
transportConfig = _buildTransport(configJson);
print('✅ 从 config 找到 transport 配置: $transportConfig');
}
// 提取 security_config
if (configJson['security_config'] != null) {
securityConfig = configJson['security_config'] as Map<String, dynamic>;
print('✅ 从 config 找到 security_config: $securityConfig');
}
} catch (e) {
print('⚠️ 解析 config 字段失败: $e');
}
}
switch (nodeListItem.protocol) {
case "shadowsocks":
// 优先使用 protocols 解析出来的 cipher其次是 method 字段,最后才是默认值
String finalMethod = nodeListItem.method.isNotEmpty
? nodeListItem.method
: "2022-blake3-aes-256-gcm";
config = {
"type": "shadowsocks",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"method": finalMethod,
"password": nodeListItem.uuid
};
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
print('📄 使用加密方法: $finalMethod');
print('📄 完整配置: $config');
break;
case "vless":
// 判断是否为域名非IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
// 🔧 检测是否是 Reality 协议
final bool isReality = securityConfig?['security_type'] == 'reality';
final bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? false;
// 🔧 确定 server_nameReality 使用 reality_server_name普通 TLS 使用 sni
String serverName = nodeListItem.serverAddr;
if (isReality && securityConfig?['reality_server_name'] != null && securityConfig!['reality_server_name'].toString().isNotEmpty) {
serverName = securityConfig['reality_server_name'].toString();
print('🔒 Reality server_name: $serverName');
} else if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
serverName = securityConfig['sni'].toString();
}
print('🔐 VLESS 配置: TLS=$vlessTlsEnabled, Reality=$isReality, isDomain=$isDomain');
print('🔐 Server Name: $serverName');
if (isReality) {
print('🔒 Reality 详细配置:');
print(' - public_key: ${securityConfig?['reality_public_key']}');
print(' - short_id: ${securityConfig?['reality_short_id']}');
print(' - server_name: ${securityConfig?['reality_server_name']}');
print(' - flow: ${securityConfig?['flow']}');
}
// 🔧 检查是否有 flowvision 等流控)
final bool hasFlow = securityConfig?['flow'] != null &&
securityConfig!['flow'].toString().isNotEmpty &&
securityConfig['flow'].toString() != 'none';
config = {
"type": "vless",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"uuid": nodeListItem.uuid,
// 🔧 新增flow 字段xtls-rprx-vision 等)
if (hasFlow)
"flow": securityConfig['flow'].toString(),
// 🔧 关键修复packet_encoding 和 flow 互斥
// 只有在没有 flow 时才添加 packet_encoding
if (!hasFlow)
"packet_encoding": "",
if (transportConfig != null) "transport": transportConfig,
if (vlessTlsEnabled) "tls": {
"enabled": true,
// 🔧 修复Reality 必须有 server_name普通 TLS 在域名时需要
if (isReality || isDomain) "server_name": serverName,
"insecure": securityConfig?['allow_insecure'] ?? true,
// 🔧 新增Reality 配置块
if (isReality) "reality": {
"enabled": true,
"public_key": securityConfig?['reality_public_key'] ?? '',
"short_id": securityConfig?['reality_short_id'] ?? '',
},
"utls": {
"enabled": true,
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
}
}
};
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
break;
case "vmess":
// 判断是否为域名非IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
// 🔧 优先使用 security_config 中的 SNI
String serverName = nodeListItem.serverAddr;
if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
serverName = securityConfig['sni'].toString();
}
// 🔧 关键修复:根据 security_config 判断是否启用 TLS
final bool tlsEnabled = securityConfig?['tls_enabled'] ?? false;
print('🔐 TLS 状态: enabled=$tlsEnabled');
config = {
"type": "vmess",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"uuid": nodeListItem.uuid,
"alter_id": 0,
"security": "auto",
if (transportConfig != null) "transport": transportConfig,
if (tlsEnabled) "tls": {
"enabled": true,
if (isDomain) "server_name": serverName,
"insecure": securityConfig?['allow_insecure'] ?? true,
"utls": {
"enabled": true,
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
}
}
};
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
break;
case "trojan":
// 判断是否为域名非IP地址
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
// 🔧 优先使用 security_config 中的 SNI
String serverName = nodeListItem.serverAddr;
if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
serverName = securityConfig['sni'].toString();
}
config = {
"type": "trojan",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"password": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig,
"tls": {
"enabled": true,
if (isDomain) "server_name": serverName,
"insecure": securityConfig?['allow_insecure'] ?? true,
"utls": {
"enabled": true,
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
}
}
};
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
break;
case "hysteria":
case "hysteria2":
// 后端的 "hysteria" 实际上是 Hysteria2 协议
print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}');
print(' - serverAddr: ${nodeListItem.serverAddr}');
print(' - port: ${nodeListItem.port}');
print(' - uuid: ${nodeListItem.uuid}');
//判断是否为域名
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
config = {
"type": "hysteria2",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"password": nodeListItem.uuid,
"tls": {
"enabled": true,
if (isDomain) "server_name": nodeListItem.serverAddr,
}
};
print('✅ Hysteria2 节点配置构建成功');
print('📄 完整配置: ${jsonEncode(config)}');
break;
default:
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
config = {};
}
}
}