# 文档管理设计文档 > 关联模块:`app/api/v1/documents.py` · `app/services/document_service.py` · `app/models/document.py` · `app/schemas/document.py` > 关联章节:[text-editor-backend-api.md](./text-editor-backend-api.md) § 2. 文档管理 API(阶段 0) --- ## 1. 概览 | 概览 | 内容 | |------|------| | 功能 | 从 Word 文档链接解析 Markdown 内容并持久化存储,提供文档 CRUD 接口供编辑器使用 | | 引入阶段 | 阶段 0 | | Base URL | `http://192.168.0.200:8000` | | 涉及端点 | `POST /api/v1/documents` · `GET /api/v1/documents` · `GET /api/v1/documents/{documentId}` · `PUT /api/v1/documents/{documentId}` · `DELETE /api/v1/documents/{sessionId}` | | 存储方式 | PostgreSQL,内容以 Markdown 文本直接存储,不限制大小 | | 关联功能 | 文档创建后可关联会话 ID;导出时通过 `documentId` 读取内容 | --- ## 2. 数据模型 ### 2.1 documents 表 | 字段 | 类型 | 说明 | |------|------|------| | `id` | VARCHAR(64) | 主键,格式 `doc-{uuid[:12]}`,自动生成 | | `content` | TEXT | Markdown 内容,从 Word 文档解析而来 | | `format` | VARCHAR(32) | 内容格式,固定为 `markdown` | | `session_id` | VARCHAR(128) | 关联聊天会话 ID,必填 | | `created_by` | VARCHAR(128) | 创建者用户 ID | | `created_at` | TIMESTAMPTZ | 创建时间,服务端自动生成 | | `updated_at` | TIMESTAMPTZ | 最后更新时间,每次 PUT 手动赋值刷新 | > `updated_at` 由 `document_service.py` 在每次更新时手动赋值 `datetime.now(timezone.utc)`,而非依赖数据库 `onupdate` 触发,确保时间准确。 ### 2.2 ORM 模型(`app/models/document.py`) ```python class Document(Base): __tablename__ = "documents" id: Mapped[str] = mapped_column( String(64), primary_key=True, default=lambda: f"doc-{uuid.uuid4().hex[:12]}" ) content: Mapped[str] = mapped_column(Text, nullable=False, default="") format: Mapped[str] = mapped_column(String(32), nullable=False, default="markdown") session_id: Mapped[str] = mapped_column(String(128), nullable=False) created_by: Mapped[str | None] = mapped_column(String(128), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) ``` --- ## 3. 端点详情 ### 3.1 创建文档 **`POST /api/v1/documents`** **触发时机**:用户在 Chat 中点击"编辑"按钮时。 **处理流程**: 1. 接收 `userId`、`fileUrl`、`sessionId` 2. 后端下载 `fileUrl` 指向的 Word 文档(.doc / .docx) 3. 使用 `python-docx` 解析文档内容,转换为 Markdown 格式 4. 将 Markdown 内容写入 `documents` 表 5. 删除临时下载文件 6. 返回 `documentId`、`format`、`createdAt` **注意**: - 不限制文件大小 - Word 文档下载完成后自动删除,不留临时文件 - 此接口只做解析和持久化,不生成任何导出文件 #### 请求 ```json { "userId": "user-xyz", "fileUrl": "https://example.com/files/report.docx", "sessionId": "chat-session-123" } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `userId` | string | 是 | 当前用户 ID,写入 `created_by` 字段 | | `fileUrl` | string | 是 | Word 文档的可访问链接,后端下载后解析为 Markdown | | `sessionId` | string | 是 | 关联聊天会话 ID,必填 | #### 响应 ```json { "code": 0, "data": { "documentId": "doc-abc123", "format": "markdown", "createdAt": 1680000000000 } } ``` #### 错误响应 | HTTP 状态码 | code | 触发条件 | |------------|------|---------| | 400 | 400 | fileUrl 无法访问或文件格式不支持 | | 500 | 500 | Word 解析失败 | --- ### 3.2 获取文档详情 **`GET /api/v1/documents/{documentId}`** **触发时机**:编辑器 Modal 打开后,根据 `documentId` 加载文档内容。 #### 请求 路径参数:`documentId` #### 响应 ```json { "code": 0, "data": { "id": "doc-abc123", "content": "# 标题\n\n这是文档内容...", "format": "markdown", "sessionId": "chat-session-123", "userId": "user-xyz", "createdAt": 1680000000000, "updatedAt": 1680000100000 } } ``` #### 错误响应 | HTTP 状态码 | code | 触发条件 | |------------|------|---------| | 404 | 404 | documentId 不存在 | --- ### 3.3 更新文档 **`PUT /api/v1/documents/{documentId}`** **触发时机**:用户在编辑器中点击"保存"按钮时。 支持两种模式,**二选一,不可同时传入**: - **全量更新**:传入 `content`,直接替换整篇内容 - **局部块更新**:传入 `blocks`,按标题级别和顺序下标定位到具体块后替换,其余内容不变 #### 局部块更新说明 块的定义:以标题行(`#` / `##` / ...)为分割点,每个标题行及其下属正文构成一个块。 定位方式:`level`(标题等级)+ `index`(该等级在全文中的出现顺序,从 0 开始)。 示例:文档有两个 `##` 标题,修改第二个时传 `{ "level": 2, "index": 1, "content": "..." }`。 #### 请求(全量更新) ```json { "content": "# 更新后的完整内容\n\n..." } ``` #### 请求(局部块更新) ```json { "blocks": [ { "level": 2, "index": 1, "content": "## 1.2 目标\n\n更新后的段落内容..." } ] } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `content` | string | 否 | 全量替换内容,与 `blocks` 二选一 | | `blocks` | array | 否 | 局部块更新列表,与 `content` 二选一 | | `blocks[].level` | integer | 是 | 标题等级,1 = H1,2 = H2,以此类推(1-6) | | `blocks[].index` | integer | 是 | 该等级标题在全文中的顺序下标,从 0 开始 | | `blocks[].content` | string | 是 | 该块的新内容,须包含标题行本身 | #### 响应 ```json { "code": 0, "data": { "documentId": "doc-abc123", "updatedAt": 1680000200000 } } ``` #### 错误响应 | HTTP 状态码 | code | 触发条件 | |------------|------|---------| | 404 | 404 | documentId 不存在 | | 422 | 422 | content 和 blocks 同时传入 | --- ### 3.4 删除会话的所有文档 **`DELETE /api/v1/documents/{sessionId}`** **触发时机**:删除对应会话时,联动删除该会话关联的所有文档记录。 硬删除,数据库记录直接移除,不支持撤销。如果 sessionId 下没有文档,也会返回成功。 #### 请求 路径参数:`sessionId` #### 响应 ```json { "code": 0, "message": "Deleted 3 document(s) successfully" } ``` #### 说明 - 删除指定 `sessionId` 下的所有文档 - 返回消息中包含实际删除的文档数量 - 如果该 sessionId 下没有文档,返回 "Deleted 0 document(s) successfully" --- ### 3.5 获取文档列表 **`GET /api/v1/documents`** 分页查询当前用户的文档记录,列表项不含 `content`,减少传输量。 #### 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `userId` | string | 是 | 当前用户 ID | | `page` | integer | 否 | 页码,默认 1 | | `pageSize` | integer | 否 | 每页数量,默认 20,最大 100 | | `sessionId` | string | 否 | 按聊天会话过滤 | | `sortBy` | string | 否 | `createdAt` / `updatedAt`,默认 `updatedAt` | | `sortOrder` | string | 否 | `asc` / `desc`,默认 `desc` | #### 响应 ```json { "code": 0, "data": { "documents": [ { "id": "doc-abc123", "sessionId": "chat-session-123", "createdAt": 1680000000000, "updatedAt": 1680000100000 } ], "pagination": { "page": 1, "pageSize": 20, "total": 100, "totalPages": 5 } } } ``` --- ## 4. 内部处理流程 ### 4.1 创建文档流程 ``` POST /api/v1/documents | v 参数校验(userId, fileUrl 不为空) | v 下载 fileUrl 文件到临时目录 - 下载失败 → 返回 400 | v python-docx 解析 Word 文档 → Markdown 内容 - 解析失败 → 返回 500 | v 写入 documents 表 - id:doc-{uuid[:12]} - content:解析后的 Markdown - format:markdown - session_id:sessionId(必填) - created_by:userId | v 删除临时下载文件 | v 返回 { documentId, format, createdAt } ``` ### 4.2 局部块更新流程(`_apply_block_updates`) ``` 接收 content(当前全文)+ blocks(待替换块列表) | v 按行扫描文档,用正则 ^(#{1,6})\s+ 找出所有标题行位置 → 得到 [(line_idx, level), ...] | v 统计每个 level 的出现顺序,生成带 index 的标题信息 → [(line_idx, level, index), ...] | v 按标题行位置将全文切分为若干 chunk → chunk[0]:首个标题前的前置内容 → chunk[1..N]:各标题块(含标题行 + 下属正文) → 同时构建 (level, index) → chunk_idx 的 O(1) 映射字典 | v 遍历 blocks 列表,按 (level, index) 定位 chunk,替换内容 | v 将所有 chunk 重新拼接,strip 后补末尾换行,返回新内容 ``` --- ## 5. 服务层核心逻辑(`document_service.py`) ### 5.1 关键方法 | 方法 | 说明 | |------|------| | `create_document(data, user_id)` | 创建文档,写入数据库 | | `get_document(document_id)` | 查询单条,不存在抛 `DocumentNotFoundError` | | `list_documents(page, page_size, session_id, sort_by, sort_order)` | 分页查询,使用 `SELECT COUNT(*) FROM subquery` 计总数 | | `update_document(document_id, data)` | 全量或局部更新,手动刷新 `updated_at` | | `delete_document(document_id)` | 硬删除 | | `_apply_block_updates(content, blocks)` | 静态方法,局部块替换核心算法 | ### 5.2 异常处理 | 异常类 | 触发条件 | HTTP 状态码 | |--------|---------|------------| | `DocumentNotFoundError` | `get_document()` 查不到记录 | 404 | --- ## 6. Schema 说明(`app/schemas/document.py`) 所有请求字段使用 camelCase 别名接收,响应字段使用 `serialization_alias` 输出 camelCase。 | Schema 类 | 用途 | |-----------|------| | `CreateDocumentRequest` | 创建请求,含 `userId`、`fileUrl`、`sessionId` | | `UpdateDocumentRequest` | 更新请求,含 `content` / `blocks` 二选一校验(`model_validator`) | | `BlockUpdate` | 局部块更新单元,含 `level`、`index`、`content` | | `DocumentResponse` | 详情响应,含完整字段 | | `DocumentListItem` | 列表项响应,不含 `content` | | `Pagination` | 分页信息 | --- ## 7. 涉及代码文件 | 文件 | 职责 | |------|------| | `app/api/v1/documents.py` | 路由层:五个端点的参数接收、服务调用、响应封装 | | `app/services/document_service.py` | 业务逻辑:CRUD、局部块更新算法 | | `app/models/document.py` | ORM 模型:`Document` 表定义 | | `app/schemas/document.py` | 请求/响应 Pydantic 模型 | | `app/core/exceptions.py` | `DocumentNotFoundError` 定义及全局 handler | | `migrations/versions/001_init_documents.py` | 建表迁移脚本 | --- ## 8. 请求示例 ### 创建文档 ```bash curl -X POST http://192.168.0.200:8000/api/v1/documents \ -H "Content-Type: application/json" \ -d '{ "userId": "user-xyz", "fileUrl": "https://example.com/files/report.docx", "sessionId": "chat-session-123" }' ``` ### 获取文档详情 ```bash curl "http://192.168.0.200:8000/api/v1/documents/doc-abc123" ``` ### 全量更新文档 ```bash curl -X PUT http://192.168.0.200:8000/api/v1/documents/doc-abc123 \ -H "Content-Type: application/json" \ -d '{"content": "# 新标题\n\n新的完整内容..."}' ``` ### 局部块更新 ```bash curl -X PUT http://192.168.0.200:8000/api/v1/documents/doc-abc123 \ -H "Content-Type: application/json" \ -d '{ "blocks": [ { "level": 2, "index": 0, "content": "## 一、背景\n\n更新后的背景描述..." } ] }' ``` ### 删除会话的所有文档 ```bash curl -X DELETE "http://192.168.0.200:8000/api/v1/documents/chat-session-123" ``` ### 获取文档列表 ```bash curl "http://192.168.0.200:8000/api/v1/documents?userId=user-xyz&page=1&pageSize=20&sessionId=chat-session-123" ``` --- **文档版本**:v1.0 **创建日期**:2026-06-24 **关联文档**:[text-editor-backend-api.md](./text-editor-backend-api.md) · [export-doc-content-mapping.md](./export-doc-content-mapping.md) · [text-editor-architecture.md](./text-editor-architecture.md)