You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

827 lines
28 KiB

import 'dart:async';
import 'dart:io';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:logger/web.dart';
import 'package:get_storage/get_storage.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:crypto/crypto.dart'; // 用于生成设备指纹
import 'dart:convert';
import '../../../data/services/db/sqflite_api.dart';
import 'facebookLogin.dart';
import 'googleLogin.dart';
import 'weixinLogin.dart';
import 'appleLogin.dart';
import '../../../routes/app_routes.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/models/appconfig_model.dart';
import '../../../data/services/network/api.dart';
import '../../../../data/models/user_Info.dart';
import '../../../data/models/channel_app_model.dart';
import '../../../data/services/version_update_service.dart';
import '../../../modules/common/widgets/privacy_policy_dialog.dart';
import '../constants/country_codes.dart';
class LoginController extends GetxController {
static LoginController get to => Get.find();
final GetStorage _storage = GetStorage();
// 添加游客登录显示状态
final _isGuestLoginVisible = false.obs;
bool get isGuestLoginVisible => _isGuestLoginVisible.value;
String _deviceId = '';
String _deviceModel = '';
String _appVersion = ''; // APP版本号
String _countryCode = ''; // 国家代码
String _languageCode = ''; // 语言代码
UserChannel _userChannel = UserChannel.OFFICIAL; // 默认渠道
final isChinaMainland = false.obs; //是否中国大陆地区
final _hasAcceptedPrivacy = false.obs; //是否同意隐私政策
bool get hasAcceptedPrivacy => _hasAcceptedPrivacy.value;
set hasAcceptedPrivacy(bool value) => _hasAcceptedPrivacy.value = value;
// 添加国家编码相关状态
final _selectedCountryCode = ''.obs;
final _selectedCountryFlag = '✉️'.obs;
// 国家编码 Getter
String get selectedCountryCode => _selectedCountryCode.value;
String get selectedCountryFlag => _selectedCountryFlag.value;
// 预设国家编码列表
final List<Map<String, String>> countryCodes = kCountryCodes;
// 初始化默认选择的国家代码
void _initDefaultCountryCode() {
if (isChinaMainland.value) {
// 中国大陆用户默认选择+86
_selectedCountryCode.value = '+86';
_selectedCountryFlag.value = '🇨🇳';
loginType = 1; // 设置为手机号登录类型
} else {
// 非中国大陆用户默认选择邮箱
_selectedCountryCode.value = '';
_selectedCountryFlag.value = '✉️';
loginType = 0; // 设置为邮箱登录类型
}
}
// 状态管理
final _isLoading = false.obs;
final _countdownSeconds = 0.obs;
int loginType = 0;
// Getters
bool get isLoading => _isLoading.value;
int get countdownSeconds => _countdownSeconds.value;
final versionService = VersionUpdateService.to;
// 文本控制器 - 延迟初始化
TextEditingController? _contactController;
TextEditingController? _verificationCodeController;
// Getter 方法,确保控制器存在
TextEditingController get contactController {
_contactController ??= TextEditingController();
return _contactController!;
}
TextEditingController get verificationCodeController {
_verificationCodeController ??= TextEditingController();
return _verificationCodeController!;
}
@override
void onInit() {
super.onInit();
// 初始化控制器
_contactController = TextEditingController();
_verificationCodeController = TextEditingController();
_initDeviceInfo();
_initAppInfo(); // 新增:初始化APP信息
_checkGuestLoginConfig(); // 检查游客登录配置
final channel = versionService.cachedChannel;
isChinaMainland.value =
Get.deviceLocale?.countryCode?.toUpperCase() == 'CN' &&
channel != UserChannel.GLOBAL_GOOGLE; //是否中国大陆地区
// 初始化默认国家代码
_initDefaultCountryCode();
if (channel != UserChannel.CHINA_HUAWEI &&
channel != UserChannel.CHINA_HONOR) {
// 非中国华为和荣耀渠道,检查是否已接受隐私政策
_hasAcceptedPrivacy.value =
_storage.read('hasAcceptedPrivacy') ?? false; // 从本地存储中读取是否已接受隐私政策
}
}
@override
void onClose() {
// 安全地销毁控制器
try {
_contactController?.dispose();
_contactController = null;
} catch (e) {
Logger().e('销毁 contactController 失败: $e');
}
try {
_verificationCodeController?.dispose();
_verificationCodeController = null;
} catch (e) {
Logger().e('销毁 verificationCodeController 失败: $e');
}
super.onClose();
}
// 检查游客登录配置
Future<void> _checkGuestLoginConfig() async {
try {
_isGuestLoginVisible.value = versionService.cachedShowTourists;
Logger().d('lxm--游客登录显示状态: ${_isGuestLoginVisible.value}');
} catch (e) {
Logger().e('检查游客登录配置失败: $e');
// 默认不显示游客登录
_isGuestLoginVisible.value = false;
}
}
// 显示隐私政策弹窗
void _showPrivacyDialog() {
PrivacyPolicyDialog.show(
onAccept: () {
_storage.write('hasAcceptedPrivacy', true); // 存储用户已接受隐私政策
_hasAcceptedPrivacy.value = true;
Get.back();
},
onReject: () => exit(0),
canDismiss: false, // 禁止通过返回键关闭弹窗
);
}
// 初始化设备信息
Future<void> _initDeviceInfo() async {
try {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (GetPlatform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
_deviceId = androidInfo.id;
_deviceModel = androidInfo.model;
} else if (GetPlatform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
//print('lxm---iosInfo: $iosInfo.toString()');
final utsname = iosInfo.utsname; // 包含更详细的设备信息
//print('lxm---utsname: $utsname');
final machineId = utsname.machine.toLowerCase(); // 例如: "iPhone14,2"
_deviceModel = machineId;
Logger().d('lxm---machineId: $machineId');
final systemVersion = utsname.release; // 例如: "16.4.1"
Logger().d('lxm---systemVersion: $systemVersion');
try {
// 组合 machine 和 systemVersion 生成指纹
final fingerprint = '$machineId|$systemVersion';
final hashedFingerprint =
md5.convert(utf8.encode(fingerprint)).toString();
Logger()
.d('生成设备指纹: $hashedFingerprint (基于 $machineId 和 $systemVersion)');
_deviceId = hashedFingerprint;
} catch (e) {
Logger().e('生成设备指纹失败: $e');
_deviceId = iosInfo.identifierForVendor ?? 'unknown';
}
}
} catch (e) {
Logger().e('获取设备信息失败: $e');
_deviceId = 'unknown';
}
}
// 新增:初始化APP信息(版本号、渠道、国家代码)
Future<void> _initAppInfo() async {
try {
_appVersion = versionService.cachedAppVersion;
Logger().d('appVersion: $_appVersion');
// 获取国家代码
_countryCode = Get.locale?.countryCode ?? 'Unknown';
Logger().d('countryCode: $_countryCode');
// 获取语言代码
_languageCode = Get.locale?.languageCode ?? 'Unknown';
Logger().d('languageCode: $_languageCode');
// 获取用户渠道(根据平台和设备信息确定)
_userChannel = versionService.cachedChannel;
Logger().d('userChannel: ${_userChannel.value}');
} catch (e) {
Logger().e('获取APP信息失败: $e');
_appVersion = 'Unknown';
_countryCode = 'Unknown';
_languageCode = 'Unknown';
_userChannel = UserChannel.UNKNOWN;
}
}
// 更新选中的国家编码
void updateCountryCode(String code, String flag) {
_selectedCountryCode.value = code;
_selectedCountryFlag.value = flag;
// 根据选择更新输入框类型
if (code == '') {
contactController.clear(); // 清空当前输入
loginType = 0; // 设置为邮箱登录类型
} else {
loginType = 1; // 设置为手机号登录类型
}
_countdownSeconds.value = 0;
}
void _syncContactLoginType() {
loginType = _selectedCountryCode.value.isEmpty ? 0 : 1;
}
// 获取验证码
void getVerificationCode() {
_syncContactLoginType();
if (contactController.text.isEmpty) {
Get.snackbar(
'tip'.tr, //提示
'pleaseEnterContactInfo'.tr, //请输入手机号或邮箱
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
// 根据登录类型验证格式
if (loginType == 0 && !isEmailContactController()) {
Get.snackbar(
'tip'.tr,
'invalidEmailFormat'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
if (loginType == 1 && !isPhoneContactController()) {
Get.snackbar(
'tip'.tr,
'invalidPhoneFormat'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
_sendVerificationCode();
}
// 开始倒计时
void _startCountdown() {
_countdownSeconds.value = 60;
Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
_countdownSeconds.value--;
return _countdownSeconds.value > 0;
});
}
// 发送验证码
Future<void> _sendVerificationCode() async {
// 实际发送验证码的逻辑
if (isEmailContactController() || isPhoneContactController()) {
_startCountdown();
Logger().d(contactController.text);
var response = await Api.getCode({
"addr": selectedCountryCode + contactController.text,
"vtype": loginType,
});
if (response != null) {
Logger().d("请求后台数据验证码params=$response");
//短信已发送
//EasyLoading.showInfo('codeSend'.tr);
}
} else {
Get.snackbar(
'tip'.tr, //提示
'pleaseEnterContactInfo'.tr, //请输入手机号或邮箱
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
}
// 检查邮箱格式
bool isEmailContactController() {
// 检查每个元素是否为单个字母或数字字符
final text = contactController.text.trim();
if (text.length < 6) return false;
// 更严格的邮箱验证
final emailRegex =
RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');
return emailRegex.hasMatch(text);
}
// 检查手机号码格式
bool isPhoneContactController() {
// 检查每个元素是否为单个字母或数字字符
final text = contactController.text.trim();
if (text.isEmpty) return false;
// 根据国家代码验证手机号长度
// 长度检查
if (text.length < 7 || text.length > 15) return false;
// 必须是纯数字
if (!RegExp(r'^[0-9]+$').hasMatch(text)) return false;
// 特定国家的特殊验证
switch (_selectedCountryCode.value) {
case '+86': // 中国
return text.length == 11 && text.startsWith('1');
case '+1': // 美国/加拿大
return text.length == 10;
case '+81': // 日本
return text.length == 10 || text.length == 11;
default:
return true; // 其他国家只做基本验证
}
}
// 检查验证码是否完整
bool isVerificationCodeComplete() {
// 检查每个元素是否为单个字母或数字字符
final text = verificationCodeController.text;
Logger().d("数据获取Facebook${text.length}");
return text.length == 4 && RegExp(r'^[0-9]+$').hasMatch(text);
}
// 登录
Future<void> login({bool fromSocial = false}) async {
if (!fromSocial) {
_syncContactLoginType();
}
// 添加基础验证
if (loginType == 0 || loginType == 1) {
// 邮箱或手机号登录
if (contactController.text.isEmpty) {
Get.snackbar(
'error'.tr,
'pleaseEnterContactInfo'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
// 验证邮箱格式
if (loginType == 0 && !isEmailContactController()) {
Get.snackbar(
'error'.tr,
'invalidEmailFormat'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
// 验证手机号格式
if (loginType == 1 && !isPhoneContactController()) {
Get.snackbar(
'error'.tr,
'invalidPhoneFormat'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
return;
}
}
//验证隐私协议勾选状态
if (!_hasAcceptedPrivacy.value) {
_showPrivacyDialog();
return;
}
_isLoading.value = true;
// 模拟验证过程
// Future.delayed(const Duration(seconds: 2), () {
// _isLoading.value = false;
// // 验证成功,跳转到配对页面
// Get.offAllNamed(Routes.pairing);
// });
try {
switch (loginType) {
case 0: //邮箱登录
{
if (!isVerificationCodeComplete()) {
Get.snackbar(
'tip'.tr,
'pleaseEnterCompleteCode'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
break;
}
await requestLogin({
"mail": contactController.text,
"phone": "",
"stype": loginType,
"vcode": verificationCodeController.text,
"ttoken": "",
"phonemodel": "",
"phonemac": _deviceId,
"name": "",
"avatar": "",
});
break;
}
case 1: //手机号登录
{
if (!isVerificationCodeComplete()) {
Get.snackbar(
'tip'.tr,
'pleaseEnterVerificationCode'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
break;
}
await requestLogin({
"mail": "",
"phone": selectedCountryCode + contactController.text,
"stype": loginType,
"vcode": verificationCodeController.text,
"ttoken": "",
"phonemodel": "",
"phonemac": _deviceId,
"name": "",
"avatar": "",
});
break;
}
case 4: //Facebook登录
{
Logger().d("数据获取Facebook");
final facebook = Facebook();
bool authResult = await facebook.signInWithFacebook();
if (authResult) {
Logger().d("数据获取_Facebook.token${facebook.token}");
Logger().d("数据获取_Facebook.pictureurl${facebook.pictureurl}");
Logger().d("数据获取_Facebook.pictureurl${facebook.email}");
// SettingsController().userName = Facebook().name;
// SettingsController().avatar = Facebook().pictureurl;
// SettingsController().userId = Facebook().id;
//SettingsController().avatar = Google().email;
// 验证成功,跳转到配对页面
await requestLogin({
"mail": facebook.email,
"phone": "",
"stype": loginType,
"vcode": "",
"ttoken": facebook.token,
"phonemodel": "",
"phonemac": _deviceId,
"name": facebook.name,
"avatar": facebook.pictureurl,
});
}
break;
}
case 3: //Google登录
{
Logger().d("数据获取_Google()");
final google = Google();
bool authResult = await google.signInWithGoogle();
Logger().d("数据获取_Google().token${authResult}");
if (authResult) {
Logger().d("数据获取_Google.token${google.token}");
Logger().d("数据获取_Google.pictureurl${google.pictureurl}");
Logger().d("数据获取_Google.email${google.email}");
Logger().d("数据获取_Google.email${google.displayName}");
// 验证成功,跳转到配对页面
// SettingsController().userName = Google().displayName!;
// //SettingsController().avatar = Google().email;
// SettingsController().userId = Google().id!;
await requestLogin({
"mail": google.email,
"phone": "",
"stype": loginType,
"vcode": "",
"ttoken": google.token,
"phonemodel": "",
"phonemac": _deviceId,
"name": google.displayName,
"avatar": google.pictureurl,
});
}
break;
}
case 2: //微信登录
{
Logger().d("数据获取_FlWX()");
bool authResult = await FlWX().authBy(
NormalAuth(
scope: "snsapi_userinfo",
state: "wechat_sdk_demo_test",
),
getUserInfo: true, // 按需获取用户信息
);
if (authResult) {
// 确保 FlWX() 返回的是同一个实例,并且 token 已经被设置
// 这里假设 FlWX().token 在 authBy 成功后被设置
Logger().d("数据获取_FlWX().token${FlWX().token}");
// SettingsController().userName = FlWX().nickname;
// SettingsController().avatar = FlWX().headimgurl;
// SettingsController().userId = FlWX().openid;
await requestLogin({
"mail": "",
"phone": "",
"stype": loginType,
"vcode": "",
"ttoken": FlWX().code,
"phonemodel": "",
"phonemac": _deviceId,
"name": FlWX().nickname,
"avatar": FlWX().headimgurl,
});
// 验证成功,跳转到配对页面
}
break;
}
case 5: //Apple登录
{
Logger().d("开始Apple登录流程");
final apple = Apple();
// 先检查环境(调试用)
if (kDebugMode) {
await Apple.checkAppleSignInEnvironment();
}
bool authResult = await apple.signInWithApple();
if (authResult) {
Logger().d("Apple登录成功");
Logger().d("认证方式: ${apple.getAuthMethodName()}");
Logger().d("Token: ${apple.token}");
Logger().d("Email: ${apple.email}");
Logger().d("Name: ${apple.name}");
Logger().d("ID: ${apple.id}");
// 获取登录数据并发送到后端
final loginData = apple.getLoginData(_deviceId);
Logger().d("发送登录数据: $loginData");
// 直接调用现有的requestLogin方法
await requestLogin(loginData);
} else {
Logger().e("Apple登录失败");
Get.snackbar(
'error'.tr,
'Apple登录失败,请重试',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withValues(alpha: 0.1),
colorText: Colors.red,
);
}
break;
}
case 6: //游客登录
{
await requestLogin({
"mail": "",
"phone": "",
"stype": loginType,
"vcode": "",
"ttoken": "",
"phonemodel": _countryCode + _userChannel.toString(),
"phonemac": _deviceId,
"name": "",
"avatar": "",
});
break;
}
default:
{
Get.snackbar(
'error'.tr,
'loginFailed'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
break;
}
}
} catch (e) {
Get.snackbar(
'error'.tr,
'loginFailed'.tr,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
} finally {
_isLoading.value = false;
}
}
// 使用登陆请求服务器
Future<void> requestLogin(Map params) async {
try {
Logger().d("请求后台数据login=$params");
Logger().d('lxm---phonemodel: $_deviceModel');
// 在Api.login调用时添加超时
var response = await Api.login({
"mail": params["mail"],
"phone": params["phone"],
"stype": params["stype"],
"vcode": params["vcode"],
"ttoken": params["ttoken"],
"phonemodel": _deviceModel,
"phonemac": params["phonemac"],
"name": params["name"],
"avatar": params["avatar"],
"language": _languageCode,
"channel": _userChannel,
"version": _appVersion,
}).timeout(
const Duration(seconds: 30), // 30秒超时
onTimeout: () {
throw TimeoutException('登录请求超时', const Duration(seconds: 30));
},
);
if (response != null) {
Logger().d("请求后台数据login回复数据=$response");
await _storage.write('loginInfo', jsonEncode(response));
try {
Logger().d('响应数据: ${response.toString()}');
if (response['token'].toString() != '') {
final user = User.fromJson(response);
Logger().d('用户初始化成功: ${response['token'].toString()}');
await _storage.write("logintoken", response['token'].toString());
await _storage.write('user_info', user.toJson());
// 获取app的配置信息
await getAppConfig();
await _initMeetingTemplate();
Get.offAllNamed(Routes.pairing);
} else {
throw Exception('接口返回错误: ${response['msg']}');
}
} catch (e, stack) {
Logger().d('用户数据处理失败: $e');
Logger().d('堆栈跟踪: $stack');
Get.snackbar(
'error'.tr,
'userDataParseFailed'.tr, // 用户数据解析失败
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
} else {
// API返回null的情况
Get.snackbar(
'error'.tr,
'networkError'.tr, // 网络错误,请重试
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
} catch (e) {
// 网络请求异常
Logger().e('登录请求失败: $e');
Get.snackbar(
'error'.tr,
'loginFailed'.tr, // 登录失败
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
} finally {
// 确保无论成功还是失败都重置加载状态
_isLoading.value = false;
}
}
// 使用密码登录
void usePasswordLogin() {
// 这里可以实现密码登录的逻辑
Get.snackbar(
'提示',
'密码登录功能尚未实现',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.blue.withOpacity(0.1),
colorText: Colors.blue,
);
}
Future<bool> getAppConfig() async {
try {
Logger().d('getAppConfig:进来了');
//if (User.getname() != "") {
final response = await Api.getappconfig();
Logger().d('getAppConfig: $response');
if (response == null) return false;
Logger().d('getAppConfig:进来了');
// 根据API设计调整
final resp = UserGetAppConfigResp.fromJson(response);
// 详细记录解析后的配置信息
Logger().d('getAppConfig: 配置解析成功');
Logger().d('getAppConfig: 环境变量数量 = ${resp.env.length}');
Logger().d('getAppConfig: 智能体数量 = ${resp.agents.length}');
Logger().d('getAppConfig: MCP服务器数量 = ${resp.mcps.length}');
Logger().d('getAppConfig: 产品设备数量 = ${resp.products.length}');
// 详细记录产品设备信息
if (resp.products.isNotEmpty) {
Logger().d('getAppConfig: ========== 产品设备详细信息 ==========');
for (int i = 0; i < resp.products.length; i++) {
final product = resp.products[i];
Logger().d(
'getAppConfig: 产品[$i] ID=${product.id}, 设备名称="${product.devicename}"');
Logger().d(
'getAppConfig: 产品[$i] 厂商ID=${product.factoryid}, 设备类型=${product.devicetype}');
Logger().d('getAppConfig: 产品[$i] 扫描UUID="${product.scanuuid}"');
Logger().d(
'getAppConfig: 产品[$i] 产品图片="${product.productimage ?? "未设置"}"');
Logger().d('getAppConfig: 产品[$i] 版本="${product.version ?? "未设置"}"');
Logger().d(
'getAppConfig: 产品[$i] 更新描述="${product.updatedescription ?? "未设置"}"');
Logger().d(
'getAppConfig: 产品[$i] 更新包地址="${product.updatepackageaddress ?? "未设置"}"');
Logger().d('getAppConfig: ----------------------------------------');
}
Logger().d('getAppConfig: ========== 产品设备信息结束 ==========');
} else {
Logger().w('getAppConfig: 警告 - 未获取到任何产品设备信息');
}
// 记录环境变量信息
if (resp.env.isNotEmpty) {
Logger().d('getAppConfig: ========== 环境变量信息 ==========');
resp.env.forEach((key, value) {
Logger().d('getAppConfig: 环境变量 $key = "$value"');
});
Logger().d('getAppConfig: ========== 环境变量信息结束 ==========');
}
AppConfig.initialize(resp);
Logger().d(
'getAppConfig: AppConfig初始化完成,产品数量验证 = ${AppConfig.config.products.length}');
// 验证AppConfig中的产品数据
final productNames = AppConfig.allproductNames();
final productUuids = AppConfig.allproductUuids();
Logger().d('getAppConfig: 可用产品名称列表 = $productNames');
Logger().d('getAppConfig: 可用产品UUID列表 = $productUuids');
Logger().d('getAppConfig: 应用配置获取和初始化成功完成');
return true;
} catch (e) {
Logger().d('getAppConfig 取失败: $e');
return false;
}
}
Future _initMeetingTemplate() async {
try {
var response = await Api.getEchomeetTemplates();
if (response != null) {
await SqfliteApi.initMeetingTemplate(response['templates']);
}
} catch (e) {
Logger().d('initMeetingTemplate 获取失败: $e');
}
}
}