From fec9933c0a0e11e7132dcd823218bb05bd773ec9 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 28 Nov 2024 14:57:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E4=B8=AD=E7=BB=A7=E5=8D=8F=E8=AE=AE=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lan/lan_zh.json | 4 +- lib/login/login/starLock_login_page.dart | 43 +- lib/main.dart | 2 + .../lockMian/lockMain/lockMain_logic.dart | 2 + lib/network/api.dart | 7 + lib/network/api_provider_base.dart | 57 ++- lib/network/start_chart_api.dart | 58 +++ .../startChart/command/message_command.dart | 69 ++++ lib/talk/startChart/constant/ip_constant.dart | 7 + .../constant/listen_addr_type_constant.dart | 5 + .../constant/payload_type_constant.dart | 10 + .../constant/protocol_flag_constant.dart | 3 + .../startChart/entity/relay_info_entity.dart | 90 +++++ .../entity/report_information_data.dart | 107 +++++ lib/talk/startChart/entity/scp_message.dart | 143 +++++++ .../star_chart_register_node_entity.dart | 59 +++ lib/talk/startChart/start_chart_manage.dart | 379 ++++++++++++++++++ lib/talk/udp/udp_help.dart | 2 +- lib/talk/udp/udp_manage.dart | 7 +- lib/talk/udp/udp_reciverData.dart | 2 +- lib/tools/deviceInfo_utils.dart | 39 ++ lib/tools/storage.dart | 38 ++ pubspec.yaml | 9 +- 23 files changed, 1116 insertions(+), 26 deletions(-) create mode 100644 lib/network/start_chart_api.dart create mode 100644 lib/talk/startChart/command/message_command.dart create mode 100644 lib/talk/startChart/constant/ip_constant.dart create mode 100644 lib/talk/startChart/constant/listen_addr_type_constant.dart create mode 100644 lib/talk/startChart/constant/payload_type_constant.dart create mode 100644 lib/talk/startChart/constant/protocol_flag_constant.dart create mode 100644 lib/talk/startChart/entity/relay_info_entity.dart create mode 100644 lib/talk/startChart/entity/report_information_data.dart create mode 100644 lib/talk/startChart/entity/scp_message.dart create mode 100644 lib/talk/startChart/entity/star_chart_register_node_entity.dart create mode 100644 lib/talk/startChart/start_chart_manage.dart create mode 100644 lib/tools/deviceInfo_utils.dart diff --git a/lan/lan_zh.json b/lan/lan_zh.json index e9662925..12437e7e 100755 --- a/lan/lan_zh.json +++ b/lan/lan_zh.json @@ -1096,10 +1096,8 @@ "支持的国家": "支持的国家", "支持的国家值": "美国、加拿大、英国、澳大利亚、印度、德国、法国、意大利、西班牙、日本", "操作流程": "操作流程", - "密码需至少包含数字/字母/字符中的2种组合": "密码需至少包含数字/字母/字符中的2种组合", - - "操作流程值":"1 用智能锁APP添加锁和网关\n\n2 在APP里开启锁的远程开锁功能(这个功能默认是关闭的)。如果没有这个选项,则锁不支持Alexa \n\n3 在Alexa中添加Skill,并用智能锁APP的账号和密码进行授权。授权成功后就可以发现账号下的设备\n\n4 在Alexa app里找到锁,开启语音开锁的功能,并设置语言密码\n\n5 可以通过Alexa操作锁了", + "操作流程值": "1 用智能锁APP添加锁和网关\n\n2 在APP里开启锁的远程开锁功能(这个功能默认是关闭的)。如果没有这个选项,则锁不支持Alexa \n\n3 在Alexa中添加Skill,并用智能锁APP的账号和密码进行授权。授权成功后就可以发现账号下的设备\n\n4 在Alexa app里找到锁,开启语音开锁的功能,并设置语言密码\n\n5 可以通过Alexa操作锁了", "Google Home": "Google Home", "Action name": "Action name", "ScienerSmart": "ScienerSmart", diff --git a/lib/login/login/starLock_login_page.dart b/lib/login/login/starLock_login_page.dart index 42f00b78..ec43b33b 100755 --- a/lib/login/login/starLock_login_page.dart +++ b/lib/login/login/starLock_login_page.dart @@ -1,10 +1,10 @@ - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:star_lock/flavors.dart'; import 'package:star_lock/login/login/starLock_login_state.dart'; +import 'package:star_lock/talk/startChart/start_chart_manage.dart'; import 'package:star_lock/tools/appFirstEnterHandle.dart'; import 'package:star_lock/tools/wechat/customer_tool.dart'; import 'package:star_lock/tools/storage.dart'; @@ -48,8 +48,8 @@ class _StarLockLoginPageState extends State { haveBack: false, backgroundColor: AppColors.mainColor, actionsList: [ - IconButton( - onPressed: (){ + IconButton( + onPressed: () { WechatManageTool.getAppInfo(CustomerTool.openCustomerService); }, icon: const Icon( @@ -84,8 +84,7 @@ class _StarLockLoginPageState extends State { width: 110.w, height: 110.w))), SizedBox(height: 50.w), Obx(() => CommonItem( - leftTitel: - '你所在的国家/地区'.tr, + leftTitel: '你所在的国家/地区'.tr, rightTitle: '', isHaveLine: true, isPadding: false, @@ -123,7 +122,7 @@ class _StarLockLoginPageState extends State { height: 36.w, ), ), - hintText:'请输入手机号或者邮箱'.tr, + hintText: '请输入手机号或者邮箱'.tr, // keyboardType: TextInputType.number, inputFormatters: [ // FilteringTextInputFormatter.allow(RegExp('[0-9]')), @@ -185,8 +184,7 @@ class _StarLockLoginPageState extends State { WidgetSpan( alignment: PlaceholderAlignment.middle, child: GestureDetector( - child: Text( - '《${'用户协议'.tr}》', + child: Text('《${'用户协议'.tr}》', style: TextStyle( color: AppColors.mainColor, fontSize: 20.sp)), @@ -201,8 +199,7 @@ class _StarLockLoginPageState extends State { WidgetSpan( alignment: PlaceholderAlignment.middle, child: GestureDetector( - child: Text( - '《${'隐私政策'.tr}》', + child: Text('《${'隐私政策'.tr}》', style: TextStyle( color: AppColors.mainColor, fontSize: 20.sp)), @@ -236,6 +233,29 @@ class _StarLockLoginPageState extends State { } } : null)), + SubmitBtn( + btnName: '发送上线请求', + onClick: () { + // 注册星图节点信息 + StartChartManage().clientRegister(); + // 查询中继信息 + StartChartManage().relayQuery(); + // 发送上线请求 + StartChartManage().onlineRelayService(); + }, + ), + SubmitBtn( + btnName: '启动心跳包', + onClick: () { + StartChartManage().sendHeartbeatMessage(); + }, + ), + SubmitBtn( + btnName: '结束心跳包', + onClick: () { + StartChartManage().stopHeartbeat(); + }, + ), SizedBox(height: 50.w), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -246,8 +266,7 @@ class _StarLockLoginPageState extends State { height: 50.h, // color: Colors.red, child: Center( - child: Text( - '${'忘记密码'.tr}?', + child: Text('${'忘记密码'.tr}?', style: TextStyle( fontSize: 22.sp, color: AppColors.mainColor)), ), diff --git a/lib/main.dart b/lib/main.dart index aa07e5a9..d4898717 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,6 +11,7 @@ import 'package:star_lock/flavors.dart'; import 'package:star_lock/mine/about/debug/debug_tool.dart'; import 'package:star_lock/network/api_provider.dart'; import 'package:star_lock/network/api_repository.dart'; +import 'package:star_lock/network/start_chart_api.dart'; import 'package:star_lock/tools/bugly/bugly_tool.dart'; import 'package:star_lock/tools/device_info_service.dart'; import 'package:star_lock/tools/platform_info_services.dart'; @@ -62,6 +63,7 @@ Future _initTranslation() async => TranslationLoader.loadTranslation(); Future _setCommonServices() async { await Get.putAsync(() => StoreService().init()); Get.put(ApiProvider()); + Get.put(StartChartApi()); Get.put(ApiRepository(Get.find())); if (F.isLite) { //上架审核注释 获取设备信息 diff --git a/lib/main/lockMian/lockMain/lockMain_logic.dart b/lib/main/lockMian/lockMain/lockMain_logic.dart index f7989255..48d89fed 100755 --- a/lib/main/lockMian/lockMain/lockMain_logic.dart +++ b/lib/main/lockMian/lockMain/lockMain_logic.dart @@ -11,6 +11,7 @@ import 'package:star_lock/flavors.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart'; import 'package:star_lock/main/lockMian/lockList/lockList_logic.dart'; +import 'package:star_lock/talk/startChart/start_chart_manage.dart'; import 'package:star_lock/tools/eventBusEventManage.dart'; import 'package:star_lock/tools/push/xs_jPhush.dart'; import 'package:star_lock/tools/showTipView.dart'; @@ -312,6 +313,7 @@ class LockMainLogic extends BaseGetXController { // AppLog.log('onReady() LockMainLogic'); UdpHelp().openUDP(); BlueManage(); + } @override diff --git a/lib/network/api.dart b/lib/network/api.dart index e670f0c4..686e9b5b 100755 --- a/lib/network/api.dart +++ b/lib/network/api.dart @@ -282,4 +282,11 @@ abstract class Api { final String updateZoneOffsetsAndLanguagesURL = '/cloudUser/updateSettings'; //更新云用户时区偏移与语言 + + final String starChartRegisterNodeURL = + '/SL-A-1.0/peer/register'; // 星图--注册节点 + final String relayQueryInfoURL = + '/SL-A-1.0/relay/query'; // 星图--中继查询 + final String reportInformationDataURL = + '/SL-A-1.0/peer/login'; // 星图--信息上报 } diff --git a/lib/network/api_provider_base.dart b/lib/network/api_provider_base.dart index e6563cdb..2d9b92d1 100755 --- a/lib/network/api_provider_base.dart +++ b/lib/network/api_provider_base.dart @@ -78,7 +78,62 @@ class BaseProvider extends GetConnect with Api { statusText: res.statusText, ); } else {} - if(isShowNetworkErrorMsg ?? true){ + if (isShowNetworkErrorMsg ?? true) { + getDataResult(res.body); + } + return res; + } + + @override + Future> get( + String url, { + String? contentType, + Map? headers, + Map? query, + Decoder? decoder, + bool? isUnShowLoading = false, // 是否显示loading + bool? isUserBaseUrl = true, // 文件上传不使用baseUrl + bool? isShowErrMsg = true, // 是否显示没有网络时的提示 + bool? isShowNetworkErrorMsg = true, // 是否显示网络其他报错 如403 500等 + }) async { + AppLog.log('get: url:$url'); + if (isUnShowLoading == false) { + EasyLoading.show(); + } + if (isUserBaseUrl == false) { + httpClient.baseUrl = ''; + } else { + httpClient.baseUrl = '${F.apiPrefix}/api'; + } + var res = await super.get(url, + contentType: contentType, + headers: headers, + query: query, + decoder: decoder); + if (EasyLoading.isShow && !isUnShowLoading!) { + await EasyLoading.dismiss(animation: true); + } + if (res.body == null) { + if (res.statusCode == null && isShowErrMsg!) { + EasyLoading.showToast('网络访问失败,请检查网络是否正常'.tr, + duration: 2000.milliseconds); + } + var rs = { + 'errorMsg': 'Network Error!', + 'errorCode': -1, + 'data': null, + 'description': '表示成功或是。' + }; + return Response( + request: res.request, + statusCode: -1, + bodyString: res.bodyString, + bodyBytes: res.bodyBytes, + body: rs as T, + statusText: res.statusText, + ); + } + if (isShowNetworkErrorMsg ?? true) { getDataResult(res.body); } return res; diff --git a/lib/network/start_chart_api.dart b/lib/network/start_chart_api.dart new file mode 100644 index 00000000..1a6901c8 --- /dev/null +++ b/lib/network/start_chart_api.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; + +import 'package:get/get.dart'; +import 'package:star_lock/network/api_provider.dart'; +import 'package:star_lock/network/api_provider_base.dart'; +import 'package:star_lock/talk/startChart/entity/relay_info_entity.dart'; +import 'package:star_lock/talk/startChart/entity/report_information_data.dart'; +import 'package:star_lock/talk/startChart/entity/star_chart_register_node_entity.dart'; + +class StartChartApi extends BaseProvider { + // 星图url + final String _startChartHost = 'http://sls1-scd.star-lock.cn:8080'; + + static StartChartApi get to => Get.find(); + + // 星图--注册节点 + Future starChartRegisterNode({ + required String product, + required String model, + required String name, + required String unique, + }) async { + final response = await post( + _startChartHost + starChartRegisterNodeURL.toUrl, + jsonEncode({ + 'product': product, + 'model': model, + 'name': name, + 'unique': unique, + }), + isUnShowLoading: true, + isUserBaseUrl: false, + ); + return StarChartRegisterNodeEntity.fromJson(response.body); + } + + // 星图--中继查询 + Future relayQueryInfo() async { + final response = await get( + _startChartHost + relayQueryInfoURL.toUrl, + isUnShowLoading: true, + isUserBaseUrl: false, + ); + return RelayInfoEntity.fromJson(response.body); + } + + // 星图--上报信息至发现服务 + Future reportInformation({ + required ReportInformationData reportInformationData, + }) async { + final response = await post( + _startChartHost + reportInformationDataURL.toUrl, + jsonEncode(reportInformationData.toJson()), + isUnShowLoading: true, + isUserBaseUrl: false, + ); + } +} diff --git a/lib/talk/startChart/command/message_command.dart b/lib/talk/startChart/command/message_command.dart new file mode 100644 index 00000000..0082d83c --- /dev/null +++ b/lib/talk/startChart/command/message_command.dart @@ -0,0 +1,69 @@ +import 'package:star_lock/talk/startChart/constant/payload_type_constant.dart'; +import 'package:star_lock/talk/startChart/constant/protocol_flag_constant.dart'; +import 'package:star_lock/talk/startChart/entity/scp_message.dart'; + +class MessageCommand { + /// 客户端去中继上线命令 + static List goOnlineRelay() { + String serializedBytesString = ScpMessage( + ProtocolFlag: ProtocolFlagConstant.scp01, + MessageType: PayloadTypeConstant.goOnline, + MessageId: 1, + SpTotal: 0, + SpIndex: 0, + ).serialize(); + return _hexToBytes(serializedBytesString); + } + + // 回声测试消息 + static List echoMessage({ + required String ToPeerId, + required String FromPeerId, + }) { + ScpMessage message = ScpMessage( + ProtocolFlag: ProtocolFlagConstant.scp01, + MessageType: PayloadTypeConstant.echoTest, + MessageId: 1, + SpTotal: 0, + SpIndex: 0, + FromPeerId: FromPeerId, + ToPeerId: ToPeerId, + Payload: 'hello', + PayloadCRC: 55230, + PayloadLength: 5, + PayloadType: 1, + ); + + String serializedBytesString = message.serialize(); + return _hexToBytes(serializedBytesString); + } + + // 心跳消息 + static List heartbeatMessage() { + ScpMessage message = ScpMessage( + ProtocolFlag: ProtocolFlagConstant.scp01, + MessageType: PayloadTypeConstant.heartbeat, + MessageId: 1, + SpTotal: 0, + SpIndex: 0, + // FromPeerId: FromPeerId, + // ToPeerId: ToPeerId, + // Payload: 'hello', + // PayloadCRC: 55230, + // PayloadLength: 5, + // PayloadType: 1, + ); + + String serializedBytesString = message.serialize(); + return _hexToBytes(serializedBytesString); + } + + // 辅助方法:将16进制字符串转换为字节列表 + static List _hexToBytes(String hex) { + final bytes = []; + for (int i = 0; i < hex.length; i += 2) { + bytes.add(int.parse(hex.substring(i, i + 2), radix: 16)); + } + return bytes; + } +} diff --git a/lib/talk/startChart/constant/ip_constant.dart b/lib/talk/startChart/constant/ip_constant.dart new file mode 100644 index 00000000..bf1f2d40 --- /dev/null +++ b/lib/talk/startChart/constant/ip_constant.dart @@ -0,0 +1,7 @@ +class IpConstant { + // 上报时需要排除的ip + static const List reportExcludeIp = ['127.0.0.1','::1%1']; + static const String udpUrl = 'udp://'; + static const String tcpUrl = 'tcp://'; + static const String httpsUrl = 'https://'; +} diff --git a/lib/talk/startChart/constant/listen_addr_type_constant.dart b/lib/talk/startChart/constant/listen_addr_type_constant.dart new file mode 100644 index 00000000..5b63a599 --- /dev/null +++ b/lib/talk/startChart/constant/listen_addr_type_constant.dart @@ -0,0 +1,5 @@ +class ListenAddrTypeConstant { + static const String local = 'local'; + static const String externally = 'externally'; + static const String relay = 'relay'; +} \ No newline at end of file diff --git a/lib/talk/startChart/constant/payload_type_constant.dart b/lib/talk/startChart/constant/payload_type_constant.dart new file mode 100644 index 00000000..eec9bd75 --- /dev/null +++ b/lib/talk/startChart/constant/payload_type_constant.dart @@ -0,0 +1,10 @@ +class PayloadTypeConstant { + // 上线 + static const int goOnline = 100; + // 回声测试 + static const int echoTest = 8; + // 心跳 + static const int heartbeat = 110; + // UDP协议的SCD发现服务器查询中继信息 + static const int query = 120; +} \ No newline at end of file diff --git a/lib/talk/startChart/constant/protocol_flag_constant.dart b/lib/talk/startChart/constant/protocol_flag_constant.dart new file mode 100644 index 00000000..366a7009 --- /dev/null +++ b/lib/talk/startChart/constant/protocol_flag_constant.dart @@ -0,0 +1,3 @@ +class ProtocolFlagConstant { + static const String scp01 = 'SC01'; +} \ No newline at end of file diff --git a/lib/talk/startChart/entity/relay_info_entity.dart b/lib/talk/startChart/entity/relay_info_entity.dart new file mode 100644 index 00000000..8c0d61d8 --- /dev/null +++ b/lib/talk/startChart/entity/relay_info_entity.dart @@ -0,0 +1,90 @@ +class RelayInfoEntity { + RelayInfoEntity({ + this.msg, + this.time, + this.stun_server, + this.client_addr, + this.relay_list, + }); + + RelayInfoEntity.fromJson(dynamic json) { + msg = json['msg']; + time = json['time']; + stun_server = json['stun_server']; + client_addr = json['client_addr']; + relay_list = json['relay_list'] != null + ? List.from( + json['relay_list'].map((x) => RelayData.fromJson(x))) + : null; + } + + String? msg; + int? time; + String? stun_server; + String? client_addr; + List? relay_list; + + Map toJson() { + return { + 'msg': msg, + 'time': time, + 'stun_server': stun_server, + 'client_addr': client_addr, + 'relay_list': relay_list?.map((x) => x.toJson()).toList(), + }; + } + + @override + String toString() { + return 'RelayInfoEntity{msg: $msg, time: $time, stun_server: $stun_server, client_addr: $client_addr, relay_list: $relay_list}'; + } +} + +class RelayData { + RelayData({ + this.peerID, + this.name, + this.listenAddr, + this.peerMax, + this.peerCurrent, + this.health, + this.latency, + }); + + RelayData.fromJson(dynamic json) { + peerID = json['peerID']; + name = json['name']; + listenAddr = json['listenAddr']; + peerMax = json['peerMax']; + peerCurrent = json['peerCurrent']; + health = json['health']; + latency = json['latency']; + } + + String? peerID; + int? time; + String? name; + String? listenAddr; + int? peerMax; + int? peerCurrent; + int? health; + int? latency; + + Map toJson() { + return { + 'peerID': peerID, + 'time': time, + 'name': name, + 'listenAddr': listenAddr, + 'peerMax': peerMax, + 'peerCurrent': peerCurrent, + 'health': health, + 'latency': latency, + }; + } + + @override + String toString() { + return 'RelayData{peerID: $peerID, time: $time, name: $name, listenAddr: $listenAddr, peerMax: $peerMax, peerCurrent: $peerCurrent, health: $health, latency: $latency}'; + } +} diff --git a/lib/talk/startChart/entity/report_information_data.dart b/lib/talk/startChart/entity/report_information_data.dart new file mode 100644 index 00000000..5fbc797f --- /dev/null +++ b/lib/talk/startChart/entity/report_information_data.dart @@ -0,0 +1,107 @@ +class ReportInformationData { + ReportInformationData({ + this.id, + this.public_key, + this.listen_addr, + this.relay_service, + this.time, + this.sign, + }); + + ReportInformationData.fromJson(dynamic json) { + id = json['id']; + public_key = json['public_key']; + time = json['time']; + sign = json['sign']; + listen_addr = json['listen_addr'] != null + ? List.from( + json['listen_addr'].map((x) => ListenAddrData.fromJson(x))) + : null; + relay_service = json['relay_service']; + } + + String? id; + String? public_key; + String? sign; + List? listen_addr; + RelayServiceData? relay_service; + int? time; + + Map toJson() { + return { + 'id': id, + 'public_key': public_key, + 'time': time, + 'sign': sign, + 'listen_addr': listen_addr?.map((x) => x.toJson()).toList(), + 'relay_service': relay_service, + }; + } + + @override + String toString() { + return 'ReportInformationData{id: $id, public_key: $public_key, sign: $sign, listen_addr: $listen_addr, relay_service: $relay_service, time: $time}'; + } +} + +class ListenAddrData { + String? type; + String? address; + + ListenAddrData({ + this.type, + this.address, + }); + + ListenAddrData.fromJson(dynamic json) { + type = json['type']; + address = json['address']; + } + + Map toJson() { + return { + 'type': type, + 'address': address, + }; + } + + @override + String toString() { + return 'ListenAddrData{type: $type, address: $address}'; + } +} + +class RelayServiceData { + String? name; + String? listen_addr; + int? peers_max; + int? peers_current; + + RelayServiceData({ + this.name, + this.listen_addr, + this.peers_max, + this.peers_current, + }); + + RelayServiceData.fromJson(dynamic json) { + name = json['name']; + listen_addr = json['listen_addr']; + peers_max = json['peers_max']; + peers_current = json['peers_current']; + } + + Map toJson() { + return { + 'name': name, + 'listen_addr': listen_addr, + 'peers_max': peers_max, + 'peers_current': peers_current, + }; + } + + @override + String toString() { + return 'RelayServiceData{name: $name, listen_addr: $listen_addr, peers_max: $peers_max, peers_current: $peers_current}'; + } +} diff --git a/lib/talk/startChart/entity/scp_message.dart b/lib/talk/startChart/entity/scp_message.dart new file mode 100644 index 00000000..f7dc124e --- /dev/null +++ b/lib/talk/startChart/entity/scp_message.dart @@ -0,0 +1,143 @@ +import 'dart:convert'; +import 'package:crc32_checksum/crc32_checksum.dart'; +import 'package:crypto/crypto.dart'; +import 'package:star_lock/app_settings/app_settings.dart'; + +class ScpMessage { + ScpMessage({ + this.ProtocolFlag, + this.MessageType, + this.MessageId, + this.SpTotal, + this.SpIndex, + this.FromPeerId, + this.ToPeerId, + this.PayloadType, + this.PayloadCRC, + this.PayloadLength, + this.Payload, + }); + + String? ProtocolFlag; + int? MessageType; + int? MessageId; + int? SpTotal; + int? SpIndex; + String? FromPeerId; + String? ToPeerId; + int? PayloadType; + int? PayloadCRC; + int? PayloadLength; + String? Payload; + + ScpMessage.fromJson(dynamic json) { + ProtocolFlag = json['ProtocolFlag']; + MessageType = json['MessageType']; + MessageId = json['MessageId']; + SpTotal = json['SpTotal']; + SpIndex = json['SpIndex']; + FromPeerId = json['FromPeerId']; + ToPeerId = json['ToPeerId']; + PayloadType = json['PayloadType']; + PayloadCRC = json['PayloadCRC']; + PayloadLength = json['PayloadLength']; + Payload = json['Payload']; + } + + Map toJson() { + return { + 'ProtocolFlag': ProtocolFlag, + 'MessageType': MessageType, + 'MessageId': MessageId, + 'SpTotal': SpTotal, + 'SpIndex': SpIndex, + 'FromPeerId': FromPeerId, + 'ToPeerId': ToPeerId, + 'PayloadType': PayloadType, + 'PayloadCRC': PayloadCRC, + 'PayloadLength': PayloadLength, + 'Payload': Payload, + }; + } + + String serialize() { + final bytes = []; + + // ProtocolFlag (4 bytes) + if (ProtocolFlag != null) { + bytes.addAll(utf8.encode(ProtocolFlag!)); + } + + // MessageType (1 byte) + if (MessageType != null) { + bytes.add(MessageType!); + } + + // MessageId (2 bytes) + if (MessageId != null) { + final highByteMessageId = (MessageId! >> 8) & 0xFF; + final lowByteMessageId = MessageId! & 0xFF; + bytes.add(lowByteMessageId); // 交换位置 + bytes.add(highByteMessageId); // 交换位置 + } + + // SpTotal (1 byte) + if (SpTotal != null) { + bytes.add(SpTotal!); + } + + // SpIndex (1 byte) + if (SpIndex != null) { + bytes.add(SpIndex!); + } + + // FromPeerId (字符串,记录长度) + if (FromPeerId != null) { + bytes.addAll(utf8.encode(FromPeerId!)); + } + + // ToPeerId (字符串,假设长度固定为32字节) + if (ToPeerId != null) { + bytes.addAll(utf8.encode(ToPeerId!)); + } + + // PayloadType (2 bytes) + if (PayloadType != null) { + final highBytePayloadType = (PayloadType! >> 8) & 0xFF; + final lowBytePayloadType = PayloadType! & 0xFF; + bytes.add(lowBytePayloadType); // 交换位置 + bytes.add(highBytePayloadType); // 交换位置 + } + + // 计算 PayloadCRC (2 bytes) + if (PayloadCRC != null) { + final highBytePayloadCRC = (PayloadCRC! >> 8) & 0xFF; + final lowBytePayloadCRC = PayloadCRC! & 0xFF; + bytes.add(lowBytePayloadCRC); // 交换位置 + bytes.add(highBytePayloadCRC); // 交换位置 + } + + // PayloadLength (4 bytes) + if (PayloadLength != null) { + bytes.add(PayloadLength! & 0xFF); + bytes.add((PayloadLength! >> 8) & 0xFF); + bytes.add((PayloadLength! >> 16) & 0xFF); + bytes.add((PayloadLength! >> 24) & 0xFF); + } + + // Payload (字符串,转换为字节) + if (Payload != null) { + bytes.addAll(utf8.encode(Payload!)); + } + + // 转16进制字符串 + final bytesToHexString = bytesToHex(bytes); + + + return bytesToHexString; + } + + static String bytesToHex(List bytes) { + return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); + } +} diff --git a/lib/talk/startChart/entity/star_chart_register_node_entity.dart b/lib/talk/startChart/entity/star_chart_register_node_entity.dart new file mode 100644 index 00000000..0d860d87 --- /dev/null +++ b/lib/talk/startChart/entity/star_chart_register_node_entity.dart @@ -0,0 +1,59 @@ +class StarChartRegisterNodeEntity { + StarChartRegisterNodeEntity({ + this.msg, + this.peer, + }); + + StarChartRegisterNodeEntity.fromJson(dynamic json) { + msg = json['msg']; + peer = json['peer'] != null ? PeerData.fromJson(json['peer']) : null; + } + + String? msg; + PeerData? peer; + + Map toJson() { + final map = {}; + map['msg'] = msg; + if (peer != null) { + map['peer'] = peer!.toJson(); + } + return map; + } + + @override + String toString() { + return 'StarChartRegisterNodeEntity{msg: $msg, peer: $peer}'; + } +} + +class PeerData { + PeerData({ + this.id, + this.publicKey, + this.privateKey, + }); + + PeerData.fromJson(dynamic json) { + id = json['id']; + publicKey = json['publicKey']; + privateKey = json['privateKey']; + } + + String? id; + String? publicKey; + String? privateKey; + + Map toJson() { + final map = {}; + map['id'] = id; + map['publicKey'] = publicKey; + map['privateKey'] = privateKey; + return map; + } + + @override + String toString() { + return 'PeerData{id: $id, publicKey: $publicKey, privateKey: $privateKey}'; + } +} diff --git a/lib/talk/startChart/start_chart_manage.dart b/lib/talk/startChart/start_chart_manage.dart new file mode 100644 index 00000000..0baa1f6a --- /dev/null +++ b/lib/talk/startChart/start_chart_manage.dart @@ -0,0 +1,379 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; +import 'package:encrypt/encrypt.dart'; +import 'package:pointycastle/asymmetric/api.dart'; +import 'package:star_lock/app_settings/app_settings.dart'; +import 'package:star_lock/flavors.dart'; +import 'package:star_lock/network/start_chart_api.dart'; +import 'package:star_lock/talk/startChart/command/message_command.dart'; +import 'package:star_lock/talk/startChart/constant/ip_constant.dart'; +import 'package:star_lock/talk/startChart/constant/listen_addr_type_constant.dart'; +import 'package:star_lock/talk/startChart/entity/relay_info_entity.dart'; +import 'package:star_lock/talk/startChart/entity/report_information_data.dart'; +import 'package:star_lock/talk/startChart/entity/star_chart_register_node_entity.dart'; +import 'package:star_lock/tools/deviceInfo_utils.dart'; +import 'package:star_lock/tools/storage.dart'; +import 'package:uuid/uuid.dart'; + +class StartChartManage { + // 私有构造函数,防止外部直接new对象 + StartChartManage._internal(); + + // 单例对象 + static final StartChartManage _instance = StartChartManage._internal(); + + // 工厂构造函数,返回单例对象 + factory StartChartManage() { + return _instance; + } + + // 产品昵称 + final String _productName = F.navTitle; + + RawDatagramSocket? _udpSocket; + late String remoteHost = ''; // 远程主机地址(服务器返回) + late int remotePort = 0; // 远程主机端口(服务器返回) + final int localPort = 62289; // 本地端口 + + int heartbeatIntervalTime = 1; // 心跳包间隔时间(s) + Timer? _heartBeatTimer; // 心跳包定时器 + bool _heartBeatTimerRunning = false; // 心跳包定时任务发送状态 + + String ToPeerId = ''; // 对端ID + String FromPeerId = ''; // 我的ID + /// 客户端注册 + Future clientRegister() async { + // 从缓存中获取星图注册节点信息 + final StarChartRegisterNodeEntity? starChartRegisterNodeInfo = + await Storage.getStarChartRegisterNodeInfo(); + if (starChartRegisterNodeInfo == null) { + _log(text: '开始注册客户端'); + final StarChartRegisterNodeEntity requestStarChartRegisterNode = + await _requestStarChartRegisterNode(); + _saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode); + } else { + final entity = await Storage.getStarChartRegisterNodeInfo(); + _log(text: '获取到星图注册节点信息:$entity'); + } + } + + // 中继查询 + Future relayQuery() async { + final RelayInfoEntity relayInfoEntity = + await StartChartApi.to.relayQueryInfo(); + _saveRelayInfoEntityToStorage(relayInfoEntity); + + if (relayInfoEntity.relay_list?.length != 0) { + final data = relayInfoEntity.relay_list?[0]; + FromPeerId = data?.peerID ?? ''; + final parseUdpUrl = _parseUdpUrl(data?.listenAddr ?? ''); + remoteHost = parseUdpUrl['host'] ?? ''; + remotePort = parseUdpUrl['port'] ?? ''; + _log(text: '中继信息:${data}'); + } + } + + void closeUdpSocket() { + if (_udpSocket != null) { + _udpSocket?.close(); + } + } + + // 在中继服务器中上线 + Future onlineRelayService() async { + await relayQuery(); + var addressIListenFrom = InternetAddress.anyIPv4; + RawDatagramSocket.bind(addressIListenFrom, localPort) + .then((RawDatagramSocket socket) { + _udpSocket = socket; + + /// 广播功能 + _udpSocket!.broadcastEnabled = true; + + /// 设置数据接收回调 + _onReceiveData(_udpSocket!); + + // 发送上线消息 + //_sendOnlineMessage(); + // 发送回声测试消息 + //_sendEchoMessage(); + + // 上报信息 + reportInformation(); + }).catchError((error) { + _log(text: 'Failed to bind UDP socket: $error'); + }); + } + + // 接收返回的数据 + void _onReceiveData(RawDatagramSocket socket) { + socket.listen((RawSocketEvent event) { + if (event == RawSocketEvent.read) { + Datagram? dg = socket.receive(); + try { + _log(text: '收到消息---> 长度:${dg?.data?.length}, 数据:${dg?.data}'); + } catch (e) { + _log(text: '❌ Udp ----> $e'); + } + } + }); + } + + // 上报信息至发现服务 + Future reportInformation() async { + _log(text: '上报信息至发现服务'); + // 构建参数 + ReportInformationData data = await _makeReportInformationData(); + await StartChartApi.to.reportInformation( + reportInformationData: data, + ); + } + + // 发送上线消息 + void _sendOnlineMessage() { + // 组装上线消息 + final message = MessageCommand.goOnlineRelay(); + _sendMessage(message: message); + } + + // 发送回声测试消息 + void _sendEchoMessage() { + final message = MessageCommand.echoMessage( + ToPeerId: ToPeerId, + FromPeerId: FromPeerId, + ); + _sendMessage(message: message); + } + + // 发送心跳包消息 + void sendHeartbeatMessage() { + if (_heartBeatTimerRunning) { + _log(text: '心跳已经开始了. 请勿重复发送心跳包消息'); + return; + } + _heartBeatTimer ??= Timer.periodic( + Duration( + seconds: heartbeatIntervalTime, + ), + (Timer timer) { + final List message = MessageCommand.heartbeatMessage(); + _sendMessage(message: message); + }, + ); + _heartBeatTimerRunning = true; + } + + // 停止定时发送心跳包 + void stopHeartbeat() { + _heartBeatTimer?.cancel(); + _heartBeatTimer = null; // 清除定时器引用 + _heartBeatTimerRunning = false; + _log(text: '发送心跳包结束'); + } + + // 发送消息 + void _sendMessage({required List message}) { + _log(text: '发送给中继的消息体:${message},序列化之后的数据:【${bytesToHex(message)}】'); + _udpSocket!.send(message, InternetAddress(remoteHost), remotePort); + } + + // 请求注册节点 + Future _requestStarChartRegisterNode() async { + // 获取设备信息 + final Map deviceInfo = await _getDeviceInfo(); + // 发送注册节点请求 + final StarChartRegisterNodeEntity response = + await StartChartApi.to.starChartRegisterNode( + product: _productName, + model: '${deviceInfo['brand']}_${deviceInfo['model']}', + name: '${deviceInfo['id']}', + unique: deviceInfo['id'] ?? Uuid().v1(), + ); + return response; + } + + // 保存星图注册节点信息至缓存 + Future _saveStarChartRegisterNodeToStorage( + StarChartRegisterNodeEntity starChartRegisterNodeEntity) async { + if (starChartRegisterNodeEntity != null) { + await Storage.saveStarChartRegisterNodeInfo(starChartRegisterNodeEntity); + _log(text: '注册成功'); + } + } + + // 保存星图中继服务器信息至缓存 + Future _saveRelayInfoEntityToStorage( + RelayInfoEntity relayInfoEntity) async { + if (relayInfoEntity != null) { + await Storage.saveRelayInfo(relayInfoEntity); + } + } + + // 构造上报信息数据参数 + Future _makeReportInformationData() async { + // 获取当前时间戳 + int currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; + // 获取公钥 + final publicKey = await getPublicKey(); + // 获取私钥 + final privateKey = await getPrivateKey(); + // 生成签名 + final sign = await _generateSign( + currentTimestamp: currentTimestamp, + privateKey: privateKey, + ); + + // 获取本机所有ip地址和中继返回的外网地址 + final List listenAddrDataList = + await _makeListenAddrDataList(); + + // 从缓存中获取中继信息 + final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); + final RelayServiceData relayServiceData = RelayServiceData( + name: relayInfoEntity?.relay_list?[0].name ?? '', + listen_addr: relayInfoEntity?.relay_list?[0].listenAddr ?? '', + peers_max: relayInfoEntity?.relay_list?[0].peerMax ?? 0, + peers_current: relayInfoEntity?.relay_list?[0].peerCurrent ?? 0, + ); + + ReportInformationData data = ReportInformationData( + id: FromPeerId, + public_key: publicKey, + listen_addr: listenAddrDataList, + relay_service: relayServiceData, + time: currentTimestamp, + sign: sign, + ); + + return data; + } + + // 获取本机所有ip地址和中继返回的外网地址 + Future> _makeListenAddrDataList() async { + final List listenAddrDataList = []; + final List localIp = await _getAllIpAddresses(); + // 从缓存中获取中继信息 + final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); + if (relayInfoEntity != null && relayInfoEntity.client_addr != null) { + listenAddrDataList.add( + ListenAddrData( + type: ListenAddrTypeConstant.relay, + address: IpConstant.udpUrl + + relayInfoEntity.client_addr! + + ':' + + localPort.toString(), + ), + ); + } + localIp.forEach((element) { + listenAddrDataList.add( + ListenAddrData( + type: ListenAddrTypeConstant.local, + address: IpConstant.udpUrl + element + ':' + localPort.toString(), + ), + ); + }); + return listenAddrDataList ?? []; + } + + /// 获取本机所有ip + Future> _getAllIpAddresses() async { + final List ipAddresses = []; + try { + final List interfaces = await NetworkInterface.list( + includeLoopback: true, + type: InternetAddressType.any, + ); + + for (final interface in interfaces) { + for (final address in interface.addresses) { + if (address.address.isNotEmpty && + !IpConstant.reportExcludeIp.contains(address.address)) { + ipAddresses.add(address.address); + } + } + } + } catch (e) { + _log(text: '❌--->获取本机IP时出现错误: $e'); + } + return ipAddresses ?? []; + } + + void _log({required String text}) { + AppLog.log('$_productName=====${text}'); + } + + /// 获取设备信息 + Future> _getDeviceInfo() async { + final Map deviceInfo = + await DeviceInfoUtils.getDeviceInfo(); + return deviceInfo; + } + + /// 解析 UDP URL 并提取 IP 地址和端口号 + Map _parseUdpUrl(String url) { + // 使用正则表达式匹配 IP 地址和端口号 + final regex = RegExp(r'udp://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)') + .firstMatch(url); + + if (regex != null) { + final ip = regex.group(1); + final portStr = regex.group(2); + final port = int.tryParse(portStr ?? ''); + + if (ip != null && port != null) { + return {'host': ip, 'port': port}; + } + } + + throw FormatException('无法解析 URL 格式: $url'); + } + + String bytesToHex(List bytes) { + return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); + } + + // 生成签名sing + Future _generateSign({ + required int currentTimestamp, + required String privateKey, + }) async { + String resultSign = ''; + try { + // 2. 将时间戳编码为小端字节序(Little Endian) + Uint8List signData = Uint8List(4); + signData.buffer + .asByteData() + .setUint32(0, currentTimestamp, Endian.little); + + // 3. 使用 SHA-256 对 signData 进行哈希运算 + final sha256Hash = sha256.convert(signData); + var parser = RSAKeyParser(); + final RSAPrivateKey parsePrivateKey = + parser.parse('-----BEGIN RSA PRIVATE KEY-----\n' + privateKey) + as RSAPrivateKey; + } catch (e) { + _log(text: '❌--->生成签名时出现错误: $e'); + } + return resultSign ?? ''; + } + + Future getPublicKey() async { + // 从缓存中获取星图注册节点信息 + final StarChartRegisterNodeEntity? starChartRegisterNodeInfo = + await Storage.getStarChartRegisterNodeInfo(); + return starChartRegisterNodeInfo?.peer?.publicKey ?? ''; + } + + Future getPrivateKey() async { + // 从缓存中获取星图注册节点信息 + final StarChartRegisterNodeEntity? starChartRegisterNodeInfo = + await Storage.getStarChartRegisterNodeInfo(); + return starChartRegisterNodeInfo?.peer?.privateKey ?? ''; + } +} diff --git a/lib/talk/udp/udp_help.dart b/lib/talk/udp/udp_help.dart index 8bde68dc..dbb4f8c1 100755 --- a/lib/talk/udp/udp_help.dart +++ b/lib/talk/udp/udp_help.dart @@ -52,7 +52,7 @@ class UdpHelp { ipList: serversList, tokenStr: 'b989fa15f75c2ac02718b7c9bb64f80e', ); - AppLog.log('发送心跳了'); + // AppLog.log('发送心跳了'); } else { timer.cancel(); } diff --git a/lib/talk/udp/udp_manage.dart b/lib/talk/udp/udp_manage.dart index c8857bcf..62c4f5d4 100755 --- a/lib/talk/udp/udp_manage.dart +++ b/lib/talk/udp/udp_manage.dart @@ -34,9 +34,6 @@ class UDPManage { StreamSubscription? _streamSubscription; RawDatagramSocket? _udpSocket; - // String host = '47.106.143.213'; - // int port = 8056; - // String? _mIp = ''; String host = ''; int port = 0; String lockId = ''; // 锁id 通过锁id来判断是哪把锁发对讲过来 @@ -46,7 +43,7 @@ class UDPManage { var listAddress = InternetAddress.lookup(host); listAddress.then((list) { list.forEach((element) { - // AppLog.log('Udp ----> element.address:${element.address} element.host:${element.host}'); + AppLog.log('Udp ----> element.address:${element.address} element.host:${element.host}'); host = element.address; }); }); @@ -59,7 +56,7 @@ class UDPManage { // AppLog.log('❌ Udp ----> _port == 0'); return; } - // AppLog.log('Udp ----> host:$host port:$port'); + AppLog.log('Udp ----> host:$host port:$port'); var addressIListenFrom = InternetAddress.anyIPv4; int portIListenOn = 62288; // if(addressIListenFrom.address != '0.0.0.0'){ diff --git a/lib/talk/udp/udp_reciverData.dart b/lib/talk/udp/udp_reciverData.dart index 6dc9dd25..5b99d14a 100755 --- a/lib/talk/udp/udp_reciverData.dart +++ b/lib/talk/udp/udp_reciverData.dart @@ -20,7 +20,7 @@ class CommandUDPReciverManager { if (dataSize < 4) { return; } - AppLog.log('appReceiveUDPData:$data'); + // AppLog.log('appReceiveUDPData:$data'); final Uint8List data1 = Uint8List.fromList(data); if (data1.length == 1) { diff --git a/lib/tools/deviceInfo_utils.dart b/lib/tools/deviceInfo_utils.dart new file mode 100644 index 00000000..c8ff3c02 --- /dev/null +++ b/lib/tools/deviceInfo_utils.dart @@ -0,0 +1,39 @@ +import 'package:device_info_plus/device_info_plus.dart'; + +import 'package:get/get.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class DeviceInfoUtils { + static Future> getDeviceInfo() async { + Map deviceInfo = {}; + + try { + // 获取设备信息 + DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + + if (GetPlatform.isAndroid) { + AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; + deviceInfo['model'] = androidInfo.model; + deviceInfo['deviceName'] = androidInfo.device; + deviceInfo['brand'] = androidInfo.brand; + deviceInfo['id'] = androidInfo.id; + // deviceInfo['uniqueIdentifier'] = androidInfo.androidId ?? 'N/A'; // 使用 androidId 作为替代 + } else if (GetPlatform.isIOS) { + IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo; + // deviceInfo['model'] = iosInfo.model; + // deviceInfo['deviceName'] = iosInfo.name; + deviceInfo['uniqueIdentifier'] = + iosInfo.identifierForVendor ?? 'N/A'; // 使用 identifierForVendor 作为替代 + } + + // 获取 APP 版本 + deviceInfo['appVersion'] = packageInfo.version; + deviceInfo['appName'] = packageInfo.appName; + } catch (e) { + print("Failed to get device info: $e"); + } + + return deviceInfo; + } +} diff --git a/lib/tools/storage.dart b/lib/tools/storage.dart index 842d18e5..9b9aff0c 100755 --- a/lib/tools/storage.dart +++ b/lib/tools/storage.dart @@ -4,6 +4,8 @@ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:star_lock/talk/startChart/entity/relay_info_entity.dart'; +import 'package:star_lock/talk/startChart/entity/star_chart_register_node_entity.dart'; import '../login/login/entity/LoginData.dart'; import '../main/lockMian/entity/lockListInfo_entity.dart'; @@ -29,6 +31,8 @@ const String saveLockMainListData = 'lockMainListData'; const String isOpenDeBug = 'isOpenDeBug'; //是否打开 debug const String automaticLockOffTime = 'automaticLockOffTime'; //自动关锁时间 const String associationUrl = 'associationUrl'; //ios跳转微信的 url +const String starChartRegisterNodeInfo = 'starChartRegisterNodeInfo'; //星图注册信息 +const String relayInfo = 'relayInfo'; //星图中继服务器信息 class Storage { factory Storage() => _instance; @@ -234,4 +238,38 @@ class Storage { final String data = await Storage.getString(associationUrl) ?? '0'; return data; } + + // 获取星图注册节点信息 + static Future + getStarChartRegisterNodeInfo() async { + StarChartRegisterNodeEntity? starChartRegisterNodeEntity; + final String? data = await Storage.getString(starChartRegisterNodeInfo); + if (data != null && data.isNotEmpty) { + starChartRegisterNodeEntity = + StarChartRegisterNodeEntity.fromJson(jsonDecode(data)); + } + return starChartRegisterNodeEntity; + } + + // 保存星图注册节点信息 + static Future saveStarChartRegisterNodeInfo( + StarChartRegisterNodeEntity starChartRegisterNodeEntity) async { + await Storage.setString( + starChartRegisterNodeInfo, jsonEncode(starChartRegisterNodeEntity)); + } + + // 保存中继服务信息 + static Future saveRelayInfo(RelayInfoEntity relayInfoEntity) async { + await Storage.setString(relayInfo, jsonEncode(relayInfoEntity)); + } + + // 获取星图注册节点信息 + static Future getRelayInfo() async { + RelayInfoEntity? relayInfoEntity; + final String? data = await Storage.getString(relayInfo); + if (data != null && data.isNotEmpty) { + relayInfoEntity = RelayInfoEntity.fromJson(jsonDecode(data)); + } + return relayInfoEntity; + } } diff --git a/pubspec.yaml b/pubspec.yaml index f27a3c15..fcb79126 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -184,6 +184,7 @@ dependencies: #加密解密 encrypt: ^5.0.1 crypto: ^3.0.3 + pointycastle: ^3.3.0 date_format: ^2.0.7 # 下拉刷新 @@ -233,7 +234,7 @@ dependencies: timelines: ^0.1.0 #侧滑删除 flutter_slidable: ^3.0.1 -# audio_service: ^0.18.12 + # audio_service: ^0.18.12 app_settings: ^5.1.1 flutter_local_notifications: ^17.0.0 fluwx: ^4.5.5 @@ -242,10 +243,13 @@ dependencies: colorfilter_generator: ^0.0.8 file_picker: ^5.3.1 # 错误日志监控 -# flutter_bugly_plugin: ^0.0.9 + # flutter_bugly_plugin: ^0.0.9 flutter_bugly: ^1.0.2 open_filex: ^4.4.0 + crc32_checksum: ^0.0.2 + fast_rsa: ^3.6.6 + dependency_overrides: #强制设置google_maps_flutter_ios 为 2.5.2 @@ -255,7 +259,6 @@ dependency_overrides: dev_dependencies: flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint fset provided by the package is # activated in the `analysis_options.yaml` file located at the root of your