接入指南
后端使用 API Key 签发文稿授权,前端通过 iframe 嵌入预览、编辑与协作界面。
HTTP API · v1
服务地址:https://slides.example.com。以下接口路径均相对于服务地址;示例中的 Client ID、Key、文件 ID
和业务网址需替换为你的配置。
快速开始
1. 创建调用方
进入管理控制台 → API Key,点击“创建 API
Key”。设置调用方名称和唯一 Client
ID,在“允许嵌入地址”填写业务前端的网址,例如
https://app.example.com,每行一个。填写协议、域名和端口,不含页面路径;保存后立即生效。
将生成的 Key 保存到业务后端。Key 仅显示一次,丢失后在控制台轮换。若选择调用方自有文件方式,还需配置文件接口地址。
2. 上传一份文稿
在后端终端设置调用参数。此处的 API Key 是控制台分发给该业务系统的 Key。
export NEXUS_URL="https://slides.example.com"
export NEXUS_CLIENT_ID="platform-a"
export NEXUS_API_KEY="替换为调用方的 API Key"
curl --fail-with-body "$NEXUS_URL/api/v1/files" \
-H "X-Client-Id: $NEXUS_CLIENT_ID" \
-H "Authorization: Bearer $NEXUS_API_KEY" \
-F 'file=@./季度报告.pptx'
成功返回 HTTP 201 和文稿元数据。保存响应中的
id,用作后续的
fileId。已上传的文稿再次打开时复用这个 ID。
3. 为当前用户签发链接
export NEXUS_FILE_ID="替换为上传返回的 id"
curl --fail-with-body "$NEXUS_URL/api/v1/files/$NEXUS_FILE_ID/grants" \
-H "X-Client-Id: $NEXUS_CLIENT_ID" \
-H "Authorization: Bearer $NEXUS_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"mode": "edit",
"userId": "user-1001",
"displayName": "Alice",
"parentOrigin": "https://app.example.com",
"expiresIn": 14400,
"allowDownload": false
}'
返回 HTTP 201,将响应的
url 交给已登录且有文稿权限的业务前端。将
mode 改为 view 即可签发只读预览链接。
{
"id": "0123456789abcdef0123456789abcdef",
"expiresAt": 1791540000,
"ticketExpiresIn": 60,
"url": "https://slides.example.com/embed/0123456789abcdef0123456789abcdef#ticket=一次性票据"
}
4. 在业务页面中打开
<iframe id="nexus-ppt" title="演示文稿"
width="100%" height="760" frameborder="0"
allow="fullscreen; clipboard-read; clipboard-write">
</iframe>
const frame = document.querySelector('#nexus-ppt');
// /api/presentations/123/open 是你自己实现的业务后端接口。
// 该接口验证登录身份和文稿权限,再向 Nexus PPT 签发授权。
const response = await fetch('/api/presentations/123/open', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({mode: 'edit'})
});
if (!response.ok) throw new Error('无法取得文稿访问授权');
const grant = await response.json();
frame.src = grant.url;
一次性链接须在 60 秒内打开。每位用户、每次进入分别签发。授权有效期与票据兑换时限不同:票据兑换后,文稿会话可以持续到
expiresAt。
鉴权与权限
一个业务调用方使用一枚长期 Key。每次后端 API 请求携带以下两个请求头,服务按 Client ID 隔离文稿与授权:
X-Client-Id: platform-a
Authorization: Bearer <该调用方的 API Key>
API Key 不能放入前端代码、iframe URL 或 localStorage。业务前端只拿到短时文稿链接;票据兑换、浏览器会话和 WOPI 凭证由嵌入页面内部处理,调用方无需自己实现这些接口。
| 凭据 / 字段 | 用途 | 由谁持有 |
|---|---|---|
Client ID + API Key |
上传、登记文件、签发与撤销授权 | 业务后端 |
userId |
当前业务用户的稳定唯一标识 | 后端从已验证登录会话取得 |
displayName |
编辑器中的参与者名称 | 后端传入,如 Alice / 张三 |
mode |
view 只读;edit 共同编辑 |
后端按业务权限决定 |
grant.url |
本次单文稿、单用户的 iframe 入口 | 获授权的业务前端 |
parentOrigin
必须与业务前端实际的协议、域名和端口一致,并已加入该调用方的“允许嵌入地址”。例如
http://localhost:8890 与
http://127.0.0.1:8890
是两个不同地址。控制台中的设置直接保存到 SQLite,无需重启。
业务后端需要校验用户是否可以访问该业务文件、是否可以编辑或下载,并维护业务文件 ID 与 Nexus 文件 ID 的映射。不要直接信任前端提交的用户名称、文件 ID 或编辑权限。
Node.js 后端签发示例
下面的函数使用 Node.js 内置 fetch。参数
user、permission 和
nexusFileId
须由业务后端验证并解析后传入;返回的授权对象可以交给前端。
export async function issuePptGrant(nexusFileId, user, permission, mode = 'view') {
if (!['view', 'edit'].includes(mode)) throw new Error('INVALID_MODE');
if (mode === 'edit' && !permission.canEdit) throw new Error('EDIT_FORBIDDEN');
const response = await fetch(
`${process.env.NEXUS_URL}/api/v1/files/${encodeURIComponent(nexusFileId)}/grants`,
{
method: 'POST',
headers: {
'X-Client-Id': process.env.NEXUS_CLIENT_ID,
'Authorization': `Bearer ${process.env.NEXUS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
mode,
userId: String(user.id),
displayName: user.name,
parentOrigin: process.env.BUSINESS_ORIGIN,
allowDownload: Boolean(permission.canDownload)
})
}
);
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'NEXUS_REQUEST_FAILED');
return result;
}
业务后端自行处理登录 Cookie、CSRF 和用户权限。调用 Nexus
的后端请求通常不带浏览器
Origin 头;不要原样转发浏览器请求头。长期 Key
的跨域浏览器请求会被服务拒绝。
文稿接口
支持两种文件来源:上传后由 Nexus 托管,或通过文件 API 读取与回存到调用方。两者都使用同一套授权和 iframe 入口。
| 方法 | 路径 | 用途 / 成功状态 |
|---|---|---|
| POST | /api/v1/files |
上传托管文稿,201 |
| POST | /api/v1/files/{fileId}/copy |
复制最近已保存的内容为独立托管文稿,201;无需请求体 |
| POST | /api/v1/files/external |
登记或更新调用方自有文稿,201 |
| GET | /api/v1/files |
当前调用方的文稿列表,200 |
| GET | /api/v1/files/{fileId} |
文稿元数据,200 |
| GET | /api/v1/files/{fileId}/download |
当前 PPTX 内容,200 |
| GET |
/api/v1/files/{fileId}/download?original=true
|
托管文稿的上传原件,200 |
| DELETE | /api/v1/files/{fileId} |
移入回收站并撤销授权,204 |
| PATCH | /api/v1/files/{fileId} |
重命名,200;JSON:{"name":"报告.pptx"} |
| GET | /api/v1/files/{fileId}/versions |
托管文稿历史,200 |
| GET |
/api/v1/files/{fileId}/versions/{versionId}/download
|
下载历史内容,200 |
| POST |
/api/v1/files/{fileId}/versions/{versionId}/restore
|
恢复历史版本,200 |
| POST | /api/v1/files/{fileId}/restore |
从回收站恢复,200 |
| DELETE | /api/v1/files/{fileId}/purge |
永久删除回收站文稿,204 |
上传参数与文稿响应
创建副本返回与上传相同的文稿元数据,名称自动添加“-副本”,重名时追加序号。 副本归属当前认证调用方;管理员操作时归入本地文稿。 副本不继承源文稿的历史、访问授权、协作会话、恢复副本或外部回写配置, 不包含尚未保存的编辑。外部文稿须能通过现有读取接口取得登记版本的完整内容; 授权失效、版本冲突或读取失败时不创建副本。复制遵守目标调用方的文稿数量与存储配额。
POST /api/v1/files 使用
multipart/form-data,文件字段为
file,文件名以
.pptx 结尾。当前大小限制可从
GET /api/v1/config 的
maxUploadBytes 读取,默认 30
MB;上传限制可由管理员调整。
{
"id": "fedcba9876543210fedcba9876543210",
"name": "季度报告.pptx",
"version": "9c5b…当前版本摘要…",
"savedAt": 1791520000,
"saveCount": 0,
"size": 123456,
"source": "managed",
"canEdit": true
}
| 字段 | 说明 |
|---|---|
id |
Nexus 文件 ID。后续打开同一文件时复用。 |
version |
当前版本标识。托管文稿为内容 SHA-256;外部文稿为调用方版本。应按不透明字符串处理。 |
savedAt |
最近保存的 Unix 时间戳,单位秒,可带小数。 |
saveCount |
成功保存次数;刚上传时为 0。 |
size |
当前文件字节数。 |
source |
managed 托管;external
调用方自有文件。
|
canEdit |
文件是否具备编辑回存能力。用户是否有编辑权限仍由调用方决定。 |
curl --fail-with-body "$NEXUS_URL/api/v1/files/$NEXUS_FILE_ID/download" \
-H "X-Client-Id: $NEXUS_CLIENT_ID" \
-H "Authorization: Bearer $NEXUS_API_KEY" \
-o './编辑结果.pptx'
下载接口返回 PPTX 字节,不是 JSON。托管文稿保存原件与历史内容;外部文稿由调用方保存历史。移入回收站会立即撤销访问,永久删除外部记录不会删除调用方原文件。
历史与回收站
查询与分页
GET /api/v1/files
默认返回当前调用方的有效文稿数组。传入
page 后返回分页对象;管理员工作台可以查询全部调用方。
| 查询参数 | 规则 |
|---|---|
page / pageSize |
页码从 1 开始,每页默认 30,最大 100。 |
search |
按文稿名称搜索。 |
clientId |
调用方筛选;业务 Key 仍只能查询自己的文稿。 |
sort |
saved 最近保存(默认)、created
最近上传、name 名称。
|
deleted=true |
查询回收站;默认只查询有效文稿。 |
{"items": [], "total": 0, "page": 1, "pageSize": 20}
文稿元数据另包含 createdAt 上传时间与
deletedAt 删除时间,单位为 Unix
秒。早期数据未记录上传时间时 createdAt 为 0;有效文稿的
deletedAt 为 null。
历史版本
每次内容发生变化的成功保存生成历史记录,返回
id、version、size、savedAt、displayName、source
和 current。versionId 使用历史记录的
id,不是内容哈希。
默认保留每份文稿最近 20
条历史(包含始终保留的上传原件),可在通用设置调整。恢复生成一条新记录,保留后续可回退的历史。存在有效编辑会话时返回
409 FILE_IN_USE,先保存并结束编辑会话再恢复。早期版本的用户或时间信息可能为空。
删除与恢复
删除后文稿进入回收站,默认保留 30 天;清理任务每小时运行。恢复文件不会恢复旧授权,需要重新签发。永久删除仅允许操作回收站文稿,删除后清理不再被任何文稿或历史引用的内容。回收站文稿与历史仍计入调用方存储用量。
预览与编辑授权
外部文稿的 edit 授权及续期仅接受所属业务后端的 API Key;管理员工作台只可预览。 业务端须先验证用户和文稿权限,详见调用方自有文稿。
/api/v1/files/{fileId}/grants
后端使用 Client ID 与 API Key 鉴权,请求体为 JSON,成功返回
201。必填字段如下:
| 字段 | 类型 / 必填 | 规则 |
|---|---|---|
mode |
string / 是 |
view 或 edit。只读授权不能保存。
|
userId |
string / 是 | 1–128 字符,来自当前用户的稳定 ID。 |
displayName |
string / 是 | 1–40 字符,参与者名称。 |
parentOrigin |
string / 是 | 业务父页面的完整 origin,须已获调用方授权。 |
expiresIn |
integer / 否 | 60–28800 秒。省略时采用控制台的默认授权时长,初始值为 240 分钟。 |
allowDownload |
boolean / 否 |
默认
false。控制嵌入页面是否可下载当前文件,与编辑权限独立。
|
响应的 id 是本次 grant ID;expiresAt
是会话到期时间,单位秒;ticketExpiresIn
为一次性票据兑换期限 60 秒;url
为前端应加载的完整链接。
查询与撤销
/api/v1/files/{fileId}/grants
返回仍在有效期内的授权数组,包含
id、mode、displayName、userId、expiresAt
和 revoked。其中
userId 为服务处理后的用户标识,业务侧应自行保存用户与
grant ID 的映射。
/api/v1/files/{fileId}/grants/{grantId}
export NEXUS_GRANT_ID="替换为签发响应的 id"
curl --fail-with-body -X DELETE \
"$NEXUS_URL/api/v1/files/$NEXUS_FILE_ID/grants/$NEXUS_GRANT_ID" \
-H "X-Client-Id: $NEXUS_CLIENT_ID" \
-H "Authorization: Bearer $NEXUS_API_KEY"
成功返回 204,没有响应体。撤销禁止该授权的后续读取与 WOPI
写入;已鉴权受理的回存仍可能完成,其他参与者的有效授权不受单独撤销影响。
嵌入页面正常每 5 秒检查授权并关闭失效编辑器;断网时页面获知撤销可能延迟,但服务端仍拒绝新的未授权请求。
业务退出、权限变更或主动关闭文稿时,可由业务后端撤销对应授权。
票据只能兑换一次,不能向多人分发同一 URL。当前标签页和业务父页面刷新可复用已兑换会话;新标签页、过期或被撤销后需重新签发。预览升级为编辑也需取得新授权。
续期当前授权
/api/v1/files/{fileId}/grants/{grantId}/renew
使用调用方后端 Key 鉴权;管理员工作台会话仅可续期托管文稿或外部预览授权,外部 edit 授权仅接受所属业务后端 Key。JSON 请求体为
{"expiresIn":900},范围 60–28800
秒,省略时采用通用设置。成功返回
{"id":"授权 ID","expiresAt":1791540000}。
只能延长仍有效且未撤销的授权,用户、文稿及读写权限保持不变。
新到期时间取原到期时间与“服务端当前时间 + expiresIn”的较大值,不会缩短,也不是在原期限上累加。过期或撤销后返回
401 SESSION_EXPIRED。业务后端应再次校验用户登录与文稿权限,并将续期时长限制在业务登录剩余时间内。
已暂停会话返回 409 SESSION_PAUSED,需重新签发;嵌入源不再被允许时返回 403 FRAME_ORIGIN_NOT_ALLOWED。
嵌入页面正常每 5 秒轮询,发现剩余不超过 120 秒时发送 expiring。
宿主须调用自己的业务后端实施续期及失败重试,iframe 不会代为调用业务接口;浏览器授权自身没有续期权限。
网络异常可能延迟提醒,业务后端也可按已知 expiresAt 提前续期。
页面发现更晚的到期时间且编辑器已加载时,获取新编辑凭据、发送更新指令,再发送 renewed。
该事件不是编辑器确认回执,也不表示保存成功;正常续期无需重载文稿。
浏览器离线期间,后端若已在有效期内续期,重连后可获取新期限继续操作;若已实际过期则必须重新签发。
grant 续期不会更新业务文件接口的 callbackToken 或签名 URL,见文件接口授权。
调用方自有文稿
文件继续保存在你的磁盘、数据库或对象存储中,Nexus 通过你的文件接口读取与回存,持久化接入元数据,并在回存前保存恢复副本。 未确认副本按内容独立保留 7 天,成功保存只清理对应内容;它不是调用方文件的完整历史或备份。 编辑引擎在协作期间会使用必要的临时缓存。副本下载与恢复流程见异常恢复。
编辑权限归属
Nexus 管理员管理服务,不能代替业务用户取得外部文稿的编辑权限。管理员工作台仅预览外部文稿;
编辑授权与续期必须由文件所属调用方后端使用 API Key 发起,工作台请求返回
403 BUSINESS_AUTH_REQUIRED。托管文稿的管理员编辑不受影响。
业务后端先核验当前用户、租户与文稿权限,再签发并记录 grantId 对应的编辑会话;关闭、撤权和到期时同步撤销。 不得把“存在其他活跃编辑者”作为另一个用户获得编辑权限的依据。 首次升级到此规则时,旧的外部编辑授权会失效,须从业务系统重新打开;文稿及恢复副本保留。
1. 授权文件接口地址
在API Key 设置的“文件接口地址”填写文件
API 的 origin,例如
https://api.example.com。它与业务前端的“允许嵌入地址”分别配置。readUrl 和
writeUrl 都必须属于已授权的接口地址。
2. 登记文件
/api/v1/files/external
curl --fail-with-body "$NEXUS_URL/api/v1/files/external" \
-H "X-Client-Id: $NEXUS_CLIENT_ID" \
-H "Authorization: Bearer $NEXUS_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"externalId": "business-file-123",
"name": "季度报告.pptx",
"version": "v1",
"size": 123456,
"readUrl": "https://api.example.com/files/123/content",
"writeUrl": "https://api.example.com/files/123/content",
"callbackToken": "replace-with-your-file-api-token"
}'
| 字段 | 规则 |
|---|---|
externalId |
必填,1–200 字符。同一调用方的同一 externalId 对应稳定的 Nexus ID。 |
name |
必填,1–200 字符,文件名以 .pptx 结尾,不含路径。 |
version |
必填,1–128 字符,可用字母、数字、下划线、点、冒号、短横线。须与读取接口 ETag 一致。 |
size |
必填,正整数,须为文件的准确字节数,并符合服务大小限制。 |
readUrl |
必填,可从 Nexus 后端访问的完整 HTTP(S) 文件地址。 |
writeUrl |
可选。省略时该文件只能预览。 |
businessUrl |
可选,业务文稿页面的完整 HTTP(S) 地址,origin 必须在调用方允许嵌入地址中。 工作台显示“前往业务系统编辑”;省略时显示返回业务系统的提示。不要在地址中携带长期凭据。 |
callbackToken |
可选,16–2048 字符,调用方文件接口负责验证的 Bearer 凭据。与 Nexus API Key 用途不同。 |
登记时服务会实际读取并验证 PPTX、大小与版本。成功返回
201 和文稿元数据,然后使用返回的 id 按授权接口签发 iframe 链接。
3. 实现文件读取与保存
| 操作 | Nexus 请求 | 文件接口响应 |
|---|---|---|
| 读取 |
GET readUrlIf-Match: "v1"
|
200、PPTX 原始字节、ETag: "v1" |
| 保存 |
PUT writeUrlIf-Match: "v1"Idempotency-Key: …请求体为 PPTX 字节 |
200 或 204,并返回新版本,如 ETag: "v2" |
| 版本冲突 | 请求版本与当前版本不同 | 409 或 412,不覆盖文件 |
PUT /files/123/content HTTP/1.1
Authorization: Bearer <callbackToken>
If-Match: "v1"
Idempotency-Key: <本次保存的幂等标识>
Content-Type: application/vnd.openxmlformats-officedocument.presentationml.presentation
<PPTX 二进制内容>
提供 callbackToken 时,读取与保存均携带 Bearer
头。也可使用有效期覆盖编辑会话的签名 URL;服务不跟随重定向。ETag
必须为强版本标识,例如 "v2",不使用 W/ 弱
ETag。
文件接口返回 401 或 403 时,Nexus 返回 502 EXTERNAL_AUTH_REJECTED,表示业务文件读写授权被拒绝,
不表示 Nexus 管理员登录失效。请从业务系统重新授权或由业务后端刷新失效的回调凭据,不要反复盲重试。
409/412 表示版本冲突;超时和其他失败返回 EXTERNAL_FILE_UNAVAILABLE,修复连接后可有限重试。
编辑上下文中的 saveError 为 {code, at} 或 null,at 为服务端 Unix 秒时间。
页面在保存回执核验及轮询中发现新错误时结束“保存中”,并向宿主发送带具体 code 的 error(stage 为 save)。
同一失败记录在当前页面去重;存在保存错误时,不会用协作者此前的版本确认本次失败保存。
新页面以打开时的错误时间为基线,不把历史错误重新报告为本次失败,恢复副本提示仍保留。
成功回写后清除此错误,只有成功确认的相同内容才会清理对应恢复副本。过期授权不能下载恢复副本;
重新取得 edit 且 allowDownload 的授权后,可下载副本,在业务端比较最新版本并合并恢复。
文件接口须原子地比较版本、保存内容并生成新 ETag,并按
Idempotency-Key 返回重复保存的同一结果。应先查询幂等记录,再检查
If-Match。回存确认后 Nexus 才更新版本;响应丢失时业务端可能已经提交,
重复请求应返回此前的成功结果,不能因旧 If-Match 再次报版本冲突。
grant 续期不会刷新 callbackToken 或签名 URL;需更新这些凭据时,用同一
externalId、当前版本及准确大小重新登记完整配置。
仅更新 callbackToken / readUrl 且版本和 writeUrl 不变时,不撤销现有授权。
版本或 writeUrl(含签名参数)变更会撤销旧授权,需重新签发;有有效编辑会话时禁止这些变更。
外部文件不支持下载“原上传文件”的历史副本。
多人协作与保存
同一调用方、同一个 Nexus 文件 ID
的参与者会进入同一文稿。为每位参与者分别签发授权,传入各自稳定的
userId 与名称,即可共同编辑、查看在线参与者与多人光标。
-
保持文件 ID 一致。不要为每个用户重新上传一份文件;外部文件也应复用同一
externalId。 -
权限分别签发。编辑者用
edit,只读参与者用view。只读限制在 WOPI 服务端执行。 -
等待保存确认。编辑器自动保存,也提供“保存”按钮;页面结合编辑器状态与服务端持久化结果确认后,才发送
saved事件。 - 外部文件保存成功才更新版本。你的回存接口必须确认新 ETag;版本冲突不会直接覆盖调用方文件。
每次发起保存前读取服务端当前保存时间,避免把此前其他参与者的保存误认为本次结果。
多人同时保存可能合并为一次写入,即使引擎只向其中一人返回回执,其他页面也会在共享文稿无待保存修改、服务端无保存错误且确认新写入后结束等待。
多个参与者收到相同 version 和 savedAt 属于正常情况;modified: false 本身不代表保存成功。
新回存失败会返回具体错误并保留未确认状态,不会用协作者此前的版本确认失败操作。 同一失败记录在当前页面去重,重试产生的新失败仍会报告;保存期间继续编辑的内容可能需要再次保存。
编辑器长连接断开时禁用保存,发送 error(stage: "connection")并保留未确认状态。
网络恢复后尝试重新连接,重新收到 ready 后可继续操作;已失效的授权须重新签发。
断网期间已经受理的回存仍可能完成,重连后应以服务端确认结果为准。
一位参与者断网或被单独撤销授权,不会撤销其他人的有效授权;其他人可以继续编辑并保存已同步的共享修改。 系统不保证离线输入被缓存或重放,未送达服务端的修改也不会进入恢复副本。 续期与异常恢复步骤见授权续期和异常恢复流程。
后端可通过文件详情的 version、savedAt 和
saveCount
核验保存结果,也可以通过下载接口取得最新结果。客户端发出“保存请求”不等于保存成功。
授权到期或撤销会阻止后续请求。失效前已经鉴权并开始回存的请求仍可完成并记录保存结果,避免外部接口已写入而元数据未更新;其他有效参与者也可能保存已经同步的修改。已下载的内容无法收回。
前端事件
嵌入页面通过 postMessage 向授权中的
parentOrigin 发送对象。每个事件包含
source: "presentation-service"、type、fileId
和 mode、grantId 和
expiresAt(秒)。此来源标识保留用于调用兼容,不是产品显示名称。
| type | 触发时机 | 附加字段 |
|---|---|---|
ready |
编辑器完成文稿加载,重连后也可能再次发送 | 无 |
modified |
待保存修改状态变化 | modified:boolean |
saving |
开始保存流程,尚未确认持久化 | 无 |
saved |
持久化结果通过核验,包括协作合并保存或重连后的确认 | version、savedAt(秒) |
expired |
授权过期、撤销、文稿不可访问或编辑连接已失效,当前编辑器终止 | message:提示文本 |
error |
加载、保存、连接或编辑器错误 | stage:open / save / connection;code、message 按错误类型提供,须兼容缺省 |
expiring |
轮询发现授权剩余不超过 120 秒;按到期时间去重,凭据刷新后重新计时,断网可能延迟 | expiresAt、remainingSeconds |
renewed |
页面已获取新编辑凭据并发送更新指令,不是编辑器确认回执或保存确认 | expiresAt |
capacity |
刷新服务容量估计 | connections、limit |
paused / resumeRequested |
空闲连接已暂停 / 用户请求继续;宿主核验权限后重新签发授权 | 无 |
closeRequested |
用户请求关闭;宿主决定是否关闭页面并撤销授权 | 无 |
objectLink |
对象联动状态、选择或翻页 | values,见宿主联动协议 |
const frame = document.querySelector('#nexus-ppt');
const nexusOrigin = 'https://slides.example.com'; // Nexus 服务的前端网址
const nexusFileId = '替换为当前文件的 Nexus ID';
const nexusGrantId = '替换为本次授权的 id';
window.addEventListener('message', async event => {
if (event.origin !== nexusOrigin || event.source !== frame.contentWindow) return;
const data = event.data;
if (data?.source !== 'presentation-service' || data.fileId !== nexusFileId) return;
if (data.grantId !== nexusGrantId) return;
switch (data.type) {
case 'ready':
console.log('文稿已加载');
break;
case 'modified':
console.log(data.modified ? '存在待保存修改' : '没有待保存修改');
break;
case 'saved':
console.log('已保存', data.version, new Date(data.savedAt * 1000));
break;
case 'saving':
console.log('保存中,等待持久化确认');
break;
case 'expiring':
// 此接口由你的业务后端实现:核验登录、文稿权限和 grantId 归属,再调用续期 API。
try {
const renewal = await fetch('/api/presentations/123/renew', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({grantId: data.grantId})
});
if (!renewal.ok) {
console.error('续期失败', renewal.status,
'请按业务接口错误处理登录、权限或有限重试;原授权仍按服务端期限到期');
}
} catch (error) {
console.error('续期请求未确认,恢复网络后由业务后端核验期限并重试', error);
}
break;
case 'renewed':
console.log('已发送编辑凭据更新', new Date(data.expiresAt * 1000));
break;
case 'expired':
// 结束保存等待;已受理的回存可能仍在进行,由业务后端核验最终结果。
frame.src = 'about:blank';
console.log('请从业务后端重新获取授权');
break;
case 'error':
// save / connection 错误应结束保存等待;重连后等待新的 ready。
console.error(data.stage, data.code, data.message || '操作未完成,请检查状态');
break;
}
});
请在 iframe 加载前注册监听。事件来自 Nexus
服务地址,而非业务父页面或编辑引擎地址。示例中的控制台输出可替换为业务页面的状态提示;modified: false
本身不是一次保存确认。宿主主动保存与对象定位请参阅宿主联动协议。
此示例展示事件分发,不包含重试按钮和调度。实际宿主须提供续期失败提示及有限重试,不能等待下一次 expiring 代替重试。 对每个 grantId 合并重复续期请求;重新签发后更新当前 grantId,并忽略旧请求的迟到响应。 续期成功与保存确认分别处理;流程及错误分类见授权续期和异常恢复。
宿主保存与对象定位
宿主页面可以向授权 iframe 发送保存与对象定位指令。请在加载 iframe 前注册监听, 校验事件的 origin、source、fileId 和 grantId;重新签发后替换当前 grantId,忽略旧授权事件。 API Key 与 callbackToken 只保留在业务后端。
const frame = document.querySelector('#nexus-ppt');
const nexusOrigin = 'https://slides.example.com';
// fileId 来自登记结果,grantId 来自本次授权的 id。
function command(type, values) {
frame.contentWindow.postMessage({
source: 'presentation-host', type, fileId, grantId, values
}, nexusOrigin);
}
// 收到 ready 后,可保存当前可编辑文稿。
command('save');
保存开始返回 saving;回写确认后返回 saved,包含
version 和 savedAt。失败返回 error,
stage: "save"、code 和 message。
保存期间的新修改仍需再次保存;不要把
modified: false 当成回写成功。 只读或尚未就绪的编辑器分别返回
EDITOR_READ_ONLY 或
EDITOR_NOT_READY,不会静默忽略保存请求。
保存过程中也可能收到连接错误(stage: "connection")或
expired。宿主应结束本次等待并显示“保存结果未确认”,核验业务版本或重连后确认,不能一直等待 saved,也不能把断连直接当作写入失败。
网络恢复后等待新的 ready 再发送操作。
对象配置与双向联动
对象功能异步加载。收到 objectLink 事件且
values.kind === "available" 后发送配置;不要仅凭文稿的
ready 就认为对象功能已就绪。收到
unavailable 时应禁用对象联动并提示重新打开。
command('objectLink', {
action: 'configure', enabled: true,
objects: [{
key: 'revenue-chart', label: '营收图表', number: 1, page: 2,
x: 0.2, y: 0.3, width: 0.5, height: 0.4
}]
});
// 等待 values.kind === 'ready' 后执行定位。
command('objectLink', {action: 'locate', key: 'revenue-chart'});
command('objectLink', {action: 'page', page: 1});
page 从 1 开始;x、y、width、height 按幻灯片尺寸归一化,与屏幕缩放无关。 key 在配置中应唯一,number 为正整数,最多配置 2000 个对象。 定位会切换幻灯片并高亮对应区域;它不会改动 PPTX 内容。 编辑器中的编号点击,以及几何范围唯一匹配的原生对象选中,都会回传业务 key。 对象移动、缩放或幻灯片结构改变后,业务方需要更新坐标配置;这些坐标不是永久对象 ID。
| values.kind | 含义 |
|---|---|
| available / unavailable | 对象功能可用 / 加载失败 |
| ready | 本次配置已接收 |
| selected | 选中对象,包含 key 和 page |
| page | 当前幻灯片变化,包含 page |
| missing | 目标不存在、所在页无效或联动未启用,包含 key |
宿主续期与重连
收到 expiring 后,由业务后端核验用户权限、文稿及 grantId 归属并调用续期 API。
宿主须处理网络异常和非成功响应,显示失败并提供有限重试;不要等待同一到期时间的下一次 expiring 才重试。
对暂时网络故障可在授权仍有效时重试,401/403 应修复登录或权限,SESSION_EXPIRED / SESSION_PAUSED 应重新签发。
不要在前端修改 expiresAt 模拟续期。
renewed 表示页面已取得新编辑凭据并发送更新指令,不是编辑器的确认回执。
后端续期成功但浏览器断网时,该事件可能延迟到恢复连接后;续期成功也不证明任何修改已经保存。
断连时保留“保存结果未确认”,重连后等待新的 ready 再操作,并核验持久化结果。
若授权已实际过期或撤销,旧凭据不能发起新的保存,由业务后端重新签发;新页面不会自动重放离线修改。
授权失效前已鉴权受理的回存可以跨过到期时间完成;旧页面可能已失去查询权限,无法收到最终保存确认。 业务后端应查询最新文件版本及回调幂等记录,再决定是否重试或使用恢复副本。
保存异常与恢复流程
文件回调失败时保留编辑器,宿主按错误类别处理。不要用其他参与者此前的版本或
modified: false 消除本次未确认状态。若编辑器显示原生错误弹窗,关闭后再继续输入;弹窗期间的按键不能当作已录入的修改。
| 异常 | 处理与重试条件 |
|---|---|
EXTERNAL_AUTH_REJECTED |
业务文件接口拒绝读写授权(401/403)。由业务后端修复 callbackToken / 签名 URL 或重新授权;仅续期 grant 无效。确认文件接口恢复且编辑授权仍有效后再保存。 |
EXTERNAL_VERSION_CONFLICT |
业务接口返回 409/412。保留恢复副本,与业务最新版本合并后条件写入,不能盲重试覆盖。 |
EXTERNAL_FILE_UNAVAILABLE / SAVE_UNCONFIRMED |
接口故障、超时或结果未确认。先核验业务版本及幂等记录,排除已经写入但响应丢失;恢复连接后有限重试并等待新的 saved。 |
error(stage: connection)/ expired |
结束保存等待,保留未确认提示;重连等 ready,或取得新授权。已受理的写入可能仍完成,不直接判定失败。 |
业务文件接口须先检查 Idempotency-Key 的已完成记录,再检查 If-Match,
让“已经写入但响应丢失”的重复请求返回原成功结果。真正冲突返回 409/412,不得绕过条件写入。
- 保留未确认提示,先由业务后端核验最新版本与回调记录;不要直接刷新丢弃编辑状态。
- 若存在恢复副本,使用仍有效的
edit且allowDownload: true授权, 点击“下载恢复副本”或请求GET /api/v1/embed/recovery/download(携带嵌入会话 Bearer token)。 授权过期先由业务后端重新授权;下载不会删除副本。 - 比较副本与业务最新版本并合并,协调结束其他有效编辑会话,再按最新版本条件写入。 如果发生冲突,重新比较;恢复副本不包含尚未送达服务端的离线输入。
- 业务文件在 Nexus 之外更新后,以原 externalId、最新 version 和 size 重新登记,再签发新授权打开。
版本或 writeUrl 变更在有有效编辑会话时返回
EXTERNAL_FILE_BUSY;允许变更后旧授权会撤销。
重新打开加载业务系统已保存版本,不会自动应用恢复副本。副本按内容独立保留 7 天,总预算 2 GiB; 空间不足时拒绝新的回存,不静默删除已有副本。成功保存只清理对应内容,下载提供最近尚未确认的副本。 新页面不会把历史保存错误重复报告为本次失败,但仍显示未确认恢复副本提示。
错误码
JSON 接口失败响应统一为以下格式;HTTP 状态码表示错误类别。下载成功返回二进制,删除成功返回空响应。
{"error": "ORIGIN_NOT_ALLOWED"}
| HTTP | 错误码 | 处理方式 |
|---|---|---|
| 401 |
AUTH_REQUIREDINVALID_CREDENTIALS
|
检查 Client ID 与 Key、调用方是否停用;轮换后更新后端凭据。 |
| 401 |
TICKET_EXPIREDSESSION_EXPIREDINVALID_TOKEN
|
向业务后端重新取得文稿授权,不重用过期或已兑换票据。 |
| 403 | ORIGIN_NOT_ALLOWEDFRAME_ORIGIN_NOT_ALLOWED |
核对 parentOrigin 是否仍在该调用方的允许嵌入地址中;后者也可能在续期时返回。 |
| 403 |
INVALID_ORIGINBACKEND_ONLY
|
检查请求来源与鉴权方式;长期 Key 从业务后端使用。 |
| 403 | FILE_ORIGIN_NOT_ALLOWED |
在该 Key 的文件接口地址中授权 readUrl / writeUrl 所在 origin。 |
| 403 |
READ_ONLYEXTERNAL_READ_ONLYDOWNLOAD_DENIED
|
使用匹配业务权限的授权;外部文件编辑需要 writeUrl。 |
| 403 | BUSINESS_AUTH_REQUIRED |
请从所属业务系统取得编辑授权;管理员工作台不能直接编辑或续期外部文稿。 |
| 502 | EXTERNAL_AUTH_REJECTED |
业务文件接口返回 401/403。重新取得业务授权或刷新回调凭据;保留恢复副本,避免盲目重试。 |
| 404 |
FILE_NOT_FOUNDGRANT_NOT_FOUND
|
检查 ID 是否属于当前调用方,记录是否已删除。 |
| 409 | EXTERNAL_VERSION_CONFLICT |
读取调用方最新版本并协调冲突,再重新登记和授权。 |
| 409 | EXTERNAL_FILE_BUSY |
等待有效编辑会话结束,再更新外部文件版本或保存接口。 |
| 413 | FILE_TOO_LARGE |
缩小文件,或由管理员调整通用设置和代理请求大小限制。 |
| 422 |
INVALID_INPUTINVALID_PPTXINVALID_FILE_URL
|
检查字段、origin 格式、文件名和 PPTX 内容。 |
| 502 | EXTERNAL_PROTOCOL_ERROR |
检查实际字节数、size 及强 ETag 是否符合文件接口约定。 |
| 502 |
EXTERNAL_FILE_UNAVAILABLESTORAGE_UNAVAILABLE
|
检查文件接口网络、凭据和存储连接;不要将失败显示为已保存。 |
| 503 |
EDITOR_UNAVAILABLESTORAGE_UNAVAILABLE
|
检查编辑器或存储服务就绪状态,恢复后重试。 |
| 507 | RECOVERY_STORAGE_FULLRECOVERY_STORAGE_UNAVAILABLE |
恢复区已满或无法持久化副本,本次不发起业务回存;保留编辑状态并修复存储后重试。 |
| 404 / 503 | RECOVERY_NOT_FOUND / RECOVERY_STORAGE_UNAVAILABLE |
下载时没有未确认副本,或恢复区暂不可读。核对业务版本和副本保留期限;存储异常须先修复。 |
| 400 | EXTERNAL_ORIGINAL_UNAVAILABLE |
外部文件没有托管原件副本,改为下载当前结果。 |
| 409 | FILE_IN_USE |
先结束有效编辑会话再恢复历史。 |
| 404 | VERSION_NOT_FOUND |
历史已清理或不属于该文稿。 |
| 409 | FILE_NOT_TRASHED |
只有回收站文稿可以永久删除。 |
| 409 | FILE_IN_TRASH |
恢复外部文稿记录后再登记。 |
| 409 |
FILE_QUOTA_EXCEEDED / STORAGE_QUOTA_EXCEEDED
|
清理文稿、历史或回收站,或调整调用方限额。 |
| 429 | SESSION_QUOTA_EXCEEDED |
结束其他会话或调整调用方会话限额。 |
| 429 | REQUEST_RATE_LIMITED |
遵循 Retry-After 秒数后重试。 |
| 503 | EDITOR_CAPACITY_EXCEEDED |
编辑引擎容量已满,稍后重试。 |
| 503 | DATABASE_BUSY |
数据库暂时繁忙,遵循 Retry-After 重试。 |
| 409 | SESSION_PAUSED |
会话已暂停,不能续期;宿主核验权限后重新签发授权。 |
| 409 | EDITOR_NOT_OPEN |
编辑器尚未打开,等待加载完成。 |
错误码表涵盖业务接入常见失败。编辑器保存错误还会在嵌入页面显示;文稿锁冲突时
WOPI 返回 409 并保持已有锁,调用方不需要自行实现 WOPI 锁协议。
SAVE_UNCONFIRMED 是前端保存事件的错误码,不是业务文件接口 HTTP 状态;
它表示结果尚未确认,需核验版本及幂等记录,不能直接认定没有写入。处理步骤见异常恢复流程。
部署与服务设置
服务由文件 API 与编辑引擎组成。生产部署为两者分别配置可访问的 HTTPS
地址,编辑引擎的反向代理须支持 WebSocket。服务间的 WOPI
走内部网络;外部业务始终使用 Nexus 签发的
/embed/ 链接。
APP_ORIGIN=https://slides.example.com
CODE_PUBLIC_URL=https://office.example.com
CODE_SERVER_NAME=office.example.com
TLS_TERMINATION=true
BIND_ADDRESS=127.0.0.1
ADMIN_USERNAME=admin
ADMIN_PASSWORD=替换为管理员密码
ADMIN_CLIENT_ID=local
从项目的 .env.example 复制部署配置到
.env,替换地址与管理员凭据后运行:
./deploy.sh
docker compose ps
curl --fail "$NEXUS_URL/health"
curl --fail "$NEXUS_URL/ready"
/health 检查服务与数据库;/ready
检查编辑引擎 discovery。未就绪时 /ready 返回 503。
指定构建镜像源
拉取基础镜像遇到 403 或网络错误时,可指定实际可访问的 Docker Hub 镜像仓库。 网络可直连 Docker Hub 时可使用 registry-1.docker.io;mirror.example.com 为占位示例,须替换为实际仓库:
./deploy.sh --registry registry-1.docker.io
./deploy.sh --registry mirror.example.com
./deploy.sh --registry mirror.example.com:5443/dockerhub --env-file /etc/nexus-ppt/production.env
地址不含 https:// 或凭据;私有仓库先 docker login。仓库须提供 library/python 与 collabora/code 的固定版本及摘要。该参数仅改变本次构建的基础镜像地址,不修改 Docker 全局配置、.env 或 Python 包下载源,下次部署需继续传入。 不传时仍使用 docker.io 并受 Docker 全局镜像代理影响;显式指定 docker.io 也不会绕过该代理。
在控制台维护运行配置
- API Key:创建、轮换、启停调用方,维护允许嵌入地址和文件接口地址,保存后立即生效。
- 文件存储:本地、S3 兼容存储、阿里云 OSS。测试连接、保存配置、迁移已有托管文件;不影响调用方自有文件的存储位置。
- 通用设置:单文件大小 1–200 MB、默认授权 1–480 分钟、管理员会话 1–24 小时。默认分别为 30 MB、240 分钟、8 小时;历史默认 20 条、回收站 30 天、审计 90 天。
新文件默认授权时长采用当前配置,已经签发的授权不会随默认值变化。管理员会话时长仅对新登录生效。轮换 Key、停用或修改调用方地址会撤销已有文稿授权,重新启用后仍需重新签发。
限额、状态与审计
调用方设置提供文稿数量、存储 MB、同时打开的授权会话数、每分钟后端请求数;0 表示不限,默认每分钟 600 次。计数在服务端原子执行;恢复回收站文件同样受文稿数量限额约束。存储用量按该调用方的托管原件、当前内容和历史哈希去重,包含回收站。
运行状态显示引擎与存储连接、数据用量和磁盘余量。在线会话按最近 120 秒有心跳的授权计数,是应用层会话估计;编辑引擎实际连接限制仍由 CODE 执行。审计记录关键写操作、下载和失败请求,支持调用方、操作、结果及用户/文稿/请求 ID 查询,不记录 Key、票据或请求正文。
数据与备份
SQLite 保存服务配置、文稿元信息、历史及审计。使用项目根目录脚本备份、校验与恢复:
./ops.sh backup /srv/backups/nexus-ppt.zip
./ops.sh verify /srv/backups/nexus-ppt.zip
./ops.sh restore /srv/backups/nexus-ppt.zip
备份暂停服务并保存数据库、配置密钥、环境文件及全部托管内容,包括对象存储中的托管原件、历史和回收站;完成后恢复原有运行状态。恢复前自动生成安全备份,校验失败不会覆盖现有数据。
恢复将托管内容落到本地存储并撤销旧会话。可在控制台重新选择对象存储并迁移。外部调用方原文件,以及对象浏览器上传但未纳入文稿管理的独立对象,需由文件所属方另外备份。备份包含环境凭据,应妥善保存。
更新容器时保留数据卷。docker compose down -v
会删除服务数据,不应用作常规停止操作。生产代理的请求大小限制需与控制台上传上限匹配。单机
Compose 部署下,多台独立编辑引擎不能直接随机分配同一协作文稿。
项目使用固定版本的 Collabora CODE 编辑引擎,当前 home mode 限制为最多 20 个连接、10 份同时打开的文稿。更大规模部署需采用适合业务容量的编辑引擎版本,并用实际 PPTX 验证显示兼容性。
常见问题
iframe 空白或被浏览器阻止?
核对 parentOrigin 与当前业务页面 origin 是否完全相同,并在控制台授权;打开链接须在 60 秒内。业务页面自己的 CSP 也需要允许 Nexus 服务和编辑引擎的 frame-src。HTTPS 页面应嵌入 HTTPS 服务,避免混合内容被浏览器阻止。
两个人打开了不同的文稿,无法同步?
确认使用同一调用方、同一个 Nexus 文件 ID。分别签发用户授权即可,不要为每个参与者重新上传。用户身份来自各自的业务登录会话。
Key 轮换后调用失败?
将新 Key 更新到业务后端,按你的部署方式重新加载配置;旧 Key 和之前的文稿授权已撤销,需要为用户重新签发链接。
编辑器提示已保存,怎样取得结果?
托管文件通过下载接口取得当前 PPTX;调用方自有文件从自己的存储读取。业务页面以 saved 事件或后端文件元数据确认结果,不仅依据按钮点击或 modified 状态。
文件 API 可访问,但 Nexus 读取失败?
确认文件接口地址已授权,Nexus 容器可以访问该 URL;容器中的 localhost 指向容器自身。核对 callbackToken、If-Match、ETag 和准确字节数,接口直接返回 PPTX 而非跳转到其他地址。