提交 bd1bb7a4 authored 作者: 史晓晨's avatar 史晓晨

feat:socket日志保存文件

上级 d665d252
...@@ -45,7 +45,7 @@ android { ...@@ -45,7 +45,7 @@ android {
applicationId "com.clx.clx_flutter_message_example" applicationId "com.clx.clx_flutter_message_example"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
......
import 'package:clx_flutter_message/clx_flutter_message.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async'; import 'package:get/get.dart';
import 'package:flutter/services.dart'; import 'message/message_config_impl.dart';
import 'package:clx_flutter_message/clx_flutter_message.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
...@@ -16,46 +17,41 @@ class MyApp extends StatefulWidget { ...@@ -16,46 +17,41 @@ class MyApp extends StatefulWidget {
} }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
final _clxFlutterMessagePlugin = ClxFlutterMessage();
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
platformVersion =
await _clxFlutterMessagePlugin.getPlatformVersion() ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return GetMaterialApp(
home: Scaffold( home: Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Plugin example app'), title: const Text('Plugin example app'),
), ),
body: Center( body: SingleChildScrollView(
child: Text('Running on: $_platformVersion\n'), child: Column(
children: [
TextButton(
onPressed: () {
// 初始化消息插件配置
messageConfig
..accessToken = "e2914fd5543142db87185db50f072c2d"
..productCode = "carrier-driver-app"
..userKey = "1021191558918987845"
..webSocketUrl =
"ws://gateway.testclx.cn/common-socket-endpoint/common"
..inAppAccessKey = "CARRIER_INTERNAL_MESSAGE_APP"
..functionKey = "default"
..dio = Dio()
..messageManagement = MessageConfigImpl();
messageConfig.messageManagement?.refreshMessage(context);
},
child: Text("链接"),
),
TextButton(
onPressed: () {
messageConfig.messageManagement?.close();
},
child: Text("断开连接"),
),
],
),
), ),
), ),
); );
......
import 'package:clx_flutter_message/clx_flutter_message.dart';
class MessageConfigImpl extends BaseMessageConfig {
@override
void handleMessage(data) {
print("MessageConfigImpl handleMessage:$data");
}
@override
Future? onJumpToMessagePage(String page, arguments) {
print("MessageConfigImpl onJumpToMessagePage:$page $arguments");
return null;
}
}
...@@ -28,6 +28,7 @@ dependencies: ...@@ -28,6 +28,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
dio: ^5.7.0
dev_dependencies: dev_dependencies:
integration_test: integration_test:
......
import 'package:clx_flutter_message/util/string_util.dart'; import 'package:clx_flutter_message/util/string_util.dart';
import 'package:clx_flutter_message/util/toast_util.dart'; import 'package:clx_flutter_message/util/toast_util.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'clx_flutter_message_platform_interface.dart'; import 'clx_flutter_message_platform_interface.dart';
import 'common/constant.dart'; import 'common/constant.dart';
import 'core/api/message_net.dart'; import 'core/api/message_net.dart';
...@@ -11,6 +12,8 @@ import 'core/notice/notice_manager.dart'; ...@@ -11,6 +12,8 @@ import 'core/notice/notice_manager.dart';
import 'core/notification/notification_layout/notification_layout_widget.dart'; import 'core/notification/notification_layout/notification_layout_widget.dart';
import 'core/notification/notification_manager.dart'; import 'core/notification/notification_manager.dart';
import 'core/socket/socket_io.dart'; import 'core/socket/socket_io.dart';
import 'util/log_utils.dart';
export 'core/model/message_config.dart'; export 'core/model/message_config.dart';
export 'core/model/message_data.dart'; export 'core/model/message_data.dart';
export 'core/notice/notice_dialog_widget.dart'; export 'core/notice/notice_dialog_widget.dart';
...@@ -124,6 +127,7 @@ abstract class BaseMessageConfig ...@@ -124,6 +127,7 @@ abstract class BaseMessageConfig
BaseMessageConfig() { BaseMessageConfig() {
notificationLayoutController = NotificationLayoutController(); notificationLayoutController = NotificationLayoutController();
noticeDialogWidgetController = NoticeDialogWidgetController(); noticeDialogWidgetController = NoticeDialogWidgetController();
LogUtil.instance.init();
} }
// 刷新消息、获取未处理消息,重新连接websocket // 刷新消息、获取未处理消息,重新连接websocket
......
...@@ -2,7 +2,9 @@ import 'dart:async'; ...@@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:clx_flutter_message/util/log_utils.dart';
import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/io.dart';
import 'socket_message.dart'; import 'socket_message.dart';
typedef MessageCallBack = void Function(dynamic message); typedef MessageCallBack = void Function(dynamic message);
...@@ -10,10 +12,13 @@ typedef MessageCallBack = void Function(dynamic message); ...@@ -10,10 +12,13 @@ typedef MessageCallBack = void Function(dynamic message);
class Socket { class Socket {
// io通道 // io通道
IOWebSocketChannel? channel; IOWebSocketChannel? channel;
// 接受的消息体 // 接受的消息体
SocketMessageBody? _lastMessage; SocketMessageBody? _lastMessage;
// 心跳包定时器 // 心跳包定时器
Timer? heartbeatTimer; Timer? heartbeatTimer;
// 心跳包发送间隔 // 心跳包发送间隔
Duration heartbeatInterval = const Duration(seconds: 15); // 心跳包发送间隔 Duration heartbeatInterval = const Duration(seconds: 15); // 心跳包发送间隔
// 私有构造函数 // 私有构造函数
...@@ -62,6 +67,8 @@ class Socket { ...@@ -62,6 +67,8 @@ class Socket {
channel?.stream.listen( channel?.stream.listen(
(message) { (message) {
log("connectId: ${params['connectId']} Received message: $message"); log("connectId: ${params['connectId']} Received message: $message");
LogUtil.instance.log(
"connectId: ${params['connectId']} Received message: $message");
if (message is String) { if (message is String) {
if (message == '') { if (message == '') {
_lastMessage = null; _lastMessage = null;
...@@ -76,21 +83,25 @@ class Socket { ...@@ -76,21 +83,25 @@ class Socket {
}, },
onError: (error) { onError: (error) {
log("WebSocket Error: $error"); log("WebSocket Error: $error");
LogUtil.instance.log("WebSocket Error: $error");
}, },
onDone: () { onDone: () {
log("WebSocket connection closed"); log("WebSocket connection closed");
LogUtil.instance.log("WebSocket connection closed $_lastMessage");
if (_lastMessage?.type == 2) { if (_lastMessage?.type == 2) {
/// 服务器主动断开连接、如果可以重连 /// 服务器主动断开连接、如果可以重连
if (_lastMessage?.content?['canReconnect'] == 1) { if (_lastMessage?.content?['canReconnect'] == 1) {
reconnectWebSocket(); reconnectWebSocket();
LogUtil.instance.log("WebSocket connection reconnectWebSocket");
} else { } else {
_clean(); _clean();
LogUtil.instance.log("WebSocket connection _clean");
} }
return; return;
} else { } else {
if (heartbeatTimer != null) { if (heartbeatTimer != null) {
reconnectWebSocket(); reconnectWebSocket();
LogUtil.instance.log("WebSocket connection reconnectWebSocket");
} }
} }
}, },
......
import 'dart:async';
import 'dart:io';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
/// 日志级别
enum LogLevel { debug, info, warning, error }
class LogUtil {
static LogUtil? _instance;
late String _logDir; // 日志文件存储目录
final int _maxRetentionDays = 7; // 日志保留天数(超过自动删除)
LogUtil._();
static LogUtil get instance {
_instance ??= LogUtil._();
return _instance!;
}
/// 初始化日志工具(必须在APP启动时调用)
Future<void> init() async {
// 获取应用文档目录(iOS/Android 私有目录,无需额外权限)
Directory appDocDir = await getApplicationDocumentsDirectory();
_logDir = "${appDocDir.path}/logs";
// 创建日志目录(若不存在)
await Directory(_logDir).create(recursive: true);
// 清理过期日志
_cleanExpiredLogs();
}
/// 写入日志(公共方法,支持不同级别)
Future<void> log(
String message, {
LogLevel level = LogLevel.debug,
String? tag, // 日志标签(如页面/功能名)
}) async {
if (_logDir.isEmpty) {
throw Exception("LogUtil 未初始化,请先调用 init()");
}
// 1. 格式化日志内容(时间 + 级别 + 标签 + 消息)
String time = DateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(DateTime.now());
String levelStr = _levelToString(level);
String logContent = "[$time] [$levelStr] ${tag != null ? "[$tag] " : ""}$message\n";
// 2. 获取当前日志文件(按天划分,如 2023-10-01.log)
String fileName = "${DateFormat("yyyy-MM-dd").format(DateTime.now())}.log";
File logFile = File("$_logDir/$fileName");
// 3. 追加写入日志(异步操作,避免阻塞UI)
await logFile.writeAsString(logContent, mode: FileMode.append, flush: true);
}
/// 辅助方法:日志级别转字符串
String _levelToString(LogLevel level) {
switch (level) {
case LogLevel.debug:
return "DEBUG";
case LogLevel.info:
return "INFO";
case LogLevel.warning:
return "WARNING";
case LogLevel.error:
return "ERROR";
}
}
/// 清理过期日志(保留最近 N 天)
Future<void> _cleanExpiredLogs() async {
final now = DateTime.now();
final dir = Directory(_logDir);
if (!await dir.exists()) return;
// 遍历目录下所有日志文件
await for (var entity in dir.list()) {
if (entity is File && entity.path.endsWith(".log")) {
// 解析文件名中的日期(如 2023-10-01.log → 2023-10-01)
String fileName = entity.path.split("/").last.replaceAll(".log", "");
try {
DateTime logDate = DateFormat("yyyy-MM-dd").parse(fileName);
// 计算与当前日期的差值
int daysDiff = now.difference(logDate).inDays;
if (daysDiff > _maxRetentionDays) {
await entity.delete(); // 删除过期文件
}
} catch (e) {
// 文件名格式错误,跳过
}
}
}
}
/// 获取所有日志文件路径(用于上传等场景)
/// Android 路径示例:/data/data/包名/app_flutter/logs/2023-10-01.log
/// iOS 路径示例:/Users/用户名/Library/Developer/CoreSimulator/Devices/.../data/Containers/Data/Application/.../Documents/logs/2023-10-01.log
Future<List<String>> getLogFilePaths() async {
final dir = Directory(_logDir);
if (!await dir.exists()) return [];
List<String> paths = [];
await for (var entity in dir.list()) {
if (entity is File && entity.path.endsWith(".log")) {
paths.add(entity.path);
}
}
return paths;
}
}
\ No newline at end of file
...@@ -18,6 +18,9 @@ dependencies: ...@@ -18,6 +18,9 @@ dependencies:
cached_network_image: ^3.3.0 cached_network_image: ^3.3.0
fluttertoast: 8.2.4 fluttertoast: 8.2.4
get: ^4.6.6 get: ^4.6.6
path_provider: ^2.0.15 # 获取文件路径
intl: ^0.18.1 # 时间格式化
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论