HarmonyOS WPS Open SDK 实践:OpenFileRequest 打开链路与沙箱拷贝

鸿蒙应用要在端内打开 Office 文档,统一版 WPS Open SDK 把入口收成 WPSApi + OpenFileRequest。专业版(ToB)与个人版(ToC)共用同一套构造函数和 sendRequest,差异在注册成功之后要不要 setWpsFileToken,而不在打开类本身再分叉。工程上真正容易散掉的是三件事:注册还没 OK 就点打开、系统选择器路径直接丢给 WPS、预览和编辑各写一套 Request。本文按分层把打开链路写清楚,并给出可放进 WpsOpen.ts 的封装。

阅读建议:已能 ohpm install @wps/wps_sdk,并且 registerApp 至少成功过一次。

为什么打开要单独成层

对接文档把调用链画成:注册 →(ToB)激活序列号 → 构造打开请求 → sendRequest。页面如果在 onClick 里同时干这四件事,日志会缠在一起。1013 是注册凭据问题,打开路径错误多半是 ResultCode.ERROR,未注册则是 Promise 异常。三层混在一个 try 里,值班同学只能猜。

职责 典型失败
Gate registerApp,ToB 再 setWpsFileToken 1013、参数不完整
Inbox 拷贝到 filesDir 外部 URI 权限不足
Open OpenFileRequest + sendRequest ERROR、未注册抛错

ToC 注册 OK 即可进入 Open 层,不必注入序列号。ToB 漏掉 Token 时,打开表现像客户端拒开,不要回头改 enableEdit。分支用 SdkConstants.isPersonalSdk(),不要用包名字符串猜测。

构造函数只收两件事

typescript 复制代码
new OpenFileRequest(context: common.UIAbilityContext, fileUri: string)

context 来自当前 UIAbility。fileUri 建议是沙箱绝对路径或应用可读 URI。文档明确建议:从系统选择器拿到的路径先拷进本应用沙箱。WPS 是独立进程,跨沙箱读文件经常失败,且 msg 不一定友好。

Inbox 层可以很薄:

typescript 复制代码
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';

export function stageFile(
  ctx: common.UIAbilityContext,
  src: string,
  name: string
): string {
  const dir = `${ctx.filesDir}/wps_stage`;
  fs.mkdirSync(dir, true);
  const dest = `${dir}/${name}`;
  fs.copyFileSync(src, dest);
  return dest;
}

文件名带时间戳或业务 id,避免连续打开同一显示名互相覆盖。拷贝放在 Worker / TaskPool 还是主线程,按文件大小决定;打开按钮在拷贝完成前保持禁用。

enableEdit 是开关,不是两种 SDK

未设置或 false 都是只读。只有 true 才是可编辑。ToB 与 ToC 这条语义相同。产品把「预览」和「填写」做成两个按钮时,底层仍应是同一个 openStaged,传入不同布尔值。

OpenFileRequest.wpsToken 保留在类型上,但不推荐。ToB 在 Gate 层全局 setWpsFileToken;ToC 忽略该字段。全仓搜 req.wpsToken 应为零业务命中。水印、extraOptionswpsTransferType 属于 Open 层之上的策略,等只读打开稳定后再加,否则 ERROR 无法归因。

sendRequest:异常不是 Result

typescript 复制代码
import {
  WPSApi,
  OpenFileRequest,
  Result,
  ResultCode,
  SdkConstants,
} from '@wps/wps_sdk';

export async function openStaged(
  ctx: common.UIAbilityContext,
  stagedPath: string,
  editable: boolean
): Promise<Result> {
  const req = new OpenFileRequest(ctx, stagedPath);
  req.enableEdit = editable;
  return WPSApi.sendRequest(req);
}

未注册成功时,这里会 throw。调用方不要写成「打开失败 code 未知」。Gate 层示例:

typescript 复制代码
let ready = false;

export function bootWps(key: string, secret: string, proSn?: string): void {
  WPSApi.registerApp(key, secret, {
    onCallback: (r: Result) => {
      if (r.code !== ResultCode.OK) {
        console.error('[WPS] gate', r.code, r.msg);
        return;
      }
      if (!SdkConstants.isPersonalSdk() && proSn) {
        WPSApi.setWpsFileToken(proSn);
      }
      ready = true;
    },
  });
}

export async function openFromPicker(
  ctx: common.UIAbilityContext,
  pickerPath: string,
  editable: boolean
): Promise<Result> {
  if (!ready) {
    throw new Error('WPS gate not ready');
  }
  const staged = stageFile(ctx, pickerPath, `${Date.now()}.docx`);
  const result = await openStaged(ctx, staged, editable);
  if (result.code === ResultCode.OK && !result.data) {
    console.info('[WPS] launched, no transfer payload');
  }
  return result;
}

未开回传时 OK + 空 data 表示拉起成功。UI 文案用「已打开」,不要用「已保存」。开启 wpsTransferType 之后,再把 fileUri / transferFd 拷回本应用沙箱,那是另一篇文章的范围。

联调时怎么看日志

固定两行前缀:[WPS] gate[WPS] open1013 只出现在 gate。打开阶段再出现鉴权失败,优先怀疑 HAR 与客户端形态不匹配,而不是再改拷贝函数。调试包与正式包 bundleName 不同,凭据必须两套;ToB 序列号也要跟当前安装的 WPS 客户端核对。

建议验收顺序:

  1. 冷启动 gate OK
  2. 沙箱只读打开一份 docx
  3. 同一文件 editable=true
  4. ToC 包日志不得出现 setWpsFileToken
  5. ToB 包在 OK 回调之后必须出现注入(不要打印 SN 明文)
  6. 全仓无 request.wpsToken 赋值

HAR 升级后:只替换依赖、clean、重跑 2 和 3,确认打开面无回归,再恢复水印与回传。不要同一周既改 Token 分支又改 enableEdit 默认值。

页面怎么绑

Ability onCreatebootWps。组件 aboutToAppear 只读 ready 刷新按钮。点击走 openFromPicker。不要在列表每一项的 aboutToAppear 里构造 Request。列表页若要显示「可打开」状态,订阅 Gate 的就绪标记即可。

预览水印、禁止分享等 extraOptions 以文档标注为准,ToC 不生效的字段不要从 ToB 示例原样拷进个人版工程。统一版的价值是学习一次 OpenFileRequest,而不是忽略版本差异表。

打开封装与 Gate 不要放进同一个源文件。WpsGate.ts 只导出 bootWpsreadyWpsInbox.ts 只导出 stageFileWpsOpen.ts 只导出 openStaged / openFromPicker。页面 import 打开函数,不直接 new OpenFileRequest。代码评审把「构造前 ready == true」「路径来自 stage 目录」写成条目。缺陷单固定五列:HAR 文件名、Bundle、isPersonalSdk()、注册 code/msg、打开 code/msg。五列齐了再讨论选择器 URI。

context 必须来自当前 UIAbility。把 Context 缓存在单例里,应用从后台回到前台后再打开,偶发 ERROR。扩展名与真实类型一致:xlsx 不要写成 docx。大文件拷贝放 TaskPool,完成后再使能按钮,避免用户连点两次覆盖同一目标名。Date.now() 做文件名在同一毫秒连点时仍可能冲突,业务 id 更稳。

Result.requestType 用来区分日志来源。ResultCode.NONE(-1)按异常处理。关闭回传的正整数业务码只在开了 wpsTransferType 之后出现,与「仅打开」联调分开记账。extraOptions 仅显式赋值生效,不要把文档整表默认值写进 Request。ToC 不生效的字段从 ToB 示例删掉,避免同事以为开关坏了。

小结

打开文档在统一版里不是「再学一套 API」,而是把 Gate、Inbox、Open 拆开:注册与 Token 进启动阶段,路径进沙箱函数,OpenFileRequest 只表达这次怎么打开。ToB / ToC 共用构造;默认只读;sendRequest 的 throw 与 Result.code 分列。把这三层写进 README 之后,预览和编辑只是同一个函数的布尔参数,值班日志也能按前缀分流。HAR 升级后先只替换依赖并重跑注册与只读打开,确认身份与路径无回归再恢复水印与回传。内部 Wiki 只保留一份对接文档链接,避免每人收藏不同副本。

技术交流 QQ 群:628436767。申请 HAR 与凭据时注明包名以及专业版 / 个人版需求。


基于 WPS Open SDK 鸿蒙版对接实践整理,仅供开发者参考。 官方对接文档:365.kdocs.cn/l/clQl5cek2...

相关推荐
0xBADCODE24 分钟前
动态DEX加载+反射+DES硬编码密钥:安卓三层逆向实战
android·java·python·安全·网络安全·逆向·ctf
Patrick在香港1 小时前
Python 拉取 C&SD 官方 API:香港 2022 年已跨过“超老龄线“,而抚养比正在爬回 1961
android·c语言·python·数据分析·时序数据库·数据可视化·香港
mmsx1 小时前
osmdroid 地图实战 03|谷歌影像的 URL,为什么不能 setTileSource 了事?
android
木子雨廷1 小时前
第 01 天|心智模型:iOS/Flutter 开发者学鸿蒙,第一步该换什么思路?
harmonyos
hai_android1 小时前
Kotlin 协程上下文(CoroutineContext)深度解析
android
恋猫de小郭2 小时前
Flutter A2UI 深度解析,它是怎么提供动态生产力的,然后为什么 A2UI 不只是 Flutter
android·前端·flutter
心平气和量大福大2 小时前
android-控件-单选框RadioButton
android
Meteors.2 小时前
Android 性能优化:08.耗电优化
android·性能优化
lilian2332 小时前
HarmonyOS 7 新特性(二十一)|FAST Kit:自然排序与向量计算
华为·harmonyos