ios-projects/docs/reading-info-integration.md
wangdl 6c2f742676 docs: add iOS reading info integration guide (IOS-INFO-026)
- Architecture diagram: Swift UI → SessionManager → Collector → Queue → API
- Core components: Context, SessionManager, Events, Position, UploadQueue
- API protocol: batch upload, progress query, continue-learning, summary
- Path resolution, marked-read store, app lifecycle, error handling

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

7.6 KiB
Raw Blame History

iOS 阅读信息采集接入文档

1. 概述

AIStudyApp 通过 Rust FFIzx_document_core和自有 Swift 层采集用户阅读行为数据,上传至 API Server为 AI 分析和个性化推荐提供数据基础。

数据流Swift UI → ReadingRuntimeSessionManager → ReadingEventCollector → ReadingEventUploadQueue → API Server


2. 架构

┌─────────────────────────────────────────────────────┐
│ SwiftUI Layer                                        │
│  MaterialReaderView → open/close/heartbeat/position │
└──────────────────┬──────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────┐
│ ReadingRuntimeSessionManager (Singleton)             │
│  职责:生命周期管理、状态机、适配器路由              │
│  状态idle → opened → reading → closed             │
└──────────────────┬──────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────┐
│ ReadingEventCollector (Singleton)                    │
│  职责:事件缓冲、去重、序列化                        │
│  事件类型opened / heartbeat / position_changed /   │
│            closed / marked_as_read / failed          │
└──────────────────┬──────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────┐
│ ReadingEventUploadQueue (Singleton)                  │
│  职责批量上传、重试、ACK 确认                      │
│  上传间隔30s默认                               │
│  最大批次50 条                                     │
└──────────────────┬──────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────┐
│ ReadingAPIService (APIClient)                        │
│  端点POST /api/reading-events/batch                │
│        GET  /api/reading/progress/:id                │
└─────────────────────────────────────────────────────┘

3. 核心组件

3.1 ReadingMaterialContext

打开阅读器时需要提供的上下文:

struct ReadingMaterialContext {
    let readingTargetType: ReadingTargetType  // .material, .knowledgeSource
    let materialId: String
    let knowledgeBaseId: String?
    let userId: String?
}

3.2 ReadingRuntimeSessionManager

方法 说明
openMaterial(_ ctx:) 打开阅读会话,触发 opened 事件
closeMaterial() 关闭会话,触发 closed 事件
updatePosition(_ pos:) 更新阅读位置,触发 position_changed 事件
sendHeartbeat() 心跳,更新活跃时间
markAsRead() 标记已读
adapter V1/V2 适配器切换

3.3 ReadingEvent

enum ReadingEvent {
    case opened(materialId: String, timestamp: UInt64)
    case heartbeat(materialId: String, position: ReadingPosition?, timestamp: UInt64)
    case positionChanged(materialId: String, position: ReadingPosition, timestamp: UInt64)
    case closed(materialId: String, totalActiveMs: UInt64, timestamp: UInt64)
    case markedAsRead(materialId: String, timestamp: UInt64)
    case failed(materialId: String, errorCode: String, timestamp: UInt64)
}

3.4 ReadingPosition

enum ReadingPosition {
    case markdown(blockId: String, scrollProgress: Float)
    case text(lineNumber: UInt32, scrollProgress: Float)
    case pdf(pageNumber: UInt32, pageProgress: Float, overallProgress: Float)
    case image(zoomScale: Float, offsetX: Float, offsetY: Float)
    case epub(chapterId: String, chapterProgress: Float, overallProgress: Float)
    case unknown
}

4. 上传协议

4.1 批量上传

POST /api/reading-events/batch
Authorization: Bearer <USER_JWT>

{
  "events": [
    {
      "eventType": "position_changed",
      "materialId": "mat-001",
      "position": { "type": "Markdown", "blockId": "b1", "scrollProgress": 0.5 },
      "timestampMs": 1700000000123,
      "sessionId": "sess-abc"
    }
  ]
}

4.2 阅读进度查询

GET /api/reading/progress/:materialId?targetType=knowledge_source
Authorization: Bearer <USER_JWT>

Response 200:
{
  "materialId": "mat-001",
  "targetType": "knowledge_source",
  "status": "in_progress",
  "totalActiveSeconds": 300,
  "isMarkedRead": false,
  "lastReadAt": "2026-06-18T12:00:00Z",
  "lastPosition": { "type": "Markdown", "blockId": "b1", "scrollProgress": 0.5 }
}

5. 位置恢复

5.1 继续学习

GET /api/reading/continue-learning
Authorization: Bearer <USER_JWT>

Response 200:
{
  "type": "knowledge_source",
  "materialId": "mat-001",
  "title": "Machine Learning Basics",
  "lastProgress": 0.5,
  "totalActiveSeconds": 600,
  "lastReadAt": "2026-06-18T10:00:00Z",
  "lastPosition": { "type": "Markdown", "blockId": "b1", "scrollProgress": 0.5 }
}

5.2 学习摘要

GET /api/reading/learning-summary
Authorization: Bearer <USER_JWT>

Response 200:
{
  "todaySeconds": 3600,
  "weekSeconds": 7200,
  "totalSeconds": 50000,
  "activeDays": 5,
  "sessionsCount": 12,
  "materialsReadCount": 3,
  "markedReadCount": 1,
  "dailyAverageSeconds": 1200
}

6. 文件路径解析

MaterialPathResolver 负责将 API 返回的 materialId 解析为本地文件路径:

优先级 策略 路径
1 Knowledge Source Documents/knowledge_sources/<id>/<filename>
2 Documents 目录 Documents/<materialId>
3 Caches 目录 Caches/<materialId>

7. 已读标记

MarkedReadStore 提供离线已读标记UserDefaults 持久化):

  • mark(materialId) — 标记已读
  • unmark(materialId) — 取消标记
  • contains(materialId) — 查询是否已读

8. App 生命周期

事件 处理
sceneDidEnterBackground UploadScheduler.flush() 强制上传
sceneWillEnterForeground RuntimeRecoveryService 恢复未完成会话
applicationWillTerminate 关闭所有活跃 Session + flush

9. 错误处理

场景 策略
网络不可用 事件缓冲在本地队列,最多保留 1000 条
上传失败 指数退避重试1s→2s→4s→8s→30s
ACK 丢失 未 ACK 事件下次上传时重发
Rust FFI 崩溃 V1 fallback 适配器NoopReadingRuntimeAdapter

10. 相关文档