在鸿蒙应用里接入 WPS Open SDK,真正需要长期维护的不是某一个接口,而是一条从应用注册到文档关闭的完整链路。页面上的按钮只负责发起请求,后面还连着凭据校验、文件路径、编辑模式、水印与修订参数、功能开关,以及关闭时的结果回传。任何一个环节没有对齐,表现都可能只是"文档打不开"或"编辑结果拿不到",排查范围却会被迅速放大。
把这条链路按层拆开,会更容易得到稳定的接入方式。注册层解决应用身份,打开层解决文件与编辑权限,编辑层解决水印、修订和功能边界,回传层解决结果如何从 WPS 沙箱回到业务沙箱。本文按这四层复盘一次完整接入,并给出可以直接复用的代码骨架和联调清单。
一、统一入口与调用链
WPS Open SDK 鸿蒙版把主要能力收敛到 WPSApi。业务侧通常只需要两个动作:先通过 registerApp 完成注册,再通过 sendRequest 发起文档请求。sendRequest 返回 Promise<Result>,成功与否都通过 Result.code 和 Result.msg 表达,关闭回传时还会在 Result.data 中携带文件信息。
| 层级 | 主要接口 | 解决的问题 |
|---|---|---|
| 注册层 | registerApp |
应用是否有权调用 SDK |
| 打开层 | OpenFileRequest、sendRequest |
打开哪个文件、以什么模式打开 |
| 编辑层 | 水印、修订、extraOptions |
文档内允许哪些操作 |
| 回传层 | ResultData、TransferType |
编辑结果如何交回业务 |
这条链路有明确的前后依赖。注册没有成功时调用 sendRequest 会直接抛异常,不会进入正常的文档回调。因此接入代码不应该把注册和打开写在两个互不相关的页面生命周期里,而应该把"SDK 是否就绪"做成一个可查询、可等待的状态。
二、注册与应用身份
registerApp 需要应用凭据,并与最终安装包的 Bundle 名称绑定。凭据不要硬编码在页面代码里,也不要进入公开仓库或完整日志;更稳妥的方式是通过构建配置或 CI 注入,只记录包名、HAR 批次和 key 的末几位用于核对。
typescript
import { WPSApi, Result, ResultCode } from '@wps/wps_sdk';
let readyPromise: Promise<void> | null = null;
export function ensureSdkReady(
appKey: string,
appSecret: string,
activationToken?: string
): Promise<void> {
if (readyPromise) {
return readyPromise;
}
readyPromise = new Promise<void>((resolve, reject) => {
WPSApi.registerApp(appKey, appSecret, {
onCallback: (result: Result): void => {
if (result.code !== ResultCode.OK) {
readyPromise = null;
reject(new Error(`register failed: ${result.code} ${result.msg}`));
return;
}
if (activationToken) {
WPSApi.setWpsFileToken(activationToken);
}
resolve();
},
});
});
return readyPromise;
}
这段封装做了两件事:把回调式注册转成 Promise,并缓存进行中的 Promise,避免页面初始化、按钮点击和后台任务同时触发多次注册。注册失败时清空缓存,让下一次调用可以重新尝试。若交付说明要求激活序列号,则在注册成功回调中调用 setWpsFileToken;不需要时保持为空即可。
联调时最常见的问题是包名不一致。调试包、正式包和应用市场登记的包名只要有一处不同,就可能返回 1013 鉴权失败。这个错误不应该先去改文档路径或编辑模式,而应该先核对当前安装包、签名配置和申请凭据是否指向同一个应用身份。
三、打开文档与编辑参数
打开文档前,建议先把目标文件放到本应用可访问的沙箱目录,再把沙箱路径传给 OpenFileRequest。直接使用外部选择器返回的路径,可能因为权限不足导致 WPS 无法读取。
typescript
import { common } from '@kit.AbilityKit';
import {
OpenFileRequest,
TransferType,
WPSApi,
Result,
ResultCode,
} from '@wps/wps_sdk';
async function openForEdit(
context: common.UIAbilityContext,
sandboxPath: string
): Promise<Result> {
await ensureSdkReady(APP_KEY, APP_SECRET, ACTIVATION_TOKEN);
const request = new OpenFileRequest(context, sandboxPath);
request.enableEdit = true;
request.enableLocalization = false;
request.wpsTransferType = TransferType.URI;
request.wpsWaterMarkParams = {
Enable: true,
WaterMaskText: '内部资料',
Angle: -30,
FontColor: '#19000000',
FontSize: 18,
};
request.wpsRevisionParams = {
UserName: '业务审阅人',
EnterReviseMode: true,
ShowRevisionPanel: true,
};
return WPSApi.sendRequest(request);
}
enableEdit 不设置或为 false 时,文档以只读模式打开;需要编辑时必须显式设为 true。wpsTransferType 决定关闭时采用 URI 还是 FD 回传。水印和修订参数按业务需要组合,不需要的字段不要赋值,避免改变默认行为。
文件路径、编辑模式和回传方式是三个独立维度。路径决定 WPS 能否读到文件,编辑模式决定用户能否修改,回传方式决定关闭时业务能否拿到结果。联调时分别验证,比一次性把所有参数都打开更容易定位问题。
四、水印、修订与功能开关
水印通过 wpsWaterMarkParams 配置,常用字段包括是否启用、水印文字、旋转角度、字体颜色和字号。修订通过 wpsRevisionParams 配置,可以指定修订作者、是否进入修订模式、是否显示修订面板,以及是否静默进入。
extraOptions 负责更细粒度的文档内功能控制。只有显式赋值的属性才会生效,未赋值的项保持 WPS 默认行为。
| 开关 | 作用 |
|---|---|
enableShare |
分享功能 |
enableCloud |
云文档相关能力 |
enableLogin |
登录引导 |
enableSaveAs |
另存为 |
enablePrint |
打印 |
enableExport |
导出 PDF、图片等 |
enableCopy / enablePaste |
复制、粘贴与剪切 |
document_save |
文档保存 |
如果启用了不落地,部分可能造成文件外泄的能力会被强制关闭,即使 extraOptions 中设置为开启也不会生效。接入时不要把功能开关和不落地当成两套互不相干的配置,最好在同一个请求构建函数里完成,避免后续维护时出现"界面上看似允许、实际被底层策略覆盖"的情况。
五、关闭回传与沙箱转存
设置 enableTransferFile 或 wpsTransferType 后,sendRequest 会等待用户关闭文档,并在回调中返回编辑结果。URI 模式下关注 result.data.fileUri,FD 模式下关注 result.data.transferFd、transferFileName 和 transferFileSize。
typescript
function handleResult(context: common.UIAbilityContext, result: Result): void {
if (result.code !== ResultCode.OK || !result.data) {
console.error('open or transfer failed', result.code, result.msg);
return;
}
const wpsFileUri = result.data.fileUri;
if (wpsFileUri) {
const localPath = copyWpsUriToSandbox(context, wpsFileUri);
if (localPath) {
console.info('saved to business sandbox', localPath);
// 回传文件已经拷贝完成,可以交给上传或本地保存流程
fs.unlink(wpsFileUri, (err) => {
if (err) console.error('delete WPS temp file failed', err);
});
}
}
}
fileUri 是 WPS 沙箱内的临时路径,不能直接作为业务长期路径使用。URI 模式要先读取再拷贝,FD 模式要按块读取并写入业务沙箱,最后关闭文件描述符。拷贝完成前不要删除 WPS 临时文件;拷贝失败时也不要用空路径覆盖业务原文件。
回传成功的时间点是用户关闭文档,而不是用户在 WPS 内点击保存。用户在编辑过程中保存,只更新 WPS 的工作副本;业务侧真正拿到最终文件,要等 sendRequest 的 Promise resolve 并完成沙箱转存。
六、不落地与异常边界
不落地的准确含义是"不在 WPS 侧形成持久化缓存副本",并不等于编辑期间完全没有临时工作态。用户关闭文档时,为了把结果交给业务,WPS 仍可能生成一个回传临时文件或文件描述符。这个对象的生命周期由 WPS 管理,文档没有给出固定目录和固定清理时长,业务侧不应依赖内部路径。
在支持不落地的交付包中,建议在回传文件拷贝成功后删除 WPS 临时文件;删除失败只记录日志,不影响业务已经拿到的副本。对于用户取消、WPS 崩溃或进程被强制结束的情况,不能假设一定会收到回调,业务侧应有超时、补偿和"未完成"状态。
| 阶段 | 业务侧应记录的状态 |
|---|---|
| 发起打开 | opening |
| WPS 已拉起 | editing |
| 收到回传 | callback_received |
| 拷贝完成 | copied_to_business_sandbox |
| 业务保存或上传完成 | saved |
| 超时、取消或异常 | failed / timeout |
这套状态机可以把"WPS 内保存""回调已收到""业务已落盘"区分开。否则一旦出现回调丢失,日志里很难判断文件到底停留在哪一步。
七、联调清单与小结
上线前可以按下面的顺序逐项核对:
- 申请凭据、HAR 批次和最终安装包的 Bundle 名称是否一致。
registerApp是否只在成功回调后放行sendRequest。- 目标文件是否已经拷贝到本应用沙箱。
enableEdit是否按业务需要显式设置。- 水印、修订和
extraOptions是否符合文档内操作边界。 - 关闭回传使用 URI 还是 FD,是否与结果处理代码一致。
- 回传文件是否完成沙箱转存,临时文件是否按策略清理。
- 取消、崩溃和超时是否有可观测的失败状态。
WPS Open SDK 鸿蒙版的接入难点,通常不在单个字段,而在整条链路的边界。把注册、打开、编辑控制和回传转存拆成四层,再用状态字段记录每一步,代码会更稳定,联调也更容易收敛。对于需要长期维护的文档模块,这种分层比在一个页面里堆满参数更值得投入。
基于 WPS Open SDK 鸿蒙版对接实践整理,仅供开发者参考。