# 文本编辑器功能设计文档 ## 1. 项目概述 ### 1.1 背景与目标 在 Axonix 平台中,添加类似豆包的富文本编辑器功能,服务于以下场景: 1. **mod-chat**:用户在聊天过程中,可将 AI 生成的文档内容直接打开进行编辑、保存 2. **工作流 + Word 模板**:工作流中上传指定 Word 模板,系统根据模板生成文档,用户可在编辑器中打开、修改、下载,下载后的文档格式与原模板一致 3. **oil-agent 跨平台**:同一套编辑器组件可在 oil-agent 平台通过免登录方式使用 ### 1.2 核心价值 - **无缝衔接工作流与编辑**:工作流生成的文档可直接在编辑器中打开,无需下载再用 Word 打开 - **格式保真**:基于 Word 模板生成的文档,下载后字体、标题样式、页眉页脚等与模板完全一致 - **跨平台复用**:一套编辑器组件同时服务 mod-chat 和 oil-agent ### 1.3 技术栈概览 | 层次 | 技术 | |------|------| | 前端框架 | React 18 + TypeScript + Vite + Ant Design | | 编辑器核心 | Slate.js | | 状态管理 | Zustand | | 后端框架 | FastAPI(Python 3.10+)| | ORM | SQLAlchemy 2.0 | | 数据库 | PostgreSQL | --- ## 2. 演进路线总览 ``` 阶段 0 打通基础链路(当前) mod-chat 中 AI 生成文档 → 编辑器打开 → 编辑 → 保存回 Chat 约 1.5 周 阶段 1 Word 模板 + 工作流文档编辑(近期目标) 上传 Word 模板 → 工作流生成文档 → 编辑器打开编辑 → 下载保持模板格式 约 2 周 阶段 2 oil-agent 跨平台集成 编辑器微前端化 → oil-agent 免登录接入 → 跨平台数据同步 约 2 周 阶段 3 编辑器增强 + 协同编辑 富文本增强、版本管理、实时多人协同 约 4 周 ``` --- ## 3. 阶段 0:打通基础链路 ### 3.1 目标 跑通 mod-chat 中"AI 生成文档 → 编辑器打开 → 编辑 → 保存回 Chat"的完整闭环。 ### 3.2 功能范围 1. **从 Chat 打开编辑器** - [ ] 识别聊天消息中的 Markdown 内容 - [ ] 点击消息右上角"编辑"按钮,弹出编辑器 Modal - [ ] 加载消息内容到编辑器 2. **基础编辑器** - [ ] Markdown 文本编辑(Slate.js) - [ ] 实时预览(分屏) - [ ] 防抖自动保存:小文件(< 200KB)防抖 3 秒直接推后端;大文件(≥ 200KB)写入 IndexedDB 缓存,手动保存时再同步 3. **保存并下载** - [ ] 点击"保存"按钮,弹出保存对话框 - [ ] 对话框中填写文件名,选择文件格式(阶段 0 仅支持 `.doc`) - [ ] 前端提交文件名、格式和文档内容到后端 - [ ] 后端生成文件,返回下载链接 - [ ] 前端根据下载链接自动触发浏览器下载 4. **文档 API** - [ ] 创建文档(POST /api/v1/documents) - [ ] 读取文档(GET /api/v1/documents/{id}) - [ ] 更新文档(PUT /api/v1/documents/{id}) - [ ] 导出 .doc(POST /api/v1/export/doc) - [ ] 获取下载记录列表(GET /api/v1/export/records) - [ ] 重新下载文件(GET /api/v1/export/records/{recordId}/download) - [ ] 删除下载记录(DELETE /api/v1/export/records/{recordId}) ### 3.3 不做的事情 - ❌ Word 模板处理(阶段 1) - ❌ 保持模板格式的 DOCX 导出(阶段 1) - ❌ oil-agent 集成 / Token 机制(阶段 2) - ❌ 版本管理 / 协同编辑(阶段 3) - ❌ Redis / S3 等外部存储(文档内容直接存 DB text 字段,上限 200KB) ### 3.4 架构 > 详见 [技术架构文档 - 阶段 0](./text-editor-architecture.md#2-阶段-0基础链路当前) ### 3.5 核心交互流程 ``` 用户在 Chat 中查看 AI 生成的文档 ↓ 点击"编辑"按钮 ↓ POST /api/v1/documents 创建文档记录,获取 documentId ↓ 打开编辑器 Modal,加载文档内容 ↓ 用户编辑 → 计算变动块(标题 level + index)→ 写入本地缓存 ↓ 判断文档大小 ├── 小文件(< 200KB)→ 防抖 3s → PUT /api/v1/documents/{id}(局部块更新) └── 大文件(≥ 200KB)→ 仅写 IndexedDB,不自动推后端 ↓ 点击"保存"按钮 → 弹出保存对话框 ↓ 用户填写文件名,选择格式(.doc)→ 确认 ↓ POST /api/v1/export/doc 提交 userId、文件名、格式、文档内容 ↓ 后端:加载默认样式文件 → DocxRenderer 渲染 .doc → 写入 tmp/{userId}/{date}/ → 写入 export_records 表 ↓ 返回 { recordId, downloadUrl, fileName, styleId, warning } ↓ 前端根据 downloadUrl 自动触发浏览器下载 ``` ### 3.6 保存对话框交互 ``` ┌─────────────────────────────────┐ │ 保存文档 │ ├─────────────────────────────────┤ │ 文件名 [季度报告_2026Q2 ] │ │ 格式 [.doc ▼ ] │ │ (目前仅支持 .doc) │ ├─────────────────────────────────┤ │ [取消] [确认下载] │ └─────────────────────────────────┘ ``` ### 3.7 前端组件结构 ``` mod-chat/src/ components/ editor/ DocumentEditor.tsx // 编辑器 Modal 主组件 EditorToolbar.tsx // 工具栏(含保存按钮) EditorPreview.tsx // Markdown 分屏预览 SaveDialog.tsx // 保存对话框(文件名 + 格式选择) store/ editorStore.ts // 编辑器 Zustand store api/ documentApi.ts // 文档 API 封装 exportApi.ts // 导出下载 API 封装 ``` ### 3.8 后端文件结构 ``` backend/ app/ main.py // FastAPI 应用入口,注册路由 config.py // 环境变量配置(DB URL、TEMP_DIR、BASE_URL 等) core/ database.py // AsyncSession 连接池 dependencies.py // get_db 依赖注入 exceptions.py // ExportError 等自定义异常 + 全局 handler models/ document.py // documents 表 ORM 模型 export_record.py // export_records 表 ORM 模型 schemas/ document.py // DocumentCreate / Update / Response export.py // ExportDocRequest / ExportDocResponse / ExportRecord(camelCase 别名) services/ document_service.py // 文档 CRUD 业务逻辑 export_service.py // DocxRenderer、load_style_file()、markdown_to_docx_bytes()、文件写入 export_record_service.py // 下载记录写入、查询、硬删除(含磁盘文件同步删除) storage_monitor.py // 磁盘配额检查、后台定时任务(每 30 分钟)、警告触发 api/ v1/ documents.py // 文档 CRUD 路由(5 个端点) export.py // 导出路由(POST /export/doc) export_records.py // 下载记录路由(列表、重新下载、删除) migrations/ versions/ 001_init_documents.py // 建 documents 表(Alembic) 002_add_export_records.py // 建 export_records 表 tmp/ default.json // 默认样式文件,阶段 0 固定使用 requirements.txt .env.example ``` ### 3.9 排期 | 任务 | 预计工时 | |------|---------| | 编辑器组件搭建(Slate.js 集成) | 3 天 | | 文档 CRUD API(FastAPI + PostgreSQL) | 2 天 | | 保存对话框 + 导出 .doc API | 2 天 | | Chat 集成(打开编辑器) | 1 天 | | 联调测试 | 1 天 | | **合计** | **约 1.5 周** | --- ## 4. 阶段 1:Word 模板 + 工作流文档编辑 ### 4.1 目标 支持工作流上传 Word 模板,工作流根据模板生成文档后,用户可在编辑器中打开、修改,并下载,**下载后的 DOCX 文件格式、字体、标题样式与原模板完全一致**。 ### 4.2 功能范围 1. **样式管理** - [ ] 用户上传任意 Word 文档,后端提取其中所有样式的完整 XML 定义,生成同名样式 JSON 文件并存入样式缓存列表 - [ ] 样式缓存列表(GET /api/v1/styles):含系统默认样式,供导出时选择 - [ ] 导出 .doc 时可通过 `styleId` 指定使用的样式;不传则使用默认样式 - [ ] 支持删除自定义样式(DELETE /api/v1/styles/{styleId}) 2. **Word 模板管理** - [ ] 工作流上传带 `{{名称}}` 占位符的 Word 模板(.docx) - [ ] 后端解析模板,提取所有占位符名称列表并返回,供用户确认模板结构 - [ ] 模板与工作流关联存储 3. **工作流生成文档** - [ ] 工作流根据模板 + 业务数据生成文档内容,存入 documents 表 - [ ] 文档记录关联对应的模板 ID 4. **编辑器打开工作流文档** - [ ] 从工作流入口打开编辑器,加载生成的文档内容 - [ ] 编辑器渲染时应用模板样式(标题层级、字体显示) 5. **下载(保持模板格式)** - [ ] 点击"下载",后端以模板为基础,按标题名称匹配 `{{占位符}}` 替换内容,生成 DOCX 文件流 - [ ] 下载的 DOCX 字体、段落格式、页眉页脚与原模板一致 - [ ] 匹配不上的占位符清空(替换为空字符串) ### 4.3 不做的事情 - ❌ PDF 导出(可在阶段 3 补充) - ❌ oil-agent 集成(阶段 2) - ❌ 版本管理(阶段 3) ### 4.4 架构 > 详见 [技术架构文档 - 阶段 1](./text-editor-architecture.md#3-阶段-1word-模板--工作流文档编辑) **核心技术**: - `python-docx`:提取样式完整 XML(`full_xml_definition`)、注入样式到导出文档、解析模板占位符、生成最终 DOCX - `mistune 3.x`:将 Markdown 解析为 AST token 流,由 `DocxRenderer` 渲染为 python-docx 元素 - `docxtpl`(可选):基于 Jinja2 模板语法填充变量,用于模板导出场景 ### 4.5 Word 模板处理流程 ``` 样式上传阶段: 上传任意 .docx/.doc → 遍历所有样式 → 提取完整 XML(full_xml_definition)→ 序列化为 JSON 存储 模板上传阶段: 上传带占位符的 .docx → 解析 {{名称}} 列表 → 存储原始模板文件 + 元数据 生成文档阶段: 工作流触发 → 基于模板填充内容 → 生成文档记录(内容存 DB,关联 templateId) 编辑器打开阶段: 加载文档内容 → 查询关联模板元数据 → 编辑器 UI 应用对应标题样式 下载阶段(样式导出): 读取编辑器最新内容 + 加载样式 JSON → inject_styles_from_json() 注入样式 → DocxRenderer 渲染 → 返回 .doc 字节流 下载阶段(模板导出): 读取编辑器最新内容 → 按标题拆分内容块 → 遍历占位符匹配标题名 → docxtpl 替换后返回 DOCX 文件流 ``` ### 4.6 数据模型新增 ```typescript // 样式文件缓存表(从上传的 Word 文档提取) interface StyleFile { id: string; name: string; // 样式文件名(源文档文件名去扩展名) sourceFile: string; // 原始上传文件名(含扩展名) filePath: string; // 样式 JSON 存储路径 summary: { defaultFont: string; defaultSizePt: number; headingFonts: string[]; totalStyles: number; styleTypes: Record; }; isDefault: boolean; createdAt: number; } // 模板表(带占位符的 Word 文档) interface DocumentTemplate { id: string; name: string; fileKey: string; // 模板文件存储路径 placeholders: string[]; // 占位符名称列表,如 ["一季度回顾", "风险提示"] hasHeader: boolean; hasFooter: boolean; createdAt: number; } // documents 表新增字段 interface Document { // ...原有字段 templateId?: string; // 关联模板 ID(工作流文档使用) source: 'chat' | 'workflow'; } ``` ### 4.7 核心交互流程 ``` 【样式管理流程】 用户上传任意 Word 文档 ↓ POST /api/v1/styles 后端提取全量样式 XML,生成 JSON,写入样式缓存列表 ↓ 返回 { styleId, name, summary, ... } 【模板管理流程】 工作流中上传带占位符的 Word 模板 ↓ POST /api/v1/templates 后端解析 {{名称}} 列表,存储模板文件和元数据 ↓ 返回 { templateId, placeholders, ... } 【工作流文档编辑 + 下载】 工作流根据业务数据 + 模板生成文档内容,POST /api/v1/documents(携带 templateId) ↓ 用户点击"在编辑器中打开" ↓ 编辑器加载文档内容,应用模板标题层级样式 ↓ 用户修改内容 → 自动保存 ↓ 点击"下载" → POST /api/v1/export/docx ↓ 后端:读取最新文档内容 → 按标题拆分内容块 → 遍历占位符匹配 → 返回 DOCX 文件流 ↓ 浏览器下载,文件格式与模板一致 【自定义样式导出流程】 用户在保存对话框选择已上传的样式(styleId) ↓ POST /api/v1/export/doc(携带 styleId) ↓ 后端:加载对应样式 JSON → inject_styles_from_json() → DocxRenderer 渲染 → 返回 .doc ``` ### 4.8 前端文件结构(新增) ``` mod-chat/src/ components/ editor/ TemplateStyleProvider.tsx // 加载模板样式并注入 CSS 变量(新增) EditorToolbar.tsx // 扩展:工具栏样式选项与模板标题级别对应 api/ templateApi.ts // 模板信息查询 API 封装(新增) ``` ### 4.9 后端文件结构(新增) ``` backend/app/ models/ document_template.py // document_templates 表 ORM 模型(新增) style_file.py // style_files 表 ORM 模型(新增) schemas/ template.py // TemplateCreate / TemplateResponse(新增) style.py // StyleFileResponse / StyleFileSummary(新增) services/ template_service.py // 模板上传、占位符解析、文件存储(新增) style_service.py // 样式上传、全量 XML 提取、JSON 序列化(新增) export_service.py // 扩展:新增 export_docx_with_template();导出 .doc 时支持 styleId 加载 api/ v1/ templates.py // 模板管理路由(新增:POST、GET、DELETE) styles.py // 样式管理路由(新增:POST、GET、GET/{id}、DELETE/{id}) export.py // 扩展:新增 /export/docx 端点 ``` ### 4.9 排期 | 任务 | 预计工时 | |------|---------| | 模板上传 + 样式解析 API | 2 天 | | 工作流文档生成 API | 1 天 | | 编辑器应用模板样式(前端) | 2 天 | | DOCX 下载导出(python-docx 合并) | 2 天 | | 联调测试 | 1 天 | | **合计** | **约 2 周** | --- ## 5. 阶段 2:oil-agent 跨平台集成 ### 5.1 目标 将编辑器组件微前端化,供 oil-agent 平台通过免登录 Token 接入,两个平台共用同一份文档数据。 ### 5.2 功能范围 1. **编辑器微前端化** - [ ] 打包为 Module Federation 远程模块 - [ ] 通过 Props 配置 API 端点、主题、权限 2. **免登录 Token 机制** - [ ] 生成临时访问 Token(JWT + Redis) - [ ] oil-agent 凭 Token 打开编辑器,访问指定文档 - [ ] Token 有效期管理(默认 1 小时) 3. **跨平台数据同步** - [ ] 编辑完成后通过 Webhook 通知 oil-agent - [ ] oil-agent 接收事件后拉取最新文档 ### 5.3 架构 > 详见 [技术架构文档 - 阶段 2](./text-editor-architecture.md#4-阶段-2oil-agent-跨平台集成) ### 5.4 核心交互流程 ``` oil-agent 请求编辑某文档 ↓ 调用后端 POST /api/v1/edit-sessions,生成一次性 Token ↓ 返回编辑器 URL(含 Token) ↓ oil-agent 打开新窗口,编辑器凭 Token 加载文档 ↓ 用户编辑保存 → 后端 Webhook 通知 oil-agent ↓ oil-agent 拉取最新文档内容 ``` ### 5.5 前端文件结构(新增) ``` mod-editor/src/ // 新建独立微前端包 components/ editor/ DocumentEditor.tsx // 从 mod-chat 迁移,支持 platform prop EditorToolbar.tsx EditorPreview.tsx SaveDialog.tsx transport/ EditorTransport.ts // SSE / WebSocket 通信适配层(新增) store/ editorStore.ts api/ documentApi.ts exportApi.ts editSessionApi.ts // Token 会话 API 封装(新增) vite.config.ts // Module Federation 打包配置(新增) ``` ### 5.6 后端文件结构(新增) ``` backend/app/ core/ cache.py // Redis 连接池(新增) models/ edit_session.py // edit_sessions 表 ORM 模型(新增) schemas/ edit_session.py // CreateEditSessionRequest / Response(新增) services/ token_service.py // JWT 生成/验证/撤销 + Redis 双重校验(新增) webhook_service.py // 触发 Webhook、签名、重试(新增) api/ v1/ edit_sessions.py // Token 会话路由(新增,3 个端点) webhooks.py // Webhook 配置路由(新增) ``` ### 5.6 排期 | 任务 | 预计工时 | |------|---------| | Module Federation 打包配置 | 1 天 | | Token 服务(JWT + Redis) | 2 天 | | 编辑会话 API | 1 天 | | Webhook 通知 | 1 天 | | oil-agent 接入联调 | 2 天 | | **合计** | **约 2 周** | --- ## 6. 阶段 3:编辑器增强 + 协同编辑 ### 6.1 目标 提升编辑器的编辑体验,并在有需要时支持多人实时协同。 ### 6.2 功能范围 1. **富文本增强** - [ ] 完整格式工具栏(H1-H6、粗体、斜体、列表、代码块、引用块、表格) - [ ] 快捷键(Ctrl+B / Ctrl+I 等) - [ ] 撤销/重做、字数统计、查找替换 - [ ] 图片上传插入 2. **PDF 导出** - [ ] 导出为 PDF(weasyprint) 3. **版本管理** - [ ] 文档版本历史列表 - [ ] 回滚到指定版本 - [ ] 版本对比(Diff) 4. **实时协同(可选,按需启用)** - [ ] 多人同时编辑(Yjs CRDT + WebSocket) - [ ] 实时光标显示、在线用户列表 - [ ] 冲突解决可视化 ### 6.3 架构 阶段 3 完整架构图见 [技术架构文档 - 阶段 3](./text-editor-architecture.md),新增依赖:Yjs、y-websocket、S3/MinIO(大文档迁移)、API Gateway。 ### 6.4 前端文件结构(新增) ``` mod-editor/src/ components/ editor/ CollabCursors.tsx // 协同光标渲染(在线用户实时位置)(新增) OnlineUserList.tsx // 在线用户列表展示(新增) toolbar/ FormatToolbar.tsx // 完整富文本工具栏(H1-H6、代码块、表格等)(新增) FindReplacePanel.tsx // 查找替换面板(新增) version/ VersionHistoryPanel.tsx // 版本历史列表(新增) VersionDiffView.tsx // 两版本对比 Diff 视图(新增) plugins/ withCollaboration.ts // Slate + Yjs 协同插件(新增) withHistory.ts // 撤销/重做扩展 withImages.ts // 图片上传插入插件(新增) api/ versionApi.ts // 版本历史 API 封装(新增) exportApi.ts // 扩展:新增 PDF 导出 ``` ### 6.5 后端文件结构(新增) ``` backend/app/ models/ document_version.py // document_versions 表 ORM 模型(新增) schemas/ version.py // VersionResponse / DiffResponse(新增) services/ version_service.py // 版本快照、回滚、Diff 逻辑(新增) collab_service.py // y-websocket 会话管理(新增) export_service.py // 扩展:新增 export_pdf() api/ v1/ versions.py // 版本管理路由(新增,3 个端点) export.py // 扩展:新增 /export/pdf 端点 collab.py // WebSocket 协同端点(新增) ``` ### 6.6 排期 | 任务 | 预计工时 | |------|---------| | 富文本工具栏增强 | 3 天 | | PDF 导出 | 2 天 | | 版本管理(DB 设计 + API + 前端) | 3 天 | | 实时协同(Yjs + WebSocket) | 5 天 | | **合计** | **约 3-4 周** | --- ## 7. 数据模型汇总 ### 7.1 documents 表 | 字段 | 类型 | 说明 | |------|------|------| | id | string | 文档唯一 ID | | title | string | 标题 | | content | text | 文档内容(Markdown / JSON),上限 200KB | | format | string | `markdown`(阶段 0/1);阶段 3 支持 `json` | | template_id | string | 关联模板 ID,工作流文档必填(阶段 1 引入) | | source | string | `chat` / `workflow` | | session_id | string | 关联聊天会话 ID(可选) | | created_by | string | 创建者 | | created_at | timestamp | | | updated_at | timestamp | | ### 7.2 export_records 表(阶段 0 引入) | 字段 | 类型 | 说明 | |------|------|------| | id | string | 记录唯一 ID | | user_id | string | 所属用户 ID | | file_name | string | 含扩展名的完整文件名 | | file_path | string | 磁盘存储绝对路径 | | file_size | integer | 文件大小(字节) | | download_url | string | 永久下载链接 | | document_id | string | 关联文档 ID(可空) | | style_id | string | 使用的样式 ID,默认 `"default"` | | created_at | timestamp | | ### 7.3 document_templates 表(阶段 1 引入) | 字段 | 类型 | 说明 | |------|------|------| | id | string | 模板唯一 ID | | name | string | 模板名称 | | file_key | string | 模板文件存储路径 | | placeholders | jsonb | 占位符名称列表,如 `["一季度回顾", "风险提示"]` | | has_header | boolean | 是否包含页眉 | | has_footer | boolean | 是否包含页脚 | | created_at | timestamp | | ### 7.4 style_files 表(阶段 1 引入) | 字段 | 类型 | 说明 | |------|------|------| | id | string | 样式文件唯一 ID | | name | string | 样式文件名(源文档文件名去扩展名) | | source_file | string | 原始上传文件名(含扩展名) | | file_path | string | 样式 JSON 存储路径 | | summary | jsonb | 摘要(默认字体、标题字体、样式总数等) | | is_default | boolean | 是否为系统默认样式 | | created_at | timestamp | | ### 7.5 edit_sessions 表(阶段 2 引入) | 字段 | 类型 | 说明 | |------|------|------| | id | string | 会话 ID | | document_id | string | 文档 ID | | token_hash | string | Token 哈希(不存明文) | | platform | string | `mod-chat` / `oil-agent` | | status | string | `active` / `closed` | | expires_at | timestamp | | --- ## 8. API 汇总 | 接口 | 方法 | 引入阶段 | 说明 | |------|------|---------|------| | /api/v1/documents | POST | 阶段 0 | 创建文档 | | /api/v1/documents | GET | 阶段 0 | 获取文档列表 | | /api/v1/documents/{id} | GET | 阶段 0 | 获取文档详情 | | /api/v1/documents/{id} | PUT | 阶段 0 | 更新文档(全量或局部块) | | /api/v1/documents/{id} | DELETE | 阶段 0 | 删除文档 | | /api/v1/export/doc | POST | 阶段 0 | 导出 .doc,写入下载记录,返回永久下载链接 | | /api/v1/export/records | GET | 阶段 0 | 获取下载记录列表 | | /api/v1/export/records/{recordId}/download | GET | 阶段 0 | 重新下载文件 | | /api/v1/export/records/{recordId} | DELETE | 阶段 0 | 删除下载记录(同步删除磁盘文件) | | /api/v1/admin/storage | GET | 阶段 0 | 管理端:查看磁盘配额与各用户占用 | | /api/v1/styles | POST | 阶段 1 | 上传 Word 文档,提取全量样式 XML,加入样式缓存列表 | | /api/v1/styles | GET | 阶段 1 | 获取样式缓存列表(含系统默认样式) | | /api/v1/styles/{styleId} | GET | 阶段 1 | 获取样式详情(含完整 XML 定义) | | /api/v1/styles/{styleId} | DELETE | 阶段 1 | 删除自定义样式 | | /api/v1/templates | POST | 阶段 1 | 上传带占位符的 Word 模板 | | /api/v1/templates | GET | 阶段 1 | 获取模板列表 | | /api/v1/templates/{id} | GET | 阶段 1 | 获取模板详情 | | /api/v1/templates/{id} | DELETE | 阶段 1 | 删除模板 | | /api/v1/export/docx | POST | 阶段 1 | 按模板导出 DOCX(占位符填充,保持模板格式) | | /api/v1/edit-sessions | POST | 阶段 2 | 创建编辑会话,返回 Token 和编辑器 URL | | /api/v1/edit-sessions/{id}/document | GET | 阶段 2 | 凭 Token 获取文档内容和权限信息 | | /api/v1/edit-sessions/{id} | DELETE | 阶段 2 | 关闭会话,立即撤销 Token | | /api/v1/webhooks | POST | 阶段 2 | 配置 Webhook(订阅 document.updated / session.closed) | | /api/v1/export/pdf | POST | 阶段 3 | 导出 PDF | | /api/v1/documents/{id}/versions | GET | 阶段 3 | 版本历史列表 | | /api/v1/documents/{id}/versions/{vId}/restore | POST | 阶段 3 | 回滚版本 | | /api/v1/documents/{id}/versions/diff | GET | 阶段 3 | 版本对比(Diff) | --- ## 9. 前端本地存储与同步策略 ### 9.1 大小文件判断与同步方向 以 **200KB** 为分界线: | 文档大小 | 自动保存行为 | |---------|------------| | **小文件(< 200KB)** | 防抖 3 秒后直接 PUT 到后端,本地不做额外缓存 | | **大文件(≥ 200KB)** | 写入前端 IndexedDB 缓存,不立即推后端;用户手动点"保存"时才同步到后端 | ### 9.2 存储分层 | 存储类型 | 用途 | 说明 | |---------|------|------| | **Cookies** | 小型临时状态 | 存储编辑器 UI 状态(当前 documentId、预览模式开关等),TTL 与会话一致 | | **localStorage** | 小文件草稿块缓存 | 按标题块存储变更内容,key 格式见 9.3,浏览器关闭后仍保留 | | **IndexedDB** | 大文件内容缓存 | 存储 ≥ 200KB 的文档内容,异步读写,不阻塞主线程 | ### 9.3 局部更新策略(按标题等级) 不管文档大小,每次编辑都按**标题等级**定位到具体块做局部更新,而不是全量覆盖整个文档。 **块的定义**:以标题节点(H1/H2/H3...)为分割点,每个标题及其下属正文构成一个块,用"第 N 个 K 级标题"来唯一定位。 ``` 文档结构示例: # 第一章 ← H1[0] ## 1.1 背景 ← H2[0] ## 1.2 目标 ← H2[1] # 第二章 ← H1[1] ## 2.1 方案 ← H2[2] 用户只修改了"1.2 目标"下的正文 ↓ 定位到块:{ level: 2, index: 1 } 即"第 2 个 H2 标题" ↓ 只更新该块,其余块不变 ``` **本地存储 key 格式**: ``` localStorage key: doc:{documentId}:h{level}:{index} 示例: doc:doc-abc123:h2:1 ``` **后端局部更新请求**(见 API 文档 2.3): ```json { "blocks": [ { "level": 2, "index": 1, "content": "更新后的内容..." } ] } ``` ### 9.4 自动保存完整流程 ``` 用户编辑内容 ↓ 计算变动块(level + index) ↓ 写入 localStorage(小文件)或 IndexedDB(大文件) ↓ 判断文档大小 ├── 小文件(< 200KB) │ 防抖 3 秒 → PUT /api/v1/documents/{id}(携带变动块) │ 后端更新成功 → 清除对应本地缓存块 │ 后端更新失败 → 保留缓存,下次打开时提示"有未同步的草稿" │ └── 大文件(≥ 200KB) 只写 IndexedDB,不自动推后端 用户手动点"保存" → 全量或按块同步到后端 ``` ### 9.5 打开编辑器时的恢复逻辑 ``` 打开编辑器,传入 documentId ↓ 检查 localStorage / IndexedDB 是否有本地缓存块 ├── 有缓存 → 对比本地 updatedAt 与服务端 updatedAt │ ├── 本地更新 → 提示"发现未同步的草稿,是否恢复?" │ │ 用户确认 → 合并本地块覆盖服务端内容 │ │ 用户拒绝 → 丢弃本地缓存,加载服务端内容 │ └── 服务端更新 → 直接加载服务端内容,清除本地缓存 └── 无缓存 → 直接从服务端加载文档内容 ``` --- ## 10. 风险与应对 | 风险 | 影响 | 应对措施 | |------|------|---------| | Word 模板样式复杂,解析不完整 | 下载格式与模板不一致 | 阶段 1 先支持标题/正文/字体基础样式,复杂样式(表格边框、页眉图片等)逐步覆盖 | | 编辑器富文本节点与 DOCX 样式映射困难 | 导出排版错乱 | 建立明确的节点类型 ↔ DOCX 样式映射表,作为开发规范 | | 大文档(> 200KB)阶段 0 无法存储 | 工作流生成超长文档时失败 | 阶段 0 强制限制,阶段 1 引入文件存储后解除 | | oil-agent Token 安全 | 数据泄露 | JWT + Redis 双重验证,Token 与文档 ID 绑定,默认 1 小时过期 | | 协同编辑并发冲突 | 数据不一致 | 引入成熟 CRDT 库(Yjs),阶段 3 单独评估 | --- ## 11. 附录 ### 相关文档 - [技术架构文档](./text-editor-architecture.md) - [后端 API 详细文档](./text-editor-backend-api.md) ### 参考资料 - [Slate.js 官方文档](https://docs.slatejs.org/) - [python-docx 文档](https://python-docx.readthedocs.io/) - [docxtpl 文档](https://docxtpl.readthedocs.io/) - [Yjs 协同编辑框架](https://yjs.dev/)(阶段 3) --- **文档版本**: v5.0 **最后更新**: 2026-06-18 **维护者**: Axonix 前端团队