更新扩展连接协议字段并且新增邀请码展示

This commit is contained in:
Rust 2025-10-22 21:49:57 +08:00
parent 0d91ce138d
commit 17ea51b583
8 changed files with 252 additions and 67 deletions

View File

@ -30,6 +30,15 @@ class KRAppRunData {
/// ID使便 UI
final Rx<int?> kr_userId = Rx<int?>(null);
///
final RxString kr_referCode = ''.obs;
///
final RxInt kr_balance = 0.obs;
///
final RxInt kr_commission = 0.obs;
///
KRLoginType? kr_loginType;
@ -146,9 +155,17 @@ class KRAppRunData {
//
kr_isLogin.value = true;
//
// Socket
KRLogUtil.kr_i('用户信息已保存,跳过用户信息接口调用', tag: 'AppRunData');
// 🔧 refer_code
KRLogUtil.kr_i('🔍 [AppRunData] 检查登录模式: account=$account', tag: 'AppRunData');
final isDevice = isDeviceLogin();
KRLogUtil.kr_i('🔍 [AppRunData] isDeviceLogin: $isDevice', tag: 'AppRunData');
if (!isDevice) {
KRLogUtil.kr_i('✅ [AppRunData] 正常登录模式,开始获取用户详细信息', tag: 'AppRunData');
await _fetchUserInfo();
} else {
KRLogUtil.kr_i('⏭️ [AppRunData] 设备登录模式,跳过用户信息接口调用', tag: 'AppRunData');
}
} catch (e) {
KRLogUtil.kr_e('保存用户信息失败: $e', tag: 'AppRunData');
@ -298,4 +315,41 @@ class KRAppRunData {
Future<void> _kr_disconnectSocket() async {
await KrSocketService.instance.disconnect();
}
///
Future<void> _fetchUserInfo() async {
try {
KRLogUtil.kr_i('📞 [AppRunData] 开始调用用户信息接口 /v1/public/user/info ...', tag: 'AppRunData');
KRLogUtil.kr_i('🔐 [AppRunData] 当前 Token: ${kr_token ?? "null"}', tag: 'AppRunData');
final result = await KRUserApi.kr_getUserInfo();
result.fold(
(error) {
KRLogUtil.kr_e('❌ [AppRunData] 获取用户信息失败: ${error.msg} (code: ${error.code})', tag: 'AppRunData');
},
(userInfo) {
KRLogUtil.kr_i('✅ [AppRunData] 获取用户信息成功', tag: 'AppRunData');
KRLogUtil.kr_i('📋 [AppRunData] refer_code: "${userInfo.referCode}"', tag: 'AppRunData');
KRLogUtil.kr_i('💰 [AppRunData] balance: ${userInfo.balance}', tag: 'AppRunData');
KRLogUtil.kr_i('💵 [AppRunData] commission: ${userInfo.commission}', tag: 'AppRunData');
KRLogUtil.kr_i('📧 [AppRunData] email: ${userInfo.email}', tag: 'AppRunData');
KRLogUtil.kr_i('🆔 [AppRunData] id: ${userInfo.id}', tag: 'AppRunData');
//
kr_referCode.value = userInfo.referCode;
kr_balance.value = userInfo.balance;
kr_commission.value = userInfo.commission;
KRLogUtil.kr_i('💾 [AppRunData] 用户信息已保存到全局状态:', tag: 'AppRunData');
KRLogUtil.kr_i(' - kr_referCode: "${kr_referCode.value}"', tag: 'AppRunData');
KRLogUtil.kr_i(' - kr_balance: ${kr_balance.value}', tag: 'AppRunData');
KRLogUtil.kr_i(' - kr_commission: ${kr_commission.value}', tag: 'AppRunData');
},
);
} catch (e, stackTrace) {
KRLogUtil.kr_e('💥 [AppRunData] 获取用户信息异常: $e', tag: 'AppRunData');
KRLogUtil.kr_e('📚 [AppRunData] 错误堆栈: $stackTrace', tag: 'AppRunData');
}
}
}

View File

@ -41,19 +41,24 @@ class KROutboundItem {
tag = nodeListItem.name; //
serverAddr = nodeListItem.serverAddr; //
// config Map<String, dynamic>
city = nodeListItem.city; //
country = nodeListItem.country; //
// config
// API格式config为空使
// API格式config包含JSON配置
if (nodeListItem.config.isEmpty) {
// 🔧 使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>;
@ -228,17 +233,31 @@ class KROutboundItem {
/// 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}');
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": "chacha20-ietf-poly1305", //
"method": finalMethod,
"password": nodeListItem.uuid
};
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
print('📄 使用加密方法: $finalMethod');
print('📄 完整配置: $config');
break;
case "vless":
config = {
@ -258,6 +277,7 @@ class KROutboundItem {
}
};
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
break;
case "vmess":
config = {
@ -276,6 +296,7 @@ class KROutboundItem {
}
};
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
break;
case "trojan":
config = {
@ -292,6 +313,7 @@ class KROutboundItem {
}
};
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
break;
case "hysteria2":
config = {
@ -310,6 +332,7 @@ class KROutboundItem {
}
};
print('✅ Hysteria2 节点配置构建成功: ${nodeListItem.name}');
print('📄 完整配置: $config');
break;
default:
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
class KRNodeList {
@ -54,7 +55,9 @@ class KrNodeListItem {
final String relayMode;
final String relayNode;
final String serverAddr;
final int port; //
final int port; //
final String method; // Shadowsocks等
final String protocols; // JSON字符串API格式
final int speedLimit;
final List<String> tags;
final int traffic;
@ -81,6 +84,8 @@ class KrNodeListItem {
this.relayNode = '',
required this.serverAddr,
this.port = 0, //
this.method = '', //
this.protocols = '', //
required this.speedLimit,
required this.tags,
required this.traffic,
@ -102,10 +107,33 @@ class KrNodeListItem {
factory KrNodeListItem.fromJson(Map<String, dynamic> json) {
try {
// API格式
// : address, port
// : protocols
// : address, port, method
// : server_addr, config port
final serverAddr = json['address']?.toString() ?? json['server_addr']?.toString() ?? '';
final port = _parseIntSafely(json['port']);
int port = _parseIntSafely(json['port']);
String method = json['method']?.toString() ?? ''; // Shadowsocks等
final protocols = json['protocols']?.toString() ?? ''; // JSON
// 🔧 protocols port cipher
if (protocols.isNotEmpty) {
try {
final protocolsList = jsonDecode(protocols) as List;
if (protocolsList.isNotEmpty) {
final firstProtocol = protocolsList[0] as Map<String, dynamic>;
// 使 protocols
if (firstProtocol['port'] != null) {
port = _parseIntSafely(firstProtocol['port']);
}
if (firstProtocol['cipher'] != null && firstProtocol['cipher'].toString().isNotEmpty) {
method = firstProtocol['cipher'].toString();
}
KRLogUtil.kr_i('从 protocols 解析: port=$port, cipher=$method', tag: 'NodeList');
}
} catch (e) {
KRLogUtil.kr_w('解析 protocols 字段失败: $e', tag: 'NodeList');
}
}
return KrNodeListItem(
id: _parseIntSafely(json['id']),
@ -116,6 +144,8 @@ class KrNodeListItem {
relayNode: json['relay_node']?.toString() ?? '',
serverAddr: serverAddr,
port: port,
method: method,
protocols: protocols,
speedLimit: _parseIntSafely(json['speed_limit']),
tags: _parseStringList(json['tags']),
traffic: _parseIntSafely(json['traffic']),
@ -142,6 +172,8 @@ class KrNodeListItem {
protocol: '',
serverAddr: '',
port: 0,
method: '',
protocols: '',
speedLimit: 0,
tags: [],
traffic: 0,

View File

@ -6,7 +6,11 @@ class KRUserInfo {
final String avatar;
final String areaCode;
final String telephone;
final int balance;
final int balance;
final int commission;
final int referralPercentage;
final bool onlyFirstPurchase;
final int giftAmount;
KRUserInfo({
required this.id,
@ -16,7 +20,11 @@ class KRUserInfo {
this.avatar = '',
this.areaCode = '',
this.telephone = '',
this.balance = 0
this.balance = 0,
this.commission = 0,
this.referralPercentage = 0,
this.onlyFirstPurchase = false,
this.giftAmount = 0,
});
factory KRUserInfo.fromJson(Map<String, dynamic> json) {
@ -28,7 +36,11 @@ class KRUserInfo {
avatar: json['avatar'] ?? '',
areaCode: json['area_code'] ?? '',
telephone: json['telephone'] ?? '',
balance: json['balance'] ?? 0,
balance: json['balance'] ?? 0,
commission: json['commission'] ?? 0,
referralPercentage: json['referral_percentage'] ?? 0,
onlyFirstPurchase: json['only_first_purchase'] ?? false,
giftAmount: json['gift_amount'] ?? 0,
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:kaer_with_panels/app/common/app_config.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/services/api_service/kr_api.user.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:flutter/services.dart';
@ -72,15 +73,31 @@ class KRInviteController extends GetxController {
}
// kr_getUserInfo
// 使 AppConfig
// AppRunData
Future<void> _kr_fetchUserInfo() async {
try {
kr_isLoading.value = true;
// 使 AppConfig
kr_referCode.value = AppConfig.kr_userReferCode;
KRLogUtil.kr_i('🔍 [InviteController] 开始获取邀请码...', tag: 'InviteController');
// AppRunData
final appData = KRAppRunData.getInstance();
KRLogUtil.kr_i('📊 [InviteController] AppRunData 状态:', tag: 'InviteController');
KRLogUtil.kr_i(' - kr_isLogin: ${appData.kr_isLogin.value}', tag: 'InviteController');
KRLogUtil.kr_i(' - kr_account: ${appData.kr_account.value}', tag: 'InviteController');
KRLogUtil.kr_i(' - kr_referCode: ${appData.kr_referCode.value}', tag: 'InviteController');
KRLogUtil.kr_i(' - kr_balance: ${appData.kr_balance.value}', tag: 'InviteController');
KRLogUtil.kr_i(' - kr_commission: ${appData.kr_commission.value}', tag: 'InviteController');
kr_referCode.value = appData.kr_referCode.value;
KRLogUtil.kr_i('📋 [InviteController] 获取到邀请码: "${kr_referCode.value}"', tag: 'InviteController');
if (kr_referCode.value.isEmpty) {
KRLogUtil.kr_w('⚠️ [InviteController] 邀请码为空!', tag: 'InviteController');
}
} catch (e) {
KRLogUtil.kr_e('❌ [InviteController] 获取邀请码失败: $e', tag: 'InviteController');
KRCommonUtil.kr_showToast(e.toString());
} finally {
kr_isLoading.value = false;

View File

@ -101,4 +101,7 @@ abstract class Api {
///
static const String kr_getPublicPaymentMethods = "/v1/public/payment/methods";
///
static const String kr_getUserInfo = "/v1/public/user/info";
}

View File

@ -83,22 +83,22 @@ class KRUserApi {
return right(baseResponse.model);
}
//
// Future<Either<HttpError, KRUserInfo>> kr_getUserInfo() async {
// final Map<String, dynamic> data = <String, dynamic>{};
// BaseResponse<KRUserInfo> baseResponse =
// await HttpUtil.getInstance().request<KRUserInfo>(
// Api.kr_getUserInfo,
// data,
// method: HttpMethod.GET,
// isShowLoading: false,
// );
// if (!baseResponse.isSuccess) {
// return left(
// HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
// }
// return right(baseResponse.model);
// }
///
static Future<Either<HttpError, KRUserInfo>> kr_getUserInfo() async {
final Map<String, dynamic> data = <String, dynamic>{};
BaseResponse<KRUserInfo> baseResponse =
await HttpUtil.getInstance().request<KRUserInfo>(
Api.kr_getUserInfo,
data,
method: HttpMethod.GET,
isShowLoading: false,
);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model);
}
Future<Either<HttpError, KRAffiliateCount>> kr_getAffiliateCount() async {
final Map<String, dynamic> data = <String, dynamic>{};

View File

@ -404,7 +404,27 @@ class KRSingBoxImp {
void kr_saveOutbounds(List<Map<String, dynamic>> outbounds) async {
KRLogUtil.kr_i('💾 开始保存配置文件...', tag: 'SingBox');
KRLogUtil.kr_i('📊 出站节点数量: ${outbounds.length}', tag: 'SingBox');
//
for (int i = 0; i < outbounds.length; i++) {
final outbound = outbounds[i];
KRLogUtil.kr_i('📋 节点[$i] 配置:', tag: 'SingBox');
KRLogUtil.kr_i(' - type: ${outbound['type']}', tag: 'SingBox');
KRLogUtil.kr_i(' - tag: ${outbound['tag']}', tag: 'SingBox');
KRLogUtil.kr_i(' - server: ${outbound['server']}', tag: 'SingBox');
KRLogUtil.kr_i(' - server_port: ${outbound['server_port']}', tag: 'SingBox');
if (outbound['method'] != null) {
KRLogUtil.kr_i(' - method: ${outbound['method']}', tag: 'SingBox');
}
if (outbound['password'] != null) {
KRLogUtil.kr_i(' - password: ${outbound['password']?.toString().substring(0, 8)}...', tag: 'SingBox');
}
if (outbound['uuid'] != null) {
KRLogUtil.kr_i(' - uuid: ${outbound['uuid']?.toString().substring(0, 8)}...', tag: 'SingBox');
}
KRLogUtil.kr_i(' - 完整配置: ${jsonEncode(outbound)}', tag: 'SingBox');
}
kr_outbounds = outbounds;
final map = {};
@ -413,10 +433,10 @@ class KRSingBoxImp {
final file = _file(kr_configName);
final temp = _tempFile(kr_configName);
final mapStr = jsonEncode(map);
KRLogUtil.kr_i('📄 配置文件内容长度: ${mapStr.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 配置文件前500字符: ${mapStr.substring(0, mapStr.length > 500 ? 500 : mapStr.length)}', tag: 'SingBox');
KRLogUtil.kr_i('📄 完整配置文件内容: $mapStr', tag: 'SingBox');
await file.writeAsString(mapStr);
await temp.writeAsString(mapStr);
@ -428,7 +448,7 @@ class KRSingBoxImp {
.mapLeft((err) {
KRLogUtil.kr_e('❌ 保存配置文件失败: $err', tag: 'SingBox');
}).run();
KRLogUtil.kr_i('✅ 配置文件保存完成', tag: 'SingBox');
}
@ -438,7 +458,7 @@ class KRSingBoxImp {
KRLogUtil.kr_i('🚀 开始启动 SingBox...', tag: 'SingBox');
KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox');
KRLogUtil.kr_i('📝 配置名称: $kr_configName', tag: 'SingBox');
//
final configFile = File(_cutPath);
if (await configFile.exists()) {
@ -448,7 +468,7 @@ class KRSingBoxImp {
} else {
KRLogUtil.kr_w('⚠️ 配置文件不存在: $_cutPath', tag: 'SingBox');
}
await kr_singBox.start(_cutPath, kr_configName, false).map(
(r) {
KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox');
@ -525,35 +545,59 @@ class KRSingBoxImp {
////
Future<void> kr_updateConnectionType(KRConnectionType newType) async {
if (kr_connectionType.value == newType) {
return;
}
try {
KRLogUtil.kr_i('🔄 开始更新连接类型...', tag: 'SingBox');
KRLogUtil.kr_i('📊 当前类型: ${kr_connectionType.value}', tag: 'SingBox');
KRLogUtil.kr_i('📊 新类型: $newType', tag: 'SingBox');
kr_connectionType.value = newType;
if (kr_connectionType.value == newType) {
KRLogUtil.kr_i('⚠️ 连接类型相同,无需更新', tag: 'SingBox');
return;
}
final oOption = _getConfigOption();
kr_connectionType.value = newType;
var mode = "";
switch (newType) {
case KRConnectionType.global:
mode = "other";
break;
case KRConnectionType.rule:
mode = KRCountryUtil.kr_getCurrentCountryCode();
break;
// case KRConnectionType.direct:
// mode = "direct";
// break;
}
oOption["region"] = mode;
final op = SingboxConfigOption.fromJson(oOption);
final oOption = _getConfigOption();
await kr_singBox.changeOptions(op)
..map((r) {}).mapLeft((err) {
KRLogUtil.kr_e('更新连接类型失败: $err');
}).run();
if (kr_status.value == SingboxStarted()) {
await kr_restart();
var mode = "";
switch (newType) {
case KRConnectionType.global:
mode = "other";
KRLogUtil.kr_i('🌍 切换到全局代理模式', tag: 'SingBox');
break;
case KRConnectionType.rule:
mode = KRCountryUtil.kr_getCurrentCountryCode();
KRLogUtil.kr_i('🎯 切换到规则代理模式: $mode', tag: 'SingBox');
break;
// case KRConnectionType.direct:
// mode = "direct";
// break;
}
oOption["region"] = mode;
KRLogUtil.kr_i('📝 更新 region 配置: $mode', tag: 'SingBox');
final op = SingboxConfigOption.fromJson(oOption);
KRLogUtil.kr_i('📄 配置选项: ${oOption.toString()}', tag: 'SingBox');
await kr_singBox.changeOptions(op)
..map((r) {
KRLogUtil.kr_i('✅ 连接类型更新成功', tag: 'SingBox');
}).mapLeft((err) {
KRLogUtil.kr_e('❌ 更新连接类型失败: $err', tag: 'SingBox');
throw err;
}).run();
if (kr_status.value == SingboxStarted()) {
KRLogUtil.kr_i('🔄 VPN已启动准备重启以应用新配置...', tag: 'SingBox');
await kr_restart();
KRLogUtil.kr_i('✅ VPN重启完成', tag: 'SingBox');
} else {
KRLogUtil.kr_i(' VPN未启动配置已更新', tag: 'SingBox');
}
} catch (e, stackTrace) {
KRLogUtil.kr_e('💥 更新连接类型异常: $e', tag: 'SingBox');
KRLogUtil.kr_e('📚 错误堆栈: $stackTrace', tag: 'SingBox');
rethrow;
}
}
@ -598,7 +642,7 @@ class KRSingBoxImp {
void kr_selectOutbound(String tag) {
KRLogUtil.kr_i('🎯 开始选择出站节点: $tag', tag: 'SingBox');
KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox');
//
for (int i = 0; i < kr_activeGroups.length; i++) {
final group = kr_activeGroups[i];