text-editor-backend-api.md 39 KB

文本编辑器后端 API 详细文档

1. 概述

1.1 基本信息

  • Base URL: https://api.axonix.com
  • API 版本: v1
  • 协议: HTTPS
  • 认证方式: 复用宿主应用 Auth(阶段 0/1);阶段 2 引入 Bearer Token (JWT)
  • 响应格式: JSON

1.2 分阶段 API 范围

阶段 新增 API
阶段 0 文档 CRUD、导出 .doc(默认样式)
阶段 1 样式管理、模板管理、导出 DOCX
阶段 2 编辑会话(Token)、Webhook
阶段 3 PDF 导出、版本管理

1.3 样式与模板的区别

这是两个独立功能,用户可以按需使用其中一个或两个都不用:

样式(Style) 模板(Template)
用途 控制输出文件的字体、颜色、标题层级等视觉格式 定义文档结构,内容填入占位符位置
上传内容 任意 Word 文档,后端提取其中的样式文件 带占位符({{名称}})的 Word 文档
导出方式 导出 .doc 时选择样式,不选用系统默认 导出 DOCX 时指定模板,内容按标题匹配占位符
是否必须 否,有默认样式兜底 否,不使用模板则走样式导出流程

1.4 通用响应格式

{
  "code": 0,
  "message": "Success",
  "data": { },
  "timestamp": 1680000000000
}

2. 文档管理 API(阶段 0)

数据结构

from pydantic import BaseModel, Field
from typing import Literal, Optional
from datetime import datetime

# 局部块更新
class BlockUpdate(BaseModel):
    level: int          # 标题等级:1=H1,2=H2,以此类推
    index: int          # 该等级标题在文档中的顺序下标(从 0 开始)
    content: str        # 该块的新内容(含标题行本身)

# 创建文档请求
class CreateDocumentRequest(BaseModel):
    title: str                                      # 文档标题,最大 255 字符
    content: str                                    # 文档内容(Markdown),最大 200KB
    format: Literal["markdown"] = "markdown"        # 内容格式,默认 markdown
    session_id: Optional[str] = None                # 关联的聊天会话 ID
    template_id: Optional[str] = None              # 关联 Word 模板 ID(阶段 1 引入)

# 更新文档请求(全量或局部块)
class UpdateDocumentRequest(BaseModel):
    title: Optional[str] = None
    content: Optional[str] = None       # 最大 200KB,与 blocks 二选一
    blocks: Optional[list[BlockUpdate]] = None  # 局部块更新,与 content 二选一

# 文档完整信息(响应)
class DocumentResponse(BaseModel):
    id: str
    title: str
    content: str
    format: str
    session_id: Optional[str] = None
    template_id: Optional[str] = None
    created_by: str
    created_at: datetime
    updated_at: datetime

# 文档列表项(不含 content,减少传输量)
class DocumentListItem(BaseModel):
    id: str
    title: str
    template_id: Optional[str] = None
    created_at: datetime
    updated_at: datetime

# 分页信息
class Pagination(BaseModel):
    page: int
    page_size: int
    total: int
    total_pages: int

2.1 创建文档

触发时机:用户在 Chat 中点击"编辑"按钮时。
目的:在数据库中建立一条文档记录,获取 documentId,后续编辑过程中的防抖自动保存(PUT)都依赖这个 ID。
注意:此接口只做持久化,不生成任何文件,不返回下载链接。

接口地址: POST /api/v1/documents

请求体:

{
  "title": "我的文档",
  "content": "# 标题\n\n这是文档内容...",
  "format": "markdown",
  "sessionId": "chat-session-123",
  "templateId": "tpl-001"
}
参数 类型 必填 说明
title string 文档标题,最大 255 字符
content string 文档内容,最大 200KB
format string 默认 markdown
sessionId string 关联聊天会话 ID
templateId string 关联 Word 模板 ID(阶段 1 引入)

响应示例:

{
  "code": 0,
  "data": {
    "documentId": "doc-abc123",
    "title": "我的文档",
    "format": "markdown",
    "createdAt": 1680000000000
  }
}

2.2 获取文档详情

触发时机:编辑器 Modal 打开后,根据 documentId 加载文档内容。
目的:拉取数据库中存储的文档内容,回填到编辑器。

接口地址: GET /api/v1/documents/{documentId}

响应示例:

{
  "code": 0,
  "data": {
    "id": "doc-abc123",
    "title": "我的文档",
    "content": "# 标题\n\n这是文档内容...",
    "format": "markdown",
    "sessionId": "chat-session-123",
    "templateId": "tpl-001",
    "createdBy": "user-xyz",
    "createdAt": 1680000000000,
    "updatedAt": 1680000100000
  }
}

2.3 更新文档

触发时机:小文件(< 200KB)编辑后防抖 3 秒自动触发;大文件由用户手动保存时触发。
目的:将编辑器变动内容同步到数据库。支持全量更新和按标题块的局部更新,局部更新只传变动的块,减少传输量。

接口地址: PUT /api/v1/documents/{documentId}

请求体(全量更新):

{
  "title": "更新后的标题",
  "content": "更新后的完整内容..."
}

请求体(局部块更新):

{
  "blocks": [
    {
      "level": 2,
      "index": 1,
      "content": "## 1.2 目标\n\n更新后的段落内容..."
    }
  ]
}
参数 类型 必填 说明
title string 新标题(全量更新时使用)
content string 新的完整内容,最大 200KB(全量更新时使用)
blocks array 局部块更新列表,与 content 二选一
blocks[].level integer 标题等级,1 = H1,2 = H2,以此类推
blocks[].index integer 该等级标题在文档中的顺序下标(从 0 开始)
blocks[].content string 该块的新内容(包含标题行本身)

响应示例:

{
  "code": 0,
  "data": {
    "documentId": "doc-abc123",
    "updatedAt": 1680000200000
  }
}

2.4 删除文档

触发时机:用户主动删除某条文档记录时。
目的:从数据库中删除文档草稿记录。

接口地址: DELETE /api/v1/documents/{documentId}

响应示例:

{
  "code": 0,
  "message": "Document deleted successfully"
}

2.5 获取文档列表

触发时机:需要展示历史文档列表时。
目的:分页查询当前用户的文档草稿记录。

接口地址: GET /api/v1/documents

参数 类型 必填 说明
page integer 页码,默认 1
pageSize integer 每页数量,默认 20,最大 100
sessionId string 按聊天会话过滤
sortBy string createdAt / updatedAt,默认 updatedAt
sortOrder string asc / desc,默认 desc

响应示例:

{
  "code": 0,
  "data": {
    "documents": [
      {
        "id": "doc-abc123",
        "title": "我的文档",
        "templateId": "tpl-001",
        "createdAt": 1680000000000,
        "updatedAt": 1680000100000
      }
    ],
    "pagination": {
      "page": 1,
      "pageSize": 20,
      "total": 100,
      "totalPages": 5
    }
  }
}

3. 导出 API(阶段 0)

本节说明:阶段 0 导出不依赖样式库和模板,使用系统内置默认样式将 Markdown 内容转换为 .doc 文件。阶段 1 引入样式管理后,本接口扩展支持用户选择自定义样式(通过 styleId 参数),接口结构不变。

导出文件按用户、按天分区保存在服务器本地,下载链接永久有效。每次导出生成一条下载记录,用户可通过下载记录接口随时重新下载或删除历史文件。后台运行磁盘监控模块,超出配额时自动向相关用户和管理员发送警告。

数据结构

from pydantic import BaseModel
from typing import Literal, Optional
from datetime import datetime

# 导出 .doc 请求
class ExportDocRequest(BaseModel):
    file_name: str                      # 文件名(不含扩展名),最大 255 字符
    format: Literal["doc"]              # 阶段 0 固定为 doc
    content: str                        # 文档当前内容(Markdown)
    document_id: Optional[str] = None  # 关联文档 ID,传入时后端同步更新草稿记录
    style_id: Optional[str] = None     # 样式 ID;不传时使用系统默认样式;阶段 1 起支持用户自定义样式

# 导出响应
class ExportDocResponse(BaseModel):
    record_id: str      # 下载记录 ID,可用于重新下载或删除
    download_url: str   # 永久下载链接
    file_name: str      # 含扩展名的完整文件名,如 季度报告_2026Q2.doc
    style_id: str       # 实际使用的样式 ID
    warning: Optional[str] = None  # 存储空间超限时附带警告信息,正常时为 null

# 下载记录
class ExportRecord(BaseModel):
    record_id: str
    user_id: str
    file_name: str                  # 含扩展名的完整文件名
    file_size: int                  # 文件大小(字节)
    download_url: str               # 永久下载链接
    document_id: Optional[str]      # 关联文档 ID(可空)
    style_id: str                   # 使用的样式 ID
    created_at: datetime

# 下载记录列表响应
class ExportRecordListResponse(BaseModel):
    records: list[ExportRecord]
    pagination: Pagination          # 分页信息,复用文档管理中的 Pagination 结构

文件存储结构

导出文件按用户、按天分区保存,路径格式如下:

tmp/
└── {user_id}/
    └── {YYYY-MM-DD}/
        └── {filename}_{token}.doc

3.1 导出 .doc

触发时机:用户点击编辑器"保存"按钮,在弹出对话框中填写文件名、选择格式并确认后触发。
目的:将 Markdown 内容按指定样式渲染生成 .doc 文件,持久化保存到服务器并写入下载记录,返回永久下载链接。输出字体、标题层级、段落间距等均由样式文件控制。
注意:此接口不操作数据库文档记录,仅负责文件生成与记录写入。如需同时更新草稿,可传入 documentId,后端会先更新一次记录再生成文件。

接口地址: POST /api/v1/export/doc

请求体:

{
  "userId": "user-xyz",
  "fileName": "季度报告_2026Q2",
  "format": "doc",
  "content": "# 标题\n\n这是文档内容...",
  "documentId": "doc-abc123",
  "styleId": "style-001"
}
参数 类型 必填 说明
userId string 当前用户 ID,用于文件分区存储和配额检查
fileName string 用户填写的文件名(不含扩展名),最大 255 字符
format string 文件格式,阶段 0 固定为 doc
content string 文档当前内容(Markdown)
documentId string 关联的文档 ID,用于同步更新自动保存记录
styleId string 样式 ID;不传时使用系统默认样式;阶段 1 起可传用户上传的自定义样式 ID

响应示例(正常):

{
  "code": 0,
  "data": {
    "recordId": "rec-abc123",
    "downloadUrl": "https://api.axonix.com/api/v1/export/records/rec-abc123/download",
    "fileName": "季度报告_2026Q2.doc",
    "styleId": "default",
    "warning": null
  }
}

响应示例(存储超限):

{
  "code": 0,
  "data": {
    "recordId": "rec-abc123",
    "downloadUrl": "https://api.axonix.com/api/v1/export/records/rec-abc123/download",
    "fileName": "季度报告_2026Q2.doc",
    "styleId": "default",
    "warning": "您的存储空间已超出限额,请删除旧文件释放空间"
  }
}

存储超限时导出仍然成功,warning 字段携带提示信息,文件可正常下载。


3.2 获取下载记录列表

触发时机:用户打开下载历史面板时。
目的:分页查询当前用户的全部导出记录,已删除的记录默认不返回。

接口地址: GET /api/v1/export/records

参数 类型 必填 说明
userId string 当前用户 ID
page integer 页码,默认 1
pageSize integer 每页数量,默认 20,最大 100
sortOrder string asc / desc,默认 desc

响应示例:

{
  "code": 0,
  "data": {
    "records": [
      {
        "recordId": "rec-abc123",
        "userId": "user-xyz",
        "fileName": "季度报告_2026Q2.doc",
        "fileSize": 204800,
        "downloadUrl": "https://api.axonix.com/api/v1/export/records/rec-abc123/download",
        "documentId": "doc-abc123",
        "styleId": "default",
        "createdAt": 1680000000000
      }
    ],
    "pagination": {
      "page": 1,
      "pageSize": 20,
      "total": 5,
      "totalPages": 1
    }
  }
}

3.3 重新下载文件

触发时机:用户在下载历史中点击某条记录的"下载"按钮时。
目的:根据记录 ID 查找本地文件,触发浏览器下载。

接口地址: GET /api/v1/export/records/{recordId}/download

参数 类型 必填 说明
userId string 当前用户 ID,校验记录归属(Query 参数)

响应:直接返回文件流。

Content-Type: application/msword
Content-Disposition: attachment; filename="季度报告_2026Q2.doc"

错误情况:

HTTP 状态码 错误码 说明
404 4041 记录不存在、文件已被删除,或 recordId 与 userId 不匹配

3.4 删除下载记录

触发时机:用户在下载历史中主动删除某条记录时。
目的:同步删除磁盘文件和数据库记录。

接口地址: DELETE /api/v1/export/records/{recordId}

参数 类型 必填 说明
userId string 当前用户 ID,校验记录归属(Query 参数)

响应示例:

{
  "code": 0,
  "message": "Record deleted successfully"
}

错误情况:

HTTP 状态码 错误码 说明
404 4041 记录不存在,或 recordId 与 userId 不匹配

3.5 磁盘监控

后台在应用启动时自动运行磁盘监控模块,每 30 分钟执行一次检查,同时每次导出后也会触发一次实时检查。

配额规则

检查项 阈值 触发行为
tmp/ 总占用 超过磁盘总容量的 50% 给所有实际有文件的用户发警告 + 给管理员发警告
单用户目录占用 超过 磁盘总容量 × 50% ÷ 实际有文件用户数 仅给该用户发警告

实际有文件用户数 = tmp/ 下存在非空目录的用户数,动态计算。

警告通知方式(当前阶段):

  • 用户侧:导出响应体 warning 字段
  • 管理员侧:服务端日志 + GET /api/v1/admin/storage 接口返回超限标记

管理端存储查询接口:

GET /api/v1/admin/storage

{
  "code": 0,
  "data": {
    "diskTotalBytes": 107374182400,
    "diskUsedBytes": 53687091200,
    "tmpTotalBytes": 10737418240,
    "quotaBytes": 53687091200,
    "quotaExceeded": false,
    "activeUsers": 5,
    "perUserQuotaBytes": 10737418240,
    "users": [
      {
        "userId": "user-xyz",
        "usedBytes": 204800,
        "quotaExceeded": false
      }
    ]
  }
}

4. 样式管理 API(阶段 1)

用户上传任意 Word 文档,后端自动提取其中所有样式的完整 XML 定义,生成与原文档同名的样式文件并持久化存储,加入用户的样式缓存列表,导出 .doc 时可从列表中选择使用。

功能说明

  • 上传文档后,后端遍历文档中的所有样式(段落样式、字符样式、表格样式、列表样式等),完整保留每个样式的原始 XML 结构(含所有属性、子元素和命名空间),不做任何简化裁剪。
  • 样式文件以上传文档的原始文件名命名(去掉扩展名),存入用户的样式库缓存。
  • 每次导出 .doc 时,用户可从缓存列表中选择一个样式应用到输出文件;不选则使用系统默认样式。

数据结构

from pydantic import BaseModel
from typing import Optional
from datetime import datetime

# 单个样式的完整定义
class StyleDefinition(BaseModel):
    name: str               # 样式名称,如 "标题 1"
    style_id: str           # 样式 ID,如 "1"
    type: str               # 样式类型:paragraph / character / table / numbering
    builtin: bool           # 是否为内置样式
    hidden: bool            # 是否隐藏
    quick_style: bool       # 是否显示在快速样式库
    priority: Optional[int] # 显示优先级
    base_style: Optional[str]           # 基础样式名称
    next_paragraph_style: Optional[str] # 下一段落样式名称
    font_summary: Optional[dict]        # 字体摘要(name, size_pt, bold, italic 等)
    paragraph_format_summary: Optional[dict]  # 段落格式摘要(alignment, indent, spacing 等)
    full_xml_definition: dict           # 完整原始 XML(转换为嵌套字典,保留所有属性和子元素)

# 样式文件摘要(列表展示用)
class StyleFileSummary(BaseModel):
    default_font: str           # 正文默认字体,如 "宋体"
    default_size_pt: float      # 正文默认字号(磅),如 12.0
    heading_fonts: list[str]    # 各级标题字体列表(H1 到 H6)
    total_styles: int           # 提取到的样式总数
    style_types: dict[str, int] # 各类型样式数量,如 {"paragraph": 120, "character": 40}

# 样式文件完整信息(响应)
class StyleFileResponse(BaseModel):
    style_id: str               # 样式文件唯一 ID
    name: str                   # 样式文件名(源文档文件名去扩展名)
    source_file: str            # 原始上传文件名(含扩展名)
    summary: StyleFileSummary   # 摘要信息,用于列表展示
    is_default: bool            # 是否为系统默认样式
    created_at: datetime

# 样式文件列表项(不含完整样式定义,减少传输量)
class StyleFileListItem(BaseModel):
    style_id: str
    name: str
    source_file: str
    summary: StyleFileSummary
    is_default: bool
    created_at: datetime

后端提取流程

上传文档后,后端执行以下步骤:

  1. 使用 python-docx 打开上传的 Word 文档(.doc / .docx)
  2. 遍历文档 doc.styles 中的所有样式对象
  3. 对每个样式提取:
    • 基本属性(namestyle_idtypebuiltinhidden 等)
    • 字体摘要(通过 style.font API 读取 namesize_ptbolditaliccolor_rgb 等)
    • 段落格式摘要(通过 style.paragraph_format API 读取 alignmentindentspacing 等)
    • 完整 XML 定义(对 style.element 递归转换为嵌套字典,保留所有属性、子元素和文本节点)
  4. 以上传文件名(去扩展名)命名,序列化为 JSON 保存到存储层
  5. 写入数据库样式缓存记录,返回摘要信息

4.1 上传样式(从 Word 文档提取全量样式 XML)

目的:用户上传任意 Word 文档,后端提取其中所有样式的完整 XML 定义,生成同名样式文件存入缓存列表,供后续导出时选用。

接口地址: POST /api/v1/styles

请求格式: multipart/form-data

参数 类型 必填 说明
file File Word 文档(.doc / .docx),最大 20MB
name string 自定义样式名称;不传时默认使用上传文件名(去扩展名)

后端处理说明

  • 样式文件名默认取自上传文件名(去扩展名),例如上传 季报模板_2026Q2.docx,生成样式文件名为 季报模板_2026Q2
  • 提取结果以 JSON 格式持久化,字段 full_xml_definition 包含每个样式的完整原始 XML,转换为嵌套字典结构,保留所有命名空间属性(w:w14: 等)和子元素

响应示例:

{
  "code": 0,
  "data": {
    "styleId": "style-001",
    "name": "季报模板_2026Q2",
    "sourceFile": "季报模板_2026Q2.docx",
    "summary": {
      "defaultFont": "宋体",
      "defaultSizePt": 12.0,
      "headingFonts": ["黑体", "黑体", "宋体", "宋体", "宋体", "宋体"],
      "totalStyles": 164,
      "styleTypes": {
        "paragraph": 120,
        "character": 40,
        "table": 3,
        "numbering": 1
      }
    },
    "isDefault": false,
    "createdAt": 1680000000000
  }
}

错误情况

HTTP 状态码 错误码 说明
400 4001 文件格式不支持(非 .doc / .docx)
413 4002 文件超过 20MB 限制
422 4003 文档无法解析(损坏或加密)
409 4004 同名样式文件已存在(可用 ?overwrite=true 强制覆盖)

4.2 获取样式列表(样式缓存)

目的:返回当前用户的样式缓存列表,包含系统默认样式,供前端展示样式选择器。列表按创建时间倒序排列,每次导出时用户从此列表中选择应用哪个样式。

接口地址: GET /api/v1/styles

参数 类型 必填 说明
page integer 页码,默认 1
pageSize integer 每页数量,默认 20,最大 100

响应示例:

{
  "code": 0,
  "data": {
    "styles": [
      {
        "styleId": "default",
        "name": "系统默认样式",
        "sourceFile": null,
        "summary": {
          "defaultFont": "宋体",
          "defaultSizePt": 12.0,
          "headingFonts": ["黑体", "黑体", "宋体", "宋体", "宋体", "宋体"],
          "totalStyles": 20,
          "styleTypes": { "paragraph": 15, "character": 5, "table": 0, "numbering": 0 }
        },
        "isDefault": true,
        "createdAt": 1680000000000
      },
      {
        "styleId": "style-001",
        "name": "季报模板_2026Q2",
        "sourceFile": "季报模板_2026Q2.docx",
        "summary": {
          "defaultFont": "宋体",
          "defaultSizePt": 12.0,
          "headingFonts": ["黑体", "黑体", "宋体", "宋体", "宋体", "宋体"],
          "totalStyles": 164,
          "styleTypes": { "paragraph": 120, "character": 40, "table": 3, "numbering": 1 }
        },
        "isDefault": false,
        "createdAt": 1680000000000
      }
    ],
    "pagination": {
      "page": 1,
      "pageSize": 20,
      "total": 2,
      "totalPages": 1
    }
  }
}

4.3 获取样式详情(含完整 XML 定义)

目的:返回指定样式文件的完整信息,包含所有样式的完整 XML 定义,供调试或样式预览使用。

接口地址: GET /api/v1/styles/{styleId}

响应示例styles 数组内每条记录结构):

{
  "code": 0,
  "data": {
    "styleId": "style-001",
    "name": "季报模板_2026Q2",
    "sourceFile": "季报模板_2026Q2.docx",
    "summary": {
      "defaultFont": "宋体",
      "defaultSizePt": 12.0,
      "headingFonts": ["黑体", "黑体", "宋体", "宋体", "宋体", "宋体"],
      "totalStyles": 164,
      "styleTypes": { "paragraph": 120, "character": 40, "table": 3, "numbering": 1 }
    },
    "isDefault": false,
    "createdAt": 1680000000000,
    "styles": [
      {
        "name": "标题 1",
        "styleId": "1",
        "type": "paragraph",
        "builtin": true,
        "hidden": false,
        "quickStyle": true,
        "priority": 9,
        "baseStyle": "正文",
        "nextParagraphStyle": "正文",
        "fontSummary": {
          "name": "黑体",
          "sizePt": 16.0,
          "bold": true,
          "italic": false,
          "underline": false,
          "colorRgb": "000000",
          "strike": false,
          "allCaps": false,
          "smallCaps": false
        },
        "paragraphFormatSummary": {
          "alignment": "LEFT",
          "leftIndentPt": null,
          "rightIndentPt": null,
          "firstLineIndentPt": null,
          "spaceBeforePt": 12.0,
          "spaceAfterPt": 6.0,
          "lineSpacing": 1.5,
          "keepTogether": false,
          "keepWithNext": true,
          "pageBreakBefore": false
        },
        "fullXmlDefinition": {
          "@tag": "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}style",
          "@attrib": {
            "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}type": "paragraph",
            "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}styleId": "1"
          },
          "@children": {
            "{...}name": { "@tag": "...", "@attrib": { "{...}val": "heading 1" } },
            "{...}rPr": { "...": "完整字符属性 XML" },
            "{...}pPr": { "...": "完整段落属性 XML" }
          }
        }
      }
    ]
  }
}

fullXmlDefinition 字段为嵌套字典格式,@tag 为完整 Clark 表示法标签名(含命名空间),@attrib 为属性字典,@children 为子元素字典(同名子元素自动合并为数组)。


4.4 删除样式

接口地址: DELETE /api/v1/styles/{styleId}

系统默认样式(styleId: "default")不可删除,返回 403。
若该样式正在被某个文档引用,返回 409,需先解除引用再删除。

响应示例:

{
  "code": 0,
  "message": "Style deleted successfully"
}

5. 模板管理 API(阶段 1)

用户上传带占位符的 Word 文档作为模板,占位符格式为 {{名称}}。导出时后端根据文档内容按标题名称匹配占位符,替换后生成 DOCX。

占位符匹配规则

  • 占位符名称与 Markdown 文档中的标题名称完全一致时匹配,替换为该标题下的完整内容块(含子标题)
  • 匹配不上的占位符清空(替换为空字符串)
  • 示例:模板含 {{一季度回顾}},文档中有 ## 一季度回顾,则该标题及其下文内容替换占位符

数据结构

from pydantic import BaseModel
from typing import Optional
from datetime import datetime

# 模板完整信息(响应)
class TemplateResponse(BaseModel):
    template_id: str
    name: str
    placeholders: list[str]     # 模板中所有占位符名称列表,如 ["一季度回顾", "二季度展望"]
    has_header: bool            # 是否包含页眉
    has_footer: bool            # 是否包含页脚
    created_at: datetime

# 模板列表项
class TemplateListItem(BaseModel):
    template_id: str
    name: str
    placeholders: list[str]
    created_at: datetime

5.1 上传模板

目的:用户上传带占位符的 Word 文档,后端解析出所有 {{名称}} 占位符列表并返回,供用户确认模板结构。

接口地址: POST /api/v1/templates

请求格式: multipart/form-data

参数 类型 必填 说明
file File Word 文档(.docx),最大 20MB,需包含 {{名称}} 格式占位符
name string 模板名称

响应示例:

{
  "code": 0,
  "data": {
    "templateId": "tpl-001",
    "name": "季度报告模板",
    "placeholders": ["报告摘要", "一季度回顾", "二季度展望", "风险提示"],
    "hasHeader": true,
    "hasFooter": true,
    "createdAt": 1680000000000
  }
}

5.2 获取模板信息

接口地址: GET /api/v1/templates/{templateId}

响应示例:

{
  "code": 0,
  "data": {
    "templateId": "tpl-001",
    "name": "季度报告模板",
    "placeholders": ["报告摘要", "一季度回顾", "二季度展望", "风险提示"],
    "hasHeader": true,
    "hasFooter": true,
    "createdAt": 1680000000000
  }
}

5.3 获取模板列表

接口地址: GET /api/v1/templates

响应示例:

{
  "code": 0,
  "data": {
    "templates": [
      {
        "templateId": "tpl-001",
        "name": "季度报告模板",
        "placeholders": ["报告摘要", "一季度回顾", "二季度展望", "风险提示"],
        "createdAt": 1680000000000
      }
    ]
  }
}

5.4 删除模板

接口地址: DELETE /api/v1/templates/{templateId}

响应示例:

{
  "code": 0,
  "message": "Template deleted successfully"
}

6. 导出 DOCX API(阶段 1)

数据结构

from pydantic import BaseModel

# 导出 DOCX(模板填充)请求
class ExportDocxRequest(BaseModel):
    document_id: str    # 文档 ID,后端查文档内容并按标题匹配占位符
    template_id: str    # 模板 ID,必须指定

# 响应为文件流,HTTP Header 如下:
# Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
# Content-Disposition: attachment; filename="{title}.docx"

6.1 导出 DOCX(模板填充)

触发时机:用户在编辑器中选择了模板并完成编辑后,点击"下载"按钮时触发。
目的:后端以指定模板为基础,解析文档 Markdown 内容按标题拆分为内容块,按标题名称匹配模板中的 {{占位符}},替换后生成 DOCX。输出文件的字体、样式、页眉页脚完全继承模板。

后端处理流程

  1. 根据 documentId 查文档内容(Markdown)
  2. 根据 templateId 加载模板文件和占位符列表
  3. 将 Markdown 按标题拆分为内容块(标题名 → 内容)
  4. 遍历占位符,标题名称匹配则替换为对应内容,匹配不上则清空
  5. 生成 DOCX 文件流返回

接口地址: POST /api/v1/export/docx

请求体:

{
  "documentId": "doc-abc123",
  "templateId": "tpl-001"
}
参数 类型 必填 说明
documentId string 文档 ID,后端据此获取内容并拆分内容块
templateId string 模板 ID,决定输出文件的结构和样式

响应:直接返回 DOCX 文件流,前端触发浏览器下载。

Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Disposition: attachment; filename="{title}.docx"

错误情况

错误码 说明
404 documentId 或 templateId 不存在
422 文档内容为空,无法生成

7. 编辑会话 API(阶段 2)

数据结构

from pydantic import BaseModel
from typing import Literal, Optional
from datetime import datetime

# 创建编辑会话请求
class CreateEditSessionRequest(BaseModel):
    document_id: str
    platform: Literal["mod-chat", "oil-agent"]
    expires_in: int = 3600      # Token 有效期(秒),默认 3600,最大 86400
    user_id: Optional[str] = None  # 用户 ID,审计用途

# 编辑会话信息(响应)
class EditSessionResponse(BaseModel):
    session_id: str
    token: str          # JWT,用于后续请求的 Bearer Token
    editor_url: str     # 带 token 的编辑器完整 URL
    expires_at: int     # Unix 毫秒时间戳

# 会话中的文档信息
class SessionDocument(BaseModel):
    id: str
    title: str
    content: str
    format: str
    template_id: Optional[str] = None

# 会话信息
class SessionInfo(BaseModel):
    session_id: str
    platform: Literal["mod-chat", "oil-agent"]
    expires_at: int

# 凭 Token 获取文档的响应
class SessionDocumentResponse(BaseModel):
    document: SessionDocument
    permissions: list[Literal["read", "write"]]
    session_info: SessionInfo

7.1 创建编辑会话

触发时机:oil-agent 需要让用户编辑某份文档时,由 oil-agent 后端调用此接口。
目的:生成一个与指定文档绑定的临时 Token 和编辑器 URL,供 oil-agent 将 URL 传给用户打开,实现免登录访问编辑器。Token 与文档 ID 强绑定,无法访问其他文档。

接口地址: POST /api/v1/edit-sessions

请求体:

{
  "documentId": "doc-abc123",
  "platform": "oil-agent",
  "expiresIn": 3600,
  "userId": "user-xyz"
}
参数 类型 必填 说明
documentId string 要编辑的文档 ID
platform string mod-chat / oil-agent
expiresIn integer Token 有效期(秒),默认 3600,最大 86400
userId string 用户 ID(审计用途)

响应示例:

{
  "code": 0,
  "data": {
    "sessionId": "edit-session-xyz",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "editorUrl": "https://axonix.com/editor?session=edit-session-xyz&token=...",
    "expiresAt": 1680003600000
  }
}

7.2 凭 Token 获取文档

触发时机:编辑器页面加载时,用 URL 中的 Token 换取文档内容。
目的:校验 Token 有效性,返回对应文档内容和权限信息,完成免登录鉴权。

接口地址: GET /api/v1/edit-sessions/{sessionId}/document

请求头: Authorization: Bearer {token}

响应示例:

{
  "code": 0,
  "data": {
    "document": {
      "id": "doc-abc123",
      "title": "我的文档",
      "content": "...",
      "format": "markdown",
      "templateId": "tpl-001"
    },
    "permissions": ["read", "write"],
    "sessionInfo": {
      "sessionId": "edit-session-xyz",
      "platform": "oil-agent",
      "expiresAt": 1680003600000
    }
  }
}

7.3 关闭编辑会话

触发时机:用户关闭编辑器或 oil-agent 主动撤回访问权限时。
目的:立即撤销 Token,使该链接失效,防止 Token 被复用。

接口地址: DELETE /api/v1/edit-sessions/{sessionId}

请求头: Authorization: Bearer {token}

响应示例:

{
  "code": 0,
  "message": "Session closed successfully"
}

8. Webhook 通知(阶段 2)

数据结构

from pydantic import BaseModel
from typing import Literal

# 配置 Webhook 请求
class CreateWebhookRequest(BaseModel):
    url: str                                    # 接收事件的回调地址
    events: list[Literal["document.updated", "session.closed"]]  # 订阅的事件类型
    secret: str                                 # 用于签名验证的密钥

# document.updated 事件数据
class DocumentUpdatedData(BaseModel):
    document_id: str
    session_id: str
    platform: Literal["mod-chat", "oil-agent"]

# session.closed 事件数据
class SessionClosedData(BaseModel):
    session_id: str
    document_id: str
    platform: Literal["mod-chat", "oil-agent"]
    duration: int   # 会话持续时长(秒)

8.1 配置 Webhook

接口地址: POST /api/v1/webhooks

请求体:

{
  "url": "https://oil-agent.com/api/webhooks/editor",
  "events": ["document.updated", "session.closed"],
  "secret": "webhook-secret-key"
}

8.2 Webhook 事件格式

document.updated

{
  "event": "document.updated",
  "timestamp": 1680000100000,
  "data": {
    "documentId": "doc-abc123",
    "sessionId": "edit-session-xyz",
    "platform": "oil-agent"
  }
}

session.closed

{
  "event": "session.closed",
  "timestamp": 1680003600000,
  "data": {
    "sessionId": "edit-session-xyz",
    "documentId": "doc-abc123",
    "platform": "oil-agent",
    "duration": 3600
  }
}

8.3 签名验证

请求头: X-Axonix-Signature: sha256=<hmac_hex>

import hmac, hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, expected)

9. 导出扩展 API(阶段 3)

数据结构

from pydantic import BaseModel
from typing import Literal, Optional

# 导出 PDF 选项
class ExportPdfOptions(BaseModel):
    page_size: Literal["A4", "A3", "Letter"] = "A4"
    orientation: Literal["portrait", "landscape"] = "portrait"
    include_page_numbers: bool = False

# 导出 PDF 请求
class ExportPdfRequest(BaseModel):
    document_id: str
    options: Optional[ExportPdfOptions] = None

# 响应为文件流,HTTP Header 如下:
# Content-Type: application/pdf
# Content-Disposition: attachment; filename="{title}.pdf"

9.1 导出为 PDF

接口地址: POST /api/v1/export/pdf

请求体:

{
  "documentId": "doc-abc123",
  "options": {
    "pageSize": "A4",
    "includePageNumbers": true
  }
}

响应:

直接返回 PDF 文件流。

Content-Type: application/pdf
Content-Disposition: attachment; filename="document.pdf"

10. 版本管理 API(阶段 3)

数据结构

from pydantic import BaseModel
from typing import Literal, Optional
from datetime import datetime

# 版本记录
class DocumentVersion(BaseModel):
    version_id: str
    created_at: datetime
    created_by: str
    summary: Optional[str] = None   # 版本说明,可选

# 单条 Diff 项
class DiffItem(BaseModel):
    type: Literal["equal", "insert", "delete"]
    text: str

# 版本对比结果
class DiffResult(BaseModel):
    diff: list[DiffItem]

# 回滚响应
class RestoreVersionResponse(BaseModel):
    document_id: str
    restored_from: str      # 回滚来源的 version_id
    updated_at: datetime

10.1 获取版本历史

接口地址: GET /api/v1/documents/{documentId}/versions

响应示例:

{
  "code": 0,
  "data": {
    "versions": [
      {
        "versionId": "ver-001",
        "createdAt": 1680000000000,
        "createdBy": "user-xyz",
        "summary": "初始版本"
      }
    ]
  }
}

10.2 回滚版本

接口地址: POST /api/v1/documents/{documentId}/versions/{versionId}/restore

响应示例:

{
  "code": 0,
  "data": {
    "documentId": "doc-abc123",
    "restoredFrom": "ver-001",
    "updatedAt": 1680005000000
  }
}

10.3 版本对比

接口地址: GET /api/v1/documents/{documentId}/versions/diff?from={versionId}&to={versionId}

响应示例:

{
  "code": 0,
  "data": {
    "diff": [
      { "type": "equal", "text": "第一段内容" },
      { "type": "delete", "text": "被删除的内容" },
      { "type": "insert", "text": "新增的内容" }
    ]
  }
}

11. 限流与配额

接口类型 限制 窗口期
文档 CRUD 100 次/分钟 滑动窗口
模板上传 10 次/小时 固定窗口
DOCX / PDF 导出 20 次/小时 固定窗口
编辑会话管理 50 次/分钟 滑动窗口

超限时返回 HTTP 429,响应头包含 X-RateLimit-RemainingRetry-After


12. 测试环境

  • Base URL: https://api-dev.axonix.com
  • 测试文档 ID: test-doc-001
  • 测试模板 ID: test-tpl-001

本地 Mock:

npm run mock-server
# http://localhost:8080/api/v1/documents

文档版本: v4.0 最后更新: 2026-06-11 维护者: Axonix API 团队