feat:增加新的中继协议逻辑

This commit is contained in:
liyi 2024-11-28 14:57:49 +08:00
parent 3d341e8d13
commit fec9933c0a
23 changed files with 1116 additions and 26 deletions

View File

@ -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",

View File

@ -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)),
),

View File

@ -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) {
//

View File

@ -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

View File

@ -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'; // --
}

View File

@ -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;

View 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,
);
}
}

View 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;
}
}

View 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://';
}

View File

@ -0,0 +1,5 @@
class ListenAddrTypeConstant {
static const String local = 'local';
static const String externally = 'externally';
static const String relay = 'relay';
}

View 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;
}

View File

@ -0,0 +1,3 @@
class ProtocolFlagConstant {
static const String scp01 = 'SC01';
}

View 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}';
}
}

View 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}';
}
}

View 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('');
}
}

View File

@ -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}';
}
}

View 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 ?? '';
}
}

View File

@ -52,7 +52,7 @@ class UdpHelp {
ipList: serversList,
tokenStr: 'b989fa15f75c2ac02718b7c9bb64f80e',
);
AppLog.log('发送心跳了');
// AppLog.log('发送心跳了');
} else {
timer.cancel();
}

View File

@ -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'){

View File

@ -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) {

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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