4.8 KiB
4.8 KiB
ReadingEvent V2 协议
概述
V2 阅读事件协议定义了一套完整的学习行为采集框架:阅读会话 → 事件生成 → buffer 暂存 → export 导出 → ack 确认。
与 V1 的区别:
- V1:无 session,无 ack,无 id 追踪
- V2:session 管理 + 全局事件 buffer + export/ack 确认 + crash recovery
核心概念
ReadingSessionV2
一次阅读会话。iOS App 一次打开资料即创建一个 session。
pub struct ReadingSessionV2 {
pub client_session_id: String, // UUID v4,会话标识
pub material: ReadingMaterialRef, // 被阅读的资料
pub started_at_ms: i64, // 会话开始时间
pub last_event_at_ms: i64, // 最后一次事件时间
pub next_sequence: u64, // 下一个事件序号(从 1 开始)
pub total_active_seconds: u32, // 累计活跃阅读秒数
pub last_position: Option<ReadingPosition>, // 最后位置
pub status: ReadingSessionStatus, // Active/Paused/Closed
}
生命周期
start → Active → (pause → Paused → resume → Active)* → close → Closed
- Active:可推送任意类型事件
- Paused:仅可推送 delta=0 事件(PositionChanged/MarkedAsRead)
- Closed:不可推送事件,不可重新打开
ReadingMaterialRef
pub struct ReadingMaterialRef {
pub material_id: String,
}
Rust 不存储 readingTargetType(如 knowledge/material/course/note),由 iOS 上传时补充。
ReadingEventV2
pub struct ReadingEventV2 {
pub event_id: String, // UUID v4,全局唯一事件 ID
pub client_session_id: String, // 关联的会话 ID
pub material_id: String, // 资料 ID
pub event_type: ReadingEventTypeV2, // 事件类型
pub position: Option<ReadingPosition>, // 阅读位置(camelCase JSON)
pub active_seconds_delta: u32, // 距上次事件的活跃秒数
pub timestamp_ms: i64, // 客户端时间戳
pub sequence: u64, // session 内递增序号(1-based)
}
eventType 取值
| Rust | API JSON |
|---|---|
MaterialOpened |
material_opened |
MaterialClosed |
material_closed |
PositionChanged |
position_changed |
Heartbeat |
heartbeat |
MarkedAsRead |
marked_as_read |
activeSecondsDelta 规则
| 事件 | delta |
|---|---|
| MaterialOpened | 0 |
| PositionChanged | 0 |
| MarkedAsRead | 0 |
| Heartbeat | ActiveTimeTracker.tick() 返回值 |
| MaterialClosed | ActiveTimeTracker.close() 返回值 |
ActiveTimeTracker
iOS 控制 tick 节奏,Rust 计算 delta:
start(ts) → tick(ts+15s) → tick(ts+30s) → close(ts+43s)
delta=15 delta=15 delta=13
- 暂停时不累计时间
- 时间倒退返回 0
- 余数毫秒累积到下次 tick
EventBuffer 状态机
push
│
▼
┌─ Pending ──┐
│ │
export() reload_stale()
│ │
▼ │
Exported ───────┘
│
┌─────┴─────┐
│ │
ack() mark_failed()
│ │
▼ ▼
(removed) Failed
│
export() ──→ 重试
- Pending:新事件,等待导出
- Exported:已导出,等待 ack。crash 后
reload_stale_events()恢复为 Pending - Failed:上传失败,下次 export 重试
- Ack 后删除:从 buffer 移除
溢出驱逐
Buffer 容量:1000 条。满时驱逐顺序:
- Failed(最早失败的)
- Exported(最早导出的)
- 最早 Pending
API 上传字段映射
| Rust ReadingEventV2 | API ReadingEventUploadItem | 来源 |
|---|---|---|
| event_id | eventId | Rust UUID |
| client_session_id | clientSessionId | Rust UUID |
| material_id | materialId | Rust 保存 |
| event_type | eventType | Rust→snake_case |
| position | position | Rust(camelCase JSON,clamped) |
| active_seconds_delta | activeSecondsDelta | ActiveTimeTracker |
| timestamp_ms | clientTimestampMs | Rust |
| sequence | sequence | session 内递增 |
| — | readingTargetType | iOS 补充 |
| — | platform | iOS 补充 |
| — | appVersion | iOS 补充 |
| — | clientTimezoneOffsetMinutes | iOS 补充 |
iOS 上传流程
1. Rust export_pending_events_v2(limit, timestamp) → Vec<ReadingEventV2>
2. iOS 遍历事件,补充 readingTargetType/platform/appVersion/timezone
3. iOS 构造 API 请求体
4. POST /reading/events
5. 成功 → Rust ack_events_v2(eventIds)
6. 失败 → Rust mark_events_failed_v2(eventIds)
7. App 启动 → Rust reload_stale_events_v2()