From c83d98dc28b97052f1e170590df68bcf60ab8830 Mon Sep 17 00:00:00 2001 From: zhangmeng <494089941@qq.com> Date: Fri, 18 Jun 2021 16:11:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0websocket=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 1 + lib/main_initialize.dart | 14 ++ .../opening_code_page/opening_code_page.dart | 14 +- lib/pages/tab_navigator.dart | 2 + lib/provider/user_provider.dart | 4 + lib/utils/message_parser.dart | 34 +-- lib/utils/websocket/fire_dialog.dart | 33 +++ lib/utils/websocket/web_socket_util.dart | 208 ++++++++++++++++++ pubspec.yaml | 2 + 9 files changed, 273 insertions(+), 39 deletions(-) create mode 100644 lib/utils/websocket/fire_dialog.dart create mode 100644 lib/utils/websocket/web_socket_util.dart diff --git a/lib/main.dart b/lib/main.dart index c2f4b4ba..afc597ae 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,6 +26,7 @@ void main() async { MainInitialize.initTheme(); await MainInitialize.initJPush(); MainInitialize.initWechat(); + MainInitialize.initWebSocket(); runApp(MyApp()); } diff --git a/lib/main_initialize.dart b/lib/main_initialize.dart index f7fc57bf..8ae07872 100644 --- a/lib/main_initialize.dart +++ b/lib/main_initialize.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:aku_community/utils/websocket/fire_dialog.dart'; +import 'package:aku_community/utils/websocket/web_socket_util.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -76,4 +78,16 @@ class MainInitialize { if (kIsWeb || Platform.isMacOS) return; registerWxApi(appId: AppConfig.wechatAppId); } + + static initWebSocket() { + WebSocketUtil().initWebSocket( + consolePrint: false, + onReceiveMes: (message) async { + await FireDialog.fireAlarm(message); + }, + onError: (e) { + LoggerData.addData(e); + }, + ); + } } diff --git a/lib/pages/opening_code_page/opening_code_page.dart b/lib/pages/opening_code_page/opening_code_page.dart index de01309a..f42f3f5b 100644 --- a/lib/pages/opening_code_page/opening_code_page.dart +++ b/lib/pages/opening_code_page/opening_code_page.dart @@ -27,6 +27,7 @@ class _OpeningCodePageState extends State { late String _qrCode; late bool _overDate; late EasyRefreshController _refreshController; + static const int seconds = 300; //有效时间 Timer? _overDateTimer; @override void initState() { @@ -46,7 +47,8 @@ class _OpeningCodePageState extends State { await NetUtil().get(API.manager.getDoorQrCode, params: { "startTime": DateUtil.formatDate(_currentTime, format: 'yyyy/MM/dd HH:mm:ss'), - "endTime": DateUtil.formatDate(_currentTime.add(Duration(minutes: 30)), + "endTime": DateUtil.formatDate( + _currentTime.add(Duration(seconds: seconds)), format: 'yyyy/MM/dd HH:mm:ss'), }); if ((baseModel.status ?? false) && baseModel.data != null) { @@ -62,12 +64,10 @@ class _OpeningCodePageState extends State { } startTimer() { - _overDateTimer = Timer.periodic(Duration(minutes: 1), (timer) { - if (timer.tick >= 5) { - _overDate = true; - endTimer(); - setState(() {}); - } + _overDateTimer = Timer.periodic(Duration(seconds: seconds), (timer) { + _overDate = true; + endTimer(); + setState(() {}); }); } diff --git a/lib/pages/tab_navigator.dart b/lib/pages/tab_navigator.dart index cbc4c0a4..7f10e169 100644 --- a/lib/pages/tab_navigator.dart +++ b/lib/pages/tab_navigator.dart @@ -1,3 +1,4 @@ +import 'package:aku_community/utils/websocket/web_socket_util.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -106,6 +107,7 @@ class _TabNavigatorState extends State return false; } //否则关闭app + WebSocketUtil().closeWebSocket(); return true; }, child: TabBarView( diff --git a/lib/provider/user_provider.dart b/lib/provider/user_provider.dart index dd41a36d..f492f237 100644 --- a/lib/provider/user_provider.dart +++ b/lib/provider/user_provider.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:aku_community/utils/websocket/web_socket_util.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -32,6 +33,8 @@ class UserProvider extends ChangeNotifier { await updateProfile(); await updateUserDetail(); await appProvider.updateHouses(await HouseFunc.passedHouses); + WebSocketUtil().setUser(userInfoModel!.id.toString()); + WebSocketUtil().startWebSocket(); notifyListeners(); } @@ -47,6 +50,7 @@ class UserProvider extends ChangeNotifier { NetUtil().dio!.options.headers.remove('App-Admin-Token'); HiveStore.appBox!.delete('token'); HiveStore.appBox!.delete('login'); + WebSocketUtil().closeWebSocket(); notifyListeners(); } diff --git a/lib/utils/message_parser.dart b/lib/utils/message_parser.dart index 7ec882c1..9fbf4092 100644 --- a/lib/utils/message_parser.dart +++ b/lib/utils/message_parser.dart @@ -1,11 +1,8 @@ import 'dart:convert'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; +import 'package:aku_community/utils/websocket/fire_dialog.dart'; -import 'package:get/get.dart'; -import 'package:aku_community/utils/headers.dart'; class MessageParser { final Map message; @@ -27,34 +24,7 @@ class MessageParser { switch (type) { case '1': - await fireAlarm(subTitle); + await FireDialog.fireAlarm(subTitle); } } - - ///火警 - fireAlarm(String content) async { - await Get.dialog( - CupertinoAlertDialog( - title: Text('发生火灾'), - content: Column( - children: [ - Text(subTitle), - 10.hb, - Icon( - CupertinoIcons.bell_fill, - color: Colors.red, - size: 48.w, - ), - ], - ), - actions: [ - CupertinoDialogAction( - child: Text('确认'), - onPressed: () => Get.back(), - ), - ], - ), - barrierDismissible: false, - ); - } } diff --git a/lib/utils/websocket/fire_dialog.dart b/lib/utils/websocket/fire_dialog.dart new file mode 100644 index 00000000..6dd75d56 --- /dev/null +++ b/lib/utils/websocket/fire_dialog.dart @@ -0,0 +1,33 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aku_community/extensions/num_ext.dart'; + +class FireDialog { + static fireAlarm(String content) async { + await Get.dialog( + CupertinoAlertDialog( + title: Text('发生火灾'), + content: Column( + children: [ + Text(content), + 10.hb, + Icon( + CupertinoIcons.bell_fill, + color: Colors.red, + size: 48.w, + ), + ], + ), + actions: [ + CupertinoDialogAction( + child: Text('确认'), + onPressed: () => Get.back(), + ), + ], + ), + barrierDismissible: false, + ); + } +} \ No newline at end of file diff --git a/lib/utils/websocket/web_socket_util.dart b/lib/utils/websocket/web_socket_util.dart new file mode 100644 index 00000000..b3ffcf25 --- /dev/null +++ b/lib/utils/websocket/web_socket_util.dart @@ -0,0 +1,208 @@ +import 'dart:async'; +import 'package:bot_toast/bot_toast.dart'; +import 'package:power_logger/power_logger.dart'; +import 'package:web_socket_channel/io.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +const String baseUri = 'wss://test.kaidalai.cn/websocket/app'; +enum SOCKETSTATUS { + CONNECTED, //已连接 + BREAKOFF, //已断开 + CLOSED, //已关闭 +} + +class WebSocketUtil { + static final WebSocketUtil _socket = WebSocketUtil._(); + +//内部构造函数 + WebSocketUtil._(); +//单例模式 + factory WebSocketUtil() => _socket; + + IOWebSocketChannel? _webSocket; + + ///用户设置不同的服务器地址 + String _user = 'admin'; + + ///连接状态 + SOCKETSTATUS _socketStatus = SOCKETSTATUS.CLOSED; + + ///心跳计时器; + Timer? _heartTimer; + + ///心跳间隔 + Duration _heartDuration = Duration(seconds: 30); + + ///重连计数器 + int _reconnectCount = 0; + + ///重连最大次数 + int _reconnectTimes = 30; + + ///重连计时器 + Timer? _reconnectTimer; + + ///连接错误回调 + Function(dynamic e)? onError; + + ///开启回调 + Function? onStart; + + ///接收消息回调 + Function(String message)? onReceiveMes; + + ///关闭连接回调; + Function? onClosed; + + ///控制台输出 + bool _consolePrint = true; + + ///注册websocket + void initWebSocket( + {Duration? heartDuration, + bool? consolePrint, + Function? onStart, + Function(String message)? onReceiveMes, + Function? onClosed, + Function(dynamic e)? onError}) { + this.onStart = onStart; + this.onReceiveMes = onReceiveMes; + this.onClosed = onClosed; + this.onError = onError; + if (consolePrint != null) { + this._consolePrint = consolePrint; + } + if (heartDuration != null) { + this._heartDuration = heartDuration; + } + print('——————————webSocket init ——————————'); + } + + ///设置用户 + void setUser(String user) { + this._user = user; + } + + ///开启websocket + void startWebSocket() { + closeWebSocket(); + try { + _webSocket = IOWebSocketChannel.connect(Uri.parse('$baseUri/$_user')); + print('webSocket已连接服务器:$baseUri/$_user'); + _socketStatus = SOCKETSTATUS.CONNECTED; + endReconnect(); + onStart?.call(); + _webSocket!.stream.listen( + (event) => webSocketReceiveMessage(event as String), + onError: webSocketOnError, + onDone: webSocketClosed); + initHeartBeat(); + } catch (e) { + BotToast.showText(text: 'webSocket连接失败'); + onError?.call(e); + LoggerData.addData(e); + } + } + + //接收消息回调 + webSocketReceiveMessage(message) { + if (message == '心跳正常') { + _dPrint('心跳正常————————${DateTime.now()}'); + } else { + onReceiveMes?.call(message); + } + } + + //关闭连接回调 + webSocketClosed() { + closeWebSocket(); + onClosed?.call(); + } + + //连接出错回调 + webSocketOnError(e) { + WebSocketChannelException ex = e; + _socketStatus = SOCKETSTATUS.BREAKOFF; + onError?.call(ex.message); + print('——————连接断开,开始重连'); + startReconnect(); + } + + //启动重连计时 + void startReconnect() { + endReconnect(); + _reconnectTimer = Timer.periodic(Duration(milliseconds: 5000), (timer) { + _reconnectCount++; + print('——————第${_reconnectCount}次重连'); + startWebSocket(); + if (_reconnectCount >= _reconnectTimes) { + print('——————重连失败'); + closeWebSocket(); + } + }); + } + + //重置重连计时 + void endReconnect() { + _reconnectTimer?.cancel(); + _reconnectTimer = null; + _reconnectCount = 0; + } + + ///初始化心跳 + void initHeartBeat() { + destoryHeart(); + _heartTimer = Timer.periodic(_heartDuration, (timer) { + sentHeart(); + }); + } + + //发送心跳 + void sentHeart() { + sendMessage('heartbeat'); + } + + ///销毁心跳 + void destoryHeart() { + _heartTimer?.cancel(); + _heartTimer = null; + } + + ///关闭websocket + void closeWebSocket() { + if (_webSocket != null) { + _webSocket!.sink.close(); + print('——————websocket连接已关闭'); + } + endReconnect(); + destoryHeart(); + _socketStatus = SOCKETSTATUS.CLOSED; + } + + ///向websocket服务器发送消息 + void sendMessage(message) { + if (_webSocket != null) { + switch (_socketStatus) { + case SOCKETSTATUS.CONNECTED: + _dPrint('发送中:' + message); + _webSocket!.sink.add(message); + break; + case SOCKETSTATUS.CLOSED: + print('连接已关闭'); + break; + case SOCKETSTATUS.BREAKOFF: + print('发送失败'); + break; + default: + break; + } + } + } + + //封装print + void _dPrint(dynamic data) { + if (this._consolePrint) { + print(data); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 22436fb5..56dd31cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,8 @@ dependencies: #用户存储路径 path_provider: ^2.0.1 + #webscoket + web_socket_channel: ^2.1.0 #支付宝支付功能 tobias: ^2.1.0