# Reading Event Protocol ## 概述 Rust Core 生成阅读事件,App 负责收集和上传。这是一个单向数据流:Rust → App → Backend。 ## 事件类型 ```rust enum ReadingEvent { MaterialOpened { material_id, timestamp_ms }, MaterialClosed { material_id, timestamp_ms, active_seconds }, PositionChanged { material_id, position, timestamp_ms }, Heartbeat { material_id, active_seconds, position?, timestamp_ms }, MarkedAsRead { material_id, timestamp_ms }, } ``` ## 事件说明 ### MaterialOpened 用户打开一份资料时触发。 ```json { "type": "material_opened", "material_id": "abc123", "timestamp_ms": 1717100000000 } ``` ### MaterialClosed 用户关闭资料时触发,`active_seconds` 为本次打开的累计活跃秒数。 ```json { "type": "material_closed", "material_id": "abc123", "timestamp_ms": 1717100300000, "active_seconds": 285 } ``` ### PositionChanged 用户滚动/翻页/缩放导致阅读位置变化。频率由 App 控制(建议每 5 秒或停止交互后触发)。 ```json { "type": "position_changed", "material_id": "abc123", "timestamp_ms": 1717100100000, "position": { "type": "markdown", "block_id": "heading-3", "scroll_progress": 0.45 } } ``` ### Heartbeat 定时心跳,用于计算阅读时长。即使位置未变化也应周期性触发(建议 10-15 秒)。 ```json { "type": "heartbeat", "material_id": "abc123", "timestamp_ms": 1717100150000, "active_seconds": 15, "position": null } ``` ### MarkedAsRead 用户手动标记已读。 ```json { "type": "marked_as_read", "material_id": "abc123", "timestamp_ms": 1717100400000 } ``` ## 事件收集流程 ```text 1. App 打开资料 → Rust 生成 MaterialOpened 2. App 启动定时器(~15s) 3. 每次定时器触发 → Rust 生成 Heartbeat 4. 用户交互(滚动/翻页)→ App 调用 update_position → Rust 生成 PositionChanged 5. App 关闭资料 → Rust 生成 MaterialClosed(含 active_seconds) 6. App 定期调用 export_pending_events() 获取所有未导出事件 7. App POST 事件到后端 /reading/events 8. App 调用 clear_exported_events() 清空缓冲区 ``` ## App 侧实现要点 - 事件应在本地缓存(内存队列),不要一次传一个 - 批量上传(每次 5-20 条)或定时上传(每 30s) - 网络失败时保留队列,下次重试 - 离线时事件不丢失,恢复网络后继续上传