https://api.axonix.comv1| 阶段 | 新增 API |
|---|---|
| 阶段 0 | 文档 CRUD、导出 .doc(默认样式) |
| 阶段 1 | 样式管理、模板管理、导出 DOCX |
| 阶段 2 | 编辑会话(Token)、Webhook |
| 阶段 3 | PDF 导出、版本管理 |
这是两个独立功能,用户可以按需使用其中一个或两个都不用:
| 样式(Style) | 模板(Template) | |
|---|---|---|
| 用途 | 控制输出文件的字体、颜色、标题层级等视觉格式 | 定义文档结构,内容填入占位符位置 |
| 上传内容 | 任意 Word 文档,后端提取其中的样式文件 | 带占位符({{名称}})的 Word 文档 |
| 导出方式 | 导出 .doc 时选择样式,不选用系统默认 | 导出 DOCX 时指定模板,内容按标题匹配占位符 |
| 是否必须 | 否,有默认样式兜底 | 否,不使用模板则走样式导出流程 |
{
"code": 0,
"message": "Success",
"data": { },
"timestamp": 1680000000000
}
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
触发时机:用户在 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
}
}
触发时机:编辑器 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
}
}
触发时机:小文件(< 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
}
}
触发时机:用户主动删除某条文档记录时。
目的:从数据库中删除文档草稿记录。
接口地址: DELETE /api/v1/documents/{documentId}
响应示例:
{
"code": 0,
"message": "Document deleted successfully"
}
触发时机:需要展示历史文档列表时。
目的:分页查询当前用户的文档草稿记录。
接口地址: 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
}
}
}
本节说明:阶段 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
触发时机:用户点击编辑器"保存"按钮,在弹出对话框中填写文件名、选择格式并确认后触发。
目的:将 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字段携带提示信息,文件可正常下载。
触发时机:用户打开下载历史面板时。
目的:分页查询当前用户的全部导出记录,已删除的记录默认不返回。
接口地址: 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
}
}
}
触发时机:用户在下载历史中点击某条记录的"下载"按钮时。
目的:根据记录 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 不匹配 |
触发时机:用户在下载历史中主动删除某条记录时。
目的:同步删除磁盘文件和数据库记录。
接口地址: DELETE /api/v1/export/records/{recordId}
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userId | string | 是 | 当前用户 ID,校验记录归属(Query 参数) |
响应示例:
{
"code": 0,
"message": "Record deleted successfully"
}
错误情况:
| HTTP 状态码 | 错误码 | 说明 |
|---|---|---|
| 404 | 4041 | 记录不存在,或 recordId 与 userId 不匹配 |
后台在应用启动时自动运行磁盘监控模块,每 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
}
]
}
}
用户上传任意 Word 文档,后端自动提取其中所有样式的完整 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
上传文档后,后端执行以下步骤:
python-docx 打开上传的 Word 文档(.doc / .docx)doc.styles 中的所有样式对象name、style_id、type、builtin、hidden 等)style.font API 读取 name、size_pt、bold、italic、color_rgb 等)style.paragraph_format API 读取 alignment、indent、spacing 等)style.element 递归转换为嵌套字典,保留所有属性、子元素和文本节点)目的:用户上传任意 Word 文档,后端提取其中所有样式的完整 XML 定义,生成同名样式文件存入缓存列表,供后续导出时选用。
接口地址: POST /api/v1/styles
请求格式: multipart/form-data
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | File | 是 | Word 文档(.doc / .docx),最大 20MB |
| name | string | 否 | 自定义样式名称;不传时默认使用上传文件名(去扩展名) |
后端处理说明:
季报模板_2026Q2.docx,生成样式文件名为 季报模板_2026Q2full_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 强制覆盖) |
目的:返回当前用户的样式缓存列表,包含系统默认样式,供前端展示样式选择器。列表按创建时间倒序排列,每次导出时用户从此列表中选择应用哪个样式。
接口地址: 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
}
}
}
目的:返回指定样式文件的完整信息,包含所有样式的完整 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为子元素字典(同名子元素自动合并为数组)。
接口地址: DELETE /api/v1/styles/{styleId}
系统默认样式(
styleId: "default")不可删除,返回 403。
若该样式正在被某个文档引用,返回 409,需先解除引用再删除。
响应示例:
{
"code": 0,
"message": "Style deleted successfully"
}
用户上传带占位符的 Word 文档作为模板,占位符格式为 {{名称}}。导出时后端根据文档内容按标题名称匹配占位符,替换后生成 DOCX。
{{一季度回顾}},文档中有 ## 一季度回顾,则该标题及其下文内容替换占位符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
目的:用户上传带占位符的 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
}
}
接口地址: GET /api/v1/templates/{templateId}
响应示例:
{
"code": 0,
"data": {
"templateId": "tpl-001",
"name": "季度报告模板",
"placeholders": ["报告摘要", "一季度回顾", "二季度展望", "风险提示"],
"hasHeader": true,
"hasFooter": true,
"createdAt": 1680000000000
}
}
接口地址: GET /api/v1/templates
响应示例:
{
"code": 0,
"data": {
"templates": [
{
"templateId": "tpl-001",
"name": "季度报告模板",
"placeholders": ["报告摘要", "一季度回顾", "二季度展望", "风险提示"],
"createdAt": 1680000000000
}
]
}
}
接口地址: DELETE /api/v1/templates/{templateId}
响应示例:
{
"code": 0,
"message": "Template deleted successfully"
}
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"
触发时机:用户在编辑器中选择了模板并完成编辑后,点击"下载"按钮时触发。
目的:后端以指定模板为基础,解析文档 Markdown 内容按标题拆分为内容块,按标题名称匹配模板中的 {{占位符}},替换后生成 DOCX。输出文件的字体、样式、页眉页脚完全继承模板。
后端处理流程:
documentId 查文档内容(Markdown)templateId 加载模板文件和占位符列表接口地址: 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 | 文档内容为空,无法生成 |
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
触发时机: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
}
}
触发时机:编辑器页面加载时,用 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
}
}
}
触发时机:用户关闭编辑器或 oil-agent 主动撤回访问权限时。
目的:立即撤销 Token,使该链接失效,防止 Token 被复用。
接口地址: DELETE /api/v1/edit-sessions/{sessionId}
请求头: Authorization: Bearer {token}
响应示例:
{
"code": 0,
"message": "Session closed successfully"
}
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 # 会话持续时长(秒)
接口地址: POST /api/v1/webhooks
请求体:
{
"url": "https://oil-agent.com/api/webhooks/editor",
"events": ["document.updated", "session.closed"],
"secret": "webhook-secret-key"
}
{
"event": "document.updated",
"timestamp": 1680000100000,
"data": {
"documentId": "doc-abc123",
"sessionId": "edit-session-xyz",
"platform": "oil-agent"
}
}
{
"event": "session.closed",
"timestamp": 1680003600000,
"data": {
"sessionId": "edit-session-xyz",
"documentId": "doc-abc123",
"platform": "oil-agent",
"duration": 3600
}
}
请求头: 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)
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"
接口地址: POST /api/v1/export/pdf
请求体:
{
"documentId": "doc-abc123",
"options": {
"pageSize": "A4",
"includePageNumbers": true
}
}
响应:
直接返回 PDF 文件流。
Content-Type: application/pdf
Content-Disposition: attachment; filename="document.pdf"
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
接口地址: GET /api/v1/documents/{documentId}/versions
响应示例:
{
"code": 0,
"data": {
"versions": [
{
"versionId": "ver-001",
"createdAt": 1680000000000,
"createdBy": "user-xyz",
"summary": "初始版本"
}
]
}
}
接口地址: POST /api/v1/documents/{documentId}/versions/{versionId}/restore
响应示例:
{
"code": 0,
"data": {
"documentId": "doc-abc123",
"restoredFrom": "ver-001",
"updatedAt": 1680005000000
}
}
接口地址: GET /api/v1/documents/{documentId}/versions/diff?from={versionId}&to={versionId}
响应示例:
{
"code": 0,
"data": {
"diff": [
{ "type": "equal", "text": "第一段内容" },
{ "type": "delete", "text": "被删除的内容" },
{ "type": "insert", "text": "新增的内容" }
]
}
}
| 接口类型 | 限制 | 窗口期 |
|---|---|---|
| 文档 CRUD | 100 次/分钟 | 滑动窗口 |
| 模板上传 | 10 次/小时 | 固定窗口 |
| DOCX / PDF 导出 | 20 次/小时 | 固定窗口 |
| 编辑会话管理 | 50 次/分钟 | 滑动窗口 |
超限时返回 HTTP 429,响应头包含 X-RateLimit-Remaining 和 Retry-After。
https://api-dev.axonix.comtest-doc-001test-tpl-001本地 Mock:
npm run mock-server
# http://localhost:8080/api/v1/documents
文档版本: v4.0 最后更新: 2026-06-11 维护者: Axonix API 团队