顾海波

【需求】添加数据缓存和上报

@@ -309,6 +309,22 @@ packages: @@ -309,6 +309,22 @@ packages:
309 url: "https://pub.flutter-io.cn" 309 url: "https://pub.flutter-io.cn"
310 source: hosted 310 source: hosted
311 version: "7.0.0" 311 version: "7.0.0"
  312 + sqflite:
  313 + dependency: transitive
  314 + description:
  315 + name: sqflite
  316 + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
  317 + url: "https://pub.flutter-io.cn"
  318 + source: hosted
  319 + version: "2.3.3+1"
  320 + sqflite_common:
  321 + dependency: transitive
  322 + description:
  323 + name: sqflite_common
  324 + sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
  325 + url: "https://pub.flutter-io.cn"
  326 + source: hosted
  327 + version: "2.5.4"
312 stack_trace: 328 stack_trace:
313 dependency: transitive 329 dependency: transitive
314 description: 330 description:
@@ -341,6 +357,14 @@ packages: @@ -341,6 +357,14 @@ packages:
341 url: "https://pub.flutter-io.cn" 357 url: "https://pub.flutter-io.cn"
342 source: hosted 358 source: hosted
343 version: "0.3.1" 359 version: "0.3.1"
  360 + synchronized:
  361 + dependency: transitive
  362 + description:
  363 + name: synchronized
  364 + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
  365 + url: "https://pub.flutter-io.cn"
  366 + source: hosted
  367 + version: "3.1.0+1"
344 term_glyph: 368 term_glyph:
345 dependency: transitive 369 dependency: transitive
346 description: 370 description:
@@ -3,27 +3,64 @@ import 'dart:convert'; @@ -3,27 +3,64 @@ import 'dart:convert';
3 import 'dart:io'; 3 import 'dart:io';
4 import 'dart:math'; 4 import 'dart:math';
5 5
  6 +import 'package:archive/archive.dart';
6 import 'package:auto_track/auto_track/config/manager.dart'; 7 import 'package:auto_track/auto_track/config/manager.dart';
7 import 'package:auto_track/auto_track/utils/track_model.dart'; 8 import 'package:auto_track/auto_track/utils/track_model.dart';
8 -import 'package:archive/archive.dart';  
9 import 'package:device_info_plus/device_info_plus.dart'; 9 import 'package:device_info_plus/device_info_plus.dart';
  10 +import 'package:path/path.dart' as path;
  11 +import 'package:sqflite/sqflite.dart';
10 12
11 import '../log/logger.dart'; 13 import '../log/logger.dart';
12 14
13 class AutoTrackQueue { 15 class AutoTrackQueue {
14 static final AutoTrackQueue instance = AutoTrackQueue._(); 16 static final AutoTrackQueue instance = AutoTrackQueue._();
  17 +
  18 + late Database database;
  19 + Future<void> _lastTask = Future.value();
  20 +
  21 + void post(Future<void> Function() task) {
  22 + _lastTask = _lastTask.then((_) => task());
  23 + }
  24 +
15 AutoTrackQueue._() { 25 AutoTrackQueue._() {
16 httpClient.badCertificateCallback = 26 httpClient.badCertificateCallback =
17 (X509Certificate cert, String host, int port) => true; 27 (X509Certificate cert, String host, int port) => true;
  28 +
  29 + //异步任务
  30 + post(() async {
  31 + database = await openTrackDatabase();
  32 + });
  33 + }
  34 +
  35 + Future<Database> openTrackDatabase() async {
  36 + final databasePath = await getDatabasesPath();
  37 + final pathString = path.join(databasePath, 'track.db');
  38 + return openDatabase(
  39 + pathString,
  40 + onCreate: (db, version) {
  41 + return db.execute(
  42 + "CREATE TABLE track("
  43 + "id INTEGER PRIMARY KEY AUTOINCREMENT, "
  44 + "event TEXT, "
  45 + "date DATE"
  46 + ")",
  47 + );
  48 + },
  49 + version: 1,
  50 + );
18 } 51 }
19 52
20 Timer? _timer; 53 Timer? _timer;
21 - final List<TrackModel> _queue = [];  
22 final httpClient = HttpClient(); 54 final httpClient = HttpClient();
23 55
24 void appendQueue(TrackModel model) { 56 void appendQueue(TrackModel model) {
25 - if (_timer == null) return;  
26 - _queue.add(model); 57 + if (database == null) return;
  58 + post(() async {
  59 + await database.insert(
  60 + "track",
  61 + Track(event: jsonEncode(model.toMap()), date: DateTime.now())
  62 + .toMap());
  63 + });
27 } 64 }
28 65
29 void start() { 66 void start() {
@@ -32,7 +69,7 @@ class AutoTrackQueue { @@ -32,7 +69,7 @@ class AutoTrackQueue {
32 Duration( 69 Duration(
33 seconds: AutoTrackConfigManager.instance.config.uploadInterval ?? 70 seconds: AutoTrackConfigManager.instance.config.uploadInterval ??
34 10), (timer) { 71 10), (timer) {
35 - flush(); 72 + post(flush);
36 }); 73 });
37 } 74 }
38 75
@@ -41,15 +78,24 @@ class AutoTrackQueue { @@ -41,15 +78,24 @@ class AutoTrackQueue {
41 _timer = null; 78 _timer = null;
42 } 79 }
43 80
44 - /**  
45 - * todo 存储数据库,目前会丢失数据  
46 - */  
47 - void flush() {  
48 - AutoTrackLogger.getInstance().debug('start flush ${_queue.length}'); 81 + Future<void> flush() async {
  82 + AutoTrackLogger.getInstance().debug("@@@start flush");
  83 +
  84 + if (database == null) {
  85 + AutoTrackLogger.getInstance().debug('数据库未初始化,跳过 flush');
  86 + return;
  87 + }
  88 +
  89 + final List<TrackModel> uploadList = [];
  90 +
  91 + List<Map<String, dynamic>> events = await database.query("track",
  92 + columns: ["id", "event", "date"], limit: 100);
  93 +
  94 + for (var event in events) {
  95 + Track model = Track.fromMap(event);
  96 + uploadList.add(TrackModel.fromMap(jsonDecode(model.event)));
  97 + }
49 98
50 - if (_queue.isEmpty) return;  
51 - final List<TrackModel> uploadList = List.from(_queue);  
52 - _queue.clear();  
53 final config = AutoTrackConfigManager.instance.config; 99 final config = AutoTrackConfigManager.instance.config;
54 final baseDeviceInfo = AutoTrackConfigManager.instance.baseDeviceInfo; 100 final baseDeviceInfo = AutoTrackConfigManager.instance.baseDeviceInfo;
55 final host = config.host; 101 final host = config.host;
@@ -64,14 +110,12 @@ class AutoTrackQueue { @@ -64,14 +110,12 @@ class AutoTrackQueue {
64 return; 110 return;
65 } 111 }
66 if (host != null) { 112 if (host != null) {
67 -  
68 List<Map> datas = []; 113 List<Map> datas = [];
69 uploadList.forEach((event) { 114 uploadList.forEach((event) {
70 Random random = Random.secure(); 115 Random random = Random.secure();
71 int id = random.nextInt(1 << 32); // 模拟 Java 的 nextInt() 116 int id = random.nextInt(1 << 32); // 模拟 Java 的 nextInt()
72 final t = DateTime.now().millisecondsSinceEpoch; 117 final t = DateTime.now().millisecondsSinceEpoch;
73 118
74 -  
75 String os = ""; 119 String os = "";
76 String os_version = ""; 120 String os_version = "";
77 String model = ""; 121 String model = "";
@@ -90,8 +134,8 @@ class AutoTrackQueue { @@ -90,8 +134,8 @@ class AutoTrackQueue {
90 manufacturer = 'apple'; 134 manufacturer = 'apple';
91 } else if (baseDeviceInfo is OhosDeviceInfo) { 135 } else if (baseDeviceInfo is OhosDeviceInfo) {
92 os = 'ohos'; 136 os = 'ohos';
93 - os_version = baseDeviceInfo.versionId??"";  
94 - model = baseDeviceInfo.productModel??""; 137 + os_version = baseDeviceInfo.versionId ?? "";
  138 + model = baseDeviceInfo.productModel ?? "";
95 manufacturer = 'huawei'; 139 manufacturer = 'huawei';
96 } 140 }
97 } 141 }
@@ -129,69 +173,74 @@ class AutoTrackQueue { @@ -129,69 +173,74 @@ class AutoTrackQueue {
129 // "$is_first_day": true 173 // "$is_first_day": true
130 }; 174 };
131 175
132 - event.params.forEach((k,v){ 176 + event.params.forEach((k, v) {
133 properties[k] = v; 177 properties[k] = v;
134 }); 178 });
135 179
136 -  
137 datas.add({ 180 datas.add({
138 '_track_id': id, 181 '_track_id': id,
139 'time': t, 182 'time': t,
140 'type': 'track', 183 'type': 'track',
141 'distinct_id': 184 'distinct_id':
142 - config.userId ?? AutoTrackConfigManager.instance.deviceId, 185 + config.userId ?? AutoTrackConfigManager.instance.deviceId,
143 'anonymous_id': AutoTrackConfigManager.instance.deviceId, 186 'anonymous_id': AutoTrackConfigManager.instance.deviceId,
144 - 'event':event.type,  
145 - 'properties':properties 187 + 'event': event.type,
  188 + 'properties': properties
146 }); 189 });
147 190
148 AutoTrackLogger.getInstance().debug('upload => data => $datas'); 191 AutoTrackLogger.getInstance().debug('upload => data => $datas');
149 -  
150 }); 192 });
151 193
  194 + try {
  195 + final httpClient = HttpClient();
  196 + final request = await httpClient.postUrl(Uri.parse(host + UPLOAD));
152 197
153 - httpClient  
154 - .postUrl(Uri.parse(host + UPLOAD))  
155 - .then((HttpClientRequest request) {  
156 - request.headers  
157 - .set(HttpHeaders.contentTypeHeader, "application/json"); 198 + request.headers.set(HttpHeaders.contentTypeHeader, "application/json");
158 request.headers.set("token", token); // 设置 header 199 request.headers.set("token", token); // 设置 header
159 200
160 // 对数据进行压缩并进行 Base64 编码 201 // 对数据进行压缩并进行 Base64 编码
161 final compressedData = encodeData(jsonEncode(datas)); 202 final compressedData = encodeData(jsonEncode(datas));
162 -  
163 final jsonPayload = jsonEncode({"base64Str": compressedData}); 203 final jsonPayload = jsonEncode({"base64Str": compressedData});
164 - print("压缩数据:$jsonPayload"); 204 +
  205 + AutoTrackLogger.getInstance().debug("压缩数据:$jsonPayload");
165 206
166 request.write(jsonPayload); 207 request.write(jsonPayload);
167 - return request.close();  
168 - }).then((HttpClientResponse response) { 208 + final response = await request.close();
  209 +
169 final responseCode = response.statusCode; 210 final responseCode = response.statusCode;
170 - print("responseCode: $responseCode");  
171 - response.transform(utf8.decoder).join().then((responseBody) {  
172 - if (responseCode >= HttpStatus.ok &&  
173 - responseCode < HttpStatus.multipleChoices) {  
174 - // 状态码 200 - 300 认为是成功  
175 - print("response: $responseBody");  
176 - AutoTrackLogger.getInstance().debug('upload => success ret_code: $responseCode ret_content: $responseBody');  
177 -  
178 - try {  
179 - final jsonResponse = jsonDecode(responseBody);  
180 - if (jsonResponse["code"] == 4005) {  
181 - AutoTrackConfigManager.instance.getToken(true); 211 + AutoTrackLogger.getInstance().debug("responseCode: $responseCode");
  212 +
  213 + final responseBody = await response.transform(utf8.decoder).join();
  214 +
  215 + if (responseCode >= HttpStatus.ok &&
  216 + responseCode < HttpStatus.multipleChoices) {
  217 + // 状态码 200 - 300 认为是成功
  218 +
  219 + AutoTrackLogger.getInstance().debug(
  220 + 'upload => success ret_code: $responseCode ret_content: $responseBody');
  221 +
  222 + try {
  223 + final jsonResponse = jsonDecode(responseBody);
  224 + if (jsonResponse["code"] == 4005) {
  225 + AutoTrackConfigManager.instance.getToken(true);
  226 + } else {
  227 + //批量删除
  228 + for (var event in events) {
  229 + await database
  230 + .delete("track", where: "id = ?", whereArgs: [event['id']]);
182 } 231 }
183 - } catch (e) {  
184 - print("JSON 解析错误: $e");  
185 } 232 }
186 - } else {  
187 - AutoTrackLogger.getInstance().debug('upload => fail ret_code: $responseCode ret_content: $responseBody');  
188 - }  
189 - if (responseCode < HttpStatus.ok ||  
190 - responseCode >= HttpStatus.multipleChoices) {  
191 - AutoTrackLogger.getInstance().debug('upload => fail ret_code: $responseCode ret_content: $responseBody'); 233 + } catch (e) {
  234 + AutoTrackLogger.getInstance().debug("JSON 解析错误: $e");
  235 +
192 } 236 }
193 - });  
194 - }); 237 + } else {
  238 + AutoTrackLogger.getInstance().debug(
  239 + 'upload => fail ret_code: $responseCode ret_content: $responseBody');
  240 + }
  241 + } catch (e) {
  242 + AutoTrackLogger.getInstance().debug("网络请求错误: $e");
  243 + }
195 } 244 }
196 } 245 }
197 246
@@ -212,3 +261,39 @@ class AutoTrackQueue { @@ -212,3 +261,39 @@ class AutoTrackQueue {
212 } 261 }
213 } 262 }
214 } 263 }
  264 +
  265 +class Track {
  266 + final int? id;
  267 + final String event;
  268 + final DateTime date;
  269 +
  270 + Track({
  271 + this.id,
  272 + required this.event,
  273 + required this.date,
  274 + });
  275 +
  276 + // 从 Map 中创建 Track 对象
  277 + factory Track.fromMap(Map<String, dynamic> map) {
  278 + return Track(
  279 + id: map['id'] as int,
  280 + event: map['event'] as String,
  281 + date: DateTime.fromMillisecondsSinceEpoch(map['date'] as int),
  282 + );
  283 + }
  284 +
  285 + // 将 Track 对象转换为 Map
  286 + Map<String, dynamic> toMap() {
  287 + return {
  288 + 'id': id,
  289 + 'event': event,
  290 + 'date': date.millisecondsSinceEpoch,
  291 + };
  292 + }
  293 +
  294 + // 用于调试的 toString 方法
  295 + @override
  296 + String toString() {
  297 + return 'Track{id: $id, event: $event, date: $date}';
  298 + }
  299 +}
@@ -13,4 +13,14 @@ class TrackModel { @@ -13,4 +13,14 @@ class TrackModel {
13 'params': params, 13 'params': params,
14 }; 14 };
15 } 15 }
  16 +
  17 + //frommap
  18 + factory TrackModel.fromMap(Map<String, dynamic> map) {
  19 + return TrackModel(
  20 + map['type'] as String,
  21 + map['time'] as int,
  22 + map['params'] as Map<String, dynamic>,
  23 + map['key'] as String,
  24 + );
  25 + }
16 } 26 }
@@ -25,6 +25,7 @@ dependencies: @@ -25,6 +25,7 @@ dependencies:
25 plugin_platform_interface: ^2.0.2 25 plugin_platform_interface: ^2.0.2
26 uuid: ^4.3.3 26 uuid: ^4.3.3
27 archive: ^3.3.7 # 确保使用最新版本 27 archive: ^3.3.7 # 确保使用最新版本
  28 + sqflite: ^2.3.0
28 29
29 30
30 31