422 lines
26 KiB
Markdown
422 lines
26 KiB
Markdown
# 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 依赖系统预览或外部打开,无内置高清渲染 |
|