打开能绿之后,产品下一句往往是:「编辑完关掉 WPS,结果要回到我们 App」。在 HarmonyOS 落地 WPS Open SDK 时,这不是另开回调通道,而是同一次 sendRequest 的等待语义 :设置关闭回传后,Promise 会等到用户关闭文档,再在 result.data 里给出 URI 或 FD。本文按等待语义 → 字段优先级 → 拷贝沙箱 → Facade写,避免把「OK 且 data 空」误判成失败。
先分清:拉起成功 ≠ 已回传
| 配置 | sendRequest 行为 |
|---|---|
| 未开回传 | 拉起成功即可 OK,data 常空 |
| 开了回传 | 等到用户关文档,才带 ResultData |
专业版与个人版共用打开 API;回传与否由 Request 字段决定。不要为此复制两套 Helper,差异收在 prepare 与可选参数。
最小联调:注册 OK → 沙箱可编辑打开(无回传)→ 再开 URI 回传 → 关文档看到 fileUri → 拷贝到本应用沙箱 → 可选再验 FD。
字段:优先用 wpsTransferType
typescript
req.enableEdit = true;
req.wpsTransferType = TransferType.URI; // 或 TransferType.FD
wpsTransferType 优先级高于 enableTransferFile。团队约定只写类型枚举,可读性更好。URI 看 result.data.fileUri;FD 看 transferFd 与 parameters。
关键:WPS 沙箱路径不能直接用
URI 回传给的是 WPS 侧路径。业务侧必须拷贝:
typescript
function copyWpsUriToSandbox(ctx: UIAbilityContext, wpsFileUri: string): string | undefined {
const file = fs.openSync(wpsFileUri, fs.OpenMode.READ_ONLY);
if (!file.path) return undefined;
const name = file.path.substring(file.path.lastIndexOf('/') + 1);
const dir = `${ctx.filesDir}/wps_callback/${Date.now()}/`;
if (!fs.accessSync(dir)) fs.mkdirSync(dir, true);
const dest = dir + name;
fs.copyFileSync(file.fd, dest);
return fileuri.getUriFromPath(dest);
}
按凭据约定需要清理临时文件时,拷贝成功后再删 WPS 侧文件;删除失败只打日志,不要丢掉已得到的本应用路径。
FD 模式从描述符读入本应用文件后,记得 close 双方 fd。文件名优先用回传参数。
Facade:把等待与拷贝收口
typescript
export async function openAndCollect(
ctx: UIAbilityContext,
src: string,
transfer: 'none' | 'uri' | 'fd' = 'uri'
): Promise<{ result: Result; localPath?: string }> {
await prepareWps(APP_KEY, APP_SECRET, PRO_SN_OR_EMPTY);
const path = toSandbox(ctx, src);
const req = new OpenFileRequest(ctx, path);
req.enableEdit = true;
if (transfer !== 'none') {
req.wpsTransferType = transfer === 'fd' ? TransferType.FD : TransferType.URI;
}
const result = await WPSApi.sendRequest(req);
if (transfer === 'none' || result.code !== ResultCode.OK || !result.data) {
return { result };
}
let localPath: string | undefined;
if (transfer === 'fd' && result.data.parameters) {
localPath = copyFdToSandbox(ctx, result.data.parameters);
} else if (result.data.fileUri) {
localPath = copyWpsUriToSandbox(ctx, result.data.fileUri);
}
return { result, localPath };
}
预览页传 transfer: 'none',编辑闭环传 'uri'。页面层不要自己 await 后再猜 data 为何为空。
联调表
| 现象 | 优先查 |
|---|---|
| 一直 pending | 是否开了回传、用户是否关文档 |
| OK + data 空 | 是否未开回传 |
| 有 URI 读失败 | 是否未拷贝 |
| FD 异常 | fd 有效性、parameters、是否重复 close |
1013 / 抛异常 |
注册、凭据、包名、clean |
日志:code / msg / transfer 模式 / 是否已得到 localPath。全仓 new OpenFileRequest 命中为一。换 HAR 后 clean;Token 全局设置;Release 不打印 secret。
协作
PR:回传必须走 Facade;业务禁止直接使用 WPS 沙箱路径;用例含「无回传预览」「URI 拷贝成功」。双形态用 flavor 注入密钥。周五正式包包名下关文档验一次回传。
路径打开前也要先落沙箱。把「等待关窗」写进产品说明,避免测试以为卡死。超时策略(提示/取消)与产品对齐。拷贝目录按时间戳隔离,防并发覆盖。
小结
关闭回传是 sendRequest 的结果层等待:显式设 wpsTransferType,关窗后解析 ResultData,再拷贝到本应用沙箱。未开回传时 OK 且 data 空属正常。字段以官方对接文档为准。技术交流可进 QQ 群对齐实践。
接入评审再确认 HAR、沙箱目录、回传模式是否统一。缺一环易「本地绿、上架红」。产品临时要 FD 时只改枚举与解析分支,不新开平行 Helper。把 Facade、对照表、PR 模板坚持几周,联调通常会从猜原因变成对表排查。
同一仓库双形态时,diff 应主要在配置。真机日志固定打 transfer 模式与 localPath 是否非空。注释写清「回传路径必须先拷贝」。路径先落沙箱;换 HAR 后 clean。把这些节奏坚持几周,回传相关误报通常会明显下降。
再补几条工程经验。用户长时间不关文档时,界面应有「等待关闭」状态,避免被当成无响应。取消等待若产品允许,要明确是否放弃本次编辑结果。URI 与 FD 的验收清单分开写:URI 看拷贝后可读;FD 看文件名与大小是否与 parameters 一致。并发打开两份文档时,回调目录不要用固定路径互相覆盖。清理临时文件失败不要阻断主流程。把这些写进联调页,新人合入会稳很多。
周五可用正式包包名再验注册,并跑一次「可编辑 + URI 回传 + 拷贝」。搜全仓 new OpenFileRequest 命中是否仍为一。需要序列号时在注册成功回调里按凭据约定设置。Release 不打印 secret。打开、回传、拷贝三层拆开排查,比一次堆满所有开关更省时间。
产品侧若同时要求水印、管控与回传,仍建议分阶段合入:先可编辑打开,再回传拷贝,再叠策略。Facade 可扩可选参数,但不要为每种组合复制打开函数。PR 写清本次改了回传模式还是拷贝目录。坚持几周后,回传层相关的反复提问通常会减少,对接节奏也会更可预期。
再强调一次用户体验:开了回传却不提示「请关闭文档」,测试与用户都容易以为应用卡死。取消等待若产品允许,要写清是否丢弃本次编辑。URI 与 FD 的测试用例分开维护,避免用同一断言。拷贝失败、打开失败、注册失败三类错误码或文案分开,客服工单才好归类。把这些写进 Wiki,比反复在群里解释「data 为什么是空的」更省时间。
阅读建议:已了解鸿蒙 Ability 与 Promise 基础。
官方对接文档:365.kdocs.cn/l/clQl5cek2...
技术交流 QQ 群:628436767