Showing
4 changed files
with
174 additions
and
54 deletions
| @@ -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 | } |
-
Please register or login to post a comment