新增游客模式
This commit is contained in:
parent
f5bc239b6a
commit
2333ad52a9
@ -1022,6 +1022,16 @@ class KRDomain {
|
||||
}
|
||||
|
||||
class AppConfig {
|
||||
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
/// 加密密钥配置
|
||||
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
/// 统一加密密钥(用于所有API接口和设备登录)
|
||||
/// 密钥来源:OmnOem 项目 ppanel.json 配置
|
||||
static const String kr_encryptionKey = 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx';
|
||||
|
||||
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
/// 请求域名地址
|
||||
/// 基础url
|
||||
// static String baseUrl = "http://103.112.98.72:8088";
|
||||
@ -1029,7 +1039,7 @@ class AppConfig {
|
||||
/// 请求域名地址
|
||||
String get baseUrl {
|
||||
if (kDebugMode) {
|
||||
return "http://192.168.0.113";
|
||||
return "http://192.168.0.113:8082";
|
||||
}
|
||||
return "${KRProtocol.kr_https}://${KRDomain.kr_api}";
|
||||
}
|
||||
|
||||
425
lib/app/model/response/kr_site_config.dart
Normal file
425
lib/app/model/response/kr_site_config.dart
Normal file
@ -0,0 +1,425 @@
|
||||
import 'dart:convert';
|
||||
|
||||
/// 网站配置信息
|
||||
class KRSiteConfig {
|
||||
final KRSiteInfo site;
|
||||
final KRVerifyConfig verify;
|
||||
final KRAuthConfig auth;
|
||||
final KRInviteConfig invite;
|
||||
final KRCurrencyConfig currency;
|
||||
final KRSubscribeConfig subscribe;
|
||||
final KRVerifyCodeConfig verifyCode;
|
||||
final List<String> oauthMethods;
|
||||
final bool webAd;
|
||||
|
||||
KRSiteConfig({
|
||||
required this.site,
|
||||
required this.verify,
|
||||
required this.auth,
|
||||
required this.invite,
|
||||
required this.currency,
|
||||
required this.subscribe,
|
||||
required this.verifyCode,
|
||||
required this.oauthMethods,
|
||||
required this.webAd,
|
||||
});
|
||||
|
||||
factory KRSiteConfig.fromJson(Map<String, dynamic> json) {
|
||||
return KRSiteConfig(
|
||||
site: KRSiteInfo.fromJson(json['site'] ?? {}),
|
||||
verify: KRVerifyConfig.fromJson(json['verify'] ?? {}),
|
||||
auth: KRAuthConfig.fromJson(json['auth'] ?? {}),
|
||||
invite: KRInviteConfig.fromJson(json['invite'] ?? {}),
|
||||
currency: KRCurrencyConfig.fromJson(json['currency'] ?? {}),
|
||||
subscribe: KRSubscribeConfig.fromJson(json['subscribe'] ?? {}),
|
||||
verifyCode: KRVerifyCodeConfig.fromJson(json['verify_code'] ?? {}),
|
||||
oauthMethods: List<String>.from(json['oauth_methods'] ?? []),
|
||||
webAd: json['web_ad'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'site': site.toJson(),
|
||||
'verify': verify.toJson(),
|
||||
'auth': auth.toJson(),
|
||||
'invite': invite.toJson(),
|
||||
'currency': currency.toJson(),
|
||||
'subscribe': subscribe.toJson(),
|
||||
'verify_code': verifyCode.toJson(),
|
||||
'oauth_methods': oauthMethods,
|
||||
'web_ad': webAd,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 站点信息
|
||||
class KRSiteInfo {
|
||||
final String host;
|
||||
final String siteName;
|
||||
final String siteDesc;
|
||||
final String siteLogo;
|
||||
final String keywords;
|
||||
final String customHtml;
|
||||
final String customData;
|
||||
final String crispId;
|
||||
|
||||
KRSiteInfo({
|
||||
required this.host,
|
||||
required this.siteName,
|
||||
required this.siteDesc,
|
||||
required this.siteLogo,
|
||||
required this.keywords,
|
||||
required this.customHtml,
|
||||
required this.customData,
|
||||
required this.crispId,
|
||||
});
|
||||
|
||||
factory KRSiteInfo.fromJson(Map<String, dynamic> json) {
|
||||
String crispId = '0';
|
||||
|
||||
// 尝试解析 custom_data 中的 kr_website_id
|
||||
try {
|
||||
final customDataStr = json['custom_data'] ?? '';
|
||||
if (customDataStr.isNotEmpty) {
|
||||
final customDataJson = jsonDecode(customDataStr) as Map<String, dynamic>;
|
||||
crispId = customDataJson['kr_website_id'] ?? '0';
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败时使用默认值
|
||||
}
|
||||
|
||||
return KRSiteInfo(
|
||||
host: json['host'] ?? '',
|
||||
siteName: json['site_name'] ?? '',
|
||||
siteDesc: json['site_desc'] ?? '',
|
||||
siteLogo: json['site_logo'] ?? '',
|
||||
keywords: json['keywords'] ?? '',
|
||||
customHtml: json['custom_html'] ?? '',
|
||||
customData: json['custom_data'] ?? '',
|
||||
crispId: crispId,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'host': host,
|
||||
'site_name': siteName,
|
||||
'site_desc': siteDesc,
|
||||
'site_logo': siteLogo,
|
||||
'keywords': keywords,
|
||||
'custom_html': customHtml,
|
||||
'custom_data': customData,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 验证配置
|
||||
class KRVerifyConfig {
|
||||
final String turnstileSiteKey;
|
||||
final bool enableLoginVerify;
|
||||
final bool enableRegisterVerify;
|
||||
final bool enableResetPasswordVerify;
|
||||
|
||||
KRVerifyConfig({
|
||||
required this.turnstileSiteKey,
|
||||
required this.enableLoginVerify,
|
||||
required this.enableRegisterVerify,
|
||||
required this.enableResetPasswordVerify,
|
||||
});
|
||||
|
||||
factory KRVerifyConfig.fromJson(Map<String, dynamic> json) {
|
||||
return KRVerifyConfig(
|
||||
turnstileSiteKey: json['turnstile_site_key'] ?? '',
|
||||
enableLoginVerify: json['enable_login_verify'] ?? false,
|
||||
enableRegisterVerify: json['enable_register_verify'] ?? false,
|
||||
enableResetPasswordVerify: json['enable_reset_password_verify'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'turnstile_site_key': turnstileSiteKey,
|
||||
'enable_login_verify': enableLoginVerify,
|
||||
'enable_register_verify': enableRegisterVerify,
|
||||
'enable_reset_password_verify': enableResetPasswordVerify,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 认证配置
|
||||
class KRAuthConfig {
|
||||
final KRMobileAuth mobile;
|
||||
final KREmailAuth email;
|
||||
final KRDeviceAuth device;
|
||||
final KRRegisterAuth register;
|
||||
|
||||
KRAuthConfig({
|
||||
required this.mobile,
|
||||
required this.email,
|
||||
required this.device,
|
||||
required this.register,
|
||||
});
|
||||
|
||||
factory KRAuthConfig.fromJson(Map<String, dynamic> json) {
|
||||
return KRAuthConfig(
|
||||
mobile: KRMobileAuth.fromJson(json['mobile'] ?? {}),
|
||||
email: KREmailAuth.fromJson(json['email'] ?? {}),
|
||||
device: KRDeviceAuth.fromJson(json['device'] ?? {}),
|
||||
register: KRRegisterAuth.fromJson(json['register'] ?? {}),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'mobile': mobile.toJson(),
|
||||
'email': email.toJson(),
|
||||
'device': device.toJson(),
|
||||
'register': register.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 手机号认证配置
|
||||
class KRMobileAuth {
|
||||
final bool enable;
|
||||
final bool enableWhitelist;
|
||||
final List<String> whitelist;
|
||||
|
||||
KRMobileAuth({
|
||||
required this.enable,
|
||||
required this.enableWhitelist,
|
||||
required this.whitelist,
|
||||
});
|
||||
|
||||
factory KRMobileAuth.fromJson(Map<String, dynamic> json) {
|
||||
return KRMobileAuth(
|
||||
enable: json['enable'] ?? false,
|
||||
enableWhitelist: json['enable_whitelist'] ?? false,
|
||||
whitelist: List<String>.from(json['whitelist'] ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'enable': enable,
|
||||
'enable_whitelist': enableWhitelist,
|
||||
'whitelist': whitelist,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 邮箱认证配置
|
||||
class KREmailAuth {
|
||||
final bool enable;
|
||||
final bool enableVerify;
|
||||
final bool enableDomainSuffix;
|
||||
final String domainSuffixList;
|
||||
|
||||
KREmailAuth({
|
||||
required this.enable,
|
||||
required this.enableVerify,
|
||||
required this.enableDomainSuffix,
|
||||
required this.domainSuffixList,
|
||||
});
|
||||
|
||||
factory KREmailAuth.fromJson(Map<String, dynamic> json) {
|
||||
return KREmailAuth(
|
||||
enable: json['enable'] ?? false,
|
||||
enableVerify: json['enable_verify'] ?? false,
|
||||
enableDomainSuffix: json['enable_domain_suffix'] ?? false,
|
||||
domainSuffixList: json['domain_suffix_list'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'enable': enable,
|
||||
'enable_verify': enableVerify,
|
||||
'enable_domain_suffix': enableDomainSuffix,
|
||||
'domain_suffix_list': domainSuffixList,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 设备认证配置
|
||||
class KRDeviceAuth {
|
||||
final bool enable;
|
||||
final bool showAds;
|
||||
final bool enableSecurity;
|
||||
final bool onlyRealDevice;
|
||||
|
||||
KRDeviceAuth({
|
||||
required this.enable,
|
||||
required this.showAds,
|
||||
required this.enableSecurity,
|
||||
required this.onlyRealDevice,
|
||||
});
|
||||
|
||||
factory KRDeviceAuth.fromJson(Map<String, dynamic> json) {
|
||||
return KRDeviceAuth(
|
||||
enable: json['enable'] ?? false,
|
||||
showAds: json['show_ads'] ?? false,
|
||||
enableSecurity: json['enable_security'] ?? false,
|
||||
onlyRealDevice: json['only_real_device'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'enable': enable,
|
||||
'show_ads': showAds,
|
||||
'enable_security': enableSecurity,
|
||||
'only_real_device': onlyRealDevice,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 注册认证配置
|
||||
class KRRegisterAuth {
|
||||
final bool stopRegister;
|
||||
final bool enableIpRegisterLimit;
|
||||
final int ipRegisterLimit;
|
||||
final int ipRegisterLimitDuration;
|
||||
|
||||
KRRegisterAuth({
|
||||
required this.stopRegister,
|
||||
required this.enableIpRegisterLimit,
|
||||
required this.ipRegisterLimit,
|
||||
required this.ipRegisterLimitDuration,
|
||||
});
|
||||
|
||||
factory KRRegisterAuth.fromJson(Map<String, dynamic> json) {
|
||||
return KRRegisterAuth(
|
||||
stopRegister: json['stop_register'] ?? false,
|
||||
enableIpRegisterLimit: json['enable_ip_register_limit'] ?? false,
|
||||
ipRegisterLimit: json['ip_register_limit'] ?? 0,
|
||||
ipRegisterLimitDuration: json['ip_register_limit_duration'] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'stop_register': stopRegister,
|
||||
'enable_ip_register_limit': enableIpRegisterLimit,
|
||||
'ip_register_limit': ipRegisterLimit,
|
||||
'ip_register_limit_duration': ipRegisterLimitDuration,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 邀请配置
|
||||
class KRInviteConfig {
|
||||
final bool forcedInvite;
|
||||
final double referralPercentage;
|
||||
final bool onlyFirstPurchase;
|
||||
|
||||
KRInviteConfig({
|
||||
required this.forcedInvite,
|
||||
required this.referralPercentage,
|
||||
required this.onlyFirstPurchase,
|
||||
});
|
||||
|
||||
factory KRInviteConfig.fromJson(Map<String, dynamic> json) {
|
||||
return KRInviteConfig(
|
||||
forcedInvite: json['forced_invite'] ?? false,
|
||||
referralPercentage: (json['referral_percentage'] ?? 0).toDouble(),
|
||||
onlyFirstPurchase: json['only_first_purchase'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'forced_invite': forcedInvite,
|
||||
'referral_percentage': referralPercentage,
|
||||
'only_first_purchase': onlyFirstPurchase,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 货币配置
|
||||
class KRCurrencyConfig {
|
||||
final String currencyUnit;
|
||||
final String currencySymbol;
|
||||
|
||||
KRCurrencyConfig({
|
||||
required this.currencyUnit,
|
||||
required this.currencySymbol,
|
||||
});
|
||||
|
||||
factory KRCurrencyConfig.fromJson(Map<String, dynamic> json) {
|
||||
return KRCurrencyConfig(
|
||||
currencyUnit: json['currency_unit'] ?? '',
|
||||
currencySymbol: json['currency_symbol'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'currency_unit': currencyUnit,
|
||||
'currency_symbol': currencySymbol,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 订阅配置
|
||||
class KRSubscribeConfig {
|
||||
final bool singleModel;
|
||||
final String subscribePath;
|
||||
final String subscribeDomain;
|
||||
final bool panDomain;
|
||||
final bool userAgentLimit;
|
||||
final String userAgentList;
|
||||
|
||||
KRSubscribeConfig({
|
||||
required this.singleModel,
|
||||
required this.subscribePath,
|
||||
required this.subscribeDomain,
|
||||
required this.panDomain,
|
||||
required this.userAgentLimit,
|
||||
required this.userAgentList,
|
||||
});
|
||||
|
||||
factory KRSubscribeConfig.fromJson(Map<String, dynamic> json) {
|
||||
return KRSubscribeConfig(
|
||||
singleModel: json['single_model'] ?? false,
|
||||
subscribePath: json['subscribe_path'] ?? '',
|
||||
subscribeDomain: json['subscribe_domain'] ?? '',
|
||||
panDomain: json['pan_domain'] ?? false,
|
||||
userAgentLimit: json['user_agent_limit'] ?? false,
|
||||
userAgentList: json['user_agent_list'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'single_model': singleModel,
|
||||
'subscribe_path': subscribePath,
|
||||
'subscribe_domain': subscribeDomain,
|
||||
'pan_domain': panDomain,
|
||||
'user_agent_limit': userAgentLimit,
|
||||
'user_agent_list': userAgentList,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 验证码配置
|
||||
class KRVerifyCodeConfig {
|
||||
final int verifyCodeInterval;
|
||||
|
||||
KRVerifyCodeConfig({
|
||||
required this.verifyCodeInterval,
|
||||
});
|
||||
|
||||
factory KRVerifyCodeConfig.fromJson(Map<String, dynamic> json) {
|
||||
return KRVerifyCodeConfig(
|
||||
verifyCodeInterval: json['verify_code_interval'] ?? 60,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'verify_code_interval': verifyCodeInterval,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,17 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:math';
|
||||
import 'package:kaer_with_panels/app/utils/kr_network_check.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||
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/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_device_info_service.dart';
|
||||
import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart';
|
||||
import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
|
||||
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'dart:async';
|
||||
@ -31,13 +36,131 @@ class KRSplashController extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_kr_initialize();
|
||||
|
||||
// 使用 print 确保日志一定会输出
|
||||
print('═══════════════════════════════════════');
|
||||
print('🎬 KRSplashController onInit 被调用了!');
|
||||
print('═══════════════════════════════════════');
|
||||
|
||||
KRLogUtil.kr_i('═══════════════════════════════════════', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('🎬 启动页控制器 onInit', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('═══════════════════════════════════════', tag: 'SplashController');
|
||||
|
||||
// 立即初始化网站配置(在任何其他逻辑之前)
|
||||
print('🌐 准备调用 _kr_initSiteConfig...');
|
||||
// 必须等待网站配置和设备登录完成后,再进行后续初始化
|
||||
_kr_initSiteConfig().then((_) {
|
||||
print('🔧 网站配置完成,开始调用 _kr_initialize...');
|
||||
_kr_initialize();
|
||||
});
|
||||
}
|
||||
|
||||
/// 初始化网站配置(最优先执行)
|
||||
Future<void> _kr_initSiteConfig() async {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('🌐 【最优先】开始初始化网站配置...');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('🌐 【最优先】初始化网站配置...', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
|
||||
try {
|
||||
print('📞 准备调用 KRSiteConfigService().initialize()...');
|
||||
final success = await KRSiteConfigService().initialize();
|
||||
print('📞 KRSiteConfigService().initialize() 返回: $success');
|
||||
|
||||
if (success) {
|
||||
print('✅ 网站配置初始化成功');
|
||||
KRLogUtil.kr_i('✅ 网站配置初始化成功', tag: 'SplashController');
|
||||
|
||||
// 检查是否支持设备登录
|
||||
await _kr_checkAndPerformDeviceLogin();
|
||||
} else {
|
||||
print('⚠️ 网站配置初始化失败,将使用默认配置');
|
||||
KRLogUtil.kr_w('⚠️ 网站配置初始化失败,将使用默认配置', tag: 'SplashController');
|
||||
}
|
||||
} catch (e) {
|
||||
print('❌ 网站配置初始化异常: $e');
|
||||
KRLogUtil.kr_e('❌ 网站配置初始化异常: $e', tag: 'SplashController');
|
||||
}
|
||||
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
}
|
||||
|
||||
/// 检查并执行设备登录
|
||||
Future<void> _kr_checkAndPerformDeviceLogin() async {
|
||||
try {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('🔍 检查是否支持设备登录...');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
final siteConfigService = KRSiteConfigService();
|
||||
|
||||
// 检查是否启用设备登录
|
||||
final isDeviceLoginEnabled = siteConfigService.isDeviceLoginEnabled();
|
||||
print('📱 设备登录状态: ${isDeviceLoginEnabled ? "已启用" : "未启用"}');
|
||||
KRLogUtil.kr_i('📱 设备登录状态: $isDeviceLoginEnabled', tag: 'SplashController');
|
||||
|
||||
if (!isDeviceLoginEnabled) {
|
||||
print('⚠️ 设备登录未启用,跳过自动登录');
|
||||
KRLogUtil.kr_w('⚠️ 设备登录未启用', tag: 'SplashController');
|
||||
return;
|
||||
}
|
||||
|
||||
print('✅ 设备登录已启用,开始初始化设备信息...');
|
||||
|
||||
// 初始化设备信息服务
|
||||
await KRDeviceInfoService().initialize();
|
||||
|
||||
print('🔐 开始执行设备登录...');
|
||||
KRLogUtil.kr_i('🔐 开始执行设备登录', tag: 'SplashController');
|
||||
|
||||
// 执行设备登录
|
||||
final authApi = KRAuthApi();
|
||||
final result = await authApi.kr_deviceLogin();
|
||||
|
||||
result.fold(
|
||||
(error) {
|
||||
print('❌ 设备登录失败: ${error.msg}');
|
||||
KRLogUtil.kr_e('❌ 设备登录失败: ${error.msg}', tag: 'SplashController');
|
||||
},
|
||||
(token) async {
|
||||
print('✅ 设备登录成功!');
|
||||
print('🎫 Token: ${token.substring(0, min(20, token.length))}...');
|
||||
KRLogUtil.kr_i('✅ 设备登录成功', tag: 'SplashController');
|
||||
|
||||
// 使用 saveUserInfo 保存完整的用户信息
|
||||
// 设备登录使用特殊的账号标识,登录类型设为邮箱(后续可以绑定真实账号)
|
||||
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
await KRAppRunData.getInstance().kr_saveUserInfo(
|
||||
token,
|
||||
'device_$deviceId', // 使用设备ID作为临时账号
|
||||
KRLoginType.kr_email, // 默认登录类型
|
||||
null, // 设备登录无需区号
|
||||
);
|
||||
print('💾 用户信息已保存(包括Token)');
|
||||
print('✅ 已标记为登录状态');
|
||||
KRLogUtil.kr_i('✅ 设备登录流程完成', tag: 'SplashController');
|
||||
},
|
||||
);
|
||||
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
} catch (e, stackTrace) {
|
||||
print('❌ 设备登录检查异常: $e');
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
KRLogUtil.kr_e('❌ 设备登录检查异常: $e', tag: 'SplashController');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _kr_initialize() async {
|
||||
try {
|
||||
KRLogUtil.kr_i('🔧 开始执行 _kr_initialize', tag: 'SplashController');
|
||||
|
||||
// 只在手机端检查网络权限
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
KRLogUtil.kr_i('📱 移动平台,检查网络权限...', tag: 'SplashController');
|
||||
final bool hasNetworkPermission = await KRNetworkCheck.kr_initialize(
|
||||
Get.context!,
|
||||
onPermissionGranted: () async {
|
||||
@ -48,13 +171,16 @@ class KRSplashController extends GetxController {
|
||||
if (!hasNetworkPermission) {
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = AppTranslations.kr_splash.kr_networkPermissionFailed;
|
||||
KRLogUtil.kr_e('❌ 网络权限检查失败', tag: 'SplashController');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 非手机端直接继续初始化
|
||||
KRLogUtil.kr_i('💻 桌面平台,直接执行初始化', tag: 'SplashController');
|
||||
await _kr_continueInitialization();
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ _kr_initialize 异常: $e', tag: 'SplashController');
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = '${AppTranslations.kr_splash.kr_initializationFailed}$e';
|
||||
}
|
||||
@ -62,25 +188,37 @@ class KRSplashController extends GetxController {
|
||||
|
||||
Future<void> _kr_continueInitialization() async {
|
||||
try {
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('🚀 启动页主流程开始...', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
|
||||
// 只在手机端检查网络连接
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
KRLogUtil.kr_i('📱 检查网络连接...', tag: 'SplashController');
|
||||
final bool isConnected = await KRNetworkCheck.kr_checkNetworkConnection();
|
||||
if (!isConnected) {
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = AppTranslations.kr_splash.kr_networkConnectionFailed;
|
||||
KRLogUtil.kr_e('❌ 网络连接失败', tag: 'SplashController');
|
||||
return;
|
||||
}
|
||||
KRLogUtil.kr_i('✅ 网络连接正常', tag: 'SplashController');
|
||||
} else {
|
||||
KRLogUtil.kr_i('💻 桌面平台,跳过网络连接检查', tag: 'SplashController');
|
||||
}
|
||||
|
||||
// 初始化配置
|
||||
KRLogUtil.kr_i('⚙️ 开始初始化应用配置...', tag: 'SplashController');
|
||||
await AppConfig().initConfig(
|
||||
onSuccess: () async {
|
||||
// 配置初始化成功,继续后续步骤
|
||||
KRLogUtil.kr_i('✅ 应用配置初始化成功,继续后续步骤', tag: 'SplashController');
|
||||
await _kr_continueAfterConfig();
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// 配置初始化失败,显示错误信息
|
||||
KRLogUtil.kr_e('❌ 启动页初始化异常: $e', tag: 'SplashController');
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = '${AppTranslations.kr_splash.kr_initializationFailed}$e';
|
||||
}
|
||||
@ -91,17 +229,24 @@ class KRSplashController extends GetxController {
|
||||
try {
|
||||
// 初始化SingBox
|
||||
await KRSingBoxImp.instance.init();
|
||||
|
||||
// 初始化用户信息
|
||||
await KRAppRunData.getInstance().kr_initializeUserInfo();
|
||||
|
||||
// 检查是否已经通过设备登录设置了用户信息
|
||||
// 如果已登录(设备登录已完成),则跳过 initializeUserInfo
|
||||
// 如果未登录(没有设备登录或设备登录失败),则尝试从本地加载
|
||||
if (!KRAppRunData.getInstance().kr_isLogin.value) {
|
||||
KRLogUtil.kr_i('未检测到设备登录,尝试从本地加载用户信息', tag: 'SplashController');
|
||||
await KRAppRunData.getInstance().kr_initializeUserInfo();
|
||||
} else {
|
||||
KRLogUtil.kr_i('设备登录已完成,跳过用户信息初始化', tag: 'SplashController');
|
||||
}
|
||||
|
||||
// 等待一小段时间确保所有初始化完成
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
|
||||
// 验证登录状态是否已正确设置
|
||||
final loginStatus = KRAppRunData.getInstance().kr_isLogin.value;
|
||||
KRLogUtil.kr_i('启动完成,最终登录状态: $loginStatus', tag: 'SplashController');
|
||||
|
||||
|
||||
// 直接导航到主页
|
||||
Get.offAllNamed(Routes.KR_MAIN);
|
||||
} catch (e) {
|
||||
|
||||
@ -2,10 +2,13 @@ import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_config.dart';
|
||||
import 'package:kaer_with_panels/app/mixins/kr_app_bar_opacity_mixin.dart';
|
||||
import 'package:kaer_with_panels/app/model/entity_from_json_util.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
|
||||
|
||||
import '../utils/kr_aes_util.dart';
|
||||
import '../utils/kr_log_util.dart';
|
||||
|
||||
/// 接口返回基础类
|
||||
class BaseResponse<T> {
|
||||
@ -18,15 +21,23 @@ class BaseResponse<T> {
|
||||
|
||||
BaseResponse.fromJson(Map<String, dynamic> json) {
|
||||
retCode = json['code'];
|
||||
final aes = AESUtils();
|
||||
final dataMap = json['data'] ?? Map<String, dynamic>();
|
||||
final cipherText = dataMap['data'] ?? "";
|
||||
|
||||
final nonce = dataMap['time'] ?? "";
|
||||
if (cipherText.isNotEmpty && nonce.isNotEmpty) {
|
||||
final encrypted = aes.decryptData(cipherText, "ne6t2qcz-szoa-rw78-egqz-lrsxxbl0dke3", nonce);
|
||||
body = jsonDecode(encrypted);
|
||||
|
||||
|
||||
// 判断是否需要解密:根据站点配置的 enable_security 字段
|
||||
final shouldDecrypt = KRSiteConfigService().isDeviceSecurityEnabled();
|
||||
|
||||
if (shouldDecrypt && cipherText.isNotEmpty && nonce.isNotEmpty) {
|
||||
try {
|
||||
KRLogUtil.kr_i('🔓 开始解密响应数据', tag: 'BaseResponse');
|
||||
final decrypted = KRAesUtil.decryptData(cipherText, nonce, AppConfig.kr_encryptionKey);
|
||||
body = jsonDecode(decrypted);
|
||||
KRLogUtil.kr_i('✅ 解密成功', tag: 'BaseResponse');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 解密失败: $e,使用原始数据', tag: 'BaseResponse');
|
||||
body = dataMap;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -11,6 +11,7 @@ import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||||
import 'package:kaer_with_panels/app/network/base_response.dart';
|
||||
import 'package:kaer_with_panels/app/localization/kr_language_utils.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
|
||||
|
||||
// import 'package:crypto/crypto.dart';
|
||||
// import 'package:encrypt/encrypt.dart';
|
||||
@ -131,11 +132,12 @@ class HttpUtil {
|
||||
}
|
||||
|
||||
var map = <String, dynamic>{};
|
||||
if (path.contains("app")) {
|
||||
final aes = AESUtils();
|
||||
// 判断是否需要加密:根据站点配置的 enable_security 字段
|
||||
final shouldEncrypt = KRSiteConfigService().isDeviceSecurityEnabled();
|
||||
if (shouldEncrypt && path.contains("app")) {
|
||||
KRLogUtil.kr_i('🔐 需要加密请求数据', tag: 'HttpUtil');
|
||||
final plainText = jsonEncode(params);
|
||||
map =
|
||||
aes.encryptData(plainText, "ne6t2qcz-szoa-rw78-egqz-lrsxxbl0dke3");
|
||||
map = KRAesUtil.encryptData(plainText, AppConfig.kr_encryptionKey);
|
||||
} else {
|
||||
map = params;
|
||||
}
|
||||
|
||||
@ -19,6 +19,10 @@ abstract class Api {
|
||||
/// 登录接口
|
||||
static const String kr_login = "/v1/app/auth/login";
|
||||
|
||||
/// 设备登录(游客登录)
|
||||
/// 参考 OmnOem 项目 ppanel.json 配置
|
||||
static const String kr_deviceLogin = "/v1/auth/login/device";
|
||||
|
||||
/// 删除账号
|
||||
static const String kr_deleteAccount = "/v1/app/user/account";
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -15,6 +16,11 @@ import 'package:kaer_with_panels/app/utils/kr_device_util.dart';
|
||||
|
||||
import '../../utils/kr_common_util.dart';
|
||||
import '../../utils/kr_log_util.dart';
|
||||
import '../../utils/kr_aes_util.dart';
|
||||
import '../kr_device_info_service.dart';
|
||||
import '../kr_site_config_service.dart';
|
||||
import '../../common/app_config.dart';
|
||||
import 'package:dio/dio.dart' as dio;
|
||||
|
||||
class KRAuthApi {
|
||||
/// 是否开启了审核开关
|
||||
@ -221,6 +227,145 @@ class KRAuthApi {
|
||||
return right(baseResponse.model.kr_token.toString());
|
||||
}
|
||||
|
||||
/// 设备登录(游客登录)
|
||||
Future<Either<HttpError, String>> kr_deviceLogin() async {
|
||||
try {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('🔐 开始设备登录(游客模式)');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
KRLogUtil.kr_i('🔐 开始设备登录(游客模式)', tag: 'KRAuthApi');
|
||||
|
||||
// 获取设备信息
|
||||
final deviceInfoService = KRDeviceInfoService();
|
||||
final deviceId = deviceInfoService.deviceId;
|
||||
final userAgent = deviceInfoService.getUserAgent();
|
||||
|
||||
if (deviceId == null) {
|
||||
print('❌ 设备ID为空,无法登录');
|
||||
return left(HttpError(msg: '设备ID获取失败', code: -1));
|
||||
}
|
||||
|
||||
print('📱 设备ID: $deviceId');
|
||||
print('📱 User-Agent: $userAgent');
|
||||
KRLogUtil.kr_i('📱 设备ID: $deviceId', tag: 'KRAuthApi');
|
||||
KRLogUtil.kr_i('📱 User-Agent: $userAgent', tag: 'KRAuthApi');
|
||||
|
||||
// 构建请求数据
|
||||
Map<String, dynamic> data = {
|
||||
'identifier': deviceId,
|
||||
'user_agent': userAgent,
|
||||
};
|
||||
|
||||
print('📤 原始请求数据: $data');
|
||||
|
||||
// 检查是否需要加密
|
||||
final siteConfigService = KRSiteConfigService();
|
||||
final needEncryption = siteConfigService.isDeviceSecurityEnabled();
|
||||
|
||||
print('🔒 是否需要加密: $needEncryption');
|
||||
KRLogUtil.kr_i('🔒 是否需要加密: $needEncryption', tag: 'KRAuthApi');
|
||||
|
||||
String? requestBody;
|
||||
if (needEncryption) {
|
||||
// 加密请求数据
|
||||
print('🔐 加密请求数据...');
|
||||
final encrypted = KRAesUtil.encryptJson(data, AppConfig.kr_encryptionKey);
|
||||
requestBody = '{"data":"${encrypted['data']}","time":"${encrypted['time']}"}';
|
||||
print('🔐 加密后请求体: $requestBody');
|
||||
KRLogUtil.kr_i('🔐 加密后请求体', tag: 'KRAuthApi');
|
||||
} else {
|
||||
// 使用明文
|
||||
requestBody = jsonEncode(data);
|
||||
print('📝 明文请求体: $requestBody');
|
||||
}
|
||||
|
||||
// 使用 Dio 直接发送请求(因为需要特殊的加密处理)
|
||||
final dioInstance = dio.Dio();
|
||||
final baseUrl = AppConfig.getInstance().baseUrl;
|
||||
final url = '$baseUrl${Api.kr_deviceLogin}';
|
||||
|
||||
print('📤 请求URL: $url');
|
||||
KRLogUtil.kr_i('📤 请求URL: $url', tag: 'KRAuthApi');
|
||||
|
||||
// 设置请求头
|
||||
final headers = <String, String>{
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (needEncryption) {
|
||||
headers['Login-Type'] = 'device';
|
||||
}
|
||||
|
||||
print('📤 请求头: $headers');
|
||||
|
||||
// 配置Dio实例的超时设置
|
||||
dioInstance.options.connectTimeout = const Duration(seconds: 10);
|
||||
dioInstance.options.sendTimeout = const Duration(seconds: 10);
|
||||
dioInstance.options.receiveTimeout = const Duration(seconds: 10);
|
||||
|
||||
final response = await dioInstance.post(
|
||||
url,
|
||||
data: requestBody,
|
||||
options: dio.Options(
|
||||
headers: headers,
|
||||
),
|
||||
);
|
||||
|
||||
print('📥 响应状态码: ${response.statusCode}');
|
||||
print('📥 响应数据: ${response.data}');
|
||||
KRLogUtil.kr_i('📥 响应状态码: ${response.statusCode}', tag: 'KRAuthApi');
|
||||
KRLogUtil.kr_i('📥 响应数据: ${response.data}', tag: 'KRAuthApi');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
Map<String, dynamic> responseData = response.data as Map<String, dynamic>;
|
||||
|
||||
// 检查是否需要解密响应
|
||||
if (needEncryption && responseData.containsKey('data')) {
|
||||
final dataField = responseData['data'];
|
||||
if (dataField is Map<String, dynamic> &&
|
||||
dataField.containsKey('data') &&
|
||||
dataField.containsKey('time')) {
|
||||
print('🔓 解密响应数据...');
|
||||
final decrypted = KRAesUtil.decryptJson(
|
||||
dataField['data'] as String,
|
||||
dataField['time'] as String,
|
||||
AppConfig.kr_encryptionKey,
|
||||
);
|
||||
responseData['data'] = decrypted;
|
||||
print('🔓 解密后数据: ${responseData['data']}');
|
||||
KRLogUtil.kr_i('🔓 解密成功', tag: 'KRAuthApi');
|
||||
}
|
||||
}
|
||||
|
||||
if (responseData['code'] == 200) {
|
||||
final token = responseData['data']['token'] as String;
|
||||
print('✅ 设备登录成功');
|
||||
print('🎫 Token: ${token.substring(0, min(20, token.length))}...');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
KRLogUtil.kr_i('✅ 设备登录成功', tag: 'KRAuthApi');
|
||||
return right(token);
|
||||
} else {
|
||||
final msg = responseData['msg'] ?? '登录失败';
|
||||
print('❌ 登录失败: $msg');
|
||||
return left(HttpError(msg: msg, code: responseData['code']));
|
||||
}
|
||||
} else {
|
||||
print('❌ HTTP错误: ${response.statusCode}');
|
||||
return left(HttpError(msg: 'HTTP错误', code: response.statusCode ?? -1));
|
||||
}
|
||||
} on dio.DioException catch (e) {
|
||||
print('❌ Dio异常: ${e.type}');
|
||||
print('❌ 错误信息: ${e.message}');
|
||||
KRLogUtil.kr_e('❌ 设备登录Dio异常: ${e.message}', tag: 'KRAuthApi');
|
||||
return left(HttpError(msg: '网络请求失败: ${e.message}', code: -1));
|
||||
} catch (e, stackTrace) {
|
||||
print('❌ 设备登录异常: $e');
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
KRLogUtil.kr_e('❌ 设备登录异常: $e', tag: 'KRAuthApi');
|
||||
return left(HttpError(msg: '设备登录失败: $e', code: -1));
|
||||
}
|
||||
}
|
||||
|
||||
String _kr_getUserAgent() {
|
||||
if (Platform.isAndroid) {
|
||||
return 'android';
|
||||
|
||||
260
lib/app/services/kr_device_info_service.dart
Normal file
260
lib/app/services/kr_device_info_service.dart
Normal file
@ -0,0 +1,260 @@
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../utils/kr_secure_storage.dart';
|
||||
import '../utils/kr_log_util.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
/// 设备信息服务
|
||||
/// 用于获取设备唯一标识和其他设备信息
|
||||
class KRDeviceInfoService {
|
||||
static final KRDeviceInfoService _instance = KRDeviceInfoService._internal();
|
||||
factory KRDeviceInfoService() => _instance;
|
||||
KRDeviceInfoService._internal();
|
||||
|
||||
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
|
||||
String? _deviceId;
|
||||
Map<String, dynamic>? _deviceDetails;
|
||||
|
||||
// 获取设备唯一标识
|
||||
String? get deviceId => _deviceId;
|
||||
|
||||
// 获取设备详细信息
|
||||
Map<String, dynamic>? get deviceDetails => _deviceDetails;
|
||||
|
||||
/// 初始化设备信息
|
||||
Future<void> initialize() async {
|
||||
try {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('📱 开始初始化设备信息服务');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
KRLogUtil.kr_i('📱 开始初始化设备信息', tag: 'KRDeviceInfoService');
|
||||
|
||||
_deviceId = await _getDeviceId();
|
||||
_deviceDetails = await _getDeviceDetails();
|
||||
|
||||
print('✅ 设备信息初始化成功');
|
||||
print('📱 设备ID: $_deviceId');
|
||||
print('📱 设备平台: ${getPlatformName()}');
|
||||
print('📱 设备型号: ${getDeviceModel()}');
|
||||
print('📱 系统版本: ${getOSVersion()}');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
|
||||
KRLogUtil.kr_i('✅ 设备信息初始化成功', tag: 'KRDeviceInfoService');
|
||||
KRLogUtil.kr_i('📱 设备ID - $_deviceId', tag: 'KRDeviceInfoService');
|
||||
KRLogUtil.kr_i('📱 设备详情 - $_deviceDetails', tag: 'KRDeviceInfoService');
|
||||
} catch (e) {
|
||||
print('❌ 设备信息初始化失败: $e');
|
||||
KRLogUtil.kr_e('❌ 设备信息初始化失败 - $e', tag: 'KRDeviceInfoService');
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取设备唯一标识
|
||||
Future<String> _getDeviceId() async {
|
||||
try {
|
||||
String? identifier;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
final androidInfo = await _deviceInfo.androidInfo;
|
||||
// Android使用androidId作为唯一标识
|
||||
identifier = androidInfo.id;
|
||||
} else if (Platform.isIOS) {
|
||||
final iosInfo = await _deviceInfo.iosInfo;
|
||||
// iOS使用identifierForVendor作为唯一标识
|
||||
identifier = iosInfo.identifierForVendor;
|
||||
} else if (Platform.isMacOS) {
|
||||
final macInfo = await _deviceInfo.macOsInfo;
|
||||
// macOS使用systemGUID
|
||||
identifier = macInfo.systemGUID;
|
||||
} else if (Platform.isWindows) {
|
||||
final windowsInfo = await _deviceInfo.windowsInfo;
|
||||
// Windows使用计算机名作为唯一标识
|
||||
identifier = windowsInfo.computerName;
|
||||
} else if (Platform.isLinux) {
|
||||
final linuxInfo = await _deviceInfo.linuxInfo;
|
||||
// Linux使用machineId
|
||||
identifier = linuxInfo.machineId;
|
||||
} else {
|
||||
// Web或其他平台,使用生成的UUID
|
||||
identifier = await _getOrCreateStoredDeviceId();
|
||||
}
|
||||
|
||||
// 如果获取失败,使用存储的或生成新的ID
|
||||
if (identifier == null || identifier.isEmpty) {
|
||||
identifier = await _getOrCreateStoredDeviceId();
|
||||
}
|
||||
|
||||
return identifier;
|
||||
} catch (e) {
|
||||
print('❌ 获取设备ID失败: $e');
|
||||
KRLogUtil.kr_e('❌ 获取设备ID失败 - $e', tag: 'KRDeviceInfoService');
|
||||
// 如果获取失败,返回存储的或生成新的ID
|
||||
return await _getOrCreateStoredDeviceId();
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取或创建存储的设备ID
|
||||
Future<String> _getOrCreateStoredDeviceId() async {
|
||||
try {
|
||||
const key = 'kr_device_unique_id';
|
||||
final storage = KRSecureStorage();
|
||||
|
||||
String? storedId = await storage.kr_readData(key: key);
|
||||
|
||||
if (storedId == null || storedId.isEmpty) {
|
||||
// 生成新的UUID
|
||||
storedId = _generateUniqueId();
|
||||
await storage.kr_saveData(key: key, value: storedId);
|
||||
print('📱 生成新的设备ID: $storedId');
|
||||
KRLogUtil.kr_i('📱 生成新的设备ID - $storedId', tag: 'KRDeviceInfoService');
|
||||
} else {
|
||||
print('📱 使用存储的设备ID: $storedId');
|
||||
KRLogUtil.kr_i('📱 使用存储的设备ID - $storedId', tag: 'KRDeviceInfoService');
|
||||
}
|
||||
|
||||
return storedId;
|
||||
} catch (e) {
|
||||
print('❌ 获取存储的设备ID失败: $e');
|
||||
KRLogUtil.kr_e('❌ 获取存储的设备ID失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return _generateUniqueId();
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成唯一ID
|
||||
String _generateUniqueId() {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
final random = DateTime.now().microsecondsSinceEpoch.toString();
|
||||
final combined = '$timestamp-$random';
|
||||
|
||||
// 使用MD5生成唯一标识
|
||||
final bytes = utf8.encode(combined);
|
||||
final digest = md5.convert(bytes);
|
||||
|
||||
return digest.toString();
|
||||
}
|
||||
|
||||
/// 获取设备详细信息
|
||||
Future<Map<String, dynamic>> _getDeviceDetails() async {
|
||||
try {
|
||||
if (Platform.isAndroid) {
|
||||
final androidInfo = await _deviceInfo.androidInfo;
|
||||
return {
|
||||
'platform': 'android',
|
||||
'device': androidInfo.device,
|
||||
'model': androidInfo.model,
|
||||
'brand': androidInfo.brand,
|
||||
'manufacturer': androidInfo.manufacturer,
|
||||
'androidId': androidInfo.id,
|
||||
'version': androidInfo.version.release,
|
||||
'sdkInt': androidInfo.version.sdkInt,
|
||||
};
|
||||
} else if (Platform.isIOS) {
|
||||
final iosInfo = await _deviceInfo.iosInfo;
|
||||
return {
|
||||
'platform': 'ios',
|
||||
'name': iosInfo.name,
|
||||
'model': iosInfo.model,
|
||||
'systemName': iosInfo.systemName,
|
||||
'systemVersion': iosInfo.systemVersion,
|
||||
'identifierForVendor': iosInfo.identifierForVendor,
|
||||
'isPhysicalDevice': iosInfo.isPhysicalDevice,
|
||||
};
|
||||
} else if (Platform.isMacOS) {
|
||||
final macInfo = await _deviceInfo.macOsInfo;
|
||||
return {
|
||||
'platform': 'macos',
|
||||
'computerName': macInfo.computerName,
|
||||
'model': macInfo.model,
|
||||
'hostName': macInfo.hostName,
|
||||
'arch': macInfo.arch,
|
||||
'systemGUID': macInfo.systemGUID,
|
||||
};
|
||||
} else if (Platform.isWindows) {
|
||||
final windowsInfo = await _deviceInfo.windowsInfo;
|
||||
return {
|
||||
'platform': 'windows',
|
||||
'computerName': windowsInfo.computerName,
|
||||
'numberOfCores': windowsInfo.numberOfCores,
|
||||
'systemMemoryInMegabytes': windowsInfo.systemMemoryInMegabytes,
|
||||
};
|
||||
} else if (Platform.isLinux) {
|
||||
final linuxInfo = await _deviceInfo.linuxInfo;
|
||||
return {
|
||||
'platform': 'linux',
|
||||
'name': linuxInfo.name,
|
||||
'version': linuxInfo.version,
|
||||
'id': linuxInfo.id,
|
||||
'machineId': linuxInfo.machineId,
|
||||
};
|
||||
} else if (kIsWeb) {
|
||||
final webInfo = await _deviceInfo.webBrowserInfo;
|
||||
return {
|
||||
'platform': 'web',
|
||||
'browserName': webInfo.browserName.toString(),
|
||||
'userAgent': webInfo.userAgent,
|
||||
'vendor': webInfo.vendor,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
'platform': 'unknown',
|
||||
};
|
||||
} catch (e) {
|
||||
print('❌ 获取设备详情失败: $e');
|
||||
KRLogUtil.kr_e('❌ 获取设备详情失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return {
|
||||
'platform': 'unknown',
|
||||
'error': e.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取平台名称
|
||||
String getPlatformName() {
|
||||
if (Platform.isAndroid) return 'Android';
|
||||
if (Platform.isIOS) return 'iOS';
|
||||
if (Platform.isMacOS) return 'macOS';
|
||||
if (Platform.isWindows) return 'Windows';
|
||||
if (Platform.isLinux) return 'Linux';
|
||||
if (kIsWeb) return 'Web';
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/// 获取设备型号
|
||||
String getDeviceModel() {
|
||||
if (_deviceDetails == null) return 'Unknown';
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
return '${_deviceDetails!['brand']} ${_deviceDetails!['model']}';
|
||||
} else if (Platform.isIOS) {
|
||||
return _deviceDetails!['model'] ?? 'Unknown';
|
||||
} else if (Platform.isMacOS) {
|
||||
return _deviceDetails!['model'] ?? 'Unknown';
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/// 获取操作系统版本
|
||||
String getOSVersion() {
|
||||
if (_deviceDetails == null) return 'Unknown';
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
return _deviceDetails!['version'] ?? 'Unknown';
|
||||
} else if (Platform.isIOS) {
|
||||
return _deviceDetails!['systemVersion'] ?? 'Unknown';
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/// 获取User-Agent信息
|
||||
String getUserAgent() {
|
||||
final platform = getPlatformName();
|
||||
final model = getDeviceModel();
|
||||
final osVersion = getOSVersion();
|
||||
|
||||
return 'BearVPN/1.0.0 ($platform; $model; $osVersion) Flutter';
|
||||
}
|
||||
}
|
||||
292
lib/app/services/kr_site_config_service.dart
Normal file
292
lib/app/services/kr_site_config_service.dart
Normal file
@ -0,0 +1,292 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../model/response/kr_site_config.dart';
|
||||
import '../common/app_config.dart';
|
||||
import '../utils/kr_log_util.dart';
|
||||
|
||||
/// 网站配置服务
|
||||
class KRSiteConfigService extends ChangeNotifier {
|
||||
static final KRSiteConfigService _instance = KRSiteConfigService._internal();
|
||||
factory KRSiteConfigService() => _instance;
|
||||
KRSiteConfigService._internal() {
|
||||
// 配置 Dio 默认超时设置
|
||||
_dio.options.connectTimeout = const Duration(seconds: 10);
|
||||
_dio.options.sendTimeout = const Duration(seconds: 10);
|
||||
_dio.options.receiveTimeout = const Duration(seconds: 10);
|
||||
}
|
||||
|
||||
KRSiteConfig? _siteConfig;
|
||||
bool _isInitialized = false;
|
||||
final Dio _dio = Dio();
|
||||
|
||||
/// 获取站点配置
|
||||
KRSiteConfig? get siteConfig => _siteConfig;
|
||||
|
||||
/// 是否已初始化
|
||||
bool get isInitialized => _isInitialized;
|
||||
|
||||
/// 初始化站点配置
|
||||
Future<bool> initialize() async {
|
||||
try {
|
||||
print('🔧 KRSiteConfigService.initialize() 开始执行');
|
||||
KRLogUtil.kr_i('🔧 开始初始化网站配置', tag: 'KRSiteConfigService');
|
||||
|
||||
// Debug 模式下使用固定地址
|
||||
final baseUrl = AppConfig().baseUrl;
|
||||
print('📍 baseUrl = $baseUrl');
|
||||
final url = '$baseUrl/v1/common/site/config';
|
||||
print('📍 完整URL = $url');
|
||||
|
||||
KRLogUtil.kr_i('📤 请求网站配置 - $url', tag: 'KRSiteConfigService');
|
||||
print('📤 准备发送 GET 请求到: $url');
|
||||
print('⏱️ 超时配置: connectTimeout=10s, sendTimeout=10s, receiveTimeout=10s');
|
||||
|
||||
print('⏳ 开始发送请求...');
|
||||
final startTime = DateTime.now();
|
||||
final response = await _dio.get(url);
|
||||
final endTime = DateTime.now();
|
||||
final duration = endTime.difference(startTime).inMilliseconds;
|
||||
print('⏱️ 请求耗时: ${duration}ms');
|
||||
|
||||
print('✅ 请求完成,状态码: ${response.statusCode}');
|
||||
KRLogUtil.kr_i('📥 响应状态码 - ${response.statusCode}', tag: 'KRSiteConfigService');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = response.data;
|
||||
print('📥 响应数据类型: ${responseData.runtimeType}');
|
||||
print('📥 响应数据: $responseData');
|
||||
KRLogUtil.kr_i('📥 响应数据 - $responseData', tag: 'KRSiteConfigService');
|
||||
|
||||
if (responseData['code'] == 200) {
|
||||
_siteConfig = KRSiteConfig.fromJson(responseData['data']);
|
||||
_isInitialized = true;
|
||||
|
||||
// 打印配置信息
|
||||
_printConfigInfo();
|
||||
|
||||
// 通知监听者配置已更新
|
||||
notifyListeners();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
KRLogUtil.kr_e('❌ API返回错误 - ${responseData['msg']}', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
KRLogUtil.kr_e('❌ HTTP错误 - ${response.statusCode}', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
}
|
||||
} on DioException catch (e, stackTrace) {
|
||||
print('❌ Dio请求异常: ${e.type}');
|
||||
print('❌ 错误信息: ${e.message}');
|
||||
print('❌ 请求URL: ${e.requestOptions.uri}');
|
||||
print('❌ 连接超时: ${e.requestOptions.connectTimeout}');
|
||||
print('❌ 发送超时: ${e.requestOptions.sendTimeout}');
|
||||
print('❌ 接收超时: ${e.requestOptions.receiveTimeout}');
|
||||
if (e.response != null) {
|
||||
print('❌ 响应状态码: ${e.response?.statusCode}');
|
||||
print('❌ 响应数据: ${e.response?.data}');
|
||||
}
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
|
||||
KRLogUtil.kr_e('❌ Dio异常 - ${e.type}: ${e.message}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_e('📚 堆栈: $stackTrace', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
} catch (e, stackTrace) {
|
||||
print('❌ 未知异常: $e');
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
KRLogUtil.kr_e('❌ 初始化失败 - $e', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_e('📚 堆栈: $stackTrace', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 打印配置信息
|
||||
void _printConfigInfo() {
|
||||
if (_siteConfig == null) return;
|
||||
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('📊 网站配置信息:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
|
||||
// 站点信息
|
||||
KRLogUtil.kr_i('🏠 站点名称: ${_siteConfig!.site.siteName}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🏠 站点描述: ${_siteConfig!.site.siteDesc}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🏠 站点域名: ${_siteConfig!.site.host}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('💬 Crisp ID: ${_siteConfig!.site.crispId}', tag: 'KRSiteConfigService');
|
||||
|
||||
// 注册相关
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('📝 注册配置:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 开放注册: ${isRegisterEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 手机号注册: ${isMobileRegisterEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 邮箱注册: ${isEmailRegisterEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 设备登录: ${isDeviceLoginEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
|
||||
// 验证相关
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🔐 验证配置:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 邮箱验证: ${isEmailVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 手机验证: ${isMobileVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 登录验证: ${isLoginVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 注册验证: ${isRegisterVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 重置密码验证: ${isResetPasswordVerificationEnabled() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
|
||||
// 邀请相关
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🎁 邀请配置:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 强制邀请码: ${isForcedInvite() ? "是" : "否"}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 推荐比例: ${_siteConfig!.invite.referralPercentage}%', tag: 'KRSiteConfigService');
|
||||
|
||||
// 货币相关
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('💰 货币配置:', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 货币单位: ${_siteConfig!.currency.currencyUnit}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i(' ✓ 货币符号: ${_siteConfig!.currency.currencySymbol}', tag: 'KRSiteConfigService');
|
||||
|
||||
// OAuth 方法
|
||||
if (_siteConfig!.oauthMethods.isNotEmpty) {
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('🔑 OAuth 方法: ${_siteConfig!.oauthMethods.join(", ")}', tag: 'KRSiteConfigService');
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('✅ 网站配置初始化成功', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'KRSiteConfigService');
|
||||
}
|
||||
|
||||
/// 是否开启手机号注册
|
||||
bool isMobileRegisterEnabled() {
|
||||
return _siteConfig?.auth.mobile.enable ?? false;
|
||||
}
|
||||
|
||||
/// 是否开启邮箱注册
|
||||
bool isEmailRegisterEnabled() {
|
||||
return _siteConfig?.auth.email.enable ?? false;
|
||||
}
|
||||
|
||||
/// 是否开放注册(未停止注册)
|
||||
bool isRegisterEnabled() {
|
||||
return !(_siteConfig?.auth.register.stopRegister ?? true);
|
||||
}
|
||||
|
||||
/// 是否开启邮箱验证
|
||||
bool isEmailVerificationEnabled() {
|
||||
return _siteConfig?.auth.email.enableVerify ?? false;
|
||||
}
|
||||
|
||||
/// 是否开启手机验证
|
||||
bool isMobileVerificationEnabled() {
|
||||
return _siteConfig?.auth.mobile.enable ?? false;
|
||||
}
|
||||
|
||||
/// 是否开启登录验证
|
||||
bool isLoginVerificationEnabled() {
|
||||
return _siteConfig?.verify.enableLoginVerify ?? false;
|
||||
}
|
||||
|
||||
/// 是否开启注册验证
|
||||
bool isRegisterVerificationEnabled() {
|
||||
return _siteConfig?.verify.enableRegisterVerify ?? false;
|
||||
}
|
||||
|
||||
/// 是否开启重置密码验证
|
||||
bool isResetPasswordVerificationEnabled() {
|
||||
return _siteConfig?.verify.enableResetPasswordVerify ?? false;
|
||||
}
|
||||
|
||||
/// 是否强制邀请码
|
||||
bool isForcedInvite() {
|
||||
return _siteConfig?.invite.forcedInvite ?? false;
|
||||
}
|
||||
|
||||
/// 获取验证码间隔时间(秒)
|
||||
int getVerifyCodeInterval() {
|
||||
return _siteConfig?.verifyCode.verifyCodeInterval ?? 60;
|
||||
}
|
||||
|
||||
/// 获取OAuth方法列表
|
||||
List<String> getOAuthMethods() {
|
||||
return _siteConfig?.oauthMethods ?? [];
|
||||
}
|
||||
|
||||
/// 检查是否支持设备模式(匿名游客模式)
|
||||
bool isDeviceModeSupported() {
|
||||
final oauthMethods = getOAuthMethods();
|
||||
return oauthMethods.contains('device');
|
||||
}
|
||||
|
||||
/// 检查是否启用设备登录
|
||||
bool isDeviceLoginEnabled() {
|
||||
return _siteConfig?.auth.device.enable ?? false;
|
||||
}
|
||||
|
||||
/// 检查是否需要设备安全加密
|
||||
bool isDeviceSecurityEnabled() {
|
||||
return _siteConfig?.auth.device.enableSecurity ?? false;
|
||||
}
|
||||
|
||||
/// 检查是否显示广告
|
||||
bool isDeviceShowAds() {
|
||||
return _siteConfig?.auth.device.showAds ?? false;
|
||||
}
|
||||
|
||||
/// 检查是否只允许真实设备
|
||||
bool isOnlyRealDevice() {
|
||||
return _siteConfig?.auth.device.onlyRealDevice ?? false;
|
||||
}
|
||||
|
||||
/// 获取站点信息
|
||||
KRSiteInfo? getSiteInfo() {
|
||||
return _siteConfig?.site;
|
||||
}
|
||||
|
||||
/// 获取货币配置
|
||||
KRCurrencyConfig? getCurrencyConfig() {
|
||||
return _siteConfig?.currency;
|
||||
}
|
||||
|
||||
/// 获取订阅配置
|
||||
KRSubscribeConfig? getSubscribeConfig() {
|
||||
return _siteConfig?.subscribe;
|
||||
}
|
||||
|
||||
/// 检查手机号是否在白名单中
|
||||
bool isMobileInWhitelist(String mobile) {
|
||||
if (!(_siteConfig?.auth.mobile.enableWhitelist ?? false)) {
|
||||
return true; // 如果未开启白名单,则允许所有手机号
|
||||
}
|
||||
|
||||
final whitelist = _siteConfig?.auth.mobile.whitelist ?? [];
|
||||
return whitelist.contains(mobile);
|
||||
}
|
||||
|
||||
/// 检查邮箱域名是否被允许
|
||||
bool isEmailDomainAllowed(String email) {
|
||||
if (!(_siteConfig?.auth.email.enableDomainSuffix ?? false)) {
|
||||
return true; // 如果未开启域名限制,则允许所有域名
|
||||
}
|
||||
|
||||
final domainSuffixList = _siteConfig?.auth.email.domainSuffixList ?? '';
|
||||
if (domainSuffixList.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final allowedDomains = domainSuffixList.split(',').map((d) => d.trim()).toList();
|
||||
final emailDomain = email.split('@').last.toLowerCase();
|
||||
|
||||
return allowedDomains.any((domain) => emailDomain.endsWith(domain.toLowerCase()));
|
||||
}
|
||||
|
||||
/// 获取Crisp客服系统ID
|
||||
String getCrispId() {
|
||||
return _siteConfig?.site.crispId ?? '0';
|
||||
}
|
||||
|
||||
/// 重置配置
|
||||
void reset() {
|
||||
_siteConfig = null;
|
||||
_isInitialized = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@ -3,20 +3,63 @@ import 'dart:typed_data';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
|
||||
/// AES加密解密工具
|
||||
class AESUtils {
|
||||
// Encrypt function: Takes plaintext and key, returns base64 ciphertext and nonce
|
||||
Map<String, String> encryptData(String plainText, String keyStr) {
|
||||
// Generate timestamp as nonce
|
||||
String nonce = DateTime.now().toIso8601String();
|
||||
/// AES加密解密工具类
|
||||
/// 算法:AES-256-CBC
|
||||
/// 填充:PKCS7
|
||||
/// 密钥生成:SHA-256(keyStr) → 32字节
|
||||
/// IV生成:SHA-256(MD5(nonce) + keyStr) → 取前16字节
|
||||
class KRAesUtil {
|
||||
/// 生成密钥(使用SHA-256)
|
||||
static encrypt.Key _generateKey(String keyStr) {
|
||||
final keyBytes = sha256.convert(utf8.encode(keyStr)).bytes;
|
||||
return encrypt.Key(Uint8List.fromList(keyBytes));
|
||||
}
|
||||
|
||||
// Generate key and IV
|
||||
final key = generateKey(keyStr);
|
||||
final iv = generateIv(nonce, keyStr);
|
||||
/// 生成IV(使用MD5 + SHA-256)
|
||||
static encrypt.IV _generateIv(String ivStr, String keyStr) {
|
||||
// 首先用MD5处理ivStr
|
||||
final md5Hash = md5.convert(utf8.encode(ivStr)).bytes;
|
||||
|
||||
// Encrypt the data
|
||||
// 将MD5结果转换为十六进制字符串
|
||||
final md5Hex = _hexEncode(md5Hash);
|
||||
|
||||
// 拼接 md5Hex + keyStr
|
||||
final combinedKey = md5Hex + keyStr;
|
||||
|
||||
// 对拼接后的字符串做SHA-256
|
||||
final finalHash = sha256.convert(utf8.encode(combinedKey)).bytes;
|
||||
|
||||
// 取前16字节作为IV
|
||||
return encrypt.IV(Uint8List.fromList(finalHash.sublist(0, 16)));
|
||||
}
|
||||
|
||||
/// 将字节数组转换为十六进制字符串
|
||||
static String _hexEncode(List<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
|
||||
}
|
||||
|
||||
/// 加密数据
|
||||
///
|
||||
/// 参数:
|
||||
/// - plainText: 要加密的明文
|
||||
/// - keyStr: 加密密钥
|
||||
///
|
||||
/// 返回:
|
||||
/// - Map包含两个字段: data(加密后的Base64字符串), time(ISO8601时间戳)
|
||||
static Map<String, String> encryptData(String plainText, String keyStr) {
|
||||
// 生成时间戳
|
||||
final nonce = DateTime.now().toIso8601String();
|
||||
|
||||
// 生成密钥和IV
|
||||
final key = _generateKey(keyStr);
|
||||
final iv = _generateIv(nonce, keyStr);
|
||||
|
||||
// 创建加密器 (AES-256-CBC with PKCS7 padding)
|
||||
final encrypter = encrypt.Encrypter(
|
||||
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: 'PKCS7'));
|
||||
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: 'PKCS7'),
|
||||
);
|
||||
|
||||
// 加密
|
||||
final encrypted = encrypter.encrypt(plainText, iv: iv);
|
||||
|
||||
return {
|
||||
@ -25,38 +68,55 @@ class AESUtils {
|
||||
};
|
||||
}
|
||||
|
||||
// Decrypt function: Takes base64 ciphertext, key, and nonce, returns plaintext
|
||||
String decryptData(String cipherText, String keyStr, String nonce) {
|
||||
// Generate key and IV
|
||||
final key = generateKey(keyStr);
|
||||
final iv = generateIv(nonce, keyStr);
|
||||
/// 解密数据
|
||||
///
|
||||
/// 参数:
|
||||
/// - encryptedData: 加密后的Base64字符串
|
||||
/// - nonce: 时间戳
|
||||
/// - keyStr: 解密密钥
|
||||
///
|
||||
/// 返回:
|
||||
/// - 解密后的明文字符串
|
||||
static String decryptData(String encryptedData, String nonce, String keyStr) {
|
||||
// 生成密钥和IV
|
||||
final key = _generateKey(keyStr);
|
||||
final iv = _generateIv(nonce, keyStr);
|
||||
|
||||
// Decrypt the data
|
||||
// 创建解密器 (AES-256-CBC with PKCS7 padding)
|
||||
final encrypter = encrypt.Encrypter(
|
||||
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: 'PKCS7'));
|
||||
final decrypted = encrypter.decrypt64(cipherText, iv: iv);
|
||||
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: 'PKCS7'),
|
||||
);
|
||||
|
||||
// 解密
|
||||
final decrypted = encrypter.decrypt64(encryptedData, iv: iv);
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
// Generate AES key from input key (32 bytes for AES-256)
|
||||
encrypt.Key generateKey(String keyStr) {
|
||||
final keyBytes = sha256.convert(utf8.encode(keyStr)).bytes;
|
||||
return encrypt.Key(Uint8List.fromList(keyBytes));
|
||||
/// 加密JSON对象
|
||||
///
|
||||
/// 参数:
|
||||
/// - jsonData: 要加密的JSON对象
|
||||
/// - keyStr: 加密密钥
|
||||
///
|
||||
/// 返回:
|
||||
/// - Map包含两个字段: data(加密后的Base64字符串), time(ISO8601时间戳)
|
||||
static Map<String, String> encryptJson(Map<String, dynamic> jsonData, String keyStr) {
|
||||
final plainText = jsonEncode(jsonData);
|
||||
return encryptData(plainText, keyStr);
|
||||
}
|
||||
|
||||
// Generate IV (Initialization Vector) from nonce and key
|
||||
encrypt.IV generateIv(String ivStr, String keyStr) {
|
||||
final md5Hash = md5.convert(utf8.encode(ivStr)).bytes;
|
||||
final combinedKey = hexEncode(md5Hash) + keyStr;
|
||||
final finalHash = sha256.convert(utf8.encode(combinedKey)).bytes;
|
||||
|
||||
return encrypt.IV(Uint8List.fromList(
|
||||
finalHash.sublist(0, 16))); // AES-CBC IV must be 16 bytes
|
||||
}
|
||||
|
||||
// Helper function to encode bytes to hex
|
||||
String hexEncode(List<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
|
||||
/// 解密JSON对象
|
||||
///
|
||||
/// 参数:
|
||||
/// - encryptedData: 加密后的Base64字符串
|
||||
/// - nonce: 时间戳
|
||||
/// - keyStr: 解密密钥
|
||||
///
|
||||
/// 返回:
|
||||
/// - 解密后的JSON对象
|
||||
static Map<String, dynamic> decryptJson(String encryptedData, String nonce, String keyStr) {
|
||||
final decrypted = decryptData(encryptedData, nonce, keyStr);
|
||||
return jsonDecode(decrypted) as Map<String, dynamic>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import 'package:kaer_with_panels/app/utils/kr_window_manager.dart';
|
||||
|
||||
import 'app/utils/kr_secure_storage.dart';
|
||||
import 'app/common/app_config.dart';
|
||||
import 'app/services/kr_site_config_service.dart';
|
||||
|
||||
// 全局导航键
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
@ -49,6 +50,7 @@ void main() async {
|
||||
// 启动域名预检测(异步,不阻塞应用启动)
|
||||
// 立即启动域名检测,不延迟
|
||||
KRDomain.kr_preCheckDomains();
|
||||
|
||||
// 初始化 FMTC
|
||||
// try {
|
||||
// if (Platform.isMacOS) {
|
||||
|
||||
@ -6,6 +6,7 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import connectivity_plus
|
||||
import device_info_plus
|
||||
import flutter_inappwebview_macos
|
||||
import flutter_udid
|
||||
import package_info_plus
|
||||
@ -18,6 +19,7 @@ import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
|
||||
@ -2,6 +2,8 @@ PODS:
|
||||
- connectivity_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_inappwebview_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- OrderedSet (~> 6.0.3)
|
||||
@ -31,6 +33,7 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
|
||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
|
||||
- flutter_udid (from `Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
@ -51,6 +54,8 @@ SPEC REPOS:
|
||||
EXTERNAL SOURCES:
|
||||
connectivity_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
|
||||
device_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||
flutter_inappwebview_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
|
||||
flutter_udid:
|
||||
@ -74,6 +79,7 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity_plus: e74b9f74717d2d99d45751750e266e55912baeb5
|
||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
|
||||
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
|
||||
16
pubspec.lock
16
pubspec.lock
@ -345,6 +345,22 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.2.3"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "11.3.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.0.3"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@ -111,6 +111,7 @@ dependencies:
|
||||
|
||||
# 新添加的依赖
|
||||
tray_manager: ^0.2.0
|
||||
device_info_plus: ^11.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user