顾海波

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

... ... @@ -309,6 +309,22 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
sqflite:
dependency: transitive
description:
name: sqflite
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.3+1"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.4"
stack_trace:
dependency: transitive
description:
... ... @@ -341,6 +357,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.1"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.0+1"
term_glyph:
dependency: transitive
description:
... ...
... ... @@ -3,27 +3,64 @@ import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:archive/archive.dart';
import 'package:auto_track/auto_track/config/manager.dart';
import 'package:auto_track/auto_track/utils/track_model.dart';
import 'package:archive/archive.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:path/path.dart' as path;
import 'package:sqflite/sqflite.dart';
import '../log/logger.dart';
class AutoTrackQueue {
static final AutoTrackQueue instance = AutoTrackQueue._();
late Database database;
Future<void> _lastTask = Future.value();
void post(Future<void> Function() task) {
_lastTask = _lastTask.then((_) => task());
}
AutoTrackQueue._() {
httpClient.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
//异步任务
post(() async {
database = await openTrackDatabase();
});
}
Future<Database> openTrackDatabase() async {
final databasePath = await getDatabasesPath();
final pathString = path.join(databasePath, 'track.db');
return openDatabase(
pathString,
onCreate: (db, version) {
return db.execute(
"CREATE TABLE track("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"event TEXT, "
"date DATE"
")",
);
},
version: 1,
);
}
Timer? _timer;
final List<TrackModel> _queue = [];
final httpClient = HttpClient();
void appendQueue(TrackModel model) {
if (_timer == null) return;
_queue.add(model);
if (database == null) return;
post(() async {
await database.insert(
"track",
Track(event: jsonEncode(model.toMap()), date: DateTime.now())
.toMap());
});
}
void start() {
... ... @@ -32,7 +69,7 @@ class AutoTrackQueue {
Duration(
seconds: AutoTrackConfigManager.instance.config.uploadInterval ??
10), (timer) {
flush();
post(flush);
});
}
... ... @@ -41,15 +78,24 @@ class AutoTrackQueue {
_timer = null;
}
/**
* todo 存储数据库,目前会丢失数据
*/
void flush() {
AutoTrackLogger.getInstance().debug('start flush ${_queue.length}');
Future<void> flush() async {
AutoTrackLogger.getInstance().debug("@@@start flush");
if (database == null) {
AutoTrackLogger.getInstance().debug('数据库未初始化,跳过 flush');
return;
}
final List<TrackModel> uploadList = [];
List<Map<String, dynamic>> events = await database.query("track",
columns: ["id", "event", "date"], limit: 100);
for (var event in events) {
Track model = Track.fromMap(event);
uploadList.add(TrackModel.fromMap(jsonDecode(model.event)));
}
if (_queue.isEmpty) return;
final List<TrackModel> uploadList = List.from(_queue);
_queue.clear();
final config = AutoTrackConfigManager.instance.config;
final baseDeviceInfo = AutoTrackConfigManager.instance.baseDeviceInfo;
final host = config.host;
... ... @@ -64,14 +110,12 @@ class AutoTrackQueue {
return;
}
if (host != null) {
List<Map> datas = [];
uploadList.forEach((event) {
Random random = Random.secure();
int id = random.nextInt(1 << 32); // 模拟 Java 的 nextInt()
final t = DateTime.now().millisecondsSinceEpoch;
String os = "";
String os_version = "";
String model = "";
... ... @@ -90,8 +134,8 @@ class AutoTrackQueue {
manufacturer = 'apple';
} else if (baseDeviceInfo is OhosDeviceInfo) {
os = 'ohos';
os_version = baseDeviceInfo.versionId??"";
model = baseDeviceInfo.productModel??"";
os_version = baseDeviceInfo.versionId ?? "";
model = baseDeviceInfo.productModel ?? "";
manufacturer = 'huawei';
}
}
... ... @@ -129,69 +173,74 @@ class AutoTrackQueue {
// "$is_first_day": true
};
event.params.forEach((k,v){
event.params.forEach((k, v) {
properties[k] = v;
});
datas.add({
'_track_id': id,
'time': t,
'type': 'track',
'distinct_id':
config.userId ?? AutoTrackConfigManager.instance.deviceId,
config.userId ?? AutoTrackConfigManager.instance.deviceId,
'anonymous_id': AutoTrackConfigManager.instance.deviceId,
'event':event.type,
'properties':properties
'event': event.type,
'properties': properties
});
AutoTrackLogger.getInstance().debug('upload => data => $datas');
});
try {
final httpClient = HttpClient();
final request = await httpClient.postUrl(Uri.parse(host + UPLOAD));
httpClient
.postUrl(Uri.parse(host + UPLOAD))
.then((HttpClientRequest request) {
request.headers
.set(HttpHeaders.contentTypeHeader, "application/json");
request.headers.set(HttpHeaders.contentTypeHeader, "application/json");
request.headers.set("token", token); // 设置 header
// 对数据进行压缩并进行 Base64 编码
final compressedData = encodeData(jsonEncode(datas));
final jsonPayload = jsonEncode({"base64Str": compressedData});
print("压缩数据:$jsonPayload");
AutoTrackLogger.getInstance().debug("压缩数据:$jsonPayload");
request.write(jsonPayload);
return request.close();
}).then((HttpClientResponse response) {
final response = await request.close();
final responseCode = response.statusCode;
print("responseCode: $responseCode");
response.transform(utf8.decoder).join().then((responseBody) {
if (responseCode >= HttpStatus.ok &&
responseCode < HttpStatus.multipleChoices) {
// 状态码 200 - 300 认为是成功
print("response: $responseBody");
AutoTrackLogger.getInstance().debug('upload => success ret_code: $responseCode ret_content: $responseBody');
try {
final jsonResponse = jsonDecode(responseBody);
if (jsonResponse["code"] == 4005) {
AutoTrackConfigManager.instance.getToken(true);
AutoTrackLogger.getInstance().debug("responseCode: $responseCode");
final responseBody = await response.transform(utf8.decoder).join();
if (responseCode >= HttpStatus.ok &&
responseCode < HttpStatus.multipleChoices) {
// 状态码 200 - 300 认为是成功
AutoTrackLogger.getInstance().debug(
'upload => success ret_code: $responseCode ret_content: $responseBody');
try {
final jsonResponse = jsonDecode(responseBody);
if (jsonResponse["code"] == 4005) {
AutoTrackConfigManager.instance.getToken(true);
} else {
//批量删除
for (var event in events) {
await database
.delete("track", where: "id = ?", whereArgs: [event['id']]);
}
} catch (e) {
print("JSON 解析错误: $e");
}
} else {
AutoTrackLogger.getInstance().debug('upload => fail ret_code: $responseCode ret_content: $responseBody');
}
if (responseCode < HttpStatus.ok ||
responseCode >= HttpStatus.multipleChoices) {
AutoTrackLogger.getInstance().debug('upload => fail ret_code: $responseCode ret_content: $responseBody');
} catch (e) {
AutoTrackLogger.getInstance().debug("JSON 解析错误: $e");
}
});
});
} else {
AutoTrackLogger.getInstance().debug(
'upload => fail ret_code: $responseCode ret_content: $responseBody');
}
} catch (e) {
AutoTrackLogger.getInstance().debug("网络请求错误: $e");
}
}
}
... ... @@ -212,3 +261,39 @@ class AutoTrackQueue {
}
}
}
class Track {
final int? id;
final String event;
final DateTime date;
Track({
this.id,
required this.event,
required this.date,
});
// 从 Map 中创建 Track 对象
factory Track.fromMap(Map<String, dynamic> map) {
return Track(
id: map['id'] as int,
event: map['event'] as String,
date: DateTime.fromMillisecondsSinceEpoch(map['date'] as int),
);
}
// 将 Track 对象转换为 Map
Map<String, dynamic> toMap() {
return {
'id': id,
'event': event,
'date': date.millisecondsSinceEpoch,
};
}
// 用于调试的 toString 方法
@override
String toString() {
return 'Track{id: $id, event: $event, date: $date}';
}
}
... ...
... ... @@ -13,4 +13,14 @@ class TrackModel {
'params': params,
};
}
//frommap
factory TrackModel.fromMap(Map<String, dynamic> map) {
return TrackModel(
map['type'] as String,
map['time'] as int,
map['params'] as Map<String, dynamic>,
map['key'] as String,
);
}
}
... ...
... ... @@ -25,6 +25,7 @@ dependencies:
plugin_platform_interface: ^2.0.2
uuid: ^4.3.3
archive: ^3.3.7 # 确保使用最新版本
sqflite: ^2.3.0
... ...