From 2333ad52a9e6c663deac05f54877df27694b701f Mon Sep 17 00:00:00 2001 From: Rust Date: Fri, 17 Oct 2025 15:28:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B8=B8=E5=AE=A2=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/app/common/app_config.dart | 12 +- lib/app/model/response/kr_site_config.dart | 425 ++++++++++++++++++ .../controllers/kr_splash_controller.dart | 157 ++++++- lib/app/network/base_response.dart | 23 +- lib/app/network/http_util.dart | 10 +- lib/app/services/api_service/api.dart | 4 + lib/app/services/api_service/kr_auth_api.dart | 145 ++++++ lib/app/services/kr_device_info_service.dart | 260 +++++++++++ lib/app/services/kr_site_config_service.dart | 292 ++++++++++++ lib/app/utils/kr_aes_util.dart | 132 ++++-- lib/main.dart | 2 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Podfile.lock | 6 + pubspec.lock | 16 + pubspec.yaml | 1 + 15 files changed, 1434 insertions(+), 53 deletions(-) create mode 100644 lib/app/model/response/kr_site_config.dart create mode 100644 lib/app/services/kr_device_info_service.dart create mode 100644 lib/app/services/kr_site_config_service.dart diff --git a/lib/app/common/app_config.dart b/lib/app/common/app_config.dart index bb3f202..318117c 100755 --- a/lib/app/common/app_config.dart +++ b/lib/app/common/app_config.dart @@ -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}"; } diff --git a/lib/app/model/response/kr_site_config.dart b/lib/app/model/response/kr_site_config.dart new file mode 100644 index 0000000..196d47e --- /dev/null +++ b/lib/app/model/response/kr_site_config.dart @@ -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 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 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.from(json['oauth_methods'] ?? []), + webAd: json['web_ad'] ?? false, + ); + } + + Map 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 json) { + String crispId = '0'; + + // 尝试解析 custom_data 中的 kr_website_id + try { + final customDataStr = json['custom_data'] ?? ''; + if (customDataStr.isNotEmpty) { + final customDataJson = jsonDecode(customDataStr) as Map; + 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 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 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 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 json) { + return KRAuthConfig( + mobile: KRMobileAuth.fromJson(json['mobile'] ?? {}), + email: KREmailAuth.fromJson(json['email'] ?? {}), + device: KRDeviceAuth.fromJson(json['device'] ?? {}), + register: KRRegisterAuth.fromJson(json['register'] ?? {}), + ); + } + + Map toJson() { + return { + 'mobile': mobile.toJson(), + 'email': email.toJson(), + 'device': device.toJson(), + 'register': register.toJson(), + }; + } +} + +/// 手机号认证配置 +class KRMobileAuth { + final bool enable; + final bool enableWhitelist; + final List whitelist; + + KRMobileAuth({ + required this.enable, + required this.enableWhitelist, + required this.whitelist, + }); + + factory KRMobileAuth.fromJson(Map json) { + return KRMobileAuth( + enable: json['enable'] ?? false, + enableWhitelist: json['enable_whitelist'] ?? false, + whitelist: List.from(json['whitelist'] ?? []), + ); + } + + Map 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 json) { + return KREmailAuth( + enable: json['enable'] ?? false, + enableVerify: json['enable_verify'] ?? false, + enableDomainSuffix: json['enable_domain_suffix'] ?? false, + domainSuffixList: json['domain_suffix_list'] ?? '', + ); + } + + Map 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 json) { + return KRDeviceAuth( + enable: json['enable'] ?? false, + showAds: json['show_ads'] ?? false, + enableSecurity: json['enable_security'] ?? false, + onlyRealDevice: json['only_real_device'] ?? false, + ); + } + + Map 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 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 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 json) { + return KRInviteConfig( + forcedInvite: json['forced_invite'] ?? false, + referralPercentage: (json['referral_percentage'] ?? 0).toDouble(), + onlyFirstPurchase: json['only_first_purchase'] ?? false, + ); + } + + Map 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 json) { + return KRCurrencyConfig( + currencyUnit: json['currency_unit'] ?? '', + currencySymbol: json['currency_symbol'] ?? '', + ); + } + + Map 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 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 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 json) { + return KRVerifyCodeConfig( + verifyCodeInterval: json['verify_code_interval'] ?? 60, + ); + } + + Map toJson() { + return { + 'verify_code_interval': verifyCodeInterval, + }; + } +} diff --git a/lib/app/modules/kr_splash/controllers/kr_splash_controller.dart b/lib/app/modules/kr_splash/controllers/kr_splash_controller.dart index d7c1c1a..3252b42 100755 --- a/lib/app/modules/kr_splash/controllers/kr_splash_controller.dart +++ b/lib/app/modules/kr_splash/controllers/kr_splash_controller.dart @@ -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 _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 _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 _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 _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) { diff --git a/lib/app/network/base_response.dart b/lib/app/network/base_response.dart index bddb850..05cd007 100755 --- a/lib/app/network/base_response.dart +++ b/lib/app/network/base_response.dart @@ -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 { @@ -18,15 +21,23 @@ class BaseResponse { BaseResponse.fromJson(Map json) { retCode = json['code']; - final aes = AESUtils(); final dataMap = json['data'] ?? Map(); 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 { diff --git a/lib/app/network/http_util.dart b/lib/app/network/http_util.dart index 330f270..45240ed 100755 --- a/lib/app/network/http_util.dart +++ b/lib/app/network/http_util.dart @@ -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 = {}; - 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; } diff --git a/lib/app/services/api_service/api.dart b/lib/app/services/api_service/api.dart index e6e97fe..8e350c0 100755 --- a/lib/app/services/api_service/api.dart +++ b/lib/app/services/api_service/api.dart @@ -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"; diff --git a/lib/app/services/api_service/kr_auth_api.dart b/lib/app/services/api_service/kr_auth_api.dart index 0da2c01..2e102d2 100755 --- a/lib/app/services/api_service/kr_auth_api.dart +++ b/lib/app/services/api_service/kr_auth_api.dart @@ -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> 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 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 = { + '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 responseData = response.data as Map; + + // 检查是否需要解密响应 + if (needEncryption && responseData.containsKey('data')) { + final dataField = responseData['data']; + if (dataField is Map && + 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'; diff --git a/lib/app/services/kr_device_info_service.dart b/lib/app/services/kr_device_info_service.dart new file mode 100644 index 0000000..0936158 --- /dev/null +++ b/lib/app/services/kr_device_info_service.dart @@ -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? _deviceDetails; + + // 获取设备唯一标识 + String? get deviceId => _deviceId; + + // 获取设备详细信息 + Map? get deviceDetails => _deviceDetails; + + /// 初始化设备信息 + Future 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 _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 _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> _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'; + } +} diff --git a/lib/app/services/kr_site_config_service.dart b/lib/app/services/kr_site_config_service.dart new file mode 100644 index 0000000..ae7e3fa --- /dev/null +++ b/lib/app/services/kr_site_config_service.dart @@ -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 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 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(); + } +} diff --git a/lib/app/utils/kr_aes_util.dart b/lib/app/utils/kr_aes_util.dart index 04c73c1..e145b64 100755 --- a/lib/app/utils/kr_aes_util.dart +++ b/lib/app/utils/kr_aes_util.dart @@ -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 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 bytes) { + return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); + } + + /// 加密数据 + /// + /// 参数: + /// - plainText: 要加密的明文 + /// - keyStr: 加密密钥 + /// + /// 返回: + /// - Map包含两个字段: data(加密后的Base64字符串), time(ISO8601时间戳) + static Map 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 encryptJson(Map 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 bytes) { - return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); + /// 解密JSON对象 + /// + /// 参数: + /// - encryptedData: 加密后的Base64字符串 + /// - nonce: 时间戳 + /// - keyStr: 解密密钥 + /// + /// 返回: + /// - 解密后的JSON对象 + static Map decryptJson(String encryptedData, String nonce, String keyStr) { + final decrypted = decryptData(encryptedData, nonce, keyStr); + return jsonDecode(decrypted) as Map; } } diff --git a/lib/main.dart b/lib/main.dart index 883aec0..c558f1a 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 navigatorKey = GlobalKey(); @@ -49,6 +50,7 @@ void main() async { // 启动域名预检测(异步,不阻塞应用启动) // 立即启动域名检测,不延迟 KRDomain.kr_preCheckDomains(); + // 初始化 FMTC // try { // if (Platform.isMacOS) { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index abfc6c2..8c33c93 100755 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 9600438..6492702 100755 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -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 diff --git a/pubspec.lock b/pubspec.lock index dd5b15d..9247a59 100755 --- a/pubspec.lock +++ b/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: diff --git a/pubspec.yaml b/pubspec.yaml index 5bcd231..38c9176 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -111,6 +111,7 @@ dependencies: # 新添加的依赖 tray_manager: ^0.2.0 + device_info_plus: ^11.3.0 dev_dependencies: flutter_test: