From a4dd5fb4631e0cc847e293e852f729223f0cddf5 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 1 Aug 2025 19:51:49 +0800 Subject: [PATCH 1/8] =?UTF-8?q?refactor(splash):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=B5=81=E7=A8=8B=E4=B8=BA=E5=B9=B6=E8=A1=8C?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=B9=B6=E4=BC=98=E5=8C=96=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将资源加载状态拆分为多个独立任务状态,包括追踪权限、版本检查、Token验证和应用配置 优化启动流程为并行执行,在1.5秒动画期间完成所有任务 添加任务完成状态检查确保所有任务完成后才进行页面跳转 --- .../splash/controllers/splash_controller.dart | 156 ++++++++++-------- 1 file changed, 89 insertions(+), 67 deletions(-) diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 4bf70d207..013ba187e 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -24,9 +24,16 @@ class SplashController extends GetxController { final _loadingProgress = 0.0.obs; double get loadingProgress => _loadingProgress.value; - // 资源加载状态 - final _isResourceLoaded = false.obs; - bool get isResourceLoaded => _isResourceLoaded.value; + // 各项任务完成状态 + final _isTrackingPermissionRequested = false.obs; // 追踪权限请求完成 + final _isVersionCheckCompleted = false.obs; // 版本检查完成 + final _isTokenValidated = false.obs; // Token验证完成 + final _isAppConfigLoaded = false.obs; // 应用配置加载完成 + final _isAnimationCompleted = false.obs; // 动画完成 + + // 登录状态 + final _isLoggedIn = false.obs; + bool get isLoggedIn => _isLoggedIn.value; final _hasAcceptedPrivacy = false.obs; //是否同意隐私政策 @@ -59,38 +66,45 @@ class SplashController extends GetxController { } }); - // 异步处理用户追踪授权,不阻塞主流程 - _requestTrackingPermissionAsync(); + // 注意:追踪权限请求将在用户同意隐私政策后执行 } - /// 异步请求追踪权限,不阻塞启动流程 - void _requestTrackingPermissionAsync() { - Future.delayed(const Duration(milliseconds: 500), () async { - try { - await AppTrackingTransparency.requestTrackingAuthorization(); - Logger.d(_tag, '用户追踪权限请求完成'); - } catch (e) { - Logger.e(_tag, '用户追踪权限请求失败: $e'); - } - }); + /// 异步请求追踪权限 + Future _requestTrackingPermissionAsync() async { + try { + await AppTrackingTransparency.requestTrackingAuthorization(); + Logger.d(_tag, '用户追踪权限请求完成'); + } catch (e) { + Logger.e(_tag, '用户追踪权限请求失败: $e'); + } finally { + _isTrackingPermissionRequested.value = true; + } } void _startNormalFlow() { _startLoadingAnimation(); - _loadAppResources(); + _startParallelTasks(); } - /// 智能调度版本检查,在页面跳转后执行,完全不影响启动流程 - void _scheduleVersionCheck() { - // 在独立的微任务中执行,避免阻塞主线程 - scheduleMicrotask(() async { - try { - Logger.d(_tag, '开始执行版本检查(后台任务)'); - _checkVersionUpdateIfNeeded(); - } catch (e) { - Logger.e(_tag, '版本检查调度失败: $e'); - } - }); + /// 启动并行任务:在1.5秒动画期间同时执行多个任务 + void _startParallelTasks() { + // 并行执行所有任务 + _requestTrackingPermissionAsync(); + _performVersionCheckAsync(); + _validateTokenAsync(); + } + + /// 异步执行版本检查 + Future _performVersionCheckAsync() async { + try { + Logger.d(_tag, '开始执行版本检查'); + await _checkVersionUpdateIfNeeded(); + } catch (e) { + Logger.e(_tag, '版本检查失败: $e'); + } + //finally { + // _isVersionCheckCompleted.value = true; + // } } void _onPrivacyAccepted() { @@ -115,10 +129,10 @@ class SplashController extends GetxController { ); } - // 进度条动画(持续2秒) + // 进度条动画(持续1.5秒) void _startLoadingAnimation() { - const totalDuration = 1500; // 动画总时长增加到1.5秒 - const steps = totalDuration ~/ 30; // 30的间隔,更平滑的动画 + const totalDuration = 1500; // 动画总时长1.5秒 + const steps = totalDuration ~/ 30; // 30ms间隔,更平滑的动画 const stepValue = 1.0 / steps; Timer.periodic(const Duration(milliseconds: 30), (timer) { @@ -126,54 +140,53 @@ class SplashController extends GetxController { _loadingProgress.value += stepValue; } else { timer.cancel(); + _isAnimationCompleted.value = true; _checkNavigateCondition(); } }); } - // 异步加载应用资源 - Future _loadAppResources() async { + /// 异步验证Token和加载应用配置 + Future _validateTokenAsync() async { try { - // 模拟资源加载(如API配置、本地缓存等) - await Future.delayed(const Duration(milliseconds: 1500)); - - // 实际资源加载代码 - // await _loadImages(); - // await _initDatabase(); + bool isLoggedIn = false; + final token = _storage.read('logintoken'); + Logger.d(_tag, 'Token验证开始: $token'); + + if (token != null && token.isNotEmpty) { + isLoggedIn = await getLoginToken(); + if (isLoggedIn) { + await getAppConfig(); + _isAppConfigLoaded.value = true; + } + } - _isResourceLoaded.value = true; - _checkNavigateCondition(); + _isLoggedIn.value = isLoggedIn; + Logger.d(_tag, 'Token验证完成,登录状态: $isLoggedIn'); } catch (e) { - Get.snackbar('错误', '资源加载失败,请检查网络连接'); - _isResourceLoaded.value = true; // 允许用户继续使用 - Logger.e(_tag, '资源加载错误: $e'); + Logger.e(_tag, 'Token验证失败: $e'); + _isLoggedIn.value = false; + } finally { + _isTokenValidated.value = true; } } - // 检查跳转条件 - void _checkNavigateCondition() async { - if (_loadingProgress.value >= 1.0 && _isResourceLoaded.value) { - // 关键改动:先执行页面跳转,并用 await 等待它完成 - await _navigateToLogin(); - - // 页面跳转完全结束后,再安心地安排版本检查 - _scheduleVersionCheck(); + // 检查跳转条件:所有任务都完成后才跳转 + void _checkNavigateCondition() { + if (_isAnimationCompleted.value && + _isTrackingPermissionRequested.value && + _isVersionCheckCompleted.value && + _isTokenValidated.value) { + _navigateToTargetPage(); } } - // 获取登录Token并跳转 - Future _navigateToLogin() async { - bool isLoggedIn = false; - final token = _storage.read('logintoken'); - Logger.d(_tag, 'getLoginToken:进来了${token}'); - if (token != null && token.isNotEmpty) { - isLoggedIn = await getLoginToken(); - } - if (isLoggedIn) { - await getAppConfig(); + // 根据登录状态跳转到目标页面 + void _navigateToTargetPage() { + if (_isLoggedIn.value && _isAppConfigLoaded.value) { Get.offAllNamed(Routes.pairing); } else { - Get.offAllNamed(Routes.login); // 跳转到登录页 + Get.offAllNamed(Routes.login); } } @@ -227,7 +240,7 @@ class SplashController extends GetxController { /// 混合检查机制:判断是否需要进行版本检查 /// 检查条件:1. 会话内未检查过 OR 2. 距离上次检查超过24小时 - void _checkVersionUpdateIfNeeded() { + Future _checkVersionUpdateIfNeeded() async { try { final now = DateTime.now(); final lastCheckTimestamp = _storage.read(_lastVersionCheckKey); @@ -259,7 +272,7 @@ class SplashController extends GetxController { if (shouldCheck) { Logger.i(_tag, '开始版本检查 - 原因: $reason'); - _performVersionCheck(); + await _performVersionCheck(); } else { Logger.i(_tag, '跳过版本检查 - 会话内已检查且未超过时间间隔'); } @@ -278,9 +291,14 @@ class SplashController extends GetxController { Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}'); // 在主线程中显示更新对话框 WidgetsBinding.instance.addPostFrameCallback((_) { - VersionUpdateDialog.show(updateInfo); + VersionUpdateDialog.show(updateInfo).then((_) { + // 无论用户选择更新还是稍后提醒,都标记版本检查完成 + _isVersionCheckCompleted.value = true; + _checkNavigateCondition(); + }); }); } else { + _isVersionCheckCompleted.value = true; Logger.i(_tag, '当前已是最新版本: ${updateInfo.currentVersion}'); } @@ -288,12 +306,16 @@ class SplashController extends GetxController { _updateVersionCheckRecord(); }).catchError((e) { Logger.e(_tag, '版本检查失败: $e'); - // 即使检查失败,也更新记录,避免频繁重试 + // 即使检查失败,也标记版本检查完成,让流程继续 + _isVersionCheckCompleted.value = true; + // 更新检查记录,避免频繁重试 _updateVersionCheckRecord(); }); } catch (e) { Logger.e(_tag, '版本检查失败: $e'); - // 即使检查失败,也更新记录,避免频繁重试 + // 即使检查失败,也标记版本检查完成,让流程继续 + _isVersionCheckCompleted.value = true; + // 更新检查记录,避免频繁重试 _updateVersionCheckRecord(); } } From cfcf2650288721aa5c3e649b8119bae8880027a6 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 1 Aug 2025 20:05:55 +0800 Subject: [PATCH 2/8] =?UTF-8?q?fix(settings):=20=E4=BB=85=E5=9C=A8Android?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E6=98=BE=E7=A4=BA=E9=9A=90=E7=A7=81=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=92=8C=E6=9D=83=E9=99=90=E7=AE=A1=E7=90=86=E9=80=89?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改设置页面,将隐私设置和权限管理选项包裹在平台判断条件中,确保这些功能只在Android平台显示 --- lib/modules/settings/views/settings_view.dart | 97 ++++++++++--------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 58d527fdb..784c905ed 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -407,8 +407,6 @@ class SettingsView extends GetView { ? Colors.white.withOpacity(0.1) : Colors.grey[200]), - - // 用户反馈 _buildSimpleNavigationSetting( title: 'feedback'.tr, // 用户反馈 @@ -427,29 +425,33 @@ class SettingsView extends GetView { }, isDarkMode: isDarkMode, ), - Divider( - height: 1, - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[200]), + if (Platform.isAndroid) ...[ + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), + + // 隐私设置 + _buildSimpleNavigationSetting( + title: 'privacySettings'.tr, // 隐私设置 + subtitle: 'privacySettingsInfo'.tr, // 管理数据收集和权限 + icon: Icons.shield_outlined, + iconBgColor: isDarkMode + ? Colors.purple[900]!.withOpacity(0.3) + : Colors.purple[100]!, + iconColor: isDarkMode + ? Colors.purple[300]! + : Colors.purple[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + AppSettings.openAppSettings(); + }, + isDarkMode: isDarkMode, + ) + ], - // 隐私设置 - _buildSimpleNavigationSetting( - title: 'privacySettings'.tr, // 隐私设置 - subtitle: 'privacySettingsInfo'.tr, // 管理数据收集和权限 - icon: Icons.shield_outlined, - iconBgColor: isDarkMode - ? Colors.purple[900]!.withOpacity(0.3) - : Colors.purple[100]!, - iconColor: - isDarkMode ? Colors.purple[300]! : Colors.purple[600]!, - titleColor: isDarkMode ? Colors.white : null, - subtitleColor: isDarkMode ? Colors.white70 : null, - onTap: () { - AppSettings.openAppSettings(); - }, - isDarkMode: isDarkMode, - ), Divider( height: 1, color: isDarkMode @@ -597,29 +599,32 @@ class SettingsView extends GetView { onTap: () => Get.toNamed(Routes.OTA_UPGRADE), isDarkMode: isDarkMode, ), - Divider( - height: 1, - color: isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[200]), + if (Platform.isAndroid) ...[ + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), - // 添加权限管理设置项 - _buildSimpleNavigationSetting( - title: 'permissionManagement'.tr, // 权限管理 - subtitle: 'permissionManagementInfo'.tr, // 管理应用权限设置 - icon: Icons.security, - iconBgColor: isDarkMode - ? Colors.orange[900]!.withOpacity(0.3) - : Colors.orange[100]!, - iconColor: - isDarkMode ? Colors.orange[300]! : Colors.orange[600]!, - titleColor: isDarkMode ? Colors.white : null, - subtitleColor: isDarkMode ? Colors.white70 : null, - onTap: () { - Get.toNamed(Routes.permissions); - }, - isDarkMode: isDarkMode, - ), + // 添加权限管理设置项 + _buildSimpleNavigationSetting( + title: 'permissionManagement'.tr, // 权限管理 + subtitle: 'permissionManagementInfo'.tr, // 管理应用权限设置 + icon: Icons.security, + iconBgColor: isDarkMode + ? Colors.orange[900]!.withOpacity(0.3) + : Colors.orange[100]!, + iconColor: isDarkMode + ? Colors.orange[300]! + : Colors.orange[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + Get.toNamed(Routes.permissions); + }, + isDarkMode: isDarkMode, + ), + ] ], ), ), From 7e12292fb9ce572f1205e4fa61bafd777764c98e Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 1 Aug 2025 20:26:38 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat(=E9=85=8D=E5=AF=B9):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0iOS=E8=93=9D=E7=89=99=E5=BC=95=E5=AF=BC=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=A1=86=E5=B9=B6=E6=94=AF=E6=8C=81=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加蓝牙引导对话框的国际化文本 使用flutter_screenutil进行响应式布局 移除未使用的导入 --- lib/core/translations/language/en_us.dart | 13 +++++ lib/core/translations/language/zh_cn.dart | 9 +++ .../controllers/pairing_controller.dart | 56 +++++++++++-------- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index f529e2a7a..51d9bd764 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -813,6 +813,19 @@ const Map enUS = { 'deviceAuthorizationFailedCheckCode': 'Device authorization failed, please check authorization code', + ///iOS蓝牙引导窗 + 'bluetoothGuideTitle': 'Manual Bluetooth Activation Required', // 需要手动开启蓝牙 + 'bluetoothGuideSteps': 'Please follow these steps:', // 请按照以下步骤操作: + 'bluetoothGuideStep1': + 'Enable Bluetooth: Settings → Bluetooth → Turn On', // 开启蓝牙:设置 → 蓝牙 → 开启 + 'bluetoothGuideStep2': + 'Connect Device: Find your device in "My Devices" and tap to connect', // 连接设备:在"我的设备"中找到您的设备并点击连接 + 'bluetoothGuideStep3': + 'Return to app to continue pairing process', // 返回应用继续配对流程 + 'bluetoothGuideTip': + 'Tip: You can also long press the Bluetooth icon in Control Center to quickly access Bluetooth settings', // 提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置 + 'iKnow': 'I Know', // 我知道了 + // 用户反馈页 "feedback": "Feedback", 'feedbackInfo': 'Provide feedback or suggestions', diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index 52a3cc093..6bd1701d6 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -746,6 +746,15 @@ const Map zhCN = { 'bleConnectionFailed': 'BLE连接失败', 'deviceAuthorizationFailedCheckCode': '设备授权失败,请检查授权码', + ///iOS蓝牙引导窗 + 'bluetoothGuideTitle': '需要手动开启蓝牙', // 需要手动开启蓝牙 + 'bluetoothGuideSteps': '请按照以下步骤操作:', // 请按照以下步骤操作: + 'bluetoothGuideStep1': '开启蓝牙:设置 → 蓝牙 → 开启', // 开启蓝牙:设置 → 蓝牙 → 开启 + 'bluetoothGuideStep2': '连接设备:在"我的设备"中找到您的设备并点击连接', // 连接设备:在"我的设备"中找到您的设备并点击连接 + 'bluetoothGuideStep3': '返回应用继续配对流程', // 返回应用继续配对流程 + 'bluetoothGuideTip': '提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置', // 提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置 + 'iKnow': '我知道了', // 我知道了 + //用户反馈页 "feedback": "用户反馈", 'feedbackInfo': '提供反馈或建议', diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index dedb8fbbb..c467e2776 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -1,12 +1,10 @@ -import 'dart:convert'; -import 'dart:typed_data'; - import 'package:app_settings/app_settings.dart'; import 'package:voitrans/data/models/appconfig_model.dart'; import '/data/models/appconfig.dart'; import '/data/models/user_Info.dart'; import '/data/services/network/api.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'dart:async'; import 'package:permission_handler/permission_handler.dart'; @@ -16,7 +14,6 @@ import 'dart:io'; import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/ble_manager.dart'; import '../../../core/utils/permission_util.dart'; -import 'package:url_launcher/url_launcher.dart'; /// 配对状态枚举 - 简化版本 enum PairingState { @@ -1028,52 +1025,65 @@ class PairingController extends GetxController { void _showBluetoothGuideDialog() { Get.dialog( AlertDialog( - title: Text('需要手动开启蓝牙'), + title: Text('bluetoothGuideTitle'.tr), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('请按照以下步骤操作:'), - SizedBox(height: 16), + Text('bluetoothGuideSteps'.tr), + SizedBox(height: 16.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('1. ', style: TextStyle(fontWeight: FontWeight.bold)), - Expanded(child: Text('开启蓝牙:设置 → 蓝牙 → 开启')), + Text('1. ', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14.sp)), + Expanded( + child: Text('bluetoothGuideStep1'.tr, + style: TextStyle(fontSize: 14.sp))), ], ), - SizedBox(height: 8), + SizedBox(height: 8.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('2. ', style: TextStyle(fontWeight: FontWeight.bold)), - Expanded(child: Text('连接设备:在"我的设备"中找到您的设备并点击连接')), + Text('2. ', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14.sp)), + Expanded( + child: Text('bluetoothGuideStep2'.tr, + style: TextStyle(fontSize: 14.sp))), ], ), - SizedBox(height: 8), + SizedBox(height: 8.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('3. ', style: TextStyle(fontWeight: FontWeight.bold)), - Expanded(child: Text('返回应用继续配对流程')), + Text('3. ', + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 14.sp)), + Expanded( + child: Text('bluetoothGuideStep3'.tr, + style: TextStyle(fontSize: 14.sp))), ], ), - SizedBox(height: 16), + SizedBox(height: 16.h), Container( - padding: EdgeInsets.all(12), + padding: EdgeInsets.all(12.w), decoration: BoxDecoration( color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(8.r), border: Border.all(color: Colors.blue.withOpacity(0.3)), ), child: Row( children: [ - Icon(Icons.info_outline, color: Colors.blue, size: 20), - SizedBox(width: 8), + Icon(Icons.info_outline, color: Colors.blue, size: 20.sp), + SizedBox(width: 8.w), Expanded( child: Text( - '提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置', - style: TextStyle(fontSize: 12, color: Colors.blue[700]), + 'bluetoothGuideTip'.tr, + style: + TextStyle(fontSize: 12.sp, color: Colors.blue[700]), ), ), ], @@ -1084,7 +1094,7 @@ class PairingController extends GetxController { actions: [ TextButton( onPressed: () => Get.back(), - child: Text('我知道了'), + child: Text('iKnow'.tr, style: TextStyle(fontSize: 14.sp)), ), ], ), From 77f4d3f656253364e42337f67c6af9390cd952a8 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Fri, 1 Aug 2025 20:27:10 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9F=B3=E9=87=8F?= =?UTF-8?q?=E8=B0=83=E8=8A=82=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent_service/AgentService.kt | 2 +- .../agent_service/AgentServiceImpl.swift | 4 +- .../agent_service/AudioSessionHub.swift | 4 +- .../chat_api/Sources/chat_api/MCPClient.swift | 77 +++++++++++++++++-- .../chat_api/SystemFunctionHandler.swift | 18 ++--- 5 files changed, 85 insertions(+), 20 deletions(-) diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 7a7f18b10..1fdaea5df 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -888,7 +888,7 @@ object AgentService : CoroutineScope { responseBuilder.append(token) if (speakResponse && reply && broadcast) { ttsService?.speakStream(token) - audioPlayer?.stopAudio() + // audioPlayer?.stopAudio() } if (reply && token.isNotEmpty()) { // 发送流式回复token diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 7e54e2c4e..bfac028cd 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -996,7 +996,7 @@ class ChatApiStreamCallback: StreamCallback { if speakResponse && reply && broadcast{ try agentService.azureTtsHelper?.speakStream(token) - try agentService.audioPlayer?.stopAwaitSound() +// try agentService.audioPlayer?.stopAwaitSound() } if (reply && token != "") { try agentService.sendEvent(name: "assistant_token", data: ["token": token]) @@ -1142,7 +1142,7 @@ extension AgentServiceImpl: TtsEventListener { isSpeaking = true restartIdleCheck() sendEvent(name: "tts_started", data: ["status": "started"]) - + audioPlayer?.stopAwaitSound() case .synthesisCompleted: isSpeaking = false restartIdleCheck() diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift index b549e5f1a..ea6aedcda 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift @@ -583,9 +583,9 @@ public class AudioSessionHub { switch action { case .increase: - newValue = min(currentValue + 0.1, 1.0) + newValue = min(currentValue + 0.2, 1.0) // 从 0.1 改为 0.2 (20%) case .decrease: - newValue = max(currentValue - 0.1, 0.0) + newValue = max(currentValue - 0.2, 0.0) // 从 0.1 改为 0.2 (20%) case .mute: newValue = 0.0 case .setMax: diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift index 3f0caeab3..b65a7aaa4 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift @@ -28,6 +28,7 @@ public protocol FunctionHandler { /// - 安全的JSON-RPC消息解析,包含错误修复逻辑 /// - 完整的SSE连接生命周期管理 /// - 与Android端CustomSseClientTransport功能对等 +// 在 MCPSubClient 类中添加工具调用超时和重试配置 public class MCPSubClient { private let serverId: String private let serverUrl: String @@ -47,6 +48,10 @@ public class MCPSubClient { private var retryCount = 0 private var reconnectTask: Task? + // 工具调用配置 - 添加这两行 + private let maxToolCallRetries = 3 + private let toolCallTimeout: TimeInterval = 30.0 + // 连接状态锁 private let connectionLock = NSLock() @@ -178,27 +183,55 @@ public class MCPSubClient { /// 调用工具前检查连接 public func callTool(name: String, arguments: [String: Any]) async -> [String: Any]? { + return await callToolWithRetry(name: name, arguments: arguments, retryCount: 0) + } + + private func callToolWithRetry(name: String, arguments: [String: Any], retryCount: Int) async -> [String: Any]? { + print("[MCPSubClient] 工具调用尝试 \(retryCount + 1)/\(maxToolCallRetries + 1): \(name)") + // 先检查连接状态 if !(await checkConnection()) { + if retryCount < maxToolCallRetries { + print("[MCPSubClient] 连接断开,尝试重连...") + if await connect() { + print("[MCPSubClient] 重连成功,重试工具调用") + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + } return [ "content": [[ "type": "text", - "text": "Tool call failed: mcp: service not connected" + "text": "工具调用失败,请重试:MCP 服务连接中断" ]], "isError": true ] } guard isConnectedFlag, let client = mcpClient else { - return createErrorResult("Not connected to MCP server") + if retryCount < maxToolCallRetries { + print("[MCPSubClient] MCP 客户端未连接,尝试重连...") + if await connect() { + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + } + return createErrorResult("工具调用失败,请重试:MCP 服务未连接") } do { - let mcpArguments = convertToMCPValue(arguments) - let (content, isError) = try await client.callTool(name: name, arguments: mcpArguments) + // 使用超时控制的工具调用 + let result = try await withTimeout(seconds: toolCallTimeout) { + let mcpArguments = self.convertToMCPValue(arguments) + return try await client.callTool(name: name, arguments: mcpArguments) + } + + let (content, isError) = result if isError == true { - return createErrorResult("Tool execution failed") + if retryCount < maxToolCallRetries { + print("[MCPSubClient] 工具执行失败,重试中...") + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + return createErrorResult("工具调用失败,请重试:工具执行错误") } var contextText = "" @@ -225,13 +258,45 @@ public class MCPSubClient { } } + print("[MCPSubClient] 工具调用成功: \(name)") return [ "content": [["type": "text", "text": contextText.trimmingCharacters(in: .whitespacesAndNewlines)]], "isError": false ] + } catch is TimeoutError { + print("[MCPSubClient] 工具调用超时: \(name)") + if retryCount < maxToolCallRetries { + print("[MCPSubClient] 超时重试中...") + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + return createErrorResult("工具调用失败,请重试:连接超时") } catch { - return createErrorResult("Tool call failed: \(error.localizedDescription)") + print("[MCPSubClient] 工具调用异常: \(name), 错误: \(error.localizedDescription)") + + // 检查是否是连接相关错误 + let errorDescription = error.localizedDescription.lowercased() + if errorDescription.contains("connection") || + errorDescription.contains("network") || + errorDescription.contains("timeout") || + errorDescription.contains("disconnected") { + + if retryCount < maxToolCallRetries { + print("[MCPSubClient] 检测到连接错误,尝试重连并重试...") + // 标记连接断开 + isConnectedFlag = false + + // 尝试重连 + if await connect() { + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + } + } else if retryCount < maxToolCallRetries { + print("[MCPSubClient] 其他错误,直接重试...") + return await callToolWithRetry(name: name, arguments: arguments, retryCount: retryCount + 1) + } + + return createErrorResult("工具调用失败,请重试:\(error.localizedDescription)") } } diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift index 98773d41f..bcacf4eb5 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift @@ -795,7 +795,7 @@ private class GetCurrentTimeHandler: FunctionHandler { private class MediaPlayHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { NotificationCenter.default.post(name: NSNotification.Name("MediaPlay"), object: nil) - return "{\"result\": \"已开始播放媒体\"}" + return "{\"notreply\": true,\"result\": \"已开始播放媒体\"}" } } @@ -803,7 +803,7 @@ private class MediaPlayHandler: FunctionHandler { private class MediaPauseHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { NotificationCenter.default.post(name: NSNotification.Name("MediaPause"), object: nil) - return "{\"result\": \"已暂停媒体播放\"}" + return "{\"broadcast\": true,\"result\": \"已暂停媒体播放\"}" } } @@ -811,7 +811,7 @@ private class MediaPauseHandler: FunctionHandler { private class MediaPreviousHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { NotificationCenter.default.post(name: NSNotification.Name("MediaPrevious"), object: nil) - return "{\"result\": \"已切换到上一首\"}" + return "{\"notreply\": true,\"result\": \"已切换到上一首\"}" } } @@ -819,7 +819,7 @@ private class MediaPreviousHandler: FunctionHandler { private class MediaNextHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { NotificationCenter.default.post(name: NSNotification.Name("MediaNext"), object: nil) - return "{\"result\": \"已切换到下一首\"}" + return "{\"notreply\": true,\"result\": \"已切换到下一首\"}" } } @@ -833,7 +833,7 @@ private class OpenRecorderHandler: FunctionHandler { } } - return "{\"result\": \"无法打开录音机应用\"}" + return "{\"notreply\": true,\"result\": \"无法打开录音机应用\"}" } } @@ -905,7 +905,7 @@ private class AddVolumeHandler: FunctionHandler { object: volumeInfo ) - return "{\"result\": \"音量增加请求已发送\"}" + return "{\"broadcast\": false,\"result\": \"音量增加请求已发送\"}" } } @@ -921,7 +921,7 @@ private class ReduceVolumeHandler: FunctionHandler { object: volumeInfo ) - return "{\"result\": \"音量降低请求已发送\"}" + return "{\"broadcast\": false,\"result\": \"音量降低请求已发送\"}" } } @@ -937,7 +937,7 @@ private class MuteVolumeHandler: FunctionHandler { object: volumeInfo ) - return "{\"result\": \"静音请求已发送\"}" + return "{\"broadcast\": false,\"result\": \"静音请求已发送\"}" } } @@ -953,6 +953,6 @@ private class MaxVolumeHandler: FunctionHandler { object: volumeInfo ) - return "{\"result\": \"最大音量请求已发送\"}" + return "{\"broadcast\": false,\"result\": \"最大音量请求已发送\"}" } } From 0c1f20d9e77f0f99cf938e058f75ed010e1b6cd3 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Fri, 1 Aug 2025 22:00:24 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E6=A3=80=E6=B5=8B=E9=80=BB=E8=BE=91=E5=87=BA?= =?UTF-8?q?=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/pairing_controller.dart | 114 +++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index c467e2776..f2783f4bb 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -44,7 +44,7 @@ enum ErrorType { } /// 配对控制器 - 基于简化状态机的实现 -class PairingController extends GetxController { +class PairingController extends GetxController with WidgetsBindingObserver { static const String _tag = 'PairingCtrl'; static PairingController get to => Get.find(); @@ -100,6 +100,11 @@ class PairingController extends GetxController { super.onInit(); Logger.i(_tag, '配对控制器初始化'); + // 注册应用生命周期监听器 + Logger.i(_tag, '正在注册应用生命周期监听器'); + WidgetsBinding.instance.addObserver(this); + Logger.i(_tag, '应用生命周期监听器注册完成'); + // 确保服务已初始化 _bluetoothManager = Get.find(); _bleManager = Get.find(); @@ -123,6 +128,10 @@ class PairingController extends GetxController { @override void onClose() { Logger.i(_tag, '配对控制器销毁'); + + // 移除应用生命周期监听器 + WidgetsBinding.instance.removeObserver(this); + // 清理所有Worker for (var worker in _workers) { worker.dispose(); @@ -135,6 +144,109 @@ class PairingController extends GetxController { super.onClose(); } + /// 应用生命周期状态变化回调 + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + // 使用多种日志级别确保输出 + Logger.i(_tag, '=== 应用生命周期状态变化: $state ==='); + print('PairingCtrl: 应用生命周期状态变化: $state'); // 直接使用print确保输出 + + switch (state) { + case AppLifecycleState.resumed: + // 应用从后台切回前台 + Logger.i(_tag, '=== 应用从后台切回前台 ==='); + print('PairingCtrl: 应用从后台切回前台'); + _onAppResumed(); + break; + case AppLifecycleState.paused: + // 应用切到后台 + Logger.i(_tag, '应用切到后台'); + print('PairingCtrl: 应用切到后台'); + _onAppPaused(); + break; + case AppLifecycleState.inactive: + // 应用处于非活跃状态(如来电话时) + Logger.i(_tag, '应用处于非活跃状态'); + print('PairingCtrl: 应用处于非活跃状态'); + break; + case AppLifecycleState.detached: + // 应用即将被销毁 + Logger.i(_tag, '应用即将被销毁'); + print('PairingCtrl: 应用即将被销毁'); + break; + case AppLifecycleState.hidden: + // 应用被隐藏(iOS 13+) + Logger.i(_tag, '应用被隐藏'); + print('PairingCtrl: 应用被隐藏'); + break; + } + } + + /// 应用从后台切回前台时的处理逻辑 + void _onAppResumed() { + Logger.i(_tag, '处理应用从后台切回前台事件'); + + // 刷新蓝牙连接状态 + _bluetoothManager.refreshDeviceConnection(); + + // 根据当前配对状态执行相应操作 + switch (_currentState.value) { + case PairingState.waitingClassic: + // 如果在等待经典蓝牙连接,重新检查连接状态 + Logger.i(_tag, '重新检查经典蓝牙连接状态'); + _checkCurrentStatus(); + break; + case PairingState.waitingBle: + // 如果在等待BLE连接,重新检查BLE状态 + Logger.i(_tag, '重新检查BLE连接状态'); + _checkBleConnectionAfterResume(); + break; + case PairingState.error: + // 如果在错误状态,根据错误类型决定是否自动重试 + if (_errorType.value == ErrorType.bluetoothOff) { + Logger.i(_tag, '检查蓝牙是否已重新开启'); + _checkCurrentStatus(); + } + break; + default: + // 其他状态暂不处理 + break; + } + } + + /// 应用切到后台时的处理逻辑 + void _onAppPaused() { + Logger.i(_tag, '处理应用切到后台事件'); + + // 可以在这里暂停一些不必要的操作,如蓝牙扫描等 + if (_currentState.value == PairingState.associating) { + Logger.i(_tag, '应用切到后台,暂停BLE扫描'); + _bleManager.stopScan(); + } + } + + /// 应用恢复后检查BLE连接状态 + Future _checkBleConnectionAfterResume() async { + try { + final currentBleState = await _bleManager.getConnectionState(); + Logger.i(_tag, '应用恢复后BLE连接状态: $currentBleState'); + + if (currentBleState == 2) { + // BLE已连接,直接完成配对 + Logger.i(_tag, '检测到BLE已连接,完成配对'); + _bleTimeoutTimer?.cancel(); + _transition(PairingState.completed); + } else { + // BLE未连接,重新启动连接流程 + Logger.i(_tag, '重新启动BLE连接流程'); + _startBleConnection(); + } + } catch (e) { + Logger.e(_tag, '检查BLE连接状态失败: $e'); + } + } + // ==================== 监听器设置 ==================== /// 集中设置所有监听器 From dad39ede02c6912443eb3984528c8ac2d507c89b Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Fri, 1 Aug 2025 22:02:01 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=94=B5=E8=AF=9D?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/Info.plist | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index f3ad0d225..75a82d84d 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -121,10 +121,6 @@ audio location - UIRequiredDeviceCapabilities - - telephony - UIDeviceFamily 1 From 3b49c944ae6a50749561a60ee509d4cf6bc65dfe Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Sat, 2 Aug 2025 11:25:00 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/azure_speech/AzureAsrHelper.swift | 227 +++++++----------- 1 file changed, 84 insertions(+), 143 deletions(-) diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift index 5c90a7c67..3106c1236 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift @@ -100,14 +100,9 @@ public class AzureAsrHelper: NSObject { // 创建语音配置 speechConfig = try SPXSpeechConfiguration(subscription: subscriptionKey, region: region) // 针对翻译场景的性能优化 - optimizeForTranslation() + // optimizeForTranslation() if isAutoDetectLanguage { - // 针对翻译场景优化的语言检测配置 speechConfig?.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode") - - // 设置语言检测的置信度阈值,提高准确性 - speechConfig?.setPropertyTo("0.7", byName: "SpeechServiceConnection_AutoDetectSourceLanguageConfidenceThreshold") - // 不在这里设置语言,让SPXAutoDetectSourceLanguageConfiguration处理 os_log("启用语言检测模式,支持语言: %{public}@", log: log, type: .info, supportedLanguages.joined(separator: ",")) } else { @@ -150,55 +145,7 @@ public class AzureAsrHelper: NSObject { } } - /** - * 检测输入文本的语言,从支持的语言列表中选择最匹配的 - * - * @param text 需要检测的文本 - * @return 从supportedLanguages中选择的最匹配语言代码 - */ - public func detectTextLanguage(_ text: String) -> String? { - // 如果只有一种支持的语言,直接返回 - if supportedLanguages.count == 1 { - return supportedLanguages[0] - } - - // 获取所有可能的语言及其置信度 - let languageResults = LanguageDetector.detectLanguageWithConfidence(from: text) - - // 遍历检测结果,找到第一个在supportedLanguages中的语言 - for result in languageResults { - let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(result.language) - if supportedLanguages.contains(azureLanguageCode) { - return azureLanguageCode - } - } - - // 如果没有找到匹配的语言,返回第一个支持的语言作为默认值 - return supportedLanguages.first - } - - /** - * 检测文本语言并返回详细信息 - * - * @param text 需要检测的文本 - * @return 包含语言代码、置信度和语言名称的字典 - */ - public func detectTextLanguageWithDetails(_ text: String) -> [String: Any]? { - let results = LanguageDetector.detectLanguageWithConfidence(from: text) - guard let topResult = results.first else { - return nil - } - - let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(topResult.language) - let languageName = LanguageDetector.getLanguageName(from: topResult.language) - - return [ - "languageCode": azureLanguageCode, - "confidence": topResult.confidence, - "languageName": languageName, - "originalCode": topResult.language - ] - } + /** * 开始连续语音识别 * @@ -249,28 +196,7 @@ public class AzureAsrHelper: NSObject { return false } } - // /** - // * 获取检测到的语言 - // */ - // private func getDetectedLanguage(from result: SPXSpeechRecognitionResult) -> String { - // if !isAutoDetectLanguage { - // return "" - // } - - // // 尝试从属性中获取语言 - // if let properties = result.properties, - // let language = properties.getPropertyByName("SpeechServiceResponse_RecognitionLanguage") { - // return language - // } - - // // 尝试另一种方式获取语言 - // do { - // let langResult = try SPXAutoDetectSourceLanguageResult(result) - // return langResult.language ?? "" - // } catch { - // return "" - // } - // } + /** * 停止连续语音识别 @@ -388,11 +314,20 @@ public class AzureAsrHelper: NSObject { // 创建识别器 if isAutoDetectLanguage { // 使用正确的初始化方法 - let autoDetectConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages) - print("autoDetectConfig: \(autoDetectConfig)") + guard let autoDetectSourceLanguageConfig = try? SPXAutoDetectSourceLanguageConfiguration(supportedLanguages) else { + print("创建自动语言检测配置失败,使用默认语言配置") + // 如果创建失败,使用默认的单语言配置 + recognizer = try SPXSpeechRecognizer( + speechConfiguration: speechConfig!, + audioConfiguration: audioConfig! + ) + return true + } + + print("autoDetectConfig: \(autoDetectSourceLanguageConfig)") recognizer = try SPXSpeechRecognizer( speechConfiguration: speechConfig!, - autoDetectSourceLanguageConfiguration: autoDetectConfig, + autoDetectSourceLanguageConfiguration: autoDetectSourceLanguageConfig, audioConfiguration: audioConfig! ) } else { @@ -484,60 +419,7 @@ public class AzureAsrHelper: NSObject { return currentLanguage } - /** - * 针对翻译场景的性能优化配置 - */ - private func optimizeForTranslation() { - guard let speechConfig = speechConfig else { return } - - // 启用实时转录,减少延迟 - speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableAudioLogging") - speechConfig.setPropertyTo("INTERACTIVE", byName: "SpeechServiceConnection_RecoMode") - speechConfig.setPropertyTo("PCM", byName: "Speech_PushStreamFormat") - // 优化语音端点检测,适合翻译场景 - speechConfig.setPropertyTo("300", byName: "SpeechServiceConnection_InitialSilenceTimeoutMs") - speechConfig.setPropertyTo("500", byName: "SpeechServiceConnection_EndSilenceTimeoutMs") - speechConfig.setPropertyTo("500", byName: "Speech_SegmentationSilenceTimeoutMs") - // 启用标点符号,提高翻译质量 - speechConfig.setPropertyTo("true", byName: "SpeechServiceResponse_RequestProfanityFilterTrueFalse") - // 设置分段策略为时间模式 - speechConfig.setPropertyTo("Time", byName: "Speech_SegmentationStrategy") - - // 【优化】语言检测配置 - 减少检测延迟,实现快速响应 - if isAutoDetectLanguage { - // 降低语言检测置信度阈值,提高检测灵敏度 - speechConfig.setPropertyTo("0.3", byName: "SpeechServiceConnection_AutoDetectSourceLanguageConfidenceThreshold") - - // 使用Continuous模式,在每句话结束后重新检测语言 - speechConfig.setPropertyTo("Continuous", byName: "SpeechServiceConnection_LanguageIdMode") - - // 启用快速语言检测 - speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableLanguageDetectionFast") - - // 【关键优化】设置语言检测的最小音频长度为100毫秒,实现快速响应 - speechConfig.setPropertyTo("100", byName: "SpeechServiceConnection_LanguageDetectionMinAudioLengthMs") - - // 【新增】设置语言检测的最大等待时间,避免长时间等待 - speechConfig.setPropertyTo("2000", byName: "SpeechServiceConnection_LanguageDetectionMaxWaitTimeMs") - - // 强制在每个语音段开始时重新检测语言 - speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_ForceLanguageDetectionOnNewSegment") - - // 【新增】启用句子级别的语言检测 - speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableLanguageDetectionPerSentence") - - // 【新增】设置语言检测的频率 - 每个识别结果后都检测 - speechConfig.setPropertyTo("PerResult", byName: "SpeechServiceConnection_LanguageDetectionFrequency") - - // 【新增】启用实时语言检测,减少缓冲延迟 - speechConfig.setPropertyTo("true", byName: "SpeechServiceConnection_EnableRealtimeLanguageDetection") - - // 【新增】设置语言检测的采样间隔为200毫秒 - speechConfig.setPropertyTo("200", byName: "SpeechServiceConnection_LanguageDetectionSamplingIntervalMs") - } - - os_log("翻译场景优化配置已应用,包含语言检测优化", log: log, type: .info) - } + /** * 动态切换语言(翻译场景中的语言切换) @@ -588,13 +470,16 @@ public class AzureAsrHelper: NSObject { let result = event.result guard !(result.text?.isEmpty ?? true) else { return } - // let detectedLanguage = self.getDetectedLanguage(from: result) - - // 检测文本语言,避免重复调用 - let detectedLanguage = self.detectTextLanguage(result.text ?? "") ?? "zh-CN" + // 使用现有的 getDetectedLanguage 方法替代 result.language + let detectedLanguage = self.getDetectedLanguage(from: result) + let text = result.text + + print("正在识别事件=检测到语言: \(detectedLanguage), 识别中: \(text)") + // // 检测文本语言,避免重复调用 + // let detectedLanguage = self.detectTextLanguage(result.text ?? "") ?? "zh-CN" - // 输出检测到的语言信息 - print("检测到的语言: \(detectedLanguage)") + // // 输出检测到的语言信息 + // print("检测到的语言: \(detectedLanguage)") DispatchQueue.main.async { callback.onRecognizing(result.text ?? "", detectedLanguage) @@ -608,11 +493,16 @@ public class AzureAsrHelper: NSObject { guard result.reason == .recognizedSpeech, !(result.text?.isEmpty ?? true) else { return } + // 使用现有的 getDetectedLanguage 方法替代 result.language + let detectedLanguage = self.getDetectedLanguage(from: result) + let text = result.text + + print("识别完成事件=检测到语言: \(detectedLanguage), 识别中: \(text)") // 检测文本语言,避免重复调用 - let detectedLanguage = self.detectTextLanguage(result.text ?? "") ?? "zh-CN" + // let detectedLanguage = self.detectTextLanguage(result.text ?? "") ?? "zh-CN" - // 输出检测到的语言信息 - print("检测到的语言: \(detectedLanguage)") + // // 输出检测到的语言信息 + // print("检测到的语言: \(detectedLanguage)") // 在主线程回调结果 DispatchQueue.main.async { @@ -1175,3 +1065,54 @@ private func runMicrophoneCapture() { func onError(_ error: String) } } + +// //ios 语言检测 +// /** +// * 检测输入文本的语言,从支持的语言列表中选择最匹配的 +// * +// * @param text 需要检测的文本 +// * @return 从supportedLanguages中选择的最匹配语言代码 +// */ +// public func detectTextLanguage(_ text: String) -> String? { +// // 如果只有一种支持的语言,直接返回 +// if supportedLanguages.count == 1 { +// return supportedLanguages[0] +// } + +// // 获取所有可能的语言及其置信度 +// let languageResults = LanguageDetector.detectLanguageWithConfidence(from: text) + +// // 遍历检测结果,找到第一个在supportedLanguages中的语言 +// for result in languageResults { +// let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(result.language) +// if supportedLanguages.contains(azureLanguageCode) { +// return azureLanguageCode +// } +// } + +// // 如果没有找到匹配的语言,返回第一个支持的语言作为默认值 +// return supportedLanguages.first +// } + +// /** +// * 检测文本语言并返回详细信息 +// * +// * @param text 需要检测的文本 +// * @return 包含语言代码、置信度和语言名称的字典 +// */ +// public func detectTextLanguageWithDetails(_ text: String) -> [String: Any]? { +// let results = LanguageDetector.detectLanguageWithConfidence(from: text) +// guard let topResult = results.first else { +// return nil +// } + +// let azureLanguageCode = LanguageDetector.convertToAzureLanguageCode(topResult.language) +// let languageName = LanguageDetector.getLanguageName(from: topResult.language) + +// return [ +// "languageCode": azureLanguageCode, +// "confidence": topResult.confidence, +// "languageName": languageName, +// "originalCode": topResult.language +// ] +// } \ No newline at end of file From 5cdd3e4a575586889a68fcfd7d0d3a5b0991ba46 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 2 Aug 2025 17:21:07 +0800 Subject: [PATCH 8/8] =?UTF-8?q?refactor(splash):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=B5=81=E7=A8=8B=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用单一反应式变量 `_canNavigate` 替代多个布尔标记 - 将并行任务封装为独立方法并通过 `Future.wait` 协调 - 新增 `AppConfig.isInitialized()` 和 `User.isLoggedIn()` 方法 - 清理用户会话时增加本地存储和内存数据的清除 - 简化导航逻辑,直接检查最终数据状态 --- lib/data/models/appconfig.dart | 11 + lib/data/models/user_Info.dart | 22 +- .../splash/controllers/splash_controller.dart | 376 ++++++------------ 3 files changed, 147 insertions(+), 262 deletions(-) diff --git a/lib/data/models/appconfig.dart b/lib/data/models/appconfig.dart index 19dcf0565..22a09d84a 100644 --- a/lib/data/models/appconfig.dart +++ b/lib/data/models/appconfig.dart @@ -4,9 +4,20 @@ import '../../../data/models/appconfig_model.dart'; class AppConfig { static late UserGetAppConfigResp config; + + // 新增一个私有的静态布尔值来跟踪初始化状态 + static bool _isInitialized = false; + + /// 检查AppConfig单例是否已经被初始化 + /// 外部通过调用此方法来安全地检查状态 + static bool isInitialized() { + return _isInitialized; + } + // 可选:提供初始化方法 static void initialize(UserGetAppConfigResp resp) { config = resp; + _isInitialized = true; // <-- 新增此行 } //环境变量 diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart index e5efa62ec..d5d369ba6 100644 --- a/lib/data/models/user_Info.dart +++ b/lib/data/models/user_Info.dart @@ -1,9 +1,15 @@ -import 'dart:ffi'; +import 'package:get_storage/get_storage.dart'; +import '../../../core/utils/logger.dart'; class User { // 单例实例 static User? _instance; + /// 安全地检查用户是否已经登录(即单例是否已初始化) + static bool isLoggedIn() { + return _instance != null; + } + // 单例访问器(增加空安全保护) static User get instance { if (_instance == null) { @@ -12,6 +18,18 @@ class User { return _instance!; } + // --- 新增 --- + /// 清理所有与用户会话相关的数据 + static Future clearUserSession() async { + final GetStorage storage = GetStorage(); + // 1. 清理本地存储 + await storage.remove('logintoken'); + await storage.remove('user_info'); + // 2. 清理内存中的单例 + _instance = null; + Logger.i("User", "用户会话数据已安全清理。"); + } + // 最终属性(不可变) String name; String uid; @@ -72,7 +90,7 @@ class User { // 修复1:安全获取嵌套的 user 字段 final userData = json['user'] as Map?; // 允许空值 final devicesData = json['devices'] as List?; // 允许空值 - print("数据获取fronTokenJson${userData} ${devicesData}"); + print("数据获取fronTokenJson$userData $devicesData"); // 更新单例实例 return _instance = User._( token: '', diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 013ba187e..5c90dacca 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -1,15 +1,14 @@ import 'dart:async'; import 'dart:io'; import 'package:app_tracking_transparency/app_tracking_transparency.dart'; - -import '../../../data/models/appconfig.dart'; -import '../../../data/models/appconfig_model.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; + +import '../../../data/models/appconfig.dart'; +import '../../../data/models/appconfig_model.dart'; import '../../../data/models/user_Info.dart'; import '../../../routes/app_routes.dart'; -import '../../../core/themes/theme_service.dart'; import '../../../data/services/network/api.dart'; import '../../../data/services/version_update_service.dart'; import '../../../modules/common/widgets/version_update_dialog.dart'; @@ -24,191 +23,174 @@ class SplashController extends GetxController { final _loadingProgress = 0.0.obs; double get loadingProgress => _loadingProgress.value; - // 各项任务完成状态 - final _isTrackingPermissionRequested = false.obs; // 追踪权限请求完成 - final _isVersionCheckCompleted = false.obs; // 版本检查完成 - final _isTokenValidated = false.obs; // Token验证完成 - final _isAppConfigLoaded = false.obs; // 应用配置加载完成 - final _isAnimationCompleted = false.obs; // 动画完成 - - // 登录状态 - final _isLoggedIn = false.obs; - bool get isLoggedIn => _isLoggedIn.value; - - final _hasAcceptedPrivacy = false.obs; //是否同意隐私政策 - - // 版本号 - //final String version = 'V1.0.0'; - - // 主题服务 - final ThemeService _themeService = ThemeService(); - - // 会话级别的版本检查标记(静态变量,整个应用会话期间保持) - static bool _hasCheckedVersionInSession = false; - - // 版本检查相关常量 - static const String _lastVersionCheckKey = 'last_version_check_timestamp'; - static const int _versionCheckIntervalHours = 24; // 24小时间隔 + // 使用一个反应式变量来决定是否可以导航,替代所有布尔标记 + final _canNavigate = false.obs; @override void onInit() { super.onInit(); - - // 首先检查隐私政策状态 - final hasAcceptedPrivacy = _storage.read('hasAcceptedPrivacy') ?? false; - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!hasAcceptedPrivacy) { - // 优先显示隐私政策弹窗,暂停其他初始化 - _showPrivacyDialog(); - } else { - // 已同意隐私政策,正常启动流程 - _startNormalFlow(); - } - }); - - // 注意:追踪权限请求将在用户同意隐私政策后执行 - } - - /// 异步请求追踪权限 - Future _requestTrackingPermissionAsync() async { - try { - await AppTrackingTransparency.requestTrackingAuthorization(); - Logger.d(_tag, '用户追踪权限请求完成'); - } catch (e) { - Logger.e(_tag, '用户追踪权限请求失败: $e'); - } finally { - _isTrackingPermissionRequested.value = true; + // 监听 _canNavigate 状态,一旦为 true,立即且仅执行一次导航 + once(_canNavigate, (_) => _navigateToTargetPage()); + // 启动整个初始化流程 + _initializeApp(); + } + + /// 启动并协调所有初始化任务 + Future _initializeApp() async { + // 步骤 1: 检查并等待隐私政策同意 + final hasAccepted = await _checkAndRequestPrivacy(); + if (!hasAccepted) { + // 如果用户拒绝隐私政策,应用会退出,后续代码不执行 + return; } - } - void _startNormalFlow() { - _startLoadingAnimation(); - _startParallelTasks(); - } + // 步骤 2: 并行执行所有启动任务,并等待它们全部完成 + // Future.wait 会等待数组中所有的 Future 都完成后才继续 + await Future.wait([ + _startLoadingAnimation(), // 任务1: 最小动画时长 + _performParallelTasks(), // 任务2: 其他所有后台任务 + ]); - /// 启动并行任务:在1.5秒动画期间同时执行多个任务 - void _startParallelTasks() { - // 并行执行所有任务 - _requestTrackingPermissionAsync(); - _performVersionCheckAsync(); - _validateTokenAsync(); + // 步骤 3: 所有任务(动画和后台)都已完成,触发导航 + _canNavigate.value = true; } - /// 异步执行版本检查 - Future _performVersionCheckAsync() async { - try { - Logger.d(_tag, '开始执行版本检查'); - await _checkVersionUpdateIfNeeded(); - } catch (e) { - Logger.e(_tag, '版本检查失败: $e'); + /// 检查隐私政策,如果未同意,则显示弹窗并等待用户操作 + /// 返回用户是否同意 + Future _checkAndRequestPrivacy() async { + if (_storage.read('hasAcceptedPrivacy') ?? false) { + return true; } - //finally { - // _isVersionCheckCompleted.value = true; - // } - } - - void _onPrivacyAccepted() { - _storage.write('hasAcceptedPrivacy', true); - Get.back(); // 关闭弹窗 - _startNormalFlow(); // 开始正常流程(已包含版本检查) - } - void _onPrivacyRejected() { - exit(0); // 退出应用 - } + final completer = Completer(); + WidgetsBinding.instance.addPostFrameCallback((_) { + PrivacyPolicyDialog.show( + onAccept: () { + _storage.write('hasAcceptedPrivacy', true); + Get.back(); // 关闭弹窗 + if (!completer.isCompleted) completer.complete(true); + }, + onReject: () { + if (!completer.isCompleted) completer.complete(false); + exit(0); // 退出应用 + }, + canDismiss: false, + ); + }); - // 显示隐私政策弹窗 - void _showPrivacyDialog() { - PrivacyPolicyDialog.show( - onAccept: () { - _onPrivacyAccepted(); - _hasAcceptedPrivacy.value = true; - }, - onReject: _onPrivacyRejected, - canDismiss: false, // 禁止通过返回键关闭弹窗 - ); + return completer.future; } - // 进度条动画(持续1.5秒) - void _startLoadingAnimation() { + /// 动画任务:返回一个在动画结束时完成的 Future + Future _startLoadingAnimation() { const totalDuration = 1500; // 动画总时长1.5秒 - const steps = totalDuration ~/ 30; // 30ms间隔,更平滑的动画 + const steps = totalDuration ~/ 30; const stepValue = 1.0 / steps; + final completer = Completer(); Timer.periodic(const Duration(milliseconds: 30), (timer) { if (_loadingProgress.value < 1.0) { _loadingProgress.value += stepValue; } else { + _loadingProgress.value = 1.0; timer.cancel(); - _isAnimationCompleted.value = true; - _checkNavigateCondition(); + if (!completer.isCompleted) completer.complete(); } }); + + return completer.future; + } + + /// 将所有并行的后台任务打包成一个 Future + Future _performParallelTasks() async { + // 同样使用 Future.wait,确保此函数在所有子任务完成后才结束 + await Future.wait([ + _requestTrackingPermissionAsync(), + _validateTokenAsync(), + _performVersionCheckAsync(), + ]); + Logger.d(_tag, '所有后台任务已完成'); + } + + /// 异步请求追踪权限 + Future _requestTrackingPermissionAsync() async { + try { + await AppTrackingTransparency.requestTrackingAuthorization(); + Logger.d(_tag, '用户追踪权限请求完成'); + } catch (e) { + Logger.e(_tag, '用户追踪权限请求失败: $e'); + // 即使失败,也视为完成,不阻塞主流程 + } } /// 异步验证Token和加载应用配置 Future _validateTokenAsync() async { try { - bool isLoggedIn = false; final token = _storage.read('logintoken'); - Logger.d(_tag, 'Token验证开始: $token'); - if (token != null && token.isNotEmpty) { - isLoggedIn = await getLoginToken(); + final isLoggedIn = await getLoginToken(); if (isLoggedIn) { + // 只有登录成功,才获取App配置 await getAppConfig(); - _isAppConfigLoaded.value = true; + } else { + await User.clearUserSession(); // } } - - _isLoggedIn.value = isLoggedIn; - Logger.d(_tag, 'Token验证完成,登录状态: $isLoggedIn'); } catch (e) { - Logger.e(_tag, 'Token验证失败: $e'); - _isLoggedIn.value = false; - } finally { - _isTokenValidated.value = true; + Logger.e(_tag, 'Token验证或配置加载失败: $e'); + await User.clearUserSession(); } } - // 检查跳转条件:所有任务都完成后才跳转 - void _checkNavigateCondition() { - if (_isAnimationCompleted.value && - _isTrackingPermissionRequested.value && - _isVersionCheckCompleted.value && - _isTokenValidated.value) { - _navigateToTargetPage(); + /// 异步执行版本检查 + Future _performVersionCheckAsync() async { + try { + final versionService = VersionUpdateService.to; + final updateInfo = await versionService.checkForUpdate(); + + if (updateInfo.hasUpdate) { + Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}'); + final completer = Completer(); + // 在主线程中显示更新对话框 + WidgetsBinding.instance.addPostFrameCallback((_) { + // 使用 whenComplete 确保无论弹窗如何关闭,Future 都会完成 + VersionUpdateDialog.show(updateInfo).whenComplete(() { + if (!completer.isCompleted) completer.complete(); + }); + }); + // 等待弹窗交互结束 + await completer.future; + } else { + Logger.i(_tag, '当前已是最新版本'); + } + } catch (e) { + Logger.e(_tag, '版本检查失败: $e'); + // 即使检查失败,也视为任务完成 } } - // 根据登录状态跳转到目标页面 + /// 根据最终状态跳转到目标页面 void _navigateToTargetPage() { - if (_isLoggedIn.value && _isAppConfigLoaded.value) { + // 导航时直接检查最终的数据状态,而不是依赖中间变量 + final token = _storage.read('logintoken'); + final isAppConfigInitialized = AppConfig.isInitialized(); + // 检查登录状态和配置初始化状态 + if (token != null && token.isNotEmpty && isAppConfigInitialized) { Get.offAllNamed(Routes.pairing); } else { Get.offAllNamed(Routes.login); } } + /// 异步获取登录Token Future getLoginToken() async { try { - Logger.d(_tag, 'getLoginToken:进来了'); - //if (User.getname() != "") { final response = await Api.tokenlogin(); - Logger.d(_tag, 'getLoginToken: $response'); - if (response == null) return false; - - if (response['user'] != '') { - Logger.d(_tag, 'getLoginToken:进来了'); - // 根据API设计调整 + if (response?['user'] != null && response['user'] != '') { final user = User.fromJson(response); - - // 存储用户信息(示例) await _storage.write('user_info', user.toJson()); return true; } - //return true; - //} return false; } catch (e) { Logger.e(_tag, 'Token获取失败: $e'); @@ -218,140 +200,14 @@ class SplashController extends GetxController { Future getAppConfig() async { try { - Logger.d(_tag, 'getAppConfig:进来了'); - //if (User.getname() != "") { final response = await Api.getappconfig(); - Logger.d(_tag, 'getAppConfig: $response'); if (response == null) return false; - // 根据API设计调整 final resp = UserGetAppConfigResp.fromJson(response); - Logger.d(_tag, 'getAppConfig:进来了'); - // 存储用户信息(示例) - // _storage.write('ENV', resp.env); - // _storage.write('AGENTS', resp.agents); - // _storage.write('MCPS', resp.mcps); AppConfig.initialize(resp); return true; } catch (e) { - Logger.e(_tag, 'getAppConfig 取失败: $e'); + Logger.e(_tag, 'getAppConfig 获取失败: $e'); return false; } } - - /// 混合检查机制:判断是否需要进行版本检查 - /// 检查条件:1. 会话内未检查过 OR 2. 距离上次检查超过24小时 - Future _checkVersionUpdateIfNeeded() async { - try { - final now = DateTime.now(); - final lastCheckTimestamp = _storage.read(_lastVersionCheckKey); - - bool shouldCheck = false; - String reason = ''; - - // 条件1:会话内未检查过 - if (!_hasCheckedVersionInSession) { - shouldCheck = true; - reason = '会话内首次检查'; - } - // 条件2:距离上次检查超过24小时 - else if (lastCheckTimestamp != null) { - final lastCheckTime = - DateTime.fromMillisecondsSinceEpoch(lastCheckTimestamp); - final timeDifference = now.difference(lastCheckTime); - - if (timeDifference.inHours >= _versionCheckIntervalHours) { - shouldCheck = true; - reason = '距离上次检查已超过${_versionCheckIntervalHours}小时'; - } - } - // 条件3:从未检查过(首次使用应用) - else { - shouldCheck = true; - reason = '首次使用应用'; - } - - if (shouldCheck) { - Logger.i(_tag, '开始版本检查 - 原因: $reason'); - await _performVersionCheck(); - } else { - Logger.i(_tag, '跳过版本检查 - 会话内已检查且未超过时间间隔'); - } - } catch (e) { - Logger.e(_tag, '版本检查判断失败: $e'); - } - } - - /// 执行版本检查(在后台进行,不阻塞启动流程) - Future _performVersionCheck() async { - try { - final versionService = VersionUpdateService.to; - // 异步执行,不等待结果 - versionService.checkForUpdate().then((updateInfo) { - if (updateInfo.hasUpdate) { - Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}'); - // 在主线程中显示更新对话框 - WidgetsBinding.instance.addPostFrameCallback((_) { - VersionUpdateDialog.show(updateInfo).then((_) { - // 无论用户选择更新还是稍后提醒,都标记版本检查完成 - _isVersionCheckCompleted.value = true; - _checkNavigateCondition(); - }); - }); - } else { - _isVersionCheckCompleted.value = true; - Logger.i(_tag, '当前已是最新版本: ${updateInfo.currentVersion}'); - } - - // 更新检查记录 - _updateVersionCheckRecord(); - }).catchError((e) { - Logger.e(_tag, '版本检查失败: $e'); - // 即使检查失败,也标记版本检查完成,让流程继续 - _isVersionCheckCompleted.value = true; - // 更新检查记录,避免频繁重试 - _updateVersionCheckRecord(); - }); - } catch (e) { - Logger.e(_tag, '版本检查失败: $e'); - // 即使检查失败,也标记版本检查完成,让流程继续 - _isVersionCheckCompleted.value = true; - // 更新检查记录,避免频繁重试 - _updateVersionCheckRecord(); - } - } - - /// 更新版本检查记录 - void _updateVersionCheckRecord() { - try { - // 更新时间戳 - final now = DateTime.now(); - _storage.write(_lastVersionCheckKey, now.millisecondsSinceEpoch); - - // 标记本次会话已检查过版本 - _hasCheckedVersionInSession = true; - - Logger.i(_tag, '版本检查记录已更新: ${now.toIso8601String()}'); - } catch (e) { - Logger.e(_tag, '更新版本检查记录失败: $e'); - } - } - - /// 重置版本检查标记(可用于强制重新检查) - /// 注意:这会清除会话标记,但不会清除时间戳记录 - static void resetVersionCheckFlag() { - _hasCheckedVersionInSession = false; - Logger.i('SplashCtrl', '版本检查会话标记已重置'); - } - - /// 完全重置版本检查状态(包括时间戳) - /// 用于测试或特殊情况下强制重新检查 - void forceResetVersionCheck() { - try { - _hasCheckedVersionInSession = false; - _storage.remove(_lastVersionCheckKey); - Logger.i(_tag, '版本检查状态已完全重置'); - } catch (e) { - Logger.e(_tag, '重置版本检查状态失败: $e'); - } - } }