ios-projects/docs/v2-event-pipeline.md
wangdl 08b4c21ca2 docs: add V2 event pipeline architecture doc (IOS-INFO-027)
- Architecture: iOS export → Rust EventBuffer → API batch upload
- Event types, sequence numbers, ACK mechanism
- Session state machine, error handling, performance comparison
- iOS UploadScheduler integration, app lifecycle handling

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-18 14:23:51 +08:00

176 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# V2 事件 Pipeline 架构
## 1. 概述
V2 事件 Pipeline 是 Document RuntimeRust FFI+ iOS Swift 层协作的阅读行为采集链路。
与 V1 的区别:
- V1每个事件独立 pushiOS 手动管理 buffer
- V2Rust 层内置 EventBuffer + 序列号 + ACK 机制iOS 仅负责定时 export
---
## 2. 架构
```
┌──────────────────────────────────────────────────┐
│ iOS Swift │
│ UploadScheduler (30s timer) │
│ → documentRuntime.exportPending() │
│ → POST /api/reading-events/batch │
│ → documentRuntime.ackExported(count) │
└──────────────────┬───────────────────────────────┘
│ FFI (uniffi)
┌──────────────────▼───────────────────────────────┐
│ Rust: zx_document_core │
│ │
│ ┌─ push_opened() │
│ ├─ push_heartbeat_v2(position?) │
│ ├─ push_position_changed() │
│ ├─ push_closed() │
│ ├─ push_marked_as_read() │
│ └─ mark_events_failed_v2(ids) │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ EventBuffer (Mutex<Vec>) │ │
│ │ - max 1000 events │ │
│ │ - auto-increment seq │ │
│ │ - session state tracking │ │
│ └──────────────────────────┘ │
│ │ │
│ ▼ │
│ export_pending() → Vec<BufferedEvent> │
│ clear_exported(count) │
│ mark_events_failed_v2(ids) │
└──────────────────────────────────────────────────┘
```
---
## 3. 事件类型
| 事件 | 触发时机 | 携带数据 |
|------|----------|----------|
| `opened` | `openMaterial()` | materialId |
| `heartbeat` | 定时器 (30s) | position? |
| `position_changed` | 滚动/翻页 | position |
| `closed` | `closeMaterial()` | totalActiveMs |
| `marked_as_read` | `markAsRead()` | — |
| `failed` | 异常/超时 | errorCode |
---
## 4. 序列号与 ACK
```
每个事件分配递增序列号session 内自增)
iOS export 获得 Vec<(seq, event)>
上传 API 成功 → ACK exported count → Rust 移除已确认事件
上传失败 → mark_events_failed_v2(ids) → 事件保留,下次重试
```
### 4.1 export → ACK 流程
```
1. iOS 调用 exportPending() → Vec<BufferedEvent>
2. iOS 将 events 序列化为 JSONPOST /api/reading-events/batch
3. 成功 → clearExported(count) → Rust 移除前 count 条
4. 失败 → 事件保留在 buffer下次 export 仍包含
```
### 4.2 会话恢复
```
App 重启 → SessionManager.reload_stale_events()
→ 将上次未 ack 的事件标记为 failed
→ 返回可重试的事件列表
```
---
## 5. iOS 集成
### 5.1 UploadScheduler
```swift
class UploadScheduler {
let interval: TimeInterval = 30
func start() {
Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in
Task { await self.flush() }
}
}
func flush() async {
let events = documentRuntime.exportPending()
guard !events.isEmpty else { return }
do {
try await api.uploadEvents(events)
documentRuntime.clearExported(events.count)
} catch {
// Events remain in buffer for retry
}
}
}
```
### 5.2 App 生命周期
```swift
// sceneDidEnterBackground
UploadScheduler.shared.flush() // 强制上传
// App 启动
RuntimeRecoveryService.shared.recover() // 恢复未完成会话
```
---
## 6. Session 状态机
```
openMaterial()
┌─ idle ─────────────────► opened
│ │
│ heartbeat/
│ position_change
│ │
│ ▼
│ reading
│ │
│ closeMaterial()
│ │
└──────── closed ◄───────────┘
```
---
## 7. 错误处理
| 场景 | 处理 |
|------|------|
| Buffer 满1000条 | 拒绝新事件iOS 应触发 flush |
| FFI 调用失败 | fallback 到 V1 NoopReadingRuntimeAdapter |
| 网络不可用 | 事件保留,下次 export 包含未 ACK 事件 |
| Session 超时(无活动 30min | cleanup_stale_sessions_v2 自动清理 |
---
## 8. 性能特征
| 指标 | V1 | V2 |
|------|:--:|:--:|
| FFI 调用频率 | 每事件 1 次 | 每批次 1 次 |
| 内存占用 | ~50 events × iOS heap | ~1000 events × Rust heap |
| 序列号 | 无 | Session 内自增 |
| ACK 粒度 | 无 | Batch 级别 |
---
## 9. 相关文档
- [阅读信息采集接入文档](./reading-info-integration.md)
- [Rust FFI 绑定更新流程](./rust-ffi-update.md)