# 01 文档阅读 — 跨项目完整能力分析 > 试点能力一:从 C 端业务能力"文档阅读"出发,跨项目完整整理。 > 项目角色分类:主链路(iOS、Rust Document Runtime、主 API、数据库/COS)、上游支撑(Python RAG)、下游消费(Heavy Runtime)、运维可观测(Admin)。 --- ## 1. 能力目标 用户能够在知习 App 中流畅地阅读上传到知识库的学习资料,支持多种文件格式,自动记录阅读位置,标记已读状态,并在下次打开时从上次中断处继续阅读。 --- ## 2. C 端入口 用户可以从以下 iOS 页面进入文档阅读: | 入口页面 | 触发方式 | 进入场景 | |---------|---------|---------| | 学习主页 | 点击主操作卡片("继续上次学习") | 继续上次未读完的资料 | | 知识库详情页·来源 Tab | 点击来源文件条目 | 阅读知识库中的资料 | | 知识点详情页 | 点击"阅读原文件"或自动渲染 | 阅读知识点关联的原始资料 | | 学习分析页 | 点击"继续阅读"卡片 | 从分析页继续阅读 | | AI 反馈页 | "加入待巩固" → 学习主页 → 资料阅读 | 间接进入 | 在资料阅读页中,用户可以执行以下操作: - 阅读内容(滚动浏览,支持 Markdown/纯文本/图片/EPUB 的原生渲染) - 系统预览 PDF、Word、Excel(通过 QuickLook) - 外部打开 PowerPoint(通过 ShareLink) - 标记已读(本地即时生效,marked_as_read 事件异步上报) - 取消标记已读(本地即时生效,服务端同步方式暂未确认) - 打开 AI 对话(资料范围,需 RAG 已完成向量化才能获得语义检索结果) - 查看资料详情(元数据、AI 处理状态、阅读进度) - 开始专注学习会话(主动计时) - 添加笔记(自动记录锚点位置) --- ## 3. 用户主流程 以下使用纯业务语言描述用户从进入资料到退出阅读的完整流程: 1. **进入资料:** 用户从知识库来源文件列表、继续学习推荐或知识点详情中点击资料,进入资料阅读页。 2. **资料加载:** App 检查设备上是否已有该文件。 - 如果有本地文件:直接打开。 - 如果没有:从云端 COS 下载文件到本地缓存目录。 - 只要资料记录存在、用户有访问权限且 COS/本地文件可用,用户即可阅读原文件,不需要等待 Python RAG 的文本解析、分块、向量化和索引完成。 3. **类型检测与渲染:** 系统检测文件类型,根据类型选择渲染方式。 - Markdown/文本/图片/EPUB:由 Rust Document Runtime 解析并渲染为可读内容。 - PDF/Word/Excel:提供"系统预览"按钮,用户点击后使用 iOS QuickLook 预览。 - PowerPoint:提供"外部打开"按钮,用户点击后使用系统分享功能在外部应用打开。 - 不支持的格式:显示"暂不支持此格式"提示。 4. **自动阅读会话启动:** 用户进入资料阅读页时,系统自动创建阅读会话(无需用户手动操作)。iOS 调用 Rust Document Runtime 开启 V2 ReadingSession,生成唯一的 client_session_id。该会话自动推送 material_opened 事件。 5. **阅读位置恢复:** 系统按优先级恢复上次阅读位置。 - 优先:从服务端 API 查询该资料的阅读进度(上次位置)。 - 其次:从本地缓存读取上次位置。 - 最后:从当前 Rust 阅读会话的 lastPosition 恢复。 6. **阅读中:** 用户滚动浏览内容。 - 系统自动记录阅读位置(2 秒防抖,iOS 调用 Rust push_position_changed)。 - iOS 创建并管理 15 秒心跳定时器,仅在阅读会话为 active 且 App 位于前台时触发。每次 Tick 调用 Rust heartbeat 接口,Rust 根据 ActiveTimeTracker 计算 activeSecondsDelta 后创建 heartbeat 事件并写入缓冲区。 - Rust 不创建也不管理 iOS 的系统定时器。 7. **前后台切换:** 用户将 App 切到后台时,iOS 暂停心跳定时器,阅读会话自动暂停,时间跟踪停止。切回前台时自动恢复。 8. **标记已读:** 用户点击工具栏"标记已读"按钮。该操作在本地 UserDefaults 立即更新,同时推送 marked_as_read 事件到 Rust 缓冲区,后续通过上传链路同步至服务端。取消标记已读可在本地即时生效,但服务端同步方式暂未确认(当前事件模型只有 marked_as_read,没有 unmarked_as_read)。 9. **添加笔记:** 用户点击工具栏笔记按钮,弹出快速笔记 Sheet。系统自动捕获当前阅读位置作为笔记锚点。用户输入笔记文字并保存(本地 UserDefaults 存储)。 10. **AI 对话(资料范围):** 用户点击工具栏 AI 按钮,跳转到 AI 对话页。对话自动限定在当前资料范围内。如果 Python RAG 已完成该资料的向量化索引,AI 可通过语义检索引用资料内容回答用户问题;如果 RAG 未完成,AI 范围对话、语义检索和知识点候选提取可能暂不可用。 11. **开始专注学习会话:** 用户点击工具栏"开始学习"按钮,启动专注学习计时器。计时器以 1 秒精度运行,默认目标 30 分钟。该学习会话是用户主动发起的专注时段,与进入资料时自动创建的阅读会话是不同的业务对象。 12. **退出阅读:** 用户返回上一页或切换到其他 Tab。 - iOS 调用 Rust close_reading_session,Rust 计算最终活跃时长增量,推送 material_closed 事件。 - 阅读位置保存到本地缓存。 - iOS 周期性地从 Rust 导出已缓冲事件,写入本地持久化上传队列,后续由队列负责 API 上传。 --- ## 4. 项目参与关系 ### iOS(AIStudyApp)— 主链路参与者 **职责:** - 提供资料阅读页的用户界面 - 调用 Rust Document Runtime(通过 UniFFI FFI)进行文件解析 - 管理 COS 文件下载和本地文件缓存 - 驱动自动阅读会话生命周期(调用 Rust V2 Session API) - 创建并管理 15 秒心跳定时器(仅在 session active 且前台时触发) - 驱动阅读事件采集(调用 Rust push 函数 + heartbeat 接口) - 管理本地上传队列:将 Rust 导出事件映射为 UploadItem、写入持久化队列、确认 Rust 交接完成 - 管理阅读位置本地缓存 - 提供系统预览和外部打开能力 - 提供笔记创建界面和本地存储 - 提供标记已读本地缓存 - 前台/后台切换时管理会话暂停/恢复和心跳定时器启停 - App 被杀死后重启时执行崩溃恢复 ### Rust Document Runtime(zhixi-document-runtime)— 主链路参与者 **职责:** - 检测文件类型(根据扩展名和文件内容推断 MaterialType) - 解析 Markdown 为结构化文档块树 - 解析纯文本为段落块 - 提取 EPUB 章节结构和元数据 - 提取 PDF 页数和基本文本(用于搜索) - 提取图片元数据 - 管理自动阅读会话(V2 ReadingSession:start、pause、resume、close、interrupt) - 管理阅读事件缓冲区(push、export、ack、reload) - 根据 ActiveTimeTracker 计算 activeSecondsDelta(由 iOS heartbeat tick 触发) - 提供阅读位置模型 - 提供文件搜索能力 - 提供笔记锚点模型 - 提供活跃时间跟踪器 - 提供崩溃恢复机制(stale event reload、stale session cleanup) **不负责:** 网络请求、COS 下载、API 调用、用户认证、UI 渲染、Office 文件高清预览、PDF 完整渲染、iOS 系统定时器创建和管理。 ### 主 API(api-server)— 主链路参与者 **职责:** - 提供资料信息查询 - 提供文件下载 URL(COS 预签名 URL) - 提供资料阅读进度查询 - 接收并处理阅读事件批量上传 - 将原始阅读事件聚合为:自动阅读会话(LearningSession)、资料阅读进度、每日学习活动、学习记录 - 提供文件上传 URL - 管理知识源的三个处理状态(parseStatus、indexStatus、learningStatus) - 提供继续学习推荐 - 提供学习摘要 - 提供阅读趋势 ### Python RAG Worker — 上游支撑 **不直接参与文档阅读。** 只要资料记录、文件访问权限和 COS/本地文件可用,用户即可阅读原文件。Python RAG 负责文档导入后的异步处理(下载上传的文件 → 解析文本内容 → 分块 → 向量化嵌入 → 存入 Qdrant → AI 提取候选知识点)。RAG 未完成时,阅读原文件不受影响,但资料范围 AI 问答的语义检索和知识点候选提取可能暂不可用。 ### Heavy Runtime — 下游数据消费者 **不直接参与文档阅读。** 消费已聚合的阅读数据,生成 AI 分析结果(掌握度评估、薄弱点识别、学习建议、测验题目、闪卡)。位于阅读数据采集链的下游,不属于用户阅读时的同步调用链。 ### Admin(admin-projects)— 运维与可观测 **不属于用户阅读时的同步调用链。** 职责包括: - 知识库来源文件列表查看 - 来源文件详情查看(含解析状态、索引状态) - 文件管理(查看、删除上传文件) - 资料阅读进度查看 - 资料诊断 - 文档导入监控与重试 --- ## 5. 项目间调用关系 ### 5.1 iOS → Rust Document Runtime | 调用目的 | 调用方向 | 业务结果 | |---------|---------|---------| | 检测文件类型 | iOS → Rust | 返回 MaterialType 和 PreviewMode 建议 | | 解析 Markdown/文本文件 | iOS → Rust | 返回 DocumentBlock 数组 | | 提取 EPUB/PDF/图片信息 | iOS → Rust | 返回对应元数据 | | 搜索文档内容 | iOS → Rust | 返回 SearchResult 列表 | | 开启自动阅读会话 | iOS → Rust | 创建 V2 ReadingSession,返回 client_session_id | | 推送阅读事件 | iOS → Rust | 事件进入内存缓冲区(Pending 状态) | | 心跳 Tick | iOS → Rust | Rust 计算 activeSecondsDelta,创建 heartbeat 事件写入缓冲区 | | 导出待交接事件 | iOS → Rust | 返回 Exported 状态的事件列表,交接给 iOS | | 确认事件交接完成 | iOS → Rust | 已 Ack 的事件从 Rust 缓冲区移除(交接给 iOS 本地队列后立即调用) | | 崩溃恢复 | iOS → Rust | Exported 未 Ack 事件重置为 Pending | | 清理过期会话 | iOS → Rust | 超时会话标记为 Interrupted 并从内存移除 | ### 5.2 iOS → 主 API | 调用目的 | 调用方向 | 业务结果 | |---------|---------|---------| | 获取文件下载 URL | iOS → API | 返回 COS 预签名下载链接 | | 查询阅读进度 | iOS → API | 返回上次阅读位置、累计阅读时长、是否已读 | | 上传阅读事件批次 | iOS → API | 从 iOS 本地持久化队列发起,返回 processed/duplicate/failed 计数 | | 查询继续学习 | iOS → API | 返回最近阅读的资料信息 | | 查询学习摘要/趋势 | iOS → API | 返回聚合数据 | | 查询资料信息 | iOS → API | 返回知识源详情 | ### 5.3 主 API 内部调用 | 调用目的 | 调用方向 | 业务结果 | |---------|---------|---------| | 阅读事件处理 | API 内部(同步事务) | 事件入库 → 聚合自动阅读会话 → 聚合阅读进度 → 聚合每日活动 → 创建学习记录 | | 资料解析触发 | API → Redis 队列 | 将资料加入 document-import 队列,由 Python RAG Worker 异步处理(不阻塞阅读) | ### 5.4 Admin → 主 API(运维可观测,非同步调用链) | 调用目的 | 调用方向 | 业务结果 | |---------|---------|---------| | 查看知识库来源文件 | Admin → API | 返回来源文件列表(含解析和索引状态) | | 查看/删除文件 | Admin → API | 文件管理 | | 查看资料阅读进度 | Admin → API | 阅读进度详情 | | 资料诊断 | Admin → API | 全部阅读数据和关联会话 | | 文档导入监控/重试 | Admin → API | 导入任务列表、重试失败任务 | --- ## 6. 核心业务数据 ### 6.1 资料(Material / KnowledgeSource) - **谁创建:** iOS 用户在导入资料页或添加知识点页上传文件后,API 创建 KnowledgeSource 记录。 - **谁更新:** Python RAG Worker 更新解析状态和索引状态(异步,不阻塞阅读);API 在处理阅读事件时更新关联的阅读状态。 - **谁查询:** iOS 在知识库详情页来源 Tab 查询列表、在资料详情页查询详情;Admin 在来源文件页查询列表。 - **用于什么功能:** 知识库内容组织、文档阅读入口、资料处理状态展示。 ### 6.2 阅读位置(ReadingPosition) - **谁创建:** Rust Document Runtime 在 iOS 推送 position_changed 事件时创建。 - **谁更新:** 每次用户滚动时更新(2 秒防抖),由 Rust Document Runtime 维护 last_position。 - **谁查询:** iOS 在进入阅读页时恢复上次位置(优先 API、其次本地缓存、最后会话位置);API 在阅读进度查询时返回。 - **用于什么功能:** 跨会话的阅读位置恢复、"继续学习"推荐。 ### 6.3 自动阅读会话(ReadingSession,Rust V2) - **谁创建:** Rust Document Runtime 在 iOS 调用 start_reading_session 时创建(用户进入资料阅读页时自动触发,无需用户手动操作)。 - **谁更新:** Rust 在每次事件推送时更新内存中的会话状态(lastEventAt、totalActiveSeconds、lastPosition)。 - **谁查询:** iOS 在恢复会话时查询 lastPosition 用于位置恢复。 - **用于什么功能:** 自动追踪单次阅读行为的持续时长和事件序号,管理阅读会话生命周期(active→paused→closed)。产生 opened、closed、position_changed、heartbeat、marked_as_read 事件。 ### 6.4 专注学习会话(LearningSessionView 计时) - **谁创建:** 用户主动点击资料阅读页工具栏"开始学习"按钮后,iOS 创建本地计时器。 - **谁更新:** 用户操作暂停/继续/结束(含确认弹窗),计时器以 1 秒精度运行,默认目标 30 分钟。 - **谁查询:** iOS 学习会话页展示当前进度。 - **用于什么功能:** 用户主动发起的专注学习时段计时。**与自动阅读会话(ReadingSession)是不同的业务对象。** 当前 iOS 是否调用专注学习会话 API(LearningSessionService)持久化仍标记为"暂未确认"。 ### 6.5 阅读事件(ReadingEvent) - **谁创建:** Rust Document Runtime 创建(5 种事件类型:material_opened、material_closed、position_changed、heartbeat、marked_as_read)。 - **谁更新:** 事件创建后不可修改。API 更新其处理状态(processing → processed/failed/duplicate)。 - **谁查询:** API 在批处理时查重;Admin 在阅读事件列表和异常检测页查询。 - **用于什么功能:** 所有学习分析的原始数据源。API 根据事件聚合自动阅读会话、阅读进度、每日活动、学习记录。 ### 6.6 资料阅读进度(MaterialReadingProgress) - **谁创建:** API 在收到该资料的第一个阅读事件时自动创建。 - **谁更新:** API 在每次事件处理事务中 upsert(递增总时长、更新最后位置、更新会话数、标记已读状态)。 - **谁查询:** iOS 在进入阅读页时查询(恢复位置);Admin 在阅读进度页查询。 - **用于什么功能:** 资料级别的阅读进度追踪、"继续学习"推荐的数据源、阅读统计。 ### 6.7 笔记(QuickNote) - **谁创建:** iOS 用户在快速笔记 Sheet 中输入并保存时创建。 - **谁更新:** 当前不支持修改(仅创建和删除)。 - **谁查询:** iOS 在资料阅读页和知识点详情页查询(本地 UserDefaults)。 - **用于什么功能:** 阅读过程中的快速记录。当前仅本地存储,未同步到服务端。 --- ## 7. 查询入口 ### 用户在哪里查看 - **阅读内容:** iOS 资料阅读页直接渲染文档内容。 - **阅读进度:** iOS 资料详情页的阅读进度卡片(累计时长、阅读状态、上次阅读时间)。 - **资料信息:** iOS 资料详情页(文件名、格式、大小、上传日期、AI 处理状态)。 - **来源文件列表:** iOS 知识库详情页的来源 Tab。 - **继续阅读:** iOS 学习主页的主操作卡片(继续上次学习)。 ### Admin 在哪里查看(运维可观测) - **来源文件列表:** Admin 知识库→来源文件页(含解析状态、索引状态)。 - **文件管理:** Admin 文件与 COS 管理页(按类型筛选、查看详情、删除)。 - **阅读进度:** Admin 学习数据→阅读进度页(按用户/资料查看进度)。 - **资料诊断:** Admin 学习数据→资料诊断页。 - **导入监控:** Admin 知识库→文档导入监控页(任务状态、步骤日志、重试)。 ### 系统内部在哪里使用 - **继续学习推荐:** API GET /learning/continue → iOS 学习主页优先级算法。 - **学习摘要:** API GET /learning/summary → iOS 分析页 + 学习主页每周摘要。 - **阅读趋势:** API GET /learning/trend → iOS 分析页阅读时长趋势图。 - **学习记录:** API GET /learning/records → iOS 分析页最近学习记录。 - **资料范围 AI 问答:** API 在 RAG 对话中根据资料范围检索相关 Chunk(依赖 Python RAG 的向量化结果;RAG 未完成时不可用)。 --- ## 8. 修改入口 ### 用户主动修改 - **标记已读:** iOS 资料阅读页工具栏按钮。本地 MarkedReadStore 即时生效,marked_as_read 事件通过事件采集链路上报。服务端同步已完成。 - **取消标记已读:** 本地 MarkedReadStore 即时生效。服务端同步方式暂未确认(当前事件模型无 unmarked_as_read 事件类型)。 - **添加笔记:** iOS 快速笔记 Sheet(本地 UserDefaults 存储)。 - **删除来源文件:** iOS 知识库详情页来源 Tab 滑动删除(API 软删除)。 - **开始/结束专注学习会话:** iOS 资料阅读页工具栏学习按钮 / 学习会话页结束按钮。 - **上传新资料:** iOS 导入资料页(文件/相册/链接)。 ### Admin 人工修改(运维可观测) - **删除文件:** Admin 文件管理页删除上传文件。 - **重试导入:** Admin 文档导入监控页重试失败任务。 - **重新解析:** Admin 可触发知识源的重新解析。 ### 系统自动修改 - **阅读位置更新:** iOS 2 秒防抖自动调用 Rust push_position_changed。 - **心跳:** iOS 15 秒定时器 Tick → 调用 Rust heartbeat 接口 → Rust 计算 activeSecondsDelta 并创建事件。 - **阅读会话状态切换:** iOS 前后台切换时自动暂停/恢复 Rust ReadingSession,同步控制心跳定时器。 - **阅读进度聚合:** API 在每次事件处理事务中自动更新 MaterialReadingProgress。 - **会话中断检测:** API 在事件处理前自动清理 30 分钟无新事件的活跃会话(标记为 interrupted)。 - **解析状态更新:** Python RAG Worker 异步更新 KnowledgeSource 的 parseStatus(不阻塞阅读)。 - **文件过期处理:** API 自动检测临时资料是否过期。 ### 系统配置修改 - **文件大小限制:** API 配置(待确认具体配置入口)。 - **COS 预签名 URL 有效期:** API 文件上传服务配置。 - **解析触发轮询间隔:** iOS 端硬编码(60×2s),非配置化。 --- ## 9. 异常与恢复 ### 9.1 文件加载失败 - **场景:** 本地文件不存在或 COS 下载失败。 - **iOS 处理:** 资料阅读页进入错误状态,显示错误提示和重试按钮。自动尝试从 COS 重新下载。 - **API 处理:** 文件下载 URL 返回 COS 预签名链接,含过期时间。过期后 iOS 需重新请求。 ### 9.2 解析失败 - **场景:** 文件内容损坏、格式不支持、或解析器报错。 - **iOS 处理:** 资料阅读页显示"暂不支持此格式"或错误提示。 - **API 处理:** Python RAG Worker 解析失败时更新 DocumentImport 状态为 FAILED,记录错误步骤。Admin 可查看步骤日志并重试。解析失败不影响用户阅读原文件。 ### 9.3 网络中断 - **场景:** 阅读过程中网络断开。 - **iOS 处理:** Rust Document Runtime 继续在本地采集事件,事件缓冲区正常运行。iOS 本地持久化队列中的事件暂停上传,NetworkMonitor 检测到网络恢复后自动触发队列 flush。阅读会话继续,心跳和位置事件照常写入 Rust 缓冲区。 ### 9.4 App 被关闭(强制杀死或系统回收) - **场景:** 用户在阅读过程中 App 被系统杀死或用户强制关闭。 - **iOS 处理(App 重新启动时):** 1. RuntimeRecoveryService.run() 执行崩溃恢复 2. Rust reload_stale_events_v2:所有 Exported 但未 Ack 的事件重置为 Pending(处理 export 后、本地写入前崩溃的场景) 3. Rust cleanup_stale_sessions_v2:清理超过 24 小时的非活跃会话 4. iOS 本地队列 reloadOnLaunch:重新从 Rust 导出事件、映射为 UploadItem、写入持久化队列、立即 Ack。已持久化但未上传的队列条目继续上传。 5. 服务器端:API 在下次处理事件前,清理超过 30 分钟的活跃会话(标记为 interrupted) ### 9.5 Export/Ack 链路中的三种崩溃场景 - **export 后、本地写入前崩溃:** Rust reload_stale_events_v2 将 Exported 事件重置为 Pending,下次启动重新导出。 - **本地写入成功、Ack 前崩溃:** 重新启动后重新导出,API 端通过 eventId 去重(已在队列中的重复事件不会产生重复数据),iOS 再调用 Ack 完成交接。 - **Ack 后、API 上传前崩溃:** 事件已从 Rust 缓冲区安全交接至 iOS 本地持久化队列。重启后本地队列继续上传,Rust 不需要再参与。 ### 9.6 阅读位置恢复失败 - **场景:** API 未返回阅读进度(首次打开)或本地缓存丢失。 - **iOS 处理:** 三级回退——API 位置 → 本地缓存位置 → Rust 会话位置 → 从头开始。 ### 9.7 事件上传失败 - **场景:** 上传请求返回错误或超时。上传完全由 iOS 本地持久化队列负责,失败时不再调用 Rust markFailed。 - **iOS 本地队列处理:** - 401 错误:暂停队列(保留 pending 状态),等待 Token 刷新 - 403/404 错误:标记条目为永久失败(dead) - 其他错误:标记条目为 failed,进入指数退避重试(5s × 2^n,最多 3 次,最长 5 分钟) - 重试次数耗尽:条目进入 dead 状态,不再重试 - **API 处理:** - 重复事件(相同 userId + eventId):标记为 duplicate,返回已处理 - 验证失败(无效类型/超限/无权限):标记为 failed,返回错误码 - 材料不存在:标记为 failed ### 9.8 资料被删除或失去权限 - **场景:** 用户正在阅读的资料被管理员删除或用户失去访问权限。 - **API 处理:** 事件处理时校验材料存在性、归属性和状态。不存在的材料标记事件为 failed;无权限的标记为 failed;已过期的临时材料标记为 failed。 - **iOS 处理:** 当前的阅读会话仍可继续(本地文件未被删除),但后续事件上传将被拒绝。 --- ## 10. 当前完成情况 统一使用以下口径: - **已完成:** 跨项目链路已接通,用户可以获得最终结果。 - **部分完成:** 只有部分项目、部分页面或部分场景完成。 - **占位实现:** 存在结构,但使用 Mock、硬编码或静态数据。 - **暂未确认:** 无法确认完整调用链。 | 组件 | 状态 | 说明 | |------|------|------| | iOS 多格式渲染(Markdown/文本/图片/EPUB) | 已完成 | Rust FFI 解析 + SwiftUI 渲染 | | iOS 系统预览(PDF/Word/Excel/PPT) | 已完成 | QuickLook + ShareLink | | iOS 阅读位置记录与恢复 | 已完成 | 三级优先级(API→本地→会话) | | iOS 标记已读(服务端同步) | 已完成 | 本地 UserDefaults + marked_as_read 事件上报链路已接通 | | iOS 取消标记已读(服务端同步) | 暂未确认 | 本地即时生效,但当前事件模型无 unmarked_as_read,API 是否支持待确认 | | iOS 自动阅读会话管理 | 已完成 | Rust V2 ReadingSession(含前后台自动切换) | | iOS 专注学习会话计时 | 暂未确认 | 本地计时已完成,API 持久化调用待确认 | | iOS 快速笔记 | 已完成 | 锚点记录,本地 UserDefaults 存储 | | iOS 15 秒心跳定时器 | 已完成 | iOS 创建 Timer,仅在 session active 且前台时触发 | | Rust Document Runtime 文件解析 | 已完成 | 9 种 MaterialType 检测 + Markdown/文本/EPUB/图片/PDF 基础解析 | | Rust Document Runtime V2 自动阅读会话 | 已完成 | 完整生命周期 + 5 种事件类型 + 缓冲区管理 | | Rust 活跃时间跟踪器 | 已完成 | ActiveTimeTracker,由 iOS heartbeat tick 驱动 | | Rust 崩溃恢复 | 已完成 | stale event reload + stale session cleanup | | Rust → iOS 事件交接链路 | 已完成 | export → 本地队列写入 → Ack(交接在本地完成,不依赖网络) | | iOS 本地上传队列 | 已完成 | JSON 持久化,独立于 Rust 缓冲区的网络上传队列 | | iOS 上传重试与网络恢复 | 已完成 | 指数退避重试 + NetworkMonitor 自动恢复 | | API 文件上传与下载 | 已完成 | COS 预签名 URL 流程 | | API 阅读事件处理管线 | 已完成 | 去重→验证→事件入库→自动阅读会话聚合→进度聚合→每日活动→学习记录 | | API 阅读进度查询 | 已完成 | GET /materials/:id/reading-progress | | API 继续学习推荐 | 已完成 | 最近的未读完资料 | | Admin 来源文件查看 | 已完成 | 列表+筛选+解析状态(运维可观测) | | Admin 资料诊断 | 已完成 | material-diagnose 端点(运维可观测) | | Admin 阅读进度查看 | 已完成 | 列表+详情(运维可观测) | | 原文件阅读(不依赖 RAG) | 已完成 | 只要资料记录+权限+COS/本地文件可用即可阅读 | | 资料范围 AI 问答(依赖 RAG) | 部分完成 | 需 RAG 完成向量化后才可用;RAG 未完成时语义检索不可用 | | iOS 解析触发轮询 | 部分完成 | 使用硬编码 60×2s 轮询,非事件驱动 | | 笔记服务端同步 | 未完成 | 当前仅本地 UserDefaults 存储 | | Office 文件高清预览 | 暂未确认 | Word/Excel/PPT 依赖系统预览或外部打开,无内置高清渲染 |