diff --git a/docs/ai-playbook/flows.zh-CN.md b/docs/ai-playbook/flows.zh-CN.md index 041a88b..a2dd209 100644 --- a/docs/ai-playbook/flows.zh-CN.md +++ b/docs/ai-playbook/flows.zh-CN.md @@ -2,6 +2,15 @@ 本文汇总 P2P 传输与信令重连的关键流程与消息序列,并给出简明的调试要点与“微方案模板”。用于在改动前快速对齐阶段、事件与入口文件。 +## 快速导航 + +- 速查:本页包含 1–5(关键流程/消息/调试要点)与 11(微方案模板)。 +- 深度阅读(已从本页拆分): + - 前端组件协作(原第 6 节):[`docs/ai-playbook/flows/frontend.zh-CN.md`](./flows/frontend.zh-CN.md) + - 背压与分片(原第 7 节):[`docs/ai-playbook/flows/backpressure-chunking.zh-CN.md`](./flows/backpressure-chunking.zh-CN.md) + - 断点续传(原第 9 节):[`docs/ai-playbook/flows/resume.zh-CN.md`](./flows/resume.zh-CN.md) + - 重连一致性(原第 10 节):[`docs/ai-playbook/flows/reconnect-consistency.zh-CN.md`](./flows/reconnect-consistency.zh-CN.md) + ## 1)文件传输(单文件) 序列(通过 DataChannel,发送端 ↔ 接收端): @@ -165,544 +174,31 @@ Socket.IO 事件处理流程: ## 6)前端组件系统与业务中枢协作流程 -### 组件架构层级 +本节已拆分到:[`docs/ai-playbook/flows/frontend.zh-CN.md`](./flows/frontend.zh-CN.md) -``` -App Router (page.tsx/layout.tsx) - ↓ -HomeClient (页面布局与SEO) - ↓ -ClipboardApp (顶层UI协调器) - ↓ -SendTabPanel/RetrieveTabPanel (功能面板) - ↓ -业务中枢 Hooks (状态管理与业务逻辑) - ↓ -Core Services (webrtcService) + Store (fileTransferStore) -``` - -### ClipboardApp 顶层协调器模式 - -**核心职责**: - -- 集成 5 个关键业务 hooks:useWebRTCConnection、useFileTransferHandler、useRoomManager、usePageSetup、useClipboardAppMessages -- 全局拖拽事件处理:dragenter/dragleave/dragover/drop,支持多文件和文件夹树遍历 -- 双标签页状态管理:发送/接收面板切换,通过 activeTab 控制 -- 统一消息系统:shareMessage/retrieveMessage 4 秒自动消失机制 - -### Hook 层级与职责分离 - -**useWebRTCConnection**(状态桥梁): - -- 计算全局传输状态(isAnyFileTransferring) -- 暴露 webrtcService 方法(broadcastDataToAllPeers、requestFile、requestFolder) -- 提供连接重置方法(resetSenderConnection、resetReceiverConnection) - -**useFileTransferHandler**(文件与内容管理): - -- 文件操作:addFilesToSend(去重)、removeFileToSend -- 下载功能:handleDownloadFile(支持文件夹压缩下载) -- 关键修复:使用 `useFileTransferStore.getState()` 获取最新状态,避免闭包问题 -- 重试机制:最大 3 次重试,50ms 间隔,详细错误日志 - -**useRoomManager**(房间生命周期管理): - -- 房间操作:joinRoom(支持缓存 ID 重连)、processRoomIdInput(750ms 防抖) -- 离开保护:传输中确认提示(isAnyFileTransferring 检查) -- 状态文本:动态更新房间状态文本 -- 链接生成:自动生成分享链接 - -**usePageSetup**(页面初始化): - -- 国际化消息加载与错误处理 -- URL 参数处理:roomId 自动提取并触发加入房间(200ms 延迟确保 DOM 就绪) -- 引荐来源追踪(trackReferrer) - -**useClipboardAppMessages**(消息管理): - -- 分离式消息状态:shareMessage(发送相关)和 retrieveMessage(接收相关) -- 统一消息显示接口:putMessageInMs(message, isShareEnd, displayTimeMs) -- 自动清理机制:4 秒后自动清空消息状态 - -### 面板组件特化设计 - -**SendTabPanel 发送面板**: - -- 房间 ID 双模式生成:4 位数字(后端 API 生成)和 UUID(前端 crypto API) -- 富文本编辑器集成(动态导入,SSR 禁用) -- 文件上传处理和文件列表管理 -- 分享链接生成与二维码显示 - -**RetrieveTabPanel 接收面板**: - -- File System Access API 集成:目录选择和直接保存到磁盘 -- 富文本内容渲染(dangerouslySetInnerHTML) -- 文件请求和下载状态管理 -- 保存位置选择和大文件/文件夹提示 - -**FileListDisplay 文件列表**: - -- 智能文件/文件夹分组和统计显示 -- 多浏览器下载策略:Chrome 自动下载,其他浏览器手动保存提示 -- 下载计数统计和传输进度跟踪 -- 断点续传和存储方式显示(内存/磁盘) - -### 关键用户体验优化 - -1. **下载状态闭包修复**:`useFileTransferHandler.ts:110` 使用 `useFileTransferStore.getState()` 获取最新状态 -2. **房间 ID 输入防抖**:`useRoomManager.ts:247` 使用 lodash debounce 750ms 延迟验证 -3. **传输中离开保护**:`useRoomManager.ts:164,218` 检查 `isAnyFileTransferring` 状态并显示确认对话框 -4. **缓存 ID 重连**:`useRoomManager.ts:91` 检测长 ID(≥8 字符)自动发送 `initiator-online` -5. **文件夹压缩下载**:`useFileTransferHandler.ts:89` 使用 JSZip 动态创建 ZIP 文件 -6. **全局拖拽优化**:ClipboardApp 使用 dragCounter 防止拖拽状态误判,支持 webkitGetAsEntry 文件树遍历 -7. **剪贴板兼容性**:useClipboardActions 支持现代 navigator.clipboard API 和 document.execCommand 降级方案 -8. **富文本安全处理**:useRichTextToPlainText 服务端渲染安全,客户端 DOM 转换处理块级元素 -9. **站内导航不中断(同一标签页)**:依赖 `frontend/stores/fileTransferStore.ts`(Zustand 单例)与 `frontend/lib/webrtcService.ts`(服务单例)。App Router 页面切换不打断传输且保留已选择/已接收内容。注意不要在路由切换副作用中调用 `webrtcService.leaveRoom()` 或重置 Store;刷新/新标签不在保证范围内。 - -### UI 连接反馈状态机(弱网/VPN 提示) - -- 入房阶段(join) - - 立即:`join_inProgress`(“正在加入房间…”)。 - - 3s 未完成:`join_slow`(“连接较慢,建议检查网络/VPN…”)。 - - 15s 超时:`join_timeout`(“加入超时…”)。 - - 等效成功信号:在等待 `joinResponse` 期间,若收到 `ready/recipient-ready/offer`,视为提前入房成功并即时清理 3s/15s 定时器与提示,避免“成功后再出现慢/超时提示”。 -- 协商阶段(WebRTC) - - 进入 `new/connecting`:归一为 “协商中” → `rtc_negotiating`。 - - 8s 未连上:`rtc_slow`(“网络可能受限,尝试关闭 VPN 或稍后再试”)。仅在页面前台可见时触发;同一次协商尝试仅提示一次(发送端/接收端任一进入协商即启动计时,提示归属以最先进入协商的一侧为准)。 -- 连接与重连 - - 首次 `connected`:`rtc_connected`(仅一次)。 - - 前台断开:`rtc_reconnecting` → 恢复后 `rtc_restored`。 - - 后台断开不提示;回到前台若仍断开立即提示 `rtc_reconnecting`。 - - 已断开期间若页面在后台,返回前台时若仍处于协商态且此前触发了慢协商计时,则会补发一次 `rtc_slow` 并标记本次协商已提示,以避免重复。 - -实现位置: -- `frontend/hooks/useRoomManager.ts`:入房阶段提示与定时器管理(3s 慢网、15s 超时),并在 join 成功/失败时清理定时器;支持“等效成功信号”提前判定成功(`ready/recipient-ready/offer`)。 -- `frontend/hooks/useConnectionFeedback.ts`:桥接 WebRTC 连接态到 UI 提示。 - - 状态归一化(mapPhase):`new/connecting`→`negotiating`;`failed/closed`→`disconnected`。 - - 协商慢提示:8s 定时器、前后台可见性节制、单次协商尝试仅提示一次(含挂起→前台补发)。 - - 一次性提示:首次 `connected` 只显示一次;断开→恢复显示 `rtc_restored`;仅前台显示 `rtc_reconnecting`。 - - 复用:慢提示定时与前后台补发由 `frontend/utils/useOneShotSlowHint.ts` 统一实现;状态归一化由 `frontend/utils/rtcPhase.ts` 提供。 - -文案与 i18n: -- 文案键均位于 `frontend/constants/messages/*.{ts}`,类型定义见 `frontend/types/messages.ts`。 -- 关键键:`join_inProgress`、`join_slow`、`join_timeout`、`rtc_negotiating`、`rtc_slow`、`rtc_connected`、`rtc_reconnecting`、`rtc_restored`(已在 en/ja/es/de/fr/ko 全部补齐)。 - -节流与展示: -- 所有提示默认 4–6 秒自动消失;通过 `useClipboardAppMessages.putMessageInMs(message, isShareEnd, ms)` 统一展示。 -- 连接反馈提示在“状态迁移 + ever/wasDisc 标记 + 可见性判断”三重约束下触发,避免提示风暴。 -10. **切到接收端自动加入(缓存ID)**:当用户切换到接收端、未在房间、URL 无 `roomId`、输入框为空且本地存在缓存 ID 时,自动填充并直接调用加入房间以提升体验。入口:`frontend/components/ClipboardApp.tsx`(监听 `activeTab` 变化,读取 `frontend/lib/roomIdCache.ts`)。 -11. **发送端“使用缓存ID”即刻加入**:发送端在 `SendTabPanel` 点击“使用缓存ID”后会立即调用加入房间(而非仅填充输入框)。入口:`frontend/components/ClipboardApp/CachedIdActionButton.tsx`(`onUseCached` 回调)+ `frontend/components/ClipboardApp/SendTabPanel.tsx`。 -12. **深色主题切换**:提供单按钮 Light/Dark 切换,入口:`frontend/components/web/ThemeToggle.tsx`;集成位置:`frontend/components/web/Header.tsx`(桌面与移动);局部样式从硬编码颜色迁移为设计令牌(例如接收面板使用 `bg-card text-card-foreground`)。 - -### 前端组件架构特化 - -**富文本编辑器模块**: - -- **RichTextEditor**:主编辑器组件,支持 contentEditable、图片粘贴、格式化工具、SSR 禁用 -- **工具栏组件分离**:BasicFormatTools(粗体/斜体/下划线)、FontTools(字体/大小/颜色)、AlignmentTools(对齐)、InsertTools(链接/图片/代码块) -- **类型安全设计**:完整的 TypeScript 类型定义(FormatType、AlignmentType、FontStyleType、CustomClipboardEvent) -- **编辑器 Hooks**:useEditorCommands(命令执行)、useSelection(选择管理)、useStyleManagement(样式管理) - -**网站页面组件设计**: - -- **Header 响应式导航**:桌面端水平导航+移动端汉堡菜单,集成 GitHub 链接和语言切换器 -- **Footer 国际化**:动态版权年份、多语言支持链接显示,使用 languageDisplayNames 配置 -- **FAQSection 灵活配置**:支持工具页面/独立页面切换、标题级别控制、自动 FAQ 数组生成 -- **内容展示组件**:HowItWorks(步骤动画+视频)、SystemDiagram(架构图)、KeyFeatures(图标+特性说明) - -**UI 组件库架构**: - -- **基于 Radix UI**:Button(CVA 多变体系统)、Accordion(手风琴)、Dialog(模态对话框)、Select、DropdownMenu -- **设计系统一致性**:统一的 cn 工具函数、主题色彩系统、动画过渡效果 -- **组件组合模式**:DialogHeader/DialogFooter/DialogTitle/DialogDescription 组合设计 -- **懒加载优化**:LazyLoadWrapper 使用 react-intersection-observer,支持 rootMargin 配置防止布局跳动 - -**通用组件工具化**: - -- **clipboard_btn**:WriteClipboardButton/ReadClipboardButton 分离设计,集成 useClipboardActions hook,支持国际化消息 -- **TableOfContents**:支持中文标题 ID 生成、滚动跟踪、层级缩进、IntersectionObserver 监听 -- **JsonLd SEO**:多类型数据支持、suppressHydrationWarning、数组/单对象处理 -- **AutoPopupDialog/YouTubePlayer**:业务场景封装,复用性设计 - -### 数据流模式 - -- **单向数据流**:Store → Hooks → Components -- **状态管理集中化**:所有状态通过 `useFileTransferStore` 统一管理 -- **错误处理标准化**:统一的消息提示机制(putMessageInMs) -- **国际化集成**:useLocale + getDictionary 提供多语言支持 +- 适用:定位前端 UI 组件/Hook/Store 的职责边界与协作方式 +- 包含:ClipboardApp 协调器、hooks 分层、连接反馈状态机、数据流模式等 ## 7)背压与分片策略深度分析 -### 发送侧双层缓冲架构 +本节已拆分到:[`docs/ai-playbook/flows/backpressure-chunking.zh-CN.md`](./flows/backpressure-chunking.zh-CN.md) -**设计原理**: - -- **文件读取层**:4MB 分片减少 FileReader 调用,8 个分片组成 32MB 批次 -- **网络传输层**:64KB 小块适配 WebRTC DataChannel 限制,避免 sendData failed 错误 -- **性能优化**:批次内高效切片,一次 FileReader.read()产生 512 个网络块 - -**配置参数**: - -```typescript -TransferConfig.FILE_CONFIG = { - CHUNK_SIZE: 4194304, // 4MB - 文件读取分片 - BATCH_SIZE: 8, // 8个分片 = 32MB批次 - NETWORK_CHUNK_SIZE: 65536, // 64KB - WebRTC安全发送大小 -}; -``` - -**背压控制机制**: - -- **DataChannel 阈值**:`bufferedAmountLowThreshold = 256KB`(Initiator)和`512KB`(NetworkTransmitter) -- **最大缓冲限制**:`maxBuffer = 1MB`,超过时等待背压释放 -- **异步等待策略**:监听`bufferedamountlow`事件,支持超时机制(10 秒) - -**嵌入元数据包格式**: - -``` -[4字节长度][JSON元数据][实际数据块] -``` - -- 每个网络块都包含:chunkIndex、totalChunks、fileOffset、fileId、isLastChunk -- 接收端可独立解析,无需依赖额外状态 - -### 接收侧智能存储策略 - -**存储选择逻辑**: - -```typescript -ReceptionConfig.shouldSaveToDisk(fileSize, hasSaveDirectory); -``` - -- **内存存储**:文件 < 1GB 且未指定保存目录 -- **磁盘存储**:文件 ≥ 1GB 或用户选择了保存目录 -- **缓冲管理**:最多缓存 100 个分片(约 6.4MB) - -**分片验证机制**: - -- **格式兼容**:支持 ArrayBuffer/Blob/Uint8Array/TypedArray 多种格式 -- **完整性检查**:验证 fileId、chunkIndex、chunkSize 一致性 -- **Firefox 兼容**:Blob size 检测和转换错误处理 - -**严格顺序写入**: - -- **SequencedDiskWriter**:确保分片按序写入磁盘,支持大文件流式处理 -- **断点续传**:通过`getPartialFileSize()`检查本地部分文件 -- **自动完成检测**:`checkAndAutoFinalize()`验证分片完整性 - -### 性能优化细节 - -**发送侧优化**: - -- **批量读取**:32MB 批次减少 I/O 操作,提升大文件读取性能 -- **网络适配**:64KB 块平衡传输效率与浏览器兼容性 -- **背压响应**:利用 WebRTC 原生背压控制,避免数据丢失 - -**接收侧优化**: - -- **格式转换**:ChunkProcessor 统一处理多种数据格式 -- **进度节流**:文件 100ms、文件夹 200ms 间隔更新,避免 UI 过载 -- **内存管理**:小文件内存组装,大文件直接写入磁盘 - -**错误处理**: - -- **发送重试**:NetworkTransmitter 返回 boolean 状态,支持上层重试逻辑 -- **转换容错**:Blob conversion failed 时返回 null,不中断整体传输 -- **超时保护**:文件完成 30 秒超时,优雅关闭 5 秒超时 - -### 调试与监控 - -**开发环境日志**: - -- **分片跟踪**:每 100 个分片或最后分片记录详细信息 -- **背压监控**:缓冲区大小变化和等待时间统计 -- **性能指标**:传输速度、批次处理时间、格式转换耗时 - -**生产环境优化**: - -- **条件日志**:`ENABLE_CHUNK_LOGGING`和`ENABLE_PROGRESS_LOGGING`开关 -- **错误上报**:关键错误通过`postLogToBackend`发送到后端 -- **性能采样**:通过`performance.now()`精确测量耗时 +- 适用:核对背压阈值、分片/批次策略、嵌入元数据包格式与性能调优点 +- 包含:发送侧双层缓冲、接收侧存储策略、调试与监控建议等 ## 9)断点续传深度分析 -### 断点续传核心机制 +本节已拆分到:[`docs/ai-playbook/flows/resume.zh-CN.md`](./flows/resume.zh-CN.md) -**续传检测与状态恢复**: - -- **发送侧初始化**:`StreamingFileReader constructor(file, startOffset)` 支持从任意偏移量开始 -- **接收侧检测**:`StreamingFileWriter.getPartialFileSize()` 通过 File System Access API 检查部分文件 -- **状态同步**:fileRequest 消息包含 offset 参数,通知发送方从指定位置继续传输 - -**分片索引计算**: - -```typescript -// 统一的分片计算逻辑 -const startChunk = Math.floor(startOffset / chunkSize); -const expectedChunks = Math.ceil((fileSize - startOffset) / chunkSize); -``` - -### ChunkRangeCalculator 统一计算器 - -**设计目的**:确保发送端和接收端使用完全相同的分片计算逻辑 - -```typescript -getChunkRange(fileSize, startOffset, chunkSize) { - const startChunk = Math.floor(startOffset / chunkSize); - const endChunk = Math.floor((fileSize - 1) / chunkSize); - return { startChunk, endChunk, totalChunks: endChunk - startChunk + 1 }; -} -``` - -**关键方法**: - -- `getRelativeChunkIndex()`:绝对索引转相对索引,用于接收端数组映射 -- `isChunkIndexValid()`:验证分片索引是否在预期范围内 -- `calculateExpectedChunks()`:计算预期分片数量,与 ReceptionConfig 保持一致 - -### 接收侧续传流程 - -**部分文件检测**: - -1. **目录准备**:`createFolderStructure()` 确保目标目录存在 -2. **文件查询**:通过 `getFileHandle(fileName, {create: false})` 检查文件是否存在 -3. **大小获取**:`file.getFile()` 获取当前文件大小作为续传起点 - -**续传决策逻辑**: - -```typescript -// FileReceiveOrchestrator.ts -const offset = await this.streamingFileWriter.getPartialFileSize( - fileInfo.name, - fileInfo.fullName -); -if (offset === fileInfo.size) { - // 文件已完整,跳过传输 - return; -} -if (offset > 0) { - // 发现部分文件,准备续传 - // 发送包含 offset 的 fileRequest -} -``` - -### 发送侧续传响应 - -**续传准备**: - -- **重置读取器**:`StreamingFileReader.reset(startOffset)` 从新的偏移量开始 -- **批次调整**:`currentBatchStartOffset` 和 `totalFileOffset` 同步更新 -- **分片索引**:`startChunkIndex` 记录传输起始点,用于边界检测 - -**续传日志**: - -```typescript -const chunkRange = ChunkRangeCalculator.getChunkRange( - fileSize, - startOffset, - chunkSize -); -postLogToBackend( - `[SEND-SUMMARY] File: ${file.name}, offset: ${startOffset}, startChunk: ${chunkRange.startChunk}, endChunk: ${chunkRange.endChunk}` -); -``` - -### 续传的优势与限制 - -**优势**: - -- **带宽节省**:避免重新传输已接收的数据 -- **时间效率**:大文件传输中断后可快速恢复 -- **用户体验**:网络波动不会导致传输进度完全丢失 - -**限制与注意点**: - -- **文件一致性**:依赖文件内容未发生变化,续传前应验证文件大小/修改时间 -- **存储位置**:仅在使用 File System Access API 选择保存目录时支持 -- **浏览器兼容**:File System Access API 主要支持 Chrome/Edge,其他浏览器降级为内存存储 - -**调试支持**: - -- **详细日志**:开发环境下记录续传起点、分片范围、预期传输量 -- **错误处理**:文件访问失败时回退到从头开始传输 -- **状态跟踪**:Store 层记录续传状态和实际接收大小 +- 适用:核对续传检测、offset 协商与分片范围计算的一致性 +- 包含:ChunkRangeCalculator、接收侧/发送侧续传流程、限制与调试要点等 ## 10)重连与状态一致性深度分析 -### WebRTC 基础层重连机制 +本节已拆分到:[`docs/ai-playbook/flows/reconnect-consistency.zh-CN.md`](./flows/reconnect-consistency.zh-CN.md) -**双重断开检测架构**: - -```typescript -// webrtc_base.ts -private isSocketDisconnected = false; // Socket.IO 连接状态 -private isPeerDisconnected = false; // P2P 连接状态 -private gracefullyDisconnectedPeers = new Set(); // 优雅断开的 peer 列表 -``` - -**重连触发条件**:仅当 Socket.IO 和 P2P 连接都断开时才启动重连: - -```typescript -// 避免重复重连:socket 断开 ≠ P2P 断开 -if ( - this.isSocketDisconnected && - this.isPeerDisconnected && - !this.reconnectionInProgress -) { - this.attemptReconnection(); -} -``` - -### ICE 候选者队列管理 - -**候选者缓存策略**: - -- **连接未就绪时**:候选者缓存到 `iceCandidatesQueue` Map,按 peerId 分组 -- **连接就绪后**:批量处理缓存的候选者,按序添加到 RTCPeerConnection -- **失效处理**:候选者失效时重新入队,验证连接状态后重试 - -**实现细节**: - -```typescript -private iceCandidatesQueue = new Map(); -// 缓存候选项直到连接就绪 -if (dataChannel?.readyState !== 'open') { - this.queueIceCandidate(candidate, peerId); -} else { - this.addIceCandidate(candidate, peerId); -} -``` - -### 数据通道发送重试机制 - -**5 次重试策略**: - -```typescript -async sendToPeer(data: string | ArrayBuffer, peerId: string): Promise { - for (let attempt = 1; attempt <= 5; attempt++) { - try { - dataChannel.send(data); - return true; - } catch (error) { - if (this.gracefullyDisconnectedPeers.has(peerId)) { - return false; // 跳过已优雅断开的 peer - } - if (attempt === 5) throw error; - await new Promise(resolve => setTimeout(resolve, attempt * 100)); // 100ms→1000ms - } - } -} -``` - -**重试间隔递增**:100ms → 200ms → 300ms → 400ms → 500ms,最大 5 次尝试 - -### 房间管理层的重连支持 - -**幂等性设计**: - -- **长 ID 重连**:≥8 字符的 roomId 支持断线重连时复用房间 -- **短 ID 限制**:4 位数字 ID 断线后需重新生成房间,避免冲突 - -**缓存 ID 重连优化**: - -```typescript -// useRoomManager.ts -if (roomId.length >= 8) { - // 长ID自动发送 initiator-online 信号 - this.sendInitiatorOnline(); -} -``` - -**状态同步序列**: - -1. **发送方重连**:`initiator-online` 信号通知接收方准备重建连接 -2. **接收方响应**:`recipient-ready` 确认就绪状态 -3. **WebRTC 协商**:重新开始 offer/answer/ICE 候选者交换 -4. **传输恢复**:在新的 DataChannel 上恢复文件传输 - -### 状态一致性保证机制 - -**Store 层单一事实来源**: - -```typescript -// fileTransferStore.ts -export const useFileTransferStore = create((set, get) => ({ - sendProgress: new Map(), - receiveProgress: new Map(), - // 提供清理 API 避免重复计数 - clearSendProgress: (fileId: string) => - set((state) => { - const newProgress = new Map(state.sendProgress); - newProgress.delete(fileId); - return { sendProgress: newProgress }; - }), -})); -``` - -**连接状态机**: - -```typescript -type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed'; - -// 状态变更时触发相应处理 -connectionStateChangeHandler(status: ConnectionStatus) { - switch (status) { - case 'connected': - this.gracefullyDisconnectedPeers.clear(peerId); - this.resetReconnectionState(); - break; - case 'disconnected': - case 'failed': - this.cleanupExistingConnection(peerId); - break; - } -} -``` - -### 移动端优化策略 - -**唤醒锁管理**: - -```typescript -// WakeLockManager -async requestWakeLock(): Promise { - try { - this.wakeLock = await navigator.wakeLock.request('screen'); - this.wakeLock.addEventListener('release', () => { - this.wakeLock = null; - }); - } catch (error) { - console.warn('Wake lock request failed:', error); - } -} -``` - -**网络切换适应**: - -- **连接检测**:监听 `connectionstatechange` 事件检测网络质量变化 -- **自动重连**:`connectionState: 'disconnected' | 'failed' | 'closed'` 时均触发重连流程(统一走 attemptReconnection) -- **状态恢复**:重连成功后恢复房间状态和传输进度 - -**移动端后台/前台切换补充策略**: - -- **socket 连接恢复自动入房**:`socket.on('connect')` 时,若已持有 `roomId` 且(`lastJoinedSocketId !== socket.id` 或 `!isInRoom`),则强制重新 `joinRoom(roomId, isInitiator, isInitiator)`;发送端会自动广播 `initiator-online`,接收端回复 `recipient-ready`。 -- **身份追踪**:成功 `joinRoom` 后记录 `lastJoinedSocketId = socket.id`,用以检测“后台恢复时 socketId 更换”的情形。 -- **门槛放宽**:`attemptReconnection` 只要满足“`roomId` 存在,且满足任一:P2P 断开 / socket 断开 / socketId 改变”,即可发起重连;不再强依赖“socket 与 P2P 同时断开”。 - -### 重连调试要点 - -**关键日志点**: - -- **双重断开检测**:记录 Socket.IO 和 P2P 断开的具体时间戳 -- **候选者队列**:统计缓存的 ICE 候选者数量和处理时间 -- **发送重试**:记录重试次数、间隔和最终结果 -- **状态恢复**:追踪 `initiator-online` → `recipient-ready` 的时序 - -**常见问题诊断**: - -- **重复重连**:检查 `reconnectionInProgress` 标志和 `gracefullyDisconnectedPeers` 集合 -- **候选者失效**:验证 `iceConnectionState` 和 `iceGatheringState` 状态 -- **状态不一致**:确认 Store 层的进度清理和连接状态同步 +- 适用:核对 WebRTC/Socket 双重断开判定、ICE 候选者队列、发送重试与一致性保障 +- 包含:重连触发条件、重试策略、移动端补充策略、调试要点等 ## 11)微方案模板(用于小改动前的对齐) diff --git a/docs/ai-playbook/flows/backpressure-chunking.zh-CN.md b/docs/ai-playbook/flows/backpressure-chunking.zh-CN.md new file mode 100644 index 0000000..73f0dc3 --- /dev/null +++ b/docs/ai-playbook/flows/backpressure-chunking.zh-CN.md @@ -0,0 +1,98 @@ +# PrivyDrop AI Playbook — 背压与分片策略深度分析(中文) + +← 返回流程入口:[`docs/ai-playbook/flows.zh-CN.md`](../flows.zh-CN.md) + +(本页从 `docs/ai-playbook/flows.zh-CN.md` 拆分,保留原章节编号与内容。) + +## 7)背压与分片策略深度分析 + +### 发送侧双层缓冲架构 + +**设计原理**: + +- **文件读取层**:4MB 分片减少 FileReader 调用,8 个分片组成 32MB 批次 +- **网络传输层**:64KB 小块适配 WebRTC DataChannel 限制,避免 sendData failed 错误 +- **性能优化**:批次内高效切片,一次 FileReader.read()产生 512 个网络块 + +**配置参数**: + +```typescript +TransferConfig.FILE_CONFIG = { + CHUNK_SIZE: 4194304, // 4MB - 文件读取分片 + BATCH_SIZE: 8, // 8个分片 = 32MB批次 + NETWORK_CHUNK_SIZE: 65536, // 64KB - WebRTC安全发送大小 +}; +``` + +**背压控制机制**: + +- **DataChannel 阈值**:`bufferedAmountLowThreshold = 256KB`(Initiator)和`512KB`(NetworkTransmitter) +- **最大缓冲限制**:`maxBuffer = 1MB`,超过时等待背压释放 +- **异步等待策略**:监听`bufferedamountlow`事件,支持超时机制(10 秒) + +**嵌入元数据包格式**: + +``` +[4字节长度][JSON元数据][实际数据块] +``` + +- 每个网络块都包含:chunkIndex、totalChunks、fileOffset、fileId、isLastChunk +- 接收端可独立解析,无需依赖额外状态 + +### 接收侧智能存储策略 + +**存储选择逻辑**: + +```typescript +ReceptionConfig.shouldSaveToDisk(fileSize, hasSaveDirectory); +``` + +- **内存存储**:文件 < 1GB 且未指定保存目录 +- **磁盘存储**:文件 ≥ 1GB 或用户选择了保存目录 +- **缓冲管理**:最多缓存 100 个分片(约 6.4MB) + +**分片验证机制**: + +- **格式兼容**:支持 ArrayBuffer/Blob/Uint8Array/TypedArray 多种格式 +- **完整性检查**:验证 fileId、chunkIndex、chunkSize 一致性 +- **Firefox 兼容**:Blob size 检测和转换错误处理 + +**严格顺序写入**: + +- **SequencedDiskWriter**:确保分片按序写入磁盘,支持大文件流式处理 +- **断点续传**:通过`getPartialFileSize()`检查本地部分文件 +- **自动完成检测**:`checkAndAutoFinalize()`验证分片完整性 + +### 性能优化细节 + +**发送侧优化**: + +- **批量读取**:32MB 批次减少 I/O 操作,提升大文件读取性能 +- **网络适配**:64KB 块平衡传输效率与浏览器兼容性 +- **背压响应**:利用 WebRTC 原生背压控制,避免数据丢失 + +**接收侧优化**: + +- **格式转换**:ChunkProcessor 统一处理多种数据格式 +- **进度节流**:文件 100ms、文件夹 200ms 间隔更新,避免 UI 过载 +- **内存管理**:小文件内存组装,大文件直接写入磁盘 + +**错误处理**: + +- **发送重试**:NetworkTransmitter 返回 boolean 状态,支持上层重试逻辑 +- **转换容错**:Blob conversion failed 时返回 null,不中断整体传输 +- **超时保护**:文件完成 30 秒超时,优雅关闭 5 秒超时 + +### 调试与监控 + +**开发环境日志**: + +- **分片跟踪**:每 100 个分片或最后分片记录详细信息 +- **背压监控**:缓冲区大小变化和等待时间统计 +- **性能指标**:传输速度、批次处理时间、格式转换耗时 + +**生产环境优化**: + +- **条件日志**:`ENABLE_CHUNK_LOGGING`和`ENABLE_PROGRESS_LOGGING`开关 +- **错误上报**:关键错误通过`postLogToBackend`发送到后端 +- **性能采样**:通过`performance.now()`精确测量耗时 diff --git a/docs/ai-playbook/flows/frontend.zh-CN.md b/docs/ai-playbook/flows/frontend.zh-CN.md new file mode 100644 index 0000000..0cd469e --- /dev/null +++ b/docs/ai-playbook/flows/frontend.zh-CN.md @@ -0,0 +1,173 @@ +# PrivyDrop AI Playbook — 前端组件系统与业务中枢协作流程(中文) + +← 返回流程入口:[`docs/ai-playbook/flows.zh-CN.md`](../flows.zh-CN.md) + +(本页从 `docs/ai-playbook/flows.zh-CN.md` 拆分,保留原章节编号与内容。) + +## 6)前端组件系统与业务中枢协作流程 + +### 组件架构层级 + +``` +App Router (page.tsx/layout.tsx) + ↓ +HomeClient (页面布局与SEO) + ↓ +ClipboardApp (顶层UI协调器) + ↓ +SendTabPanel/RetrieveTabPanel (功能面板) + ↓ +业务中枢 Hooks (状态管理与业务逻辑) + ↓ +Core Services (webrtcService) + Store (fileTransferStore) +``` + +### ClipboardApp 顶层协调器模式 + +**核心职责**: + +- 集成 5 个关键业务 hooks:useWebRTCConnection、useFileTransferHandler、useRoomManager、usePageSetup、useClipboardAppMessages +- 全局拖拽事件处理:dragenter/dragleave/dragover/drop,支持多文件和文件夹树遍历 +- 双标签页状态管理:发送/接收面板切换,通过 activeTab 控制 +- 统一消息系统:shareMessage/retrieveMessage 4 秒自动消失机制 + +### Hook 层级与职责分离 + +**useWebRTCConnection**(状态桥梁): + +- 计算全局传输状态(isAnyFileTransferring) +- 暴露 webrtcService 方法(broadcastDataToAllPeers、requestFile、requestFolder) +- 提供连接重置方法(resetSenderConnection、resetReceiverConnection) + +**useFileTransferHandler**(文件与内容管理): + +- 文件操作:addFilesToSend(去重)、removeFileToSend +- 下载功能:handleDownloadFile(支持文件夹压缩下载) +- 关键修复:使用 `useFileTransferStore.getState()` 获取最新状态,避免闭包问题 +- 重试机制:最大 3 次重试,50ms 间隔,详细错误日志 + +**useRoomManager**(房间生命周期管理): + +- 房间操作:joinRoom(支持缓存 ID 重连)、processRoomIdInput(750ms 防抖) +- 离开保护:传输中确认提示(isAnyFileTransferring 检查) +- 状态文本:动态更新房间状态文本 +- 链接生成:自动生成分享链接 + +**usePageSetup**(页面初始化): + +- 国际化消息加载与错误处理 +- URL 参数处理:roomId 自动提取并触发加入房间(200ms 延迟确保 DOM 就绪) +- 引荐来源追踪(trackReferrer) + +**useClipboardAppMessages**(消息管理): + +- 分离式消息状态:shareMessage(发送相关)和 retrieveMessage(接收相关) +- 统一消息显示接口:putMessageInMs(message, isShareEnd, displayTimeMs) +- 自动清理机制:4 秒后自动清空消息状态 + +### 面板组件特化设计 + +**SendTabPanel 发送面板**: + +- 房间 ID 双模式生成:4 位数字(后端 API 生成)和 UUID(前端 crypto API) +- 富文本编辑器集成(动态导入,SSR 禁用) +- 文件上传处理和文件列表管理 +- 分享链接生成与二维码显示 + +**RetrieveTabPanel 接收面板**: + +- File System Access API 集成:目录选择和直接保存到磁盘 +- 富文本内容渲染(dangerouslySetInnerHTML) +- 文件请求和下载状态管理 +- 保存位置选择和大文件/文件夹提示 + +**FileListDisplay 文件列表**: + +- 智能文件/文件夹分组和统计显示 +- 多浏览器下载策略:Chrome 自动下载,其他浏览器手动保存提示 +- 下载计数统计和传输进度跟踪 +- 断点续传和存储方式显示(内存/磁盘) + +### 关键用户体验优化 + +1. **下载状态闭包修复**:`useFileTransferHandler.ts:110` 使用 `useFileTransferStore.getState()` 获取最新状态 +2. **房间 ID 输入防抖**:`useRoomManager.ts:247` 使用 lodash debounce 750ms 延迟验证 +3. **传输中离开保护**:`useRoomManager.ts:164,218` 检查 `isAnyFileTransferring` 状态并显示确认对话框 +4. **缓存 ID 重连**:`useRoomManager.ts:91` 检测长 ID(≥8 字符)自动发送 `initiator-online` +5. **文件夹压缩下载**:`useFileTransferHandler.ts:89` 使用 JSZip 动态创建 ZIP 文件 +6. **全局拖拽优化**:ClipboardApp 使用 dragCounter 防止拖拽状态误判,支持 webkitGetAsEntry 文件树遍历 +7. **剪贴板兼容性**:useClipboardActions 支持现代 navigator.clipboard API 和 document.execCommand 降级方案 +8. **富文本安全处理**:useRichTextToPlainText 服务端渲染安全,客户端 DOM 转换处理块级元素 +9. **站内导航不中断(同一标签页)**:依赖 `frontend/stores/fileTransferStore.ts`(Zustand 单例)与 `frontend/lib/webrtcService.ts`(服务单例)。App Router 页面切换不打断传输且保留已选择/已接收内容。注意不要在路由切换副作用中调用 `webrtcService.leaveRoom()` 或重置 Store;刷新/新标签不在保证范围内。 + +### UI 连接反馈状态机(弱网/VPN 提示) + +- 入房阶段(join) + - 立即:`join_inProgress`(“正在加入房间…”)。 + - 3s 未完成:`join_slow`(“连接较慢,建议检查网络/VPN…”)。 + - 15s 超时:`join_timeout`(“加入超时…”)。 + - 等效成功信号:在等待 `joinResponse` 期间,若收到 `ready/recipient-ready/offer`,视为提前入房成功并即时清理 3s/15s 定时器与提示,避免“成功后再出现慢/超时提示”。 +- 协商阶段(WebRTC) + - 进入 `new/connecting`:归一为 “协商中” → `rtc_negotiating`。 + - 8s 未连上:`rtc_slow`(“网络可能受限,尝试关闭 VPN 或稍后再试”)。仅在页面前台可见时触发;同一次协商尝试仅提示一次(发送端/接收端任一进入协商即启动计时,提示归属以最先进入协商的一侧为准)。 +- 连接与重连 + - 首次 `connected`:`rtc_connected`(仅一次)。 + - 前台断开:`rtc_reconnecting` → 恢复后 `rtc_restored`。 + - 后台断开不提示;回到前台若仍断开立即提示 `rtc_reconnecting`。 + - 已断开期间若页面在后台,返回前台时若仍处于协商态且此前触发了慢协商计时,则会补发一次 `rtc_slow` 并标记本次协商已提示,以避免重复。 + +实现位置: +- `frontend/hooks/useRoomManager.ts`:入房阶段提示与定时器管理(3s 慢网、15s 超时),并在 join 成功/失败时清理定时器;支持“等效成功信号”提前判定成功(`ready/recipient-ready/offer`)。 +- `frontend/hooks/useConnectionFeedback.ts`:桥接 WebRTC 连接态到 UI 提示。 + - 状态归一化(mapPhase):`new/connecting`→`negotiating`;`failed/closed`→`disconnected`。 + - 协商慢提示:8s 定时器、前后台可见性节制、单次协商尝试仅提示一次(含挂起→前台补发)。 + - 一次性提示:首次 `connected` 只显示一次;断开→恢复显示 `rtc_restored`;仅前台显示 `rtc_reconnecting`。 + - 复用:慢提示定时与前后台补发由 `frontend/utils/useOneShotSlowHint.ts` 统一实现;状态归一化由 `frontend/utils/rtcPhase.ts` 提供。 + +文案与 i18n: +- 文案键均位于 `frontend/constants/messages/*.{ts}`,类型定义见 `frontend/types/messages.ts`。 +- 关键键:`join_inProgress`、`join_slow`、`join_timeout`、`rtc_negotiating`、`rtc_slow`、`rtc_connected`、`rtc_reconnecting`、`rtc_restored`(已在 en/ja/es/de/fr/ko 全部补齐)。 + +节流与展示: +- 所有提示默认 4–6 秒自动消失;通过 `useClipboardAppMessages.putMessageInMs(message, isShareEnd, ms)` 统一展示。 +- 连接反馈提示在“状态迁移 + ever/wasDisc 标记 + 可见性判断”三重约束下触发,避免提示风暴。 +10. **切到接收端自动加入(缓存ID)**:当用户切换到接收端、未在房间、URL 无 `roomId`、输入框为空且本地存在缓存 ID 时,自动填充并直接调用加入房间以提升体验。入口:`frontend/components/ClipboardApp.tsx`(监听 `activeTab` 变化,读取 `frontend/lib/roomIdCache.ts`)。 +11. **发送端“使用缓存ID”即刻加入**:发送端在 `SendTabPanel` 点击“使用缓存ID”后会立即调用加入房间(而非仅填充输入框)。入口:`frontend/components/ClipboardApp/CachedIdActionButton.tsx`(`onUseCached` 回调)+ `frontend/components/ClipboardApp/SendTabPanel.tsx`。 +12. **深色主题切换**:提供单按钮 Light/Dark 切换,入口:`frontend/components/web/ThemeToggle.tsx`;集成位置:`frontend/components/web/Header.tsx`(桌面与移动);局部样式从硬编码颜色迁移为设计令牌(例如接收面板使用 `bg-card text-card-foreground`)。 + +### 前端组件架构特化 + +**富文本编辑器模块**: + +- **RichTextEditor**:主编辑器组件,支持 contentEditable、图片粘贴、格式化工具、SSR 禁用 +- **工具栏组件分离**:BasicFormatTools(粗体/斜体/下划线)、FontTools(字体/大小/颜色)、AlignmentTools(对齐)、InsertTools(链接/图片/代码块) +- **类型安全设计**:完整的 TypeScript 类型定义(FormatType、AlignmentType、FontStyleType、CustomClipboardEvent) +- **编辑器 Hooks**:useEditorCommands(命令执行)、useSelection(选择管理)、useStyleManagement(样式管理) + +**网站页面组件设计**: + +- **Header 响应式导航**:桌面端水平导航+移动端汉堡菜单,集成 GitHub 链接和语言切换器 +- **Footer 国际化**:动态版权年份、多语言支持链接显示,使用 languageDisplayNames 配置 +- **FAQSection 灵活配置**:支持工具页面/独立页面切换、标题级别控制、自动 FAQ 数组生成 +- **内容展示组件**:HowItWorks(步骤动画+视频)、SystemDiagram(架构图)、KeyFeatures(图标+特性说明) + +**UI 组件库架构**: + +- **基于 Radix UI**:Button(CVA 多变体系统)、Accordion(手风琴)、Dialog(模态对话框)、Select、DropdownMenu +- **设计系统一致性**:统一的 cn 工具函数、主题色彩系统、动画过渡效果 +- **组件组合模式**:DialogHeader/DialogFooter/DialogTitle/DialogDescription 组合设计 +- **懒加载优化**:LazyLoadWrapper 使用 react-intersection-observer,支持 rootMargin 配置防止布局跳动 + +**通用组件工具化**: + +- **clipboard_btn**:WriteClipboardButton/ReadClipboardButton 分离设计,集成 useClipboardActions hook,支持国际化消息 +- **TableOfContents**:支持中文标题 ID 生成、滚动跟踪、层级缩进、IntersectionObserver 监听 +- **JsonLd SEO**:多类型数据支持、suppressHydrationWarning、数组/单对象处理 +- **AutoPopupDialog/YouTubePlayer**:业务场景封装,复用性设计 + +### 数据流模式 + +- **单向数据流**:Store → Hooks → Components +- **状态管理集中化**:所有状态通过 `useFileTransferStore` 统一管理 +- **错误处理标准化**:统一的消息提示机制(putMessageInMs) +- **国际化集成**:useLocale + getDictionary 提供多语言支持 diff --git a/docs/ai-playbook/flows/reconnect-consistency.zh-CN.md b/docs/ai-playbook/flows/reconnect-consistency.zh-CN.md new file mode 100644 index 0000000..9433c5d --- /dev/null +++ b/docs/ai-playbook/flows/reconnect-consistency.zh-CN.md @@ -0,0 +1,182 @@ +# PrivyDrop AI Playbook — 重连与状态一致性深度分析(中文) + +← 返回流程入口:[`docs/ai-playbook/flows.zh-CN.md`](../flows.zh-CN.md) + +(本页从 `docs/ai-playbook/flows.zh-CN.md` 拆分,保留原章节编号与内容。) + +## 10)重连与状态一致性深度分析 + +### WebRTC 基础层重连机制 + +**双重断开检测架构**: + +```typescript +// webrtc_base.ts +private isSocketDisconnected = false; // Socket.IO 连接状态 +private isPeerDisconnected = false; // P2P 连接状态 +private gracefullyDisconnectedPeers = new Set(); // 优雅断开的 peer 列表 +``` + +**重连触发条件**:仅当 Socket.IO 和 P2P 连接都断开时才启动重连: + +```typescript +// 避免重复重连:socket 断开 ≠ P2P 断开 +if ( + this.isSocketDisconnected && + this.isPeerDisconnected && + !this.reconnectionInProgress +) { + this.attemptReconnection(); +} +``` + +### ICE 候选者队列管理 + +**候选者缓存策略**: + +- **连接未就绪时**:候选者缓存到 `iceCandidatesQueue` Map,按 peerId 分组 +- **连接就绪后**:批量处理缓存的候选者,按序添加到 RTCPeerConnection +- **失效处理**:候选者失效时重新入队,验证连接状态后重试 + +**实现细节**: + +```typescript +private iceCandidatesQueue = new Map(); +// 缓存候选项直到连接就绪 +if (dataChannel?.readyState !== 'open') { + this.queueIceCandidate(candidate, peerId); +} else { + this.addIceCandidate(candidate, peerId); +} +``` + +### 数据通道发送重试机制 + +**5 次重试策略**: + +```typescript +async sendToPeer(data: string | ArrayBuffer, peerId: string): Promise { + for (let attempt = 1; attempt <= 5; attempt++) { + try { + dataChannel.send(data); + return true; + } catch (error) { + if (this.gracefullyDisconnectedPeers.has(peerId)) { + return false; // 跳过已优雅断开的 peer + } + if (attempt === 5) throw error; + await new Promise(resolve => setTimeout(resolve, attempt * 100)); // 100ms→1000ms + } + } +} +``` + +**重试间隔递增**:100ms → 200ms → 300ms → 400ms → 500ms,最大 5 次尝试 + +### 房间管理层的重连支持 + +**幂等性设计**: + +- **长 ID 重连**:≥8 字符的 roomId 支持断线重连时复用房间 +- **短 ID 限制**:4 位数字 ID 断线后需重新生成房间,避免冲突 + +**缓存 ID 重连优化**: + +```typescript +// useRoomManager.ts +if (roomId.length >= 8) { + // 长ID自动发送 initiator-online 信号 + this.sendInitiatorOnline(); +} +``` + +**状态同步序列**: + +1. **发送方重连**:`initiator-online` 信号通知接收方准备重建连接 +2. **接收方响应**:`recipient-ready` 确认就绪状态 +3. **WebRTC 协商**:重新开始 offer/answer/ICE 候选者交换 +4. **传输恢复**:在新的 DataChannel 上恢复文件传输 + +### 状态一致性保证机制 + +**Store 层单一事实来源**: + +```typescript +// fileTransferStore.ts +export const useFileTransferStore = create((set, get) => ({ + sendProgress: new Map(), + receiveProgress: new Map(), + // 提供清理 API 避免重复计数 + clearSendProgress: (fileId: string) => + set((state) => { + const newProgress = new Map(state.sendProgress); + newProgress.delete(fileId); + return { sendProgress: newProgress }; + }), +})); +``` + +**连接状态机**: + +```typescript +type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed'; + +// 状态变更时触发相应处理 +connectionStateChangeHandler(status: ConnectionStatus) { + switch (status) { + case 'connected': + this.gracefullyDisconnectedPeers.clear(peerId); + this.resetReconnectionState(); + break; + case 'disconnected': + case 'failed': + this.cleanupExistingConnection(peerId); + break; + } +} +``` + +### 移动端优化策略 + +**唤醒锁管理**: + +```typescript +// WakeLockManager +async requestWakeLock(): Promise { + try { + this.wakeLock = await navigator.wakeLock.request('screen'); + this.wakeLock.addEventListener('release', () => { + this.wakeLock = null; + }); + } catch (error) { + console.warn('Wake lock request failed:', error); + } +} +``` + +**网络切换适应**: + +- **连接检测**:监听 `connectionstatechange` 事件检测网络质量变化 +- **自动重连**:`connectionState: 'disconnected' | 'failed' | 'closed'` 时均触发重连流程(统一走 attemptReconnection) +- **状态恢复**:重连成功后恢复房间状态和传输进度 + +**移动端后台/前台切换补充策略**: + +- **socket 连接恢复自动入房**:`socket.on('connect')` 时,若已持有 `roomId` 且(`lastJoinedSocketId !== socket.id` 或 `!isInRoom`),则强制重新 `joinRoom(roomId, isInitiator, isInitiator)`;发送端会自动广播 `initiator-online`,接收端回复 `recipient-ready`。 +- **身份追踪**:成功 `joinRoom` 后记录 `lastJoinedSocketId = socket.id`,用以检测“后台恢复时 socketId 更换”的情形。 +- **门槛放宽**:`attemptReconnection` 只要满足“`roomId` 存在,且满足任一:P2P 断开 / socket 断开 / socketId 改变”,即可发起重连;不再强依赖“socket 与 P2P 同时断开”。 + +### 重连调试要点 + +**关键日志点**: + +- **双重断开检测**:记录 Socket.IO 和 P2P 断开的具体时间戳 +- **候选者队列**:统计缓存的 ICE 候选者数量和处理时间 +- **发送重试**:记录重试次数、间隔和最终结果 +- **状态恢复**:追踪 `initiator-online` → `recipient-ready` 的时序 + +**常见问题诊断**: + +- **重复重连**:检查 `reconnectionInProgress` 标志和 `gracefullyDisconnectedPeers` 集合 +- **候选者失效**:验证 `iceConnectionState` 和 `iceGatheringState` 状态 +- **状态不一致**:确认 Store 层的进度清理和连接状态同步 diff --git a/docs/ai-playbook/flows/resume.zh-CN.md b/docs/ai-playbook/flows/resume.zh-CN.md new file mode 100644 index 0000000..e714633 --- /dev/null +++ b/docs/ai-playbook/flows/resume.zh-CN.md @@ -0,0 +1,108 @@ +# PrivyDrop AI Playbook — 断点续传深度分析(中文) + +← 返回流程入口:[`docs/ai-playbook/flows.zh-CN.md`](../flows.zh-CN.md) + +(本页从 `docs/ai-playbook/flows.zh-CN.md` 拆分,保留原章节编号与内容。) + +## 9)断点续传深度分析 + +### 断点续传核心机制 + +**续传检测与状态恢复**: + +- **发送侧初始化**:`StreamingFileReader constructor(file, startOffset)` 支持从任意偏移量开始 +- **接收侧检测**:`StreamingFileWriter.getPartialFileSize()` 通过 File System Access API 检查部分文件 +- **状态同步**:fileRequest 消息包含 offset 参数,通知发送方从指定位置继续传输 + +**分片索引计算**: + +```typescript +// 统一的分片计算逻辑 +const startChunk = Math.floor(startOffset / chunkSize); +const expectedChunks = Math.ceil((fileSize - startOffset) / chunkSize); +``` + +### ChunkRangeCalculator 统一计算器 + +**设计目的**:确保发送端和接收端使用完全相同的分片计算逻辑 + +```typescript +getChunkRange(fileSize, startOffset, chunkSize) { + const startChunk = Math.floor(startOffset / chunkSize); + const endChunk = Math.floor((fileSize - 1) / chunkSize); + return { startChunk, endChunk, totalChunks: endChunk - startChunk + 1 }; +} +``` + +**关键方法**: + +- `getRelativeChunkIndex()`:绝对索引转相对索引,用于接收端数组映射 +- `isChunkIndexValid()`:验证分片索引是否在预期范围内 +- `calculateExpectedChunks()`:计算预期分片数量,与 ReceptionConfig 保持一致 + +### 接收侧续传流程 + +**部分文件检测**: + +1. **目录准备**:`createFolderStructure()` 确保目标目录存在 +2. **文件查询**:通过 `getFileHandle(fileName, {create: false})` 检查文件是否存在 +3. **大小获取**:`file.getFile()` 获取当前文件大小作为续传起点 + +**续传决策逻辑**: + +```typescript +// FileReceiveOrchestrator.ts +const offset = await this.streamingFileWriter.getPartialFileSize( + fileInfo.name, + fileInfo.fullName +); +if (offset === fileInfo.size) { + // 文件已完整,跳过传输 + return; +} +if (offset > 0) { + // 发现部分文件,准备续传 + // 发送包含 offset 的 fileRequest +} +``` + +### 发送侧续传响应 + +**续传准备**: + +- **重置读取器**:`StreamingFileReader.reset(startOffset)` 从新的偏移量开始 +- **批次调整**:`currentBatchStartOffset` 和 `totalFileOffset` 同步更新 +- **分片索引**:`startChunkIndex` 记录传输起始点,用于边界检测 + +**续传日志**: + +```typescript +const chunkRange = ChunkRangeCalculator.getChunkRange( + fileSize, + startOffset, + chunkSize +); +postLogToBackend( + `[SEND-SUMMARY] File: ${file.name}, offset: ${startOffset}, startChunk: ${chunkRange.startChunk}, endChunk: ${chunkRange.endChunk}` +); +``` + +### 续传的优势与限制 + +**优势**: + +- **带宽节省**:避免重新传输已接收的数据 +- **时间效率**:大文件传输中断后可快速恢复 +- **用户体验**:网络波动不会导致传输进度完全丢失 + +**限制与注意点**: + +- **文件一致性**:依赖文件内容未发生变化,续传前应验证文件大小/修改时间 +- **存储位置**:仅在使用 File System Access API 选择保存目录时支持 +- **浏览器兼容**:File System Access API 主要支持 Chrome/Edge,其他浏览器降级为内存存储 + +**调试支持**: + +- **详细日志**:开发环境下记录续传起点、分片范围、预期传输量 +- **错误处理**:文件访问失败时回退到从头开始传输 +- **状态跟踪**:Store 层记录续传状态和实际接收大小 diff --git a/docs/ai-playbook/index.zh-CN.md b/docs/ai-playbook/index.zh-CN.md index d6d5bbe..4d1f088 100644 --- a/docs/ai-playbook/index.zh-CN.md +++ b/docs/ai-playbook/index.zh-CN.md @@ -19,6 +19,7 @@ - 代码地图:`docs/ai-playbook/code-map.zh-CN.md` - 流程(含微方案模板):`docs/ai-playbook/flows.zh-CN.md` + - 流程(深度阅读拆分):`docs/ai-playbook/flows/frontend.zh-CN.md`、`docs/ai-playbook/flows/backpressure-chunking.zh-CN.md`、`docs/ai-playbook/flows/resume.zh-CN.md`、`docs/ai-playbook/flows/reconnect-consistency.zh-CN.md` - 协作规则:`docs/ai-playbook/collab-rules.zh-CN.md` - 系统与架构