530 lines
12 KiB
Markdown
530 lines
12 KiB
Markdown
## 一、统一模型 & 规范(细化)
|
||
|
||
### 1.1 SDKConfig 统一规范
|
||
|
||
所有端保持字段一致,命名按各端风格适配:
|
||
|
||
jsonc
|
||
|
||
```
|
||
{
|
||
"systemCode": "OA_APP",
|
||
"endpointBaseUrl": "https://host/api/ExternalEventlogs",
|
||
"clientType": 1, // 1 Android, 2 iOS, 3 Flutter
|
||
"enableDebug": false,
|
||
|
||
"batchSize": 20, // 单次最多上传条数
|
||
"flushInterval": 15, // 秒,定时 flush 间隔
|
||
"maxCacheSize": 5000, // 本地缓存最大事件条数
|
||
"maxRetryCount": 3, // 最大重试次数
|
||
|
||
"connectTimeout": 5000, // ms
|
||
"readTimeout": 5000 // ms
|
||
|
||
// 预留,将来可扩:
|
||
// "globalTags": { "appVersion": "1.0.0", "channel": "official" }
|
||
}
|
||
```
|
||
|
||
### 1.2 Event 内部字段规范
|
||
|
||
统一内部 Event 类字段(各端自行建模):
|
||
|
||
ts
|
||
|
||
```
|
||
class Event {
|
||
String id; // 本地ID/主键
|
||
String systemCode;
|
||
String eventType;
|
||
|
||
UserInfo? userInfo;
|
||
int clientType;
|
||
int clientTimestamp; // ms
|
||
String timestamp; // ISO8601
|
||
|
||
DeviceInfo deviceInfo;
|
||
|
||
Map<String, dynamic>? eventParams;
|
||
Map<String, dynamic>? customTags;
|
||
|
||
int retryCount; // 已重试次数
|
||
DateTime createTime;
|
||
}
|
||
```
|
||
|
||
------
|
||
|
||
## 二、Phase 1:可用 + 稳定(细化到类 & 表)
|
||
|
||
### 2.1 通用逻辑(跨端统一)
|
||
|
||
#### 2.1.1 发送流程(时序)
|
||
|
||
1. App 调用 `Analytics.init(config)`
|
||
2. SDK:
|
||
- 保存 config
|
||
- 采集 DeviceInfo
|
||
- 初始化 Storage
|
||
- 启动定时任务(每 flushInterval 秒执行 flush)
|
||
3. App 调用 `Analytics.setUser(...)`(可多次)
|
||
4. App 调用 `Analytics.track(eventType, {...})`:
|
||
- 构造 Event
|
||
- 持久化入库
|
||
- 如果缓存条数 ≥ batchSize:启动一次 flush
|
||
5. flush 流程:
|
||
- 从存储取前 `batchSize` 条事件;
|
||
- 调用 `AddEventListLog`:
|
||
- 成功:删除这些事件;
|
||
- 网络错/5xx:更新 retryCount,重排队,按指数退避调度;
|
||
- 4xx:记录日志,删除这些事件(视为「业务不可重试」)。
|
||
|
||
#### 2.1.2 本地表结构(推荐统一)
|
||
|
||
SQLite 表 `events`:
|
||
|
||
sql
|
||
|
||
```
|
||
CREATE TABLE events (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
payload TEXT NOT NULL, -- 事件完整 JSON
|
||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||
create_time INTEGER NOT NULL -- ms since epoch
|
||
);
|
||
```
|
||
|
||
索引:
|
||
|
||
sql
|
||
|
||
```
|
||
CREATE INDEX idx_events_create_time ON events(create_time);
|
||
```
|
||
|
||
> Flutter 可用 sqflite 按此结构实现。
|
||
|
||
------
|
||
|
||
### 2.2 Flutter SDK(Dart 独立实现)
|
||
|
||
#### 2.2.1 目录结构建议
|
||
|
||
text
|
||
|
||
```
|
||
lib/
|
||
analytics.dart // 对外 Facade
|
||
src/
|
||
config/
|
||
analytics_config.dart
|
||
model/
|
||
event.dart
|
||
user_info.dart
|
||
device_info.dart
|
||
storage/
|
||
event_storage.dart // 接口
|
||
sqflite_event_storage.dart
|
||
network/
|
||
api_client.dart
|
||
core/
|
||
analytics_core.dart // EventManager + Scheduler
|
||
scheduler.dart
|
||
util/
|
||
logger.dart
|
||
time_util.dart
|
||
```
|
||
|
||
#### 2.2.2 关键类职责
|
||
|
||
- `Analytics`(facade)
|
||
- 静态方法:`init / track / setUser / setDeviceInfo / flush / setDebug`
|
||
- 内部持有单例 `AnalyticsCore`
|
||
- `AnalyticsCore`
|
||
- 字段:
|
||
- `AnalyticsConfig _config`
|
||
- `UserInfo? _user`
|
||
- `DeviceInfo _device`
|
||
- `EventStorage _storage`
|
||
- `ApiClient _apiClient`
|
||
- `Timer? _timer`
|
||
- `bool _isFlushing`
|
||
- 方法:
|
||
- `init(config)`
|
||
- `setUser`
|
||
- `setDeviceInfo`
|
||
- `track(...)`
|
||
- `flush({bool force = false})`
|
||
- `EventStorage` 接口 + `SqfliteEventStorage` 实现
|
||
- `Future<void> insert(Event event)`
|
||
- `Future<List<StoredEvent>> fetchBatch(int limit)`
|
||
- `Future<void> deleteByIds(List<int> ids)`
|
||
- `Future<int> count()`
|
||
- `Future<void> trimToMaxSize(int maxSize)`
|
||
- `ApiClient`
|
||
- `Future<bool> sendBatch(List<Event> events)`
|
||
- POST `/AddEventListLog`,Body 为数组;
|
||
- 成功返回 true,失败 false 或抛异常。
|
||
- (可选)单条 `sendSingle(Event)` 作降级方案。
|
||
- `Scheduler`
|
||
- 内含 `Timer.periodic`;
|
||
- 每次 tick 调用 `core.flush()`;
|
||
- 通过 `_isFlushing` 防并发。
|
||
|
||
#### 2.2.3 技术要求(Flutter)
|
||
|
||
- 使用 `async/await`,所有 IO 非阻塞;
|
||
- `Analytics` 所有对外 API 均返回 `Future`(init / track / flush)便于业务 await;
|
||
- 避免 `Timer` 仍在运行却对象已销毁:在必要时提供 `dispose`(可选)。
|
||
|
||
------
|
||
|
||
### 2.3 Android SDK(Kotlin)
|
||
|
||
#### 2.3.1 目录结构建议
|
||
|
||
text
|
||
|
||
```
|
||
com.company.analytics/
|
||
Analytics.kt // Facade
|
||
config/
|
||
AnalyticsConfig.kt
|
||
model/
|
||
Event.kt
|
||
UserInfo.kt
|
||
DeviceInfo.kt
|
||
storage/
|
||
EventDao.kt
|
||
EventDatabase.kt
|
||
network/
|
||
ApiService.kt
|
||
NetworkClient.kt
|
||
core/
|
||
AnalyticsCore.kt
|
||
FlushScheduler.kt
|
||
util/
|
||
Logger.kt
|
||
TimeUtil.kt
|
||
```
|
||
|
||
#### 2.3.2 关键实现要点
|
||
|
||
- Room 表结构:
|
||
|
||
kotlin
|
||
|
||
```
|
||
@Entity(tableName = "events")
|
||
data class EventEntity(
|
||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||
@ColumnInfo(name = "payload") val payload: String,
|
||
@ColumnInfo(name = "retry_count") val retryCount: Int = 0,
|
||
@ColumnInfo(name = "create_time") val createTime: Long = System.currentTimeMillis()
|
||
)
|
||
```
|
||
|
||
- DAO:
|
||
|
||
kotlin
|
||
|
||
```
|
||
@Dao
|
||
interface EventDao {
|
||
@Insert
|
||
suspend fun insert(entity: EventEntity)
|
||
|
||
@Query("SELECT * FROM events ORDER BY create_time ASC LIMIT :limit")
|
||
suspend fun fetchBatch(limit: Int): List<EventEntity>
|
||
|
||
@Query("DELETE FROM events WHERE id IN (:ids)")
|
||
suspend fun deleteByIds(ids: List<Long>)
|
||
|
||
@Query("SELECT COUNT(*) FROM events")
|
||
suspend fun count(): Int
|
||
|
||
@Query("""
|
||
DELETE FROM events WHERE id IN (
|
||
SELECT id FROM events ORDER BY create_time ASC LIMIT
|
||
(SELECT MAX(0, COUNT(*) - :maxSize) FROM events)
|
||
)
|
||
""")
|
||
suspend fun trimToMaxSize(maxSize: Int)
|
||
}
|
||
```
|
||
|
||
- 调度:
|
||
- 使用 `CoroutineScope(SupervisorJob() + Dispatchers.IO)`;
|
||
- `FlushScheduler` 用 `delay` 或 `TickerChannel` 实现周期任务;
|
||
- 使用 `Mutex` 或 flag 控制“单一 flush”。
|
||
|
||
------
|
||
|
||
### 2.4 iOS SDK(Swift)
|
||
|
||
#### 2.4.1 目录结构建议
|
||
|
||
text
|
||
|
||
```
|
||
AnalyticsSDK/
|
||
Analytics.swift // Facade
|
||
Config/
|
||
AnalyticsConfig.swift
|
||
Model/
|
||
Event.swift
|
||
UserInfo.swift
|
||
DeviceInfo.swift
|
||
Storage/
|
||
EventStorage.swift
|
||
SQLiteEventStorage.swift
|
||
Network/
|
||
ApiClient.swift
|
||
Core/
|
||
AnalyticsCore.swift
|
||
FlushScheduler.swift
|
||
Util/
|
||
Logger.swift
|
||
TimeUtil.swift
|
||
```
|
||
|
||
#### 2.4.2 关键实现要点
|
||
|
||
- 存储:
|
||
- 使用 SQLite(用 FMDB/GRDB 等封装,或自己写简单 wrapper);
|
||
- 网络:
|
||
- URLSession + Codable 序列化;
|
||
- 定时:
|
||
- `Timer.scheduledTimer` 或 `DispatchSourceTimer`;
|
||
- 生命周期:
|
||
- 订阅 `UIApplication` 通知,在进入后台前调用 `flush()`。
|
||
|
||
------
|
||
|
||
### 2.5 Phase 1 验收 checklist(落地用)
|
||
|
||
- 三端提供初始化、用户设置、事件上报、flush API;
|
||
- 本地缓存可持久化,多次启动数据不丢;
|
||
- 断网 → 上报 → 恢复,事件能补发到后端;
|
||
- 压测 10K 条事件,上报成功率可接受(> 99%,无 crash);
|
||
- 业务方集成 Demo App 并通过功能测试。
|
||
|
||
------
|
||
|
||
## 三、Phase 2:配置化 + 校验 + 调试(细化)
|
||
|
||
### 3.1 新增数据结构
|
||
|
||
#### 3.1.1 维度配置缓存(本地)
|
||
|
||
以通用视角描述,三端类似:
|
||
|
||
ts
|
||
|
||
```
|
||
class SystemDimInfo {
|
||
SystemInfo systemInfo;
|
||
List<EventDefinition> eventDefinitions;
|
||
List<TagDefinition> tagDefinitions;
|
||
DateTime lastFetchedAt;
|
||
String? version; // 从后端 header 或 body 获得
|
||
}
|
||
```
|
||
|
||
- `EventDefinition` 来自 `systemEventTypes`:
|
||
- `eventCode`, `eventName`, `description`
|
||
- `TagDefinition` 来自 `systemCustonTas`:
|
||
- `tag_name`, `tag_type`, `is_required`, `description`
|
||
|
||
本地可存储在:
|
||
|
||
- Flutter:sqflite/hive 单独表/box;
|
||
- Android:Room 新表 `config`;
|
||
- iOS:UserDefaults / SQLite。
|
||
|
||
### 3.2 ConfigManager / DimensionManager
|
||
|
||
职责:
|
||
|
||
- 拉取 `GetSystemAllDimInfo(system_code)`;
|
||
- 解析为 `SystemDimInfo`;
|
||
- 序列化到本地;
|
||
- 提供只读访问 API 给 Validator。
|
||
|
||
调用时机:
|
||
|
||
- init 成功后异步拉取;
|
||
- 间隔 N 小时(如 12 / 24h)自动刷新;
|
||
- 提供 `forceRefresh()` 接口(仅 debug 使用)。
|
||
|
||
### 3.3 Validator 详细逻辑
|
||
|
||
以伪代码描述:
|
||
|
||
ts
|
||
|
||
```
|
||
validateEvent(event: Event): ValidationResult {
|
||
let result = new ValidationResult()
|
||
|
||
// 1. eventType 是否在配置中
|
||
if (!config.hasEvent(event.eventType)) {
|
||
result.addError("UNKNOWN_EVENT_TYPE")
|
||
}
|
||
|
||
// 2. 校验必填 tag(customTags)
|
||
let requiredTags = config.getRequiredTags()
|
||
for (tag in requiredTags) {
|
||
if (!event.customTags?.containsKey(tag.name)) {
|
||
result.addWarning("MISSING_REQUIRED_TAG", tag.name)
|
||
}
|
||
else {
|
||
// 3. 简单类型校验
|
||
if (!typeMatch(event.customTags[tag.name], tag.type)) {
|
||
result.addWarning("TYPE_MISMATCH", tag.name)
|
||
}
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
```
|
||
|
||
- Debug 模式:
|
||
- 所有 `errors` + `warnings` 打到日志;
|
||
- 可选策略:有 error 的事件不发送。
|
||
- Release 模式:
|
||
- 仍发送,但将错误信息 encode 到 `customTags._sdk_validation` 中。
|
||
|
||
### 3.4 调试支持细化
|
||
|
||
#### 3.4.1 日志规范
|
||
|
||
统一日志前缀与级别:
|
||
|
||
- `[AnalyticsSDK][DEBUG] ...`
|
||
- `[AnalyticsSDK][ERROR] ...`
|
||
|
||
在 debug 打开时:
|
||
|
||
- 打印:
|
||
- 初始化配置;
|
||
- 事件构造后的 JSON;
|
||
- 每次发送的批次大小、结果状态码;
|
||
- 校验结果(仅 debug)。
|
||
|
||
#### 3.4.2 Demo 调试面板(可选但推荐)
|
||
|
||
Flutter / 原生各自 Demo App 中:
|
||
|
||
- 提供一个 Debug 页面:
|
||
- 显示当前缓存条数;
|
||
- 最近 20 条事件简要信息(id、eventType、createTime、是否校验通过);
|
||
- 按钮:`Flush Now`,调用 `Analytics.flush()`。
|
||
|
||
------
|
||
|
||
## 四、Phase 3:监控 + 动态策略 + 插件(细化)
|
||
|
||
### 4.1 SDK 自监控事件设计
|
||
|
||
定义一批内部保留事件(只你们自己分析用):
|
||
|
||
- `SDK_METRICS_SEND`:
|
||
- 字段:
|
||
- `sentCount`
|
||
- `failedCount`
|
||
- `retryCount`
|
||
- `avgLatencyMs`
|
||
- `SDK_METRICS_QUEUE`:
|
||
- 字段:
|
||
- `queueSize`
|
||
- `droppedEvents`
|
||
|
||
实现:
|
||
|
||
- 在 SDK 内部维护计数器;
|
||
- 每隔 M 分钟(如 10 分钟)封装成 Event,正常通过日志接口上报。
|
||
|
||
### 4.2 策略配置结构
|
||
|
||
可通过扩展 `GetSystemAllDimInfo` 或另建接口下发 JSON,例如:
|
||
|
||
jsonc
|
||
|
||
```
|
||
{
|
||
"sdkStrategy": {
|
||
"enabled": true,
|
||
"defaultSampleRate": 1.0,
|
||
"eventSettings": {
|
||
"PAGE_VIEW": { "enabled": true, "sampleRate": 0.2 },
|
||
"DEBUG_EVENT": { "enabled": false, "sampleRate": 0.0 }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
SDK 行为:
|
||
|
||
- 加载策略后:
|
||
- `if !strategy.enabled`:`track` 直接返回,不做任何事情(紧急开关)。
|
||
- 查找具体 eventType 的策略:
|
||
- `enabled=false` → 直接丢;
|
||
- `sampleRate<1` → 按 sampleRate 随机采样(或使用 eventId hash)。
|
||
|
||
### 4.3 插件机制设计
|
||
|
||
通用接口(以伪代码):
|
||
|
||
ts
|
||
|
||
```
|
||
interface EventInterceptor {
|
||
beforeSend(event: Event): Event | null | Promise<Event | null>
|
||
afterSend(event: Event, result: SendResult): void | Promise<void>
|
||
}
|
||
```
|
||
|
||
- SDK 内部维护 `List<EventInterceptor>`;
|
||
- 在真正上传前,按顺序调用 `beforeSend`:
|
||
- 任一返回 null → 事件被拦截,标记为丢弃;
|
||
- 上传结束后,按顺序调用 `afterSend`。
|
||
|
||
错误隔离:
|
||
|
||
ts
|
||
|
||
```
|
||
for interceptor in interceptors:
|
||
try {
|
||
event = interceptor.beforeSend(event)
|
||
} catch (e) {
|
||
log.warn("interceptor error: ", e)
|
||
}
|
||
```
|
||
|
||
示例内置插件:
|
||
|
||
- DebugInterceptor:
|
||
- beforeSend 打印事件 JSON(受 debug 开关控制);
|
||
- CommonTagsInterceptor:
|
||
- 自动在 customTags 注入 `_sdk_version`、`_platform` 等。
|
||
|
||
------
|
||
|
||
## 五、落地实施建议(简短)
|
||
|
||
### 5.1 Phase 1 任务拆分(多端并行)
|
||
|
||
- 架构公共约定 & 协议定义(1~2 天)
|
||
- Flutter SDK Phase 1 实现(约 5~10 个工作日)
|
||
- Android SDK Phase 1 实现(约 5~10 天)
|
||
- iOS SDK Phase 1 实现(约 5~10 天)
|
||
- 后端联调 & Demo App 验证(3~5 天)
|
||
|
||
### 5.2 后续 Phase 2/3 可以按版本迭代
|
||
|
||
- v1.x:只做 Phase 1,快速在一个业务 App 落地;
|
||
- v2.x:引入配置拉取 + 校验 + Debug 面板;
|
||
- v3.x:按需要逐步加上策略控制 & 插件。 |