Initial commit: 91dj android client dev_1.0.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/// API 配置管理类:统一管理所有 API 端点路径
|
||||
class ApiConfig {
|
||||
ApiConfig._();
|
||||
|
||||
// ==================== API 路径(固定不变)====================
|
||||
|
||||
/// 上报接口路径(固定)
|
||||
static const String defaultReportPath = '/api/eventTracking/batchReport.json';
|
||||
|
||||
/// 上报接口路径
|
||||
static String get reportPath => defaultReportPath;
|
||||
|
||||
// ==================== 配置方法 ====================
|
||||
|
||||
/// 构建完整的 URL
|
||||
/// [baseDomain] 基础域名,如 'https://api.example.com'
|
||||
/// [path] API 路径,如 '/api/analytics/report'
|
||||
/// [appId] 应用ID(可选),如果提供会作为查询参数添加到URL中
|
||||
/// 返回完整的 URL,如 'https://api.example.com/api/analytics/report?appId=xxx'
|
||||
static String buildUrl(String baseDomain, String path, {String? appId}) {
|
||||
// 确保路径以 / 开头
|
||||
final normalizedPath = _normalizePath(path);
|
||||
|
||||
// 解析基础域名
|
||||
final uri = Uri.parse(baseDomain);
|
||||
final scheme = uri.scheme.isEmpty ? 'https' : uri.scheme;
|
||||
final host = uri.host.isEmpty ? baseDomain : uri.host;
|
||||
|
||||
// 保留非默认端口(如 :8080),避免 IP+端口 场景下端口丢失
|
||||
final defaultPort = scheme == 'https' ? 443 : 80;
|
||||
final portSuffix =
|
||||
(uri.port > 0 && uri.port != defaultPort) ? ':${uri.port}' : '';
|
||||
|
||||
// 保留 baseDomain 中的路径前缀(如 /v2),仅在 host 成功解析时使用
|
||||
// 去掉末尾 / 避免与 normalizedPath 拼出双斜杠
|
||||
final basePath = uri.host.isNotEmpty && uri.path.isNotEmpty && uri.path != '/'
|
||||
? (uri.path.endsWith('/')
|
||||
? uri.path.substring(0, uri.path.length - 1)
|
||||
: uri.path)
|
||||
: '';
|
||||
|
||||
// 构建完整 URL
|
||||
String url = '$scheme://$host$portSuffix$basePath$normalizedPath';
|
||||
|
||||
// 如果提供了 appId,添加到查询参数
|
||||
if (appId != null && appId.isNotEmpty) {
|
||||
url = _addQueryParameter(url, 'appId', appId);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/// 规范化路径:确保路径以 / 开头
|
||||
static String _normalizePath(String path) {
|
||||
if (path.isEmpty) {
|
||||
return '/';
|
||||
}
|
||||
return path.startsWith('/') ? path : '/$path';
|
||||
}
|
||||
|
||||
/// 获取上报接口 URL
|
||||
/// [baseDomain] 基础域名
|
||||
/// [appId] 应用ID(可选,如果提供会作为查询参数)
|
||||
static String getReportUrl(String baseDomain, {String? appId}) {
|
||||
return buildUrl(baseDomain, reportPath, appId: appId);
|
||||
}
|
||||
|
||||
/// 添加查询参数到 URL
|
||||
/// [url] 原始 URL
|
||||
/// [key] 参数名
|
||||
/// [value] 参数值
|
||||
static String _addQueryParameter(String url, String key, String value) {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
final queryParams = Map<String, String>.from(uri.queryParameters);
|
||||
queryParams[key] = value;
|
||||
return uri.replace(queryParameters: queryParams).toString();
|
||||
} catch (e) {
|
||||
// 如果 URL 解析失败,直接拼接
|
||||
final separator = url.contains('?') ? '&' : '?';
|
||||
return '$url$separator$key=${Uri.encodeComponent(value)}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/// SDK 配置管理类:统一管理所有 SDK 相关的配置常量
|
||||
class SdkConfig {
|
||||
SdkConfig._();
|
||||
|
||||
// ==================== 默认值常量(单一来源)====================
|
||||
// 字段声明和 reset() 均引用这些常量,避免三处重复。
|
||||
|
||||
static const int _defaultMaxQueueSize = 5000;
|
||||
static const int _defaultAutoUploadThreshold = 10;
|
||||
static const int _defaultMaxBatchSize = 500;
|
||||
static const int _defaultIsolateEncodingThreshold = 100;
|
||||
static const Duration _defaultUploadInterval = Duration(seconds: 5);
|
||||
static const Duration _defaultPageLoadTimeout = Duration(seconds: 5);
|
||||
static const Duration _defaultClickThrottleDuration =
|
||||
Duration(milliseconds: 300);
|
||||
static const Duration _defaultConnectionTimeout = Duration(seconds: 10);
|
||||
static const Duration _defaultReadTimeout = Duration(seconds: 30);
|
||||
static const Duration _defaultSpeedTestTimeout = Duration(seconds: 5);
|
||||
static const Duration _defaultConfigRequestTimeout = Duration(seconds: 10);
|
||||
static const int _defaultMaxBackoffLevel = 5;
|
||||
static const Duration _defaultBaseRetryDelay = Duration(seconds: 2);
|
||||
static const Duration _defaultMaxRetryDelay = Duration(seconds: 60);
|
||||
static const String _defaultCacheFileName = 'data_plus_events_cache.jsonl';
|
||||
static const String _defaultDebugReportEventLogFileName =
|
||||
'data_plus_report_events_debug.jsonl';
|
||||
static const bool _defaultEnableDebugReportLog = false;
|
||||
static const String _defaultEventTypeConfigCacheFileName =
|
||||
'analytics_event_types_config.json';
|
||||
static const String _defaultDomainCacheFileName =
|
||||
'data_plus_domains_cache.json';
|
||||
static const int _defaultMaxCacheLines = 2000;
|
||||
static const int _defaultMaxCacheBytes = 2 * 1024 * 1024;
|
||||
static const int _defaultTombstoneCompactionThreshold = 200;
|
||||
static const String _defaultTombstoneFileName =
|
||||
'data_plus_events_tombstone.txt';
|
||||
static const int _defaultMaxJsonSize = 5 * 1024 * 1024;
|
||||
static const int _defaultMaxSingleEventSize = 300 * 1024;
|
||||
static const int _defaultMaxConfigResponseSize = 1 * 1024 * 1024;
|
||||
static const int _defaultMaxPageStackSize = 100;
|
||||
static const int _defaultMaxAdImpressionCapacity = 10000;
|
||||
static const int _defaultCacheBufferThreshold = 10;
|
||||
static const Duration _defaultCacheFlushInterval = Duration(seconds: 5);
|
||||
|
||||
// ==================== 事件队列配置 ====================
|
||||
|
||||
/// 事件队列最大容量限制,防止内存无限增长
|
||||
static int maxQueueSize = _defaultMaxQueueSize;
|
||||
|
||||
/// 自动上报阈值:队列达到此数量时立即触发上报
|
||||
static int autoUploadThreshold = _defaultAutoUploadThreshold;
|
||||
|
||||
/// 单次上报的最大事件数量限制,避免请求过大导致超时
|
||||
static int maxBatchSize = _defaultMaxBatchSize;
|
||||
|
||||
/// JSON 编码的批次大小阈值,超过此大小使用 isolate 编码
|
||||
static int isolateEncodingThreshold = _defaultIsolateEncodingThreshold;
|
||||
|
||||
// ==================== 定时器配置 ====================
|
||||
|
||||
/// 定时上报间隔(秒)
|
||||
static Duration uploadInterval = _defaultUploadInterval;
|
||||
|
||||
/// 页面加载超时时间(秒)
|
||||
static Duration pageLoadTimeout = _defaultPageLoadTimeout;
|
||||
|
||||
/// 点击事件节流时间(毫秒)
|
||||
static Duration clickThrottleDuration = _defaultClickThrottleDuration;
|
||||
|
||||
// ==================== 网络超时配置 ====================
|
||||
|
||||
/// HTTP 连接超时时间
|
||||
static Duration connectionTimeout = _defaultConnectionTimeout;
|
||||
|
||||
/// HTTP 读取超时时间
|
||||
static Duration readTimeout = _defaultReadTimeout;
|
||||
|
||||
/// HTTP 写入超时时间(与连接超时相同)
|
||||
static Duration get writeTimeout => connectionTimeout;
|
||||
|
||||
/// HTTP 总超时时间(连接 + 写入 + 读取)
|
||||
static Duration get totalRequestTimeout =>
|
||||
connectionTimeout + writeTimeout + readTimeout;
|
||||
|
||||
/// 域名测速超时时间
|
||||
static Duration speedTestTimeout = _defaultSpeedTestTimeout;
|
||||
|
||||
/// 配置请求超时时间
|
||||
static Duration configRequestTimeout = _defaultConfigRequestTimeout;
|
||||
|
||||
// ==================== 加密配置 ====================
|
||||
|
||||
/// AES-GCM 解密密钥(固定密钥,用于 decryptResponseAuto 等)
|
||||
static const String decryptKey =
|
||||
'en1BNo0VBrN/zi+mI2LO7E9W40ehCBYwC+frBn8s3rQ';
|
||||
|
||||
// ==================== 重试配置 ====================
|
||||
|
||||
/// 最大退避级别
|
||||
static int maxBackoffLevel = _defaultMaxBackoffLevel;
|
||||
|
||||
/// 基础延迟时间
|
||||
static Duration baseRetryDelay = _defaultBaseRetryDelay;
|
||||
|
||||
/// 最大延迟时间
|
||||
static Duration maxRetryDelay = _defaultMaxRetryDelay;
|
||||
|
||||
// ==================== 缓存配置 ====================
|
||||
|
||||
/// 事件缓存文件名
|
||||
static String cacheFileName = _defaultCacheFileName;
|
||||
|
||||
/// Debug 包完整事件 JSONL 文件名(Native 平台 applicationDocumentsDirectory 下)
|
||||
static String debugReportEventLogFileName =
|
||||
_defaultDebugReportEventLogFileName;
|
||||
|
||||
/// 是否启用调试事件日志。
|
||||
///
|
||||
/// 默认关闭;开启后才会本地记录上传事件完整 JSONL,并打印 accepted event 完整 JSON。
|
||||
static bool enableDebugReportLog = _defaultEnableDebugReportLog;
|
||||
|
||||
/// 事件类型配置缓存文件名
|
||||
static String eventTypeConfigCacheFileName =
|
||||
_defaultEventTypeConfigCacheFileName;
|
||||
|
||||
/// 域名列表缓存文件名
|
||||
static String domainCacheFileName = _defaultDomainCacheFileName;
|
||||
|
||||
/// 最大缓存行数限制,防止文件过大导致内存问题
|
||||
/// 默认 2000 行,避免在网络不稳定时生成过大的缓存文件
|
||||
static int maxCacheLines = _defaultMaxCacheLines;
|
||||
|
||||
/// 缓存文件最大字节数限制,超过后从文件开头裁剪旧事件
|
||||
/// 默认 2MB
|
||||
static int maxCacheBytes = _defaultMaxCacheBytes;
|
||||
|
||||
/// tombstone 触发压缩的 event_id 数量阈值
|
||||
/// 默认 200:每积累 200 条已上报 ID 就压缩一次主缓存文件
|
||||
static int tombstoneCompactionThreshold =
|
||||
_defaultTombstoneCompactionThreshold;
|
||||
|
||||
/// tombstone 缓存文件名
|
||||
static String tombstoneFileName = _defaultTombstoneFileName;
|
||||
|
||||
/// 持久化 buffer 攒批阈值(达到此数量立即写盘)
|
||||
static int cacheBufferThreshold = _defaultCacheBufferThreshold;
|
||||
|
||||
/// 持久化 buffer 定时刷写间隔
|
||||
static Duration cacheFlushInterval = _defaultCacheFlushInterval;
|
||||
|
||||
// ==================== 请求大小限制 ====================
|
||||
|
||||
/// JSON 最大大小限制(5MB)
|
||||
static int maxJsonSize = _defaultMaxJsonSize;
|
||||
|
||||
/// 单条事件最大字节数限制,超过则丢弃(300KB)
|
||||
static int maxSingleEventSize = _defaultMaxSingleEventSize;
|
||||
|
||||
/// 配置接口响应体最大字节数限制(1MB),防止恶意/异常服务器返回超大数据导致 OOM
|
||||
static int maxConfigResponseSize = _defaultMaxConfigResponseSize;
|
||||
|
||||
// ==================== 容量配置 ====================
|
||||
|
||||
/// 页面栈最大容量
|
||||
static int maxPageStackSize = _defaultMaxPageStackSize;
|
||||
|
||||
/// 广告去重最大容量
|
||||
static int maxAdImpressionCapacity = _defaultMaxAdImpressionCapacity;
|
||||
|
||||
// ==================== 配置方法 ====================
|
||||
|
||||
/// 配置所有 SDK 参数
|
||||
///
|
||||
/// 示例:
|
||||
/// ```dart
|
||||
/// SdkConfig.configure(
|
||||
/// maxQueueSize: 20000,
|
||||
/// uploadInterval: Duration(seconds: 30),
|
||||
/// connectionTimeout: Duration(seconds: 15),
|
||||
/// );
|
||||
/// ```
|
||||
static void configure({
|
||||
int? maxQueueSize,
|
||||
int? autoUploadThreshold,
|
||||
int? maxBatchSize,
|
||||
int? isolateEncodingThreshold,
|
||||
Duration? uploadInterval,
|
||||
Duration? pageLoadTimeout,
|
||||
Duration? clickThrottleDuration,
|
||||
Duration? connectionTimeout,
|
||||
Duration? readTimeout,
|
||||
Duration? speedTestTimeout,
|
||||
Duration? configRequestTimeout,
|
||||
int? maxBackoffLevel,
|
||||
Duration? baseRetryDelay,
|
||||
Duration? maxRetryDelay,
|
||||
String? cacheFileName,
|
||||
String? debugReportEventLogFileName,
|
||||
bool? enableDebugReportLog,
|
||||
String? eventTypeConfigCacheFileName,
|
||||
String? domainCacheFileName,
|
||||
int? maxCacheLines,
|
||||
int? maxCacheBytes,
|
||||
int? maxJsonSize,
|
||||
int? maxSingleEventSize,
|
||||
int? maxPageStackSize,
|
||||
int? maxAdImpressionCapacity,
|
||||
}) {
|
||||
if (maxQueueSize != null && maxQueueSize > 0) {
|
||||
SdkConfig.maxQueueSize = maxQueueSize;
|
||||
}
|
||||
if (autoUploadThreshold != null && autoUploadThreshold > 0) {
|
||||
SdkConfig.autoUploadThreshold = autoUploadThreshold;
|
||||
}
|
||||
if (maxBatchSize != null && maxBatchSize > 0) {
|
||||
SdkConfig.maxBatchSize = maxBatchSize;
|
||||
}
|
||||
if (isolateEncodingThreshold != null && isolateEncodingThreshold > 0) {
|
||||
SdkConfig.isolateEncodingThreshold = isolateEncodingThreshold;
|
||||
}
|
||||
if (uploadInterval != null) {
|
||||
SdkConfig.uploadInterval = uploadInterval;
|
||||
}
|
||||
if (pageLoadTimeout != null) {
|
||||
SdkConfig.pageLoadTimeout = pageLoadTimeout;
|
||||
}
|
||||
if (clickThrottleDuration != null) {
|
||||
SdkConfig.clickThrottleDuration = clickThrottleDuration;
|
||||
}
|
||||
if (connectionTimeout != null) {
|
||||
SdkConfig.connectionTimeout = connectionTimeout;
|
||||
}
|
||||
if (readTimeout != null) {
|
||||
SdkConfig.readTimeout = readTimeout;
|
||||
}
|
||||
if (speedTestTimeout != null) {
|
||||
SdkConfig.speedTestTimeout = speedTestTimeout;
|
||||
}
|
||||
if (configRequestTimeout != null) {
|
||||
SdkConfig.configRequestTimeout = configRequestTimeout;
|
||||
}
|
||||
if (maxBackoffLevel != null && maxBackoffLevel >= 0) {
|
||||
SdkConfig.maxBackoffLevel = maxBackoffLevel;
|
||||
}
|
||||
if (baseRetryDelay != null) {
|
||||
SdkConfig.baseRetryDelay = baseRetryDelay;
|
||||
}
|
||||
if (maxRetryDelay != null) {
|
||||
SdkConfig.maxRetryDelay = maxRetryDelay;
|
||||
}
|
||||
if (cacheFileName != null &&
|
||||
cacheFileName.isNotEmpty &&
|
||||
!cacheFileName.contains('/') &&
|
||||
!cacheFileName.contains('\\') &&
|
||||
!cacheFileName.contains('..')) {
|
||||
SdkConfig.cacheFileName = cacheFileName;
|
||||
}
|
||||
if (debugReportEventLogFileName != null &&
|
||||
debugReportEventLogFileName.isNotEmpty &&
|
||||
!debugReportEventLogFileName.contains('/') &&
|
||||
!debugReportEventLogFileName.contains('\\') &&
|
||||
!debugReportEventLogFileName.contains('..')) {
|
||||
SdkConfig.debugReportEventLogFileName = debugReportEventLogFileName;
|
||||
}
|
||||
if (enableDebugReportLog != null) {
|
||||
SdkConfig.enableDebugReportLog = enableDebugReportLog;
|
||||
}
|
||||
if (eventTypeConfigCacheFileName != null &&
|
||||
eventTypeConfigCacheFileName.isNotEmpty &&
|
||||
!eventTypeConfigCacheFileName.contains('/') &&
|
||||
!eventTypeConfigCacheFileName.contains('\\') &&
|
||||
!eventTypeConfigCacheFileName.contains('..')) {
|
||||
SdkConfig.eventTypeConfigCacheFileName = eventTypeConfigCacheFileName;
|
||||
}
|
||||
if (domainCacheFileName != null &&
|
||||
domainCacheFileName.isNotEmpty &&
|
||||
!domainCacheFileName.contains('/') &&
|
||||
!domainCacheFileName.contains('\\') &&
|
||||
!domainCacheFileName.contains('..')) {
|
||||
SdkConfig.domainCacheFileName = domainCacheFileName;
|
||||
}
|
||||
if (maxCacheLines != null && maxCacheLines > 0) {
|
||||
SdkConfig.maxCacheLines = maxCacheLines;
|
||||
}
|
||||
if (maxCacheBytes != null && maxCacheBytes > 0) {
|
||||
SdkConfig.maxCacheBytes = maxCacheBytes;
|
||||
}
|
||||
if (maxJsonSize != null && maxJsonSize > 0) {
|
||||
SdkConfig.maxJsonSize = maxJsonSize;
|
||||
}
|
||||
if (maxSingleEventSize != null && maxSingleEventSize > 0) {
|
||||
SdkConfig.maxSingleEventSize = maxSingleEventSize;
|
||||
}
|
||||
if (maxPageStackSize != null && maxPageStackSize > 0) {
|
||||
SdkConfig.maxPageStackSize = maxPageStackSize;
|
||||
}
|
||||
if (maxAdImpressionCapacity != null && maxAdImpressionCapacity > 0) {
|
||||
SdkConfig.maxAdImpressionCapacity = maxAdImpressionCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// 重置所有配置为默认值
|
||||
static void reset() {
|
||||
maxQueueSize = _defaultMaxQueueSize;
|
||||
autoUploadThreshold = _defaultAutoUploadThreshold;
|
||||
maxBatchSize = _defaultMaxBatchSize;
|
||||
isolateEncodingThreshold = _defaultIsolateEncodingThreshold;
|
||||
uploadInterval = _defaultUploadInterval;
|
||||
pageLoadTimeout = _defaultPageLoadTimeout;
|
||||
clickThrottleDuration = _defaultClickThrottleDuration;
|
||||
connectionTimeout = _defaultConnectionTimeout;
|
||||
readTimeout = _defaultReadTimeout;
|
||||
speedTestTimeout = _defaultSpeedTestTimeout;
|
||||
configRequestTimeout = _defaultConfigRequestTimeout;
|
||||
maxBackoffLevel = _defaultMaxBackoffLevel;
|
||||
baseRetryDelay = _defaultBaseRetryDelay;
|
||||
maxRetryDelay = _defaultMaxRetryDelay;
|
||||
cacheFileName = _defaultCacheFileName;
|
||||
debugReportEventLogFileName = _defaultDebugReportEventLogFileName;
|
||||
enableDebugReportLog = _defaultEnableDebugReportLog;
|
||||
eventTypeConfigCacheFileName = _defaultEventTypeConfigCacheFileName;
|
||||
domainCacheFileName = _defaultDomainCacheFileName;
|
||||
maxCacheLines = _defaultMaxCacheLines;
|
||||
maxCacheBytes = _defaultMaxCacheBytes;
|
||||
tombstoneCompactionThreshold = _defaultTombstoneCompactionThreshold;
|
||||
tombstoneFileName = _defaultTombstoneFileName;
|
||||
cacheBufferThreshold = _defaultCacheBufferThreshold;
|
||||
cacheFlushInterval = _defaultCacheFlushInterval;
|
||||
maxJsonSize = _defaultMaxJsonSize;
|
||||
maxSingleEventSize = _defaultMaxSingleEventSize;
|
||||
maxConfigResponseSize = _defaultMaxConfigResponseSize;
|
||||
maxPageStackSize = _defaultMaxPageStackSize;
|
||||
maxAdImpressionCapacity = _defaultMaxAdImpressionCapacity;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user