108 lines
2.4 KiB
Markdown
108 lines
2.4 KiB
Markdown
# 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)
|
||
- 网络失败时保留队列,下次重试
|
||
- 离线时事件不丢失,恢复网络后继续上传
|