ios-projects/docs/system-map/capabilities/02-学习信息收集.md

445 lines
27 KiB
Markdown
Raw Permalink 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.

# 02 学习信息收集 — 跨项目完整能力分析
> 试点能力二:从 C 端业务能力"学习信息收集"出发,跨项目完整整理。
> 项目角色分类主链路iOS、Rust Document Runtime、主 API、数据库、上游支撑无直接上游、下游消费Heavy Runtime、运维可观测Admin
---
## 1. 能力目标
自动采集用户在学习过程中的所有行为数据(阅读行为、学习时长、阅读位置、标记已读等),经过 Rust 内存缓冲、iOS 本地持久化队列、批量上传、服务端去重验证、聚合计算后,为学习分析、继续学习推荐提供数据基础。
---
## 2. C 端入口
学习信息收集是一个**用户不可见的自动能力**。用户不需要主动触发,系统在以下场景自动采集:
| 触发场景 | 收集的数据 | iOS 页面 | 驱动方式 |
|---------|-----------|---------|---------|
| 用户打开一份资料 | material_opened 事件 | 资料阅读页 | iOS 调用 Rust push |
| 用户滚动阅读内容 | position_changed 事件2 秒防抖) | 资料阅读页 | iOS 调用 Rust push |
| iOS 15 秒定时器触发 | heartbeat 事件 | 资料阅读页(自动) | iOS Timer → Rust heartbeat 接口 |
| 用户关闭/离开资料 | material_closed 事件 | 资料阅读页(自动) | iOS 调用 Rust close → push |
| 用户点击"标记已读" | marked_as_read 事件 | 资料阅读页 | iOS 调用 Rust push |
| 用户开始专注学习 | 学习会话计时开始 | 学习会话页 | iOS 本地计时器(用户主动触发) |
用户在分析页和学习主页**消费**这些数据的结果,但数据采集本身是透明的。
---
## 3. 用户主流程
### 3.1 数据采集阶段iOS + Rust Document Runtime
1. 用户打开资料进入阅读页。iOS 调用 Rust start_reading_sessionRust 创建 V2 自动阅读会话,生成唯一的 client_session_id。iOS 调用 Rust push_material_opened事件进入 Rust 内存缓冲区Pending
2. 阅读过程中:
- 用户滚动时iOS 以 2 秒防抖调用 Rust push_position_changed。
- iOS 创建并管理 15 秒心跳定时器。仅在阅读会话为 active 且 App 位于前台时触发。每次 TickiOS 调用 Rust heartbeat 接口Rust 根据 ActiveTimeTracker 计算 activeSecondsDelta创建 heartbeat 事件并写入缓冲区。
- Rust 不创建也不管理 iOS 的系统定时器。
3. 用户点击"标记已读"iOS 本地更新 MarkedReadStore同时调用 Rust push_marked_as_read。
4. 用户离开资料iOS 调用 Rust close_reading_sessionRust 计算最终活跃时长增量,推送 material_closed 事件。
### 3.2 Rust 内存缓冲与交接阶段Rust → iOS 本地队列)
5. 所有事件暂存在 Rust 内存缓冲区中,状态为 Pending。
6. iOS 周期性地调用 Rust export_pending_events_v2 导出事件(事件状态变为 Exported。**Rust 缓冲区不是网络上传队列**——导出仅完成从 Rust 到 iOS 的内存交接。
7. iOS 将导出的原始事件映射为 UploadItem补充 knowledgeBaseId、platform、appVersion、timezone 等元数据)。
8. iOS 将 UploadItem 成功写入持久化 ReadingEventUploadQueueJSON 文件)后,**立即**调用 Rust ack_events_v2。Rust 从内存缓冲区删除已完成交接的事件。
9. 后续 API 上传完全由 iOS 本地持久化队列负责。API 上传失败时,只更新本地队列条目的 failed/dead/retry 状态,不再调用 Rust markFailed。
### 3.3 上传阶段iOS 本地持久化队列 → API
10. iOS 上传队列定期调用 flush从本地持久化队列中取出事件批量 POST /learning/reading-events/batch每批最多 100 条)。
11. 上传中的条目状态为 uploading。API 上传成功后,从 iOS 本地队列删除该条目记录。
12. 上传失败时:
- 401未授权暂停队列保留 pending等待 Token 刷新
- 403/404禁止/不存在标记条目为永久失败dead
- 其他错误:标记条目为 failed进入指数退避重试5s × 2^n最多 3 次,最长 5 分钟)
- 重试耗尽:条目进入 dead 状态
### 3.4 崩溃恢复的三种场景
13. **export 后、本地写入前崩溃:** Rust reload_stale_events_v2 将所有 Exported 未 Ack 事件重置为 Pending。下次启动重新导出。
14. **本地写入成功、Ack 前崩溃:** 重新启动后从 Rust 重新导出(含已持久化但未 Ack 的事件。API 端通过 eventId 去重。iOS 再次 Ack 完成交接。
15. **Ack 后、API 上传前崩溃:** 事件已安全在 iOS 本地持久化队列中Rust 不再参与。重启后本地队列继续上传。
### 3.5 服务端处理阶段API
16. API 收到事件批次后,逐条处理每个事件。单条事件处理在一个数据库事务中完成:
- **验证:** 校检事件类型、目标类型、活跃时长(上限 300 秒)、时间戳。
- **去重:** 检查 userId + eventId 是否已处理,已处理的标记为 duplicate。
- **权限校验:** 验证材料存在、属于当前用户、未删除、未过期。
- **事件入库:** 插入 ReadingEvent 行。
- **自动阅读会话聚合:** Upsert LearningSession按 clientSessionId增量累加 totalActiveSeconds
- **进度聚合:** Upsert MaterialReadingProgress按 userId+materialId
- **每日活动聚合:** Upsert DailyLearningActivity按 userId+localDate时区感知
- **学习记录:** 为里程碑事件opened/closed/marked_as_read插入 LearningRecord。
- **标记完成:** 事件 status 更新为 processed。
### 3.6 数据消费阶段iOS 查询)
17. 用户在分析页和学习主页看到的结果:
- **继续学习:** GET /learning/continue → 最近未读完的资料。
- **学习摘要:** GET /learning/summary → 今日/本周/总计分钟、活跃天数。
- **阅读趋势:** GET /learning/trend?days=7 → 每日阅读时长序列。
- **连续天数:** GET /activity/streak → 当前连续、最长连续。
- **活动摘要:** GET /activity/summary → 总分钟、复习卡片、活跃天、日均。
- **学习记录:** GET /learning/records → 分页学习记录列表。
---
## 4. 项目参与关系
### iOSAIStudyApp— 主链路参与者
**职责:**
- 驱动 Rust Document Runtime 的自动阅读会话和事件采集
- 创建并管理 15 秒心跳定时器(仅在 session active 且前台时触发,每次 Tick 调用 Rust heartbeat 接口)
- 管理阅读位置本地缓存和标记已读本地缓存
- 管理本地上传队列(独立于 Rust 缓冲区的网络上传队列事件映射、JSON 持久化、重试策略、从队列删除已上传条目
- Rust → iOS 事件交接export → UploadItem 映射 → 持久化写入 → **立即 Ack**(不等待 API 上传)
- 提供网络恢复自动上传NetworkMonitor 监听)
- 崩溃恢复App 重启时调用 RuntimeRecoveryService
- 提供专注学习会话页的本地计时
- 查询并展示学习分析数据
### Rust Document Runtimezhixi-document-runtime— 主链路参与者
**职责:**
- V2 自动阅读会话管理start/pause/resume/close/interrupt
- 活跃时间跟踪器ActiveTimeTracker由 iOS heartbeat tick 驱动计算 activeSecondsDelta
- 5 种阅读事件类型的创建和推送opened/closed/position_changed/heartbeat/marked_as_read
- 事件内存缓冲区管理Pending → Exported 状态机)
- 事件导出export_pending_events_v2将事件从内存交接给 iOS
- 事件确认ack_events_v2iOS 本地持久化写入成功后立即调用Rust 删除已交接事件
- 崩溃恢复stale event reloadExported → Pending、stale session cleanup
- 缓冲区容量管理(最大 1000 条,溢出时按优先级淘汰)
- 阅读位置模型
**不负责:** 网络请求、API 调用、事件上传、iOS 系统定时器创建和管理。
### 主 APIapi-server— 主链路参与者
**职责:**
- 接收阅读事件批量上传POST /learning/reading-events/batch每批最多 100 条)
- 单条事件处理管线(验证→去重→权限→入库→聚合→记录→标记)
- 事件去重userId + eventId 唯一约束)
- 自动阅读会话聚合(按 clientSessionId upsert自动处理 completed/interrupted 状态)
- 资料阅读进度聚合(按 userId + materialId upsert
- 每日学习活动聚合(按 userId + localDate upsert时区感知
- 学习记录创建(里程碑事件记录)
- 异常检测和警告代码
- 提供学习数据查询端点(/learning/*, /activity/*
- 提供内部管理端点(事件重放、失败事件重处理)
### Heavy Runtime — 下游数据消费者
**不直接参与学习信息收集。** 消费已聚合的学习数据,生成 AI 分析结果(掌握度评估、薄弱点识别、学习建议、测验题目、闪卡)。位于数据采集链的下游,不属于学习信息采集链路。
### Python RAG Worker — 上游支撑(不直接参与)
**不直接参与学习信息收集。** 负责文档导入后的预处理(解析→分块→嵌入→索引),产生的知识库结构化数据是阅读的前提,但不参与阅读行为的采集、传输和聚合。
### Adminadmin-projects— 运维与可观测
**不属于学习信息采集链路的同步环节。** 职责包括:
- 学习数据总览仪表板
- 阅读事件/会话/进度/活动/记录查询
- 用户时间线与诊断
- 异常数据页(失败/警告/重复事件)
- 事件重放(单条 + 批量)
- 学习数据重算
- 数据导出
---
## 5. 项目间调用关系
### 5.1 数据采集链路iOS → Rust Document Runtime
| 调用目的 | 方向 | 业务结果 |
|---------|------|---------|
| 开启自动阅读会话 | iOS → Rust | 创建 V2 ReadingSession返回 client_session_id |
| 推送阅读事件opened/closed/position_changed/marked_as_read | iOS → Rust | 事件进入内存缓冲区Pending |
| 心跳 Tick | iOS → Rust | Rust 计算 activeSecondsDelta创建 heartbeat 事件写入缓冲区 |
| 活跃时间累计 | iOS → Rust | ActiveTimeTracker 由 iOS heartbeat tick 驱动,暂停时停止 |
| 导出待交接事件 | iOS → Rust | 返回 Exported 状态的事件列表 |
| 确认事件交接完成 | iOS → Rust | iOS 本地队列写入成功后立即 AckRust 删除已交接事件 |
| 崩溃恢复 | iOS → Rust | Exported 未 Ack 事件重置为 Pending清理超时 24h 的过期会话 |
| 暂停/恢复/关闭会话 | iOS → Rust | 会话状态切换,时间跟踪同步 |
### 5.2 数据上传链路iOS 本地持久化队列 → API
| 调用目的 | 方向 | 业务结果 |
|---------|------|---------|
| 批量上传阅读事件 | iOS 本地队列 → API | POST /learning/reading-events/batch返回 processed/duplicate/failed |
| 查询阅读进度 | iOS → API | GET /materials/:id/reading-progress |
| 查询继续学习 | iOS → API | GET /learning/continue |
| 查询学习摘要 | iOS → API | GET /learning/summary |
| 查询学习趋势 | iOS → API | GET /learning/trend?days=N |
| 查询学习记录 | iOS → API | GET /learning/records |
| 查询活动摘要/连续天数/推荐 | iOS → API | GET /activity/summary, /activity/streak, /activity/recommendations |
### 5.3 数据处理链路API 内部)
| 调用目的 | 方向 | 业务结果 |
|---------|------|---------|
| 事件批处理 | API Controller → Event Processor | 逐条处理,单条一个 DB 事务 |
| 事件去重 | Event Processor → DB | 检查 userId+eventId 唯一性 |
| 材料权限校验 | Event Processor → DB | 验证存在性、所有权、状态 |
| 自动阅读会话聚合 | Event Processor → DBLearningSession | Upsert by clientSessionId |
| 进度聚合 | Event Processor → DBMaterialReadingProgress | Upsert by userId+materialId |
| 每日活动聚合 | Event Processor → DBDailyLearningActivity | Upsert by userId+localDate |
| 学习记录创建 | Event Processor → DBLearningRecord | 里程碑事件插入 |
| 事件重放 | API → Event Processor | 重新处理失败事件 |
| 数据重算 | API → LearningActivityService | 用户或全局学习数据重新计算 |
### 5.4 Admin 查询链路Admin → API运维可观测
| 调用目的 | 方向 | 业务结果 |
|---------|------|---------|
| 学习仪表板 | Admin → API | 总览 KPI |
| 阅读事件/会话/进度/活动/记录列表 | Admin → API | 分页列表(多条件筛选+排序) |
| 用户时间线与诊断 | Admin → API | 时序视图+完整诊断 |
| 异常检测 | Admin → API | 失败/警告/重复事件列表 |
| 事件重放/数据重算/导出 | Admin → API | 运维操作 |
---
## 6. 核心业务数据
### 6.1 阅读事件ReadingEvent
- **谁创建:** Rust Document Runtime在 iOS 端内存中创建API 入库MySQL
- **谁更新:** API 更新处理状态processing → processed/failed/duplicate和警告代码。
- **谁查询:** API 在批处理时查重Admin 在事件列表和异常检测页查询。
- **用于什么功能:** 所有学习分析的原始数据源。每条事件包含 eventId、clientSessionId、materialId、eventType、position、activeSecondsDelta、timestampMs、sequence、platform、appVersion、timezoneOffset。
### 6.2 自动阅读会话LearningSessionAPI 聚合自 ReadingEvent
- **谁创建:** API 在收到第一个 material_opened 事件时通过 upsert 创建(按 clientSessionId
- **谁更新:** API 在每次事件处理中 upsert增量 totalActiveSeconds、更新 lastPosition、更新 lastEventAt。material_closed 事件触发状态变为 completed。30 分钟无新事件自动标记为 interrupted。
- **谁查询:** iOS 学习主页(通过继续学习 API 间接使用Admin 在学习会话页和用户诊断页查询。
- **用于什么功能:** 自动阅读会话的服务端聚合记录。注意:这是 API 根据 Rust V2 ReadingSession 产生的事件聚合而来的服务端数据对象,与客户端 Rust 内存中的 ReadingSession 是不同的存储层对象。
### 6.3 专注学习会话LearningSessionView 本地计时)
- **谁创建:** 用户主动点击"开始学习"按钮后iOS 创建本地计时器。
- **谁更新:** 用户操作暂停/继续/结束(含确认弹窗),计时器以 1 秒精度运行。
- **谁查询:** iOS 学习会话页展示当前进度。
- **用于什么功能:** 用户主动发起的专注学习时段计时。**与自动阅读会话是不同的业务对象。** 当前 iOS 是否调用专注学习会话 APILearningSessionService持久化仍标记为"暂未确认"。
- **关键区别:** 自动阅读会话随进入/离开资料自动产生;专注学习会话由用户主动开始/结束。自动阅读会话已通过事件链路上报至 API专注学习会话的 API 持久化调用链待确认。
### 6.4 资料阅读进度MaterialReadingProgress
- **谁创建:** API 在收到某资料第一个事件时 upsert 创建。
- **谁更新:** API 每次事件处理中 upsert。累计 totalActiveSeconds、sessionCount。更新 lastPosition、lastProgress。marked_as_read 时设置 isMarkedRead=true、status=read。
- **谁查询:** iOS 在进入阅读页时查询Admin 在阅读进度页查询。
- **用于什么功能:** 阅读位置恢复、"继续学习"推荐、阅读进度展示。
### 6.5 每日学习活动DailyLearningActivity
- **谁创建:** API 在收到某用户某日第一个事件时 upsert 创建。
- **谁更新:** API 每次事件处理中 upsert。增量累加 durationSeconds、readingSeconds、materialsReadCount、markedReadCount。日期按用户时区clientTimezoneOffsetMinutes计算。
- **谁查询:** API 在学习摘要/趋势/活跃热力图/连续天数计算中查询Admin 在每日活动页查询。
- **用于什么功能:** 日级别的学习活动聚合、趋势分析、连续天数计算、分析页数据。
### 6.6 学习记录LearningRecord
- **谁创建:** API 为里程碑事件material_opened/closed/marked_as_read创建。
- **谁更新:** 创建后不可修改。
- **谁查询:** iOS 在分析页查询(最近 5 条Admin 在学习记录页查询。
- **用于什么功能:** 用户学习时间线、分析页"最近学习记录"展示。
### 6.7 继续学习状态ContinueLearning
- **谁创建:** API 实时计算(查询最近阅读的未完成资料)。
- **谁更新:** 不持久化(每次查询实时计算)。
- **谁查询:** iOS 学习主页(优先级算法首选项)。
- **用于什么功能:** 用户快速回到上次阅读位置继续学习。
### 6.8 学习摘要LearningSummary
- **谁创建:** API 实时聚合(查询 DailyLearningActivity
- **谁更新:** 不持久化(每次查询实时聚合)。
- **谁查询:** iOS 学习主页和学习分析页。
- **用于什么功能:** 今日/本周/总计的阅读分钟、活跃天数等汇总展示。
### 6.9 学习趋势LearningTrend
- **谁创建:** API 实时计算(过去 N 天每日阅读时长序列)。
- **谁更新:** 不持久化。
- **谁查询:** iOS 分析页阅读时长趋势图。
- **用于什么功能:** 展示用户阅读时长的日间变化趋势。
---
## 7. 查询入口
### 用户在哪里查看
- **继续学习:** iOS 学习主页主操作卡片。
- **每周摘要:** iOS 学习主页每周摘要卡片。
- **学习分析:** iOS 学习分析页(掌握度/总分钟/复习卡片/活跃天/阅读统计/趋势图/连续统计/学习记录/推荐/AI 综合分析)。
- **资料阅读进度:** iOS 资料详情页(阅读进度卡片)。
### Admin 在哪里查看(运维可观测)
- **学习仪表板:** Admin 学习数据→总览KPI 汇总)。
- **阅读事件列表:** Admin 学习数据→阅读事件页(按用户/材料/状态/类型筛选)。
- **学习会话列表:** Admin 学习数据→学习会话页。
- **阅读进度/每日活动/学习记录列表:** Admin 学习数据→对应页面。
- **用户时间线:** Admin 学习数据→用户时间线。
- **用户诊断:** Admin 学习数据→用户诊断。
- **异常数据:** Admin 学习数据→异常数据(失败/警告/重复三 Tab
- **数据导出:** Admin 学习数据→导出。
### 系统内部在哪里使用
- **活动趋势 AI 分析:** API GET /activity/trend → Heavy Runtime 生成学习趋势洞察Heavy Runtime 是下游消费者)。
- **推荐系统:** API GET /activity/recommendations → 学习主页优先级算法。
- **学习连续天数:** API GET /activity/streak → 连续天数计算。
- **活跃热力图:** API GET /activity/heatmap → 每日阅读时长热力图。
---
## 8. 修改入口
### 用户主动修改
- **标记已读:** iOS 资料阅读页工具栏按钮。本地 MarkedReadStore 即时生效marked_as_read 事件通过采集链路上报至 API。
- **取消标记已读:** 本地 MarkedReadStore 即时生效。服务端同步方式暂未确认(当前事件模型只有 marked_as_read没有 unmarked_as_read 事件类型)。
- **开始专注学习会话:** iOS 资料阅读页工具栏"开始学习"按钮。
- **结束专注学习会话:** iOS 学习会话页结束按钮(含确认弹窗)。
### Admin 人工修改(运维可观测)
- **事件重放:** Admin 单条或批量重放失败事件。
- **事件重处理:** API 内部端点批量重处理失败事件。
- **数据重算:** Admin 触发用户或全局学习数据重新计算。
- **数据导出:** Admin 导出原始事件或会话数据。
### 系统自动修改
- **事件去重:** API 自动检测并标记重复事件为 duplicate。
- **活跃时长封顶:** API 将超过 300 秒的 activeSecondsDelta 截断为 300 秒,添加警告。
- **时间戳偏差检测:** API 检测客户端时间戳与服务器时间的偏差(>5 分钟),添加警告。
- **乱序事件检测:** API 检测 sequence 不连续的事件,添加警告。
- **会话自动关闭:** API 在事件处理前自动将 30 分钟无新事件的活跃会话标记为 interrupted。
- **过期材料自动检测:** API 在事件处理时自动检测临时材料的过期状态。
- **崩溃恢复:** Rust + iOS 在 App 重启时自动恢复Rust reload + iOS 队列 reloadOnLaunch
### 系统配置修改
- **批次大小上限:** API 配置(每批最多 100 条)。
- **活跃时长上限:** API 硬编码 300 秒。
- **时间戳偏差阈值:** API 硬编码 5 分钟。
- **会话中断超时:** API 硬编码 30 分钟。
- **事件缓冲区上限:** Rust Document Runtime 硬编码 1000 条。
- **上传重试策略:** iOS 硬编码(指数退避 5s×2^n最多 3 次,最长 5 分钟)。
---
## 9. 异常与恢复
### 9.1 App 崩溃 / 系统杀死恢复
- **Rust 侧:** App 重启时调用 reload_stale_events_v2() → 所有 Exported 未 Ack 事件重置为 Pending。调用 cleanup_stale_sessions_v2(now_ms, 24h) → 清理超过 24 小时的过期会话。
- **iOS 侧:** RuntimeRecoveryService.run() 在 App 启动时执行。处理三种场景:
1. export 后、本地写入前崩溃 → Rust reload 恢复 Pending重新导出
2. 本地写入成功、Ack 前崩溃 → 重新导出后 API 通过 eventId 去重,再 Ack
3. Ack 后、API 上传前崩溃 → 本地持久化队列重启后继续上传
- **API 侧:** 下次事件处理时清理超过 30 分钟无新事件的活跃会话(标记为 interrupted
### 9.2 网络失败重试
- **iOS 本地队列:** 401 → 暂停pending等待 Token 刷新。403/404 → 永久失败dead。其他错误 → failed + 指数退避重试。重试耗尽 → dead。
- **NetworkMonitor** 检测到网络恢复时,自动触发本地队列 flush。
- **API 鉴权:** Token 过期时 APIClient 自动尝试 refresh成功后重试原请求一次。
### 9.3 事件去重
- **API 处理:** userId + eventId 唯一约束。重复事件跳过处理、不更新聚合数据,标记为 duplicate。
- **客户端保障:** Rust V2 每个事件有 UUID v4 的 eventId崩溃恢复时 eventId 不变,确保重传不会产生重复聚合。
### 9.4 事件乱序
- **API 处理:** 检测 sequence 不连续 → 添加 OUT_OF_ORDER_EVENT 警告。事件仍然正常处理(不拒绝)。通常由崩溃恢复后重传较早事件引起,不影响聚合结果(增量累加而非绝对值替换)。
### 9.5 活跃时长异常
- **API 处理:** activeSecondsDelta > 300 秒 → 截断为 300 秒,添加 ACTIVE_SECONDS_CAPPED 警告。Admin 异常页可筛选复核。
### 9.6 时间戳偏差
- **API 处理:** 客户端时间戳与服务器时差 > 5 分钟 → 添加 CLIENT_TIMESTAMP_SKEWED 警告,事件正常处理。
### 9.7 材料被删除或失去权限
- **API 处理:** 标记为 FATAL_MATERIAL_NOT_FOUND / FATAL_ACCESS_DENIED / FATAL_TEMPORARY_MATERIAL_EXPIRED。
- **Admin 恢复:** 无法恢复已删除材料的关联事件。
### 9.8 用户切换隔离
- **实现方式:** 所有 API 操作基于 JWT Token 中的 userId 进行用户隔离。
- **阅读事件:** 每个事件在 API 处理时使用当前登录用户的 userId 关联。
- **所有聚合数据:** 按 userId 分区存储和查询,用户只能看到自己的数据。
- **Admin** 可通过 userId 筛选查看任一用户的数据,需 admin 权限。
---
## 10. 当前完成情况
统一使用以下口径:
- **已完成:** 跨项目链路已接通,用户可以获得最终结果。
- **部分完成:** 只有部分项目、部分页面或部分场景完成。
- **占位实现:** 存在结构,但使用 Mock、硬编码或静态数据。
- **暂未确认:** 无法确认完整调用链。
| 组件 | 状态 | 说明 |
|------|------|------|
| Rust Document Runtime V2 自动阅读会话 | 已完成 | start/pause/resume/close/interrupt 完整生命周期 |
| Rust Document Runtime V2 事件采集5 种) | 已完成 | material_opened/closed/position_changed/heartbeat/marked_as_read |
| Rust 活跃时间跟踪器 | 已完成 | ActiveTimeTracker由 iOS heartbeat tick 驱动 |
| Rust 事件缓冲区管理 | 已完成 | Pending → Exportedexport/ack/reload |
| iOS 15 秒心跳定时器 | 已完成 | iOS 创建 Timer仅在 session active 且前台时触发Tick 调用 Rust heartbeat 接口 |
| Rust → iOS 事件交接链路 | 已完成 | export → 本地队列写入 → 立即 Ack交接在本地完成不依赖网络 |
| iOS 本地上传队列 | 已完成 | JSON 持久化,独立于 Rust 缓冲区的网络上传队列 |
| iOS 上传重试与网络恢复 | 已完成 | 指数退避 + NetworkMonitor 自动恢复 |
| iOS 阅读位置本地缓存 | 已完成 | ReadingPositionStore |
| iOS 标记已读本地缓存 | 已完成 | MarkedReadStore本地即时生效 |
| iOS 标记已读服务端同步 | 已完成 | marked_as_read 事件 → 上传链路 → API 聚合 |
| iOS 取消标记已读服务端同步 | 暂未确认 | 当前事件模型无 unmarked_as_readAPI 是否支持待确认 |
| iOS 崩溃恢复 | 已完成 | RuntimeRecoveryService + 本地队列 reloadOnLaunch |
| Rust 崩溃恢复 | 已完成 | stale event reload + stale session cleanup |
| API 事件批处理端点 | 已完成 | POST /learning/reading-events/batch |
| API 事件去重 | 已完成 | userId+eventId 唯一约束 |
| API 事件验证 | 已完成 | 类型校验 + 时长上限 + 时间戳检测 + 乱序检测 |
| API 权限校验 | 已完成 | 材料存在性、归属、状态、过期校验 |
| API 自动阅读会话聚合 | 已完成 | 按 clientSessionId upsert自动完成/中断 |
| API 资料阅读进度聚合 | 已完成 | 按 userId+materialId upsert |
| API 每日学习活动聚合 | 已完成 | 按 userId+localDate upsert时区感知 |
| API 学习记录创建 | 已完成 | 里程碑事件自动创建 |
| API 学习摘要/趋势/连续天数 | 已完成 | GET /learning/*, GET /activity/* |
| API 继续学习推荐 | 已完成 | GET /learning/continue |
| API 活动推荐 | 已完成 | GET /activity/recommendations |
| API 活跃时长封顶 | 已完成 | 超 300s 截断 + 警告码 |
| API 时间戳偏差检测 | 已完成 | >5 分钟偏差警告 |
| 用户切换隔离 | 已完成 | JWT userId + 所有查询按 userId 分区 |
| Admin 学习仪表板 | 已完成 | KPI 总览(运维可观测) |
| Admin 事件/会话/进度/活动/记录查看 | 已完成 | 列表+筛选+详情(运维可观测) |
| Admin 用户时间线与诊断 | 已完成 | 时序视图+完整诊断(运维可观测) |
| Admin 异常检测 | 已完成 | 失败/警告/重复三视图(运维可观测) |
| Admin 事件重放 | 已完成 | 单条+批量(运维可观测) |
| Admin 数据重算 | 已完成 | 触发重算(运维可观测) |
| Admin 数据导出 | 已完成 | CSV/JSON 导出(运维可观测) |
| iOS 专注学习会话 API 持久化 | 暂未确认 | 本地计时已完成,代码中未发现调用 LearningSessionService API 的代码 |