queue.dart 6.78 KB
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';

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 '../log/logger.dart';

class AutoTrackQueue {
  static final AutoTrackQueue instance = AutoTrackQueue._();
  AutoTrackQueue._() {
    httpClient.badCertificateCallback =
        (X509Certificate cert, String host, int port) => true;
  }

  Timer? _timer;
  final List<TrackModel> _queue = [];
  final httpClient = HttpClient();

  void appendQueue(TrackModel model) {
    if (_timer == null) return;
    _queue.add(model);
  }

  void start() {
    if (_timer != null) return;
    _timer = Timer.periodic(
        Duration(
            seconds: AutoTrackConfigManager.instance.config.uploadInterval ??
                10), (timer) {
      flush();
    });
  }

  void stop() {
    _timer?.cancel();
    _timer = null;
  }

  /**
   * todo 存储数据库,目前会丢失数据
   */
  void flush() {
    AutoTrackLogger.getInstance().debug('start flush ${_queue.length}');

    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;
    if (config.samplingRate != 1) {
      if (Random().nextDouble() > config.samplingRate) {
        // 不在采样范围不上传
        return;
      }
    }
    String? token = config.token;
    if (token == null) {
      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 = "";
        String manufacturer = "";

        if (baseDeviceInfo != null) {
          if (baseDeviceInfo is AndroidDeviceInfo) {
            os = 'android';
            os_version = baseDeviceInfo.version.release;
            model = baseDeviceInfo.model;
            manufacturer = baseDeviceInfo.manufacturer;
          } else if (baseDeviceInfo is IosDeviceInfo) {
            os = 'ios';
            os_version = baseDeviceInfo.systemVersion;
            model = baseDeviceInfo.model;
            manufacturer = 'apple';
          } else if (baseDeviceInfo is OhosDeviceInfo) {
            os = 'ohos';
            os_version = baseDeviceInfo.versionId??"";
            model = baseDeviceInfo.productModel??"";
            manufacturer = 'huawei';
          }
        }
        final properties = {
          '\$os': os,
          '\$os_version': os_version,
          '\$model': model,
          '\$lib': "Flutter",
          '\$app_name': AutoTrackConfigManager.instance.appName,
          '\$app_version': AutoTrackConfigManager.instance.appVersion,
          '\$device_id': AutoTrackConfigManager.instance.deviceId,
          '\$timezone_offset': getZoneOffset(),
          "\$manufacturer": manufacturer,
          // "$carrier": "NONE",
          // "$os_version": "13",
          // "$model": "C310CS",
          // "$os": "Android",
          // "$screen_width": 1200,
          // "$brand": "BOE",
          // "$screen_height": 1920,
          // "$device_id": "afa4c7a98b3f6467",
          // "$app_name": "Ewin Reading",
          // "$lib_version": "5.3.3",
          // "$timezone_offset": -480,
          // "$app_id": "com.ewin.tech.reading",
          // "$mac": "020000000000",
          // "$manufacturer": "BOE",
          // "$sn": "C310CS014820000006",
          // "$wifi": true,
          // "$network_type": "WIFI",
          // "$screen_orientation": "portrait",
          // "$screen_brightness": 204,
          // "$event_duration": 0,
          // "$lib_method": "autoTrack",
          // "$is_first_day": true
        };

        event.params.forEach((k,v){
          properties[k] = v;
        });


        datas.add({
          '_track_id': id,
          'time': t,
          'type': 'track',
          'distinct_id':
          config.userId ?? AutoTrackConfigManager.instance.deviceId,
          'anonymous_id': AutoTrackConfigManager.instance.deviceId,
          'event':event.type,
          'properties':properties
        });

        AutoTrackLogger.getInstance().debug('upload => data => $datas');

      });


      httpClient
          .postUrl(Uri.parse(host + UPLOAD))
          .then((HttpClientRequest request) {
        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");

        request.write(jsonPayload);
        return request.close();
      }).then((HttpClientResponse response) {
        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);
              }
            } 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');
          }
        });
      });
    }
  }

  int getZoneOffset() {
    final now = DateTime.now();
    final localOffset = now.timeZoneOffset.inMinutes; // 获取时区偏移量(分钟)
    return -localOffset; // 取反,保持与 Java 代码一致
  }

  String encodeData(String rawMessage) {
    try {
      // 使用 GZip 压缩
      List<int> compressed = GZipEncoder().encode(utf8.encode(rawMessage))!;
      // Base64 编码
      return base64.encode(compressed);
    } catch (e) {
      throw FormatException('Invalid data: ${e.toString()}');
    }
  }
}