feat:增加新的中继协议逻辑
This commit is contained in:
parent
3d341e8d13
commit
fec9933c0a
@ -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",
|
||||
|
||||
@ -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<StarLockLoginPage> {
|
||||
haveBack: false,
|
||||
backgroundColor: AppColors.mainColor,
|
||||
actionsList: <Widget>[
|
||||
IconButton(
|
||||
onPressed: (){
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
WechatManageTool.getAppInfo(CustomerTool.openCustomerService);
|
||||
},
|
||||
icon: const Icon(
|
||||
@ -84,8 +84,7 @@ class _StarLockLoginPageState extends State<StarLockLoginPage> {
|
||||
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<StarLockLoginPage> {
|
||||
height: 36.w,
|
||||
),
|
||||
),
|
||||
hintText:'请输入手机号或者邮箱'.tr,
|
||||
hintText: '请输入手机号或者邮箱'.tr,
|
||||
// keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
// FilteringTextInputFormatter.allow(RegExp('[0-9]')),
|
||||
@ -185,8 +184,7 @@ class _StarLockLoginPageState extends State<StarLockLoginPage> {
|
||||
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<StarLockLoginPage> {
|
||||
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<StarLockLoginPage> {
|
||||
}
|
||||
}
|
||||
: 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<StarLockLoginPage> {
|
||||
height: 50.h,
|
||||
// color: Colors.red,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${'忘记密码'.tr}?',
|
||||
child: Text('${'忘记密码'.tr}?',
|
||||
style: TextStyle(
|
||||
fontSize: 22.sp, color: AppColors.mainColor)),
|
||||
),
|
||||
|
||||
@ -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<void> _initTranslation() async => TranslationLoader.loadTranslation();
|
||||
Future<void> _setCommonServices() async {
|
||||
await Get.putAsync(() => StoreService().init());
|
||||
Get.put(ApiProvider());
|
||||
Get.put(StartChartApi());
|
||||
Get.put(ApiRepository(Get.find<ApiProvider>()));
|
||||
if (F.isLite) {
|
||||
//上架审核注释 获取设备信息
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'; // 星图--信息上报
|
||||
}
|
||||
|
||||
@ -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<Response<T>> get<T>(
|
||||
String url, {
|
||||
String? contentType,
|
||||
Map<String, String>? headers,
|
||||
Map<String, dynamic>? query,
|
||||
Decoder<T>? 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;
|
||||
|
||||
58
lib/network/start_chart_api.dart
Normal file
58
lib/network/start_chart_api.dart
Normal file
@ -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<StartChartApi>();
|
||||
|
||||
// 星图--注册节点
|
||||
Future<StarChartRegisterNodeEntity> starChartRegisterNode({
|
||||
required String product,
|
||||
required String model,
|
||||
required String name,
|
||||
required String unique,
|
||||
}) async {
|
||||
final response = await post(
|
||||
_startChartHost + starChartRegisterNodeURL.toUrl,
|
||||
jsonEncode(<String, dynamic>{
|
||||
'product': product,
|
||||
'model': model,
|
||||
'name': name,
|
||||
'unique': unique,
|
||||
}),
|
||||
isUnShowLoading: true,
|
||||
isUserBaseUrl: false,
|
||||
);
|
||||
return StarChartRegisterNodeEntity.fromJson(response.body);
|
||||
}
|
||||
|
||||
// 星图--中继查询
|
||||
Future<RelayInfoEntity> relayQueryInfo() async {
|
||||
final response = await get(
|
||||
_startChartHost + relayQueryInfoURL.toUrl,
|
||||
isUnShowLoading: true,
|
||||
isUserBaseUrl: false,
|
||||
);
|
||||
return RelayInfoEntity.fromJson(response.body);
|
||||
}
|
||||
|
||||
// 星图--上报信息至发现服务
|
||||
Future<void> reportInformation({
|
||||
required ReportInformationData reportInformationData,
|
||||
}) async {
|
||||
final response = await post(
|
||||
_startChartHost + reportInformationDataURL.toUrl,
|
||||
jsonEncode(reportInformationData.toJson()),
|
||||
isUnShowLoading: true,
|
||||
isUserBaseUrl: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
69
lib/talk/startChart/command/message_command.dart
Normal file
69
lib/talk/startChart/command/message_command.dart
Normal file
@ -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<int> goOnlineRelay() {
|
||||
String serializedBytesString = ScpMessage(
|
||||
ProtocolFlag: ProtocolFlagConstant.scp01,
|
||||
MessageType: PayloadTypeConstant.goOnline,
|
||||
MessageId: 1,
|
||||
SpTotal: 0,
|
||||
SpIndex: 0,
|
||||
).serialize();
|
||||
return _hexToBytes(serializedBytesString);
|
||||
}
|
||||
|
||||
// 回声测试消息
|
||||
static List<int> 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<int> 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<int> _hexToBytes(String hex) {
|
||||
final bytes = <int>[];
|
||||
for (int i = 0; i < hex.length; i += 2) {
|
||||
bytes.add(int.parse(hex.substring(i, i + 2), radix: 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
7
lib/talk/startChart/constant/ip_constant.dart
Normal file
7
lib/talk/startChart/constant/ip_constant.dart
Normal file
@ -0,0 +1,7 @@
|
||||
class IpConstant {
|
||||
// 上报时需要排除的ip
|
||||
static const List<String> reportExcludeIp = ['127.0.0.1','::1%1'];
|
||||
static const String udpUrl = 'udp://';
|
||||
static const String tcpUrl = 'tcp://';
|
||||
static const String httpsUrl = 'https://';
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
class ListenAddrTypeConstant {
|
||||
static const String local = 'local';
|
||||
static const String externally = 'externally';
|
||||
static const String relay = 'relay';
|
||||
}
|
||||
10
lib/talk/startChart/constant/payload_type_constant.dart
Normal file
10
lib/talk/startChart/constant/payload_type_constant.dart
Normal file
@ -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;
|
||||
}
|
||||
3
lib/talk/startChart/constant/protocol_flag_constant.dart
Normal file
3
lib/talk/startChart/constant/protocol_flag_constant.dart
Normal file
@ -0,0 +1,3 @@
|
||||
class ProtocolFlagConstant {
|
||||
static const String scp01 = 'SC01';
|
||||
}
|
||||
90
lib/talk/startChart/entity/relay_info_entity.dart
Normal file
90
lib/talk/startChart/entity/relay_info_entity.dart
Normal file
@ -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<RelayData>.from(
|
||||
json['relay_list'].map((x) => RelayData.fromJson(x)))
|
||||
: null;
|
||||
}
|
||||
|
||||
String? msg;
|
||||
int? time;
|
||||
String? stun_server;
|
||||
String? client_addr;
|
||||
List<RelayData>? relay_list;
|
||||
|
||||
Map<String, dynamic> 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<String, dynamic> 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}';
|
||||
}
|
||||
}
|
||||
107
lib/talk/startChart/entity/report_information_data.dart
Normal file
107
lib/talk/startChart/entity/report_information_data.dart
Normal file
@ -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<ListenAddrData>.from(
|
||||
json['listen_addr'].map((x) => ListenAddrData.fromJson(x)))
|
||||
: null;
|
||||
relay_service = json['relay_service'];
|
||||
}
|
||||
|
||||
String? id;
|
||||
String? public_key;
|
||||
String? sign;
|
||||
List<ListenAddrData>? listen_addr;
|
||||
RelayServiceData? relay_service;
|
||||
int? time;
|
||||
|
||||
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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}';
|
||||
}
|
||||
}
|
||||
143
lib/talk/startChart/entity/scp_message.dart
Normal file
143
lib/talk/startChart/entity/scp_message.dart
Normal file
@ -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<String, dynamic> 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 = <int>[];
|
||||
|
||||
// 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<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
|
||||
}
|
||||
}
|
||||
@ -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<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
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<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['id'] = id;
|
||||
map['publicKey'] = publicKey;
|
||||
map['privateKey'] = privateKey;
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PeerData{id: $id, publicKey: $publicKey, privateKey: $privateKey}';
|
||||
}
|
||||
}
|
||||
379
lib/talk/startChart/start_chart_manage.dart
Normal file
379
lib/talk/startChart/start_chart_manage.dart
Normal file
@ -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<void> 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<void> 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<void> 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<void> 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<int> message = MessageCommand.heartbeatMessage();
|
||||
_sendMessage(message: message);
|
||||
},
|
||||
);
|
||||
_heartBeatTimerRunning = true;
|
||||
}
|
||||
|
||||
// 停止定时发送心跳包
|
||||
void stopHeartbeat() {
|
||||
_heartBeatTimer?.cancel();
|
||||
_heartBeatTimer = null; // 清除定时器引用
|
||||
_heartBeatTimerRunning = false;
|
||||
_log(text: '发送心跳包结束');
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
void _sendMessage({required List<int> message}) {
|
||||
_log(text: '发送给中继的消息体:${message},序列化之后的数据:【${bytesToHex(message)}】');
|
||||
_udpSocket!.send(message, InternetAddress(remoteHost), remotePort);
|
||||
}
|
||||
|
||||
// 请求注册节点
|
||||
Future<StarChartRegisterNodeEntity> _requestStarChartRegisterNode() async {
|
||||
// 获取设备信息
|
||||
final Map<String, String> 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<void> _saveStarChartRegisterNodeToStorage(
|
||||
StarChartRegisterNodeEntity starChartRegisterNodeEntity) async {
|
||||
if (starChartRegisterNodeEntity != null) {
|
||||
await Storage.saveStarChartRegisterNodeInfo(starChartRegisterNodeEntity);
|
||||
_log(text: '注册成功');
|
||||
}
|
||||
}
|
||||
|
||||
// 保存星图中继服务器信息至缓存
|
||||
Future<void> _saveRelayInfoEntityToStorage(
|
||||
RelayInfoEntity relayInfoEntity) async {
|
||||
if (relayInfoEntity != null) {
|
||||
await Storage.saveRelayInfo(relayInfoEntity);
|
||||
}
|
||||
}
|
||||
|
||||
// 构造上报信息数据参数
|
||||
Future<ReportInformationData> _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<ListenAddrData> 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<List<ListenAddrData>> _makeListenAddrDataList() async {
|
||||
final List<ListenAddrData> listenAddrDataList = [];
|
||||
final List<String> 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<List<String>> _getAllIpAddresses() async {
|
||||
final List<String> ipAddresses = [];
|
||||
try {
|
||||
final List<NetworkInterface> 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<Map<String, String>> _getDeviceInfo() async {
|
||||
final Map<String, String> deviceInfo =
|
||||
await DeviceInfoUtils.getDeviceInfo();
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
/// 解析 UDP URL 并提取 IP 地址和端口号
|
||||
Map<String, dynamic> _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<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
|
||||
}
|
||||
|
||||
// 生成签名sing
|
||||
Future<String> _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<String> getPublicKey() async {
|
||||
// 从缓存中获取星图注册节点信息
|
||||
final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
||||
await Storage.getStarChartRegisterNodeInfo();
|
||||
return starChartRegisterNodeInfo?.peer?.publicKey ?? '';
|
||||
}
|
||||
|
||||
Future<String> getPrivateKey() async {
|
||||
// 从缓存中获取星图注册节点信息
|
||||
final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
||||
await Storage.getStarChartRegisterNodeInfo();
|
||||
return starChartRegisterNodeInfo?.peer?.privateKey ?? '';
|
||||
}
|
||||
}
|
||||
@ -52,7 +52,7 @@ class UdpHelp {
|
||||
ipList: serversList,
|
||||
tokenStr: 'b989fa15f75c2ac02718b7c9bb64f80e',
|
||||
);
|
||||
AppLog.log('发送心跳了');
|
||||
// AppLog.log('发送心跳了');
|
||||
} else {
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
@ -34,9 +34,6 @@ class UDPManage {
|
||||
|
||||
StreamSubscription<EventSendModel>? _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'){
|
||||
|
||||
@ -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) {
|
||||
|
||||
39
lib/tools/deviceInfo_utils.dart
Normal file
39
lib/tools/deviceInfo_utils.dart
Normal file
@ -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<Map<String, String>> getDeviceInfo() async {
|
||||
Map<String, String> 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;
|
||||
}
|
||||
}
|
||||
@ -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<StarChartRegisterNodeEntity?>
|
||||
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<void> saveStarChartRegisterNodeInfo(
|
||||
StarChartRegisterNodeEntity starChartRegisterNodeEntity) async {
|
||||
await Storage.setString(
|
||||
starChartRegisterNodeInfo, jsonEncode(starChartRegisterNodeEntity));
|
||||
}
|
||||
|
||||
// 保存中继服务信息
|
||||
static Future<void> saveRelayInfo(RelayInfoEntity relayInfoEntity) async {
|
||||
await Storage.setString(relayInfo, jsonEncode(relayInfoEntity));
|
||||
}
|
||||
|
||||
// 获取星图注册节点信息
|
||||
static Future<RelayInfoEntity?> getRelayInfo() async {
|
||||
RelayInfoEntity? relayInfoEntity;
|
||||
final String? data = await Storage.getString(relayInfo);
|
||||
if (data != null && data.isNotEmpty) {
|
||||
relayInfoEntity = RelayInfoEntity.fromJson(jsonDecode(data));
|
||||
}
|
||||
return relayInfoEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user