完善游客模式登录 并且新增设备管理

This commit is contained in:
2025-10-17 21:11:40 +08:00
parent 2333ad52a9
commit de9cf751f3
46 changed files with 4590 additions and 543 deletions
+109 -1
View File
@@ -45,7 +45,24 @@ class KROutboundItem {
city = nodeListItem.city; // 设置城市
country = nodeListItem.country; // 设置国家
final json = jsonDecode(nodeListItem.config);
// 安全解析 config 字段
// 新API格式:config为空,直接使用节点字段构建配置
// 旧API格式:config包含JSON配置
if (nodeListItem.config.isEmpty) {
print('️ 节点 ${nodeListItem.name} 使用直接字段构建配置');
_buildConfigFromFields(nodeListItem);
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 =
@@ -208,4 +225,95 @@ class KROutboundItem {
return {};
}
}
/// 直接从节点字段构建配置(新API格式)
void _buildConfigFromFields(KrNodeListItem nodeListItem) {
switch (nodeListItem.protocol) {
case "shadowsocks":
config = {
"type": "shadowsocks",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"method": "chacha20-ietf-poly1305", // 默认加密方法
"password": nodeListItem.uuid
};
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
break;
case "vless":
config = {
"type": "vless",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"uuid": nodeListItem.uuid,
"tls": {
"enabled": true,
"server_name": nodeListItem.serverAddr,
"insecure": false,
"utls": {
"enabled": true,
"fingerprint": "chrome"
}
}
};
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
break;
case "vmess":
config = {
"type": "vmess",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"uuid": nodeListItem.uuid,
"alter_id": 0,
"security": "auto",
"tls": {
"enabled": true,
"server_name": nodeListItem.serverAddr,
"insecure": false,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
break;
case "trojan":
config = {
"type": "trojan",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"password": nodeListItem.uuid,
"tls": {
"enabled": true,
"server_name": nodeListItem.serverAddr,
"insecure": false,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
break;
case "hysteria2":
config = {
"type": "hysteria2",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": nodeListItem.port,
"password": nodeListItem.uuid,
"up_mbps": 100,
"down_mbps": 100,
"tls": {
"enabled": true,
"server_name": nodeListItem.serverAddr,
"insecure": false,
"alpn": ["h3"]
}
};
print('✅ Hysteria2 节点配置构建成功: ${nodeListItem.name}');
break;
default:
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
config = {};
}
}
}
@@ -45,8 +45,15 @@ class KrOutboundsList {
}
final KROutboundItem item = KROutboundItem(element);
// 检查节点配置是否有效(必须包含 type 字段)
if (item.config.isEmpty || !item.config.containsKey('type')) {
print('⚠️ 跳过无效节点: ${element.name},配置为空或缺少 type 字段');
continue; // 跳过无效节点
}
allList.add(item);
// 根据标签分组出站项
for (var tag in element.tags) {
tagGroups.putIfAbsent(tag, () => []);
+33 -7
View File
@@ -5,23 +5,39 @@ class KRNodeList {
final String subscribeId;
final String startTime;
final String expireTime;
final bool isTryOut; // 是否是试用订阅
const KRNodeList({
required this.list,
this.subscribeId = "0",
this.startTime = "",
this.expireTime = "",
this.isTryOut = false,
});
factory KRNodeList.fromJson(Map<String, dynamic> json) {
try {
final List<dynamic>? jsonList= json['list'] as List<dynamic>?;
// 新的 API 返回格式: {"list": [{"id": 24, "is_try_out": true, "nodes": [...]}]}
final List<dynamic>? listData = json['list'] as List<dynamic>?;
if (listData == null || listData.isEmpty) {
KRLogUtil.kr_w('节点列表为空', tag: 'NodeList');
return const KRNodeList(list: []);
}
// 获取第一个订阅对象
final subscribeData = listData[0] as Map<String, dynamic>;
final bool isTryOut = subscribeData['is_try_out'] as bool? ?? false;
final List<dynamic>? nodesData = subscribeData['nodes'] as List<dynamic>?;
KRLogUtil.kr_i('节点列表解析: is_try_out=$isTryOut, 节点数=${nodesData?.length ?? 0}', tag: 'NodeList');
return KRNodeList(
list: jsonList?.map((e) => KrNodeListItem.fromJson(e as Map<String, dynamic>)).toList() ?? [],
subscribeId: json['id']?.toString() ?? "0",
startTime: json['start_time']?.toString() ?? "",
expireTime: json['expire_time']?.toString() ?? "",
list: nodesData?.map((e) => KrNodeListItem.fromJson(e as Map<String, dynamic>)).toList() ?? [],
subscribeId: subscribeData['id']?.toString() ?? "0",
startTime: subscribeData['start_time']?.toString() ?? "",
expireTime: subscribeData['expire_time']?.toString() ?? "",
isTryOut: isTryOut,
);
} catch (err) {
KRLogUtil.kr_e('KRNodeList解析错误: $err', tag: 'NodeList');
@@ -38,6 +54,7 @@ class KrNodeListItem {
final String relayMode;
final String relayNode;
final String serverAddr;
final int port; // 新增:端口字段
final int speedLimit;
final List<String> tags;
final int traffic;
@@ -63,6 +80,7 @@ class KrNodeListItem {
this.relayMode = '',
this.relayNode = '',
required this.serverAddr,
this.port = 0, // 默认值
required this.speedLimit,
required this.tags,
required this.traffic,
@@ -83,6 +101,12 @@ class KrNodeListItem {
factory KrNodeListItem.fromJson(Map<String, dynamic> json) {
try {
// 支持新旧两种API格式
// 新格式: address, port
// 旧格式: server_addr, config 中包含 port
final serverAddr = json['address']?.toString() ?? json['server_addr']?.toString() ?? '';
final port = _parseIntSafely(json['port']);
return KrNodeListItem(
id: _parseIntSafely(json['id']),
name: json['name']?.toString() ?? '',
@@ -90,7 +114,8 @@ class KrNodeListItem {
protocol: json['protocol']?.toString() ?? '',
relayMode: json['relay_mode']?.toString() ?? '',
relayNode: json['relay_node']?.toString() ?? '',
serverAddr: json['server_addr']?.toString() ?? '',
serverAddr: serverAddr,
port: port,
speedLimit: _parseIntSafely(json['speed_limit']),
tags: _parseStringList(json['tags']),
traffic: _parseIntSafely(json['traffic']),
@@ -116,6 +141,7 @@ class KrNodeListItem {
uuid: '',
protocol: '',
serverAddr: '',
port: 0,
speedLimit: 0,
tags: [],
traffic: 0,
@@ -10,6 +10,7 @@ class KRUserAvailableSubscribeItem {
final String startTime;
final String expireTime;
final List<dynamic> list;
final bool isTryOut; // 试用标志:true=试用,false=付费
const KRUserAvailableSubscribeItem({
this.id = 0,
@@ -21,19 +22,35 @@ class KRUserAvailableSubscribeItem {
this.startTime = '',
this.expireTime = '',
this.list = const [],
this.isTryOut = false,
});
factory KRUserAvailableSubscribeItem.fromJson(Map<String, dynamic> json) {
// 从 subscribe 对象中获取订阅信息
final subscribe = json['subscribe'] as Map<String, dynamic>?;
// 时间字段可能是 int (毫秒时间戳) 或 String (ISO 8601)
String convertTime(dynamic value) {
if (value == null) return '';
if (value is String) return value;
if (value is int) {
// 将毫秒时间戳转换为 ISO 8601 字符串
return DateTime.fromMillisecondsSinceEpoch(value).toIso8601String();
}
return value.toString();
}
return KRUserAvailableSubscribeItem(
id: json['id'] as int? ?? 0,
name: json['name'] as String? ?? '',
deviceLimit: json['device_limit'] as int? ?? 0,
name: subscribe?['name'] as String? ?? '',
deviceLimit: subscribe?['device_limit'] as int? ?? 0,
download: json['download'] as int? ?? 0,
upload: json['upload'] as int? ?? 0,
traffic: json['traffic'] as int? ?? 0,
startTime: json['start_time'] as String? ?? '',
expireTime: json['expire_time'] as String? ?? '',
startTime: convertTime(json['start_time']),
expireTime: convertTime(json['expire_time']),
list: (json['list'] as List<dynamic>?) ?? const [],
isTryOut: subscribe?['is_try_out'] as bool? ?? false,
);
}
@@ -48,6 +65,7 @@ class KRUserAvailableSubscribeItem {
'start_time': startTime,
'expire_time': expireTime,
'list': list,
'is_try_out': isTryOut,
};
}
}