HarmonyOS WPS Open SDK 实践:用能力分层重构 OpenFileRequest

做鸿蒙文档二开时,我更愿意把 WPS Open SDK 看成一条「能力流水线」,而不是散落的 API 清单。注册决定流水线能不能启动;OpenFileRequest 决定打开模式与策略;Result 决定要不要回收编辑产物。本文从工程结构出发,整理一份可落地的能力分层与封装建议,方便在 HarmonyOS 项目里做模块边界划分。

为什么需要「能力分层」

若每个页面直接 new OpenFileRequest,很快会出现三类问题:注册时序被生命周期冲散;策略字段复制粘贴不一致;回传语义和「切回应用」混谈。分层后,页面只关心场景(预览 / 编辑 / 带回传),能力细节收在 wps/ 模块。

推荐目录约定:

arduino 复制代码
wps/
  register.ts   // ensureRegistered
  sandbox.ts    // copy 到 filesDir
  open.ts       // buildRequest + sendRequest
  transfer.ts   // 解析 fileUri / transferFd
  types.ts      // CapabilityOptions

能力矩阵(对齐接口,而非对齐口号)

能力名 接口落点 默认行为 常见误用
注册 RegisterAppRequest 未成功则后续 reject 冷启动连点 open
激活序列号 setWpsFileToken 按凭据形态需要 每次 open 塞 wpsToken
只读/编辑 enableEdit 默认只读 以为「打不开」其实只读
沙箱路径 业务侧 copy 外部路径可能 ERROR 直接传选择器 URI
水印 WaterMark 不设则无 透明度格式写错
菜单开关 OpenFileExtraOptions 未赋值保持默认 期望全关却只设一项
不落地 enableLocalization 特定形态默认不落地 与 extraOptions 冲突
关窗回传 TransferType 默认不等待 OK 无 data 当失败
typescript 复制代码
import { common } from '@kit.AbilityKit';
import {
  WPSApi,
  RegisterAppRequest,
  OpenFileRequest,
  OpenFileExtraOptions,
  WaterMark,
  TransferType,
  ResultCode,
  SdkConstants,
} from '@wps/wps_sdk';

let wpsReady = false;

async function ensureRegistered(ctx: common.UIAbilityContext): Promise<void> {
  if (wpsReady) return;
  const r = await WPSApi.sendRequest(
    new RegisterAppRequest(ctx, APP_KEY, APP_SECRET)
  );
  if (r.code === ResultCode.ERROR_CODE_AUTH_FAILURE) {
    throw new Error(`1013: ${r.msg ?? ''}`);
  }
  if (r.code !== ResultCode.OK) {
    throw new Error(`register ${r.code}`);
  }
  if (!SdkConstants.isPersonalSdk() && ACTIVATION_SN) {
    WPSApi.setWpsFileToken(ACTIVATION_SN);
  }
  wpsReady = true;
}

打开与策略:一份请求,多种组合

OpenFileRequest 的价值在于组合。同一路径可以只读预览,也可以可编辑 + 水印 + 关分享 + URI 回传。关键是把组合写成显式选项,避免魔法布尔散落。

typescript 复制代码
import fs from '@ohos.file.fs';

function toSandbox(ctx: common.UIAbilityContext, src: string): string {
  const dir = `${ctx.filesDir}/wps_cap`;
  fs.mkdirSync(dir, true);
  const dest = `${dir}/${Date.now()}.docx`;
  fs.copyFileSync(src, dest);
  return dest;
}

async function openWithCapability(
  ctx: common.UIAbilityContext,
  src: string,
  opts: {
    editable: boolean;
    watermark?: string;
    transfer?: boolean;
    lockShare?: boolean;
  }
) {
  await ensureRegistered(ctx);
  const path = toSandbox(ctx, src);
  const req = new OpenFileRequest(ctx, path);
  req.enableEdit = opts.editable;

  if (opts.watermark) {
    const wm = new WaterMark();
    wm.Enable = true;
    wm.WaterMaskText = opts.watermark;
    wm.Angle = -30;
    wm.FontColor = '#19000000';
    wm.FontSize = 16;
    req.wpsWaterMarkParams = wm;
  }

  if (opts.transfer) {
    req.wpsTransferType = TransferType.URI;
  }

  if (opts.lockShare) {
    const extra = new OpenFileExtraOptions();
    extra.enableShare = false;
    extra.enableCloud = false;
    extra.enablePrint = false;
    extra.enableExport = false;
    req.extraOptions = extra;
  }

  const res = await WPSApi.sendRequest(req);
  if (res.code !== ResultCode.OK) {
    throw new Error(res.msg ?? String(res.code));
  }
  return res;
}

调用侧示例:

typescript 复制代码
// 预览
await openWithCapability(ctx, picker, { editable: false });

// 可编辑并关外发
await openWithCapability(ctx, picker, {
  editable: true,
  watermark: deptStamp,
  lockShare: true,
});

// 编辑并等待关窗回传
await openWithCapability(ctx, picker, {
  editable: true,
  transfer: true,
});

策略叠加顺序建议:可编辑 → 不落地/落地决策 → extraOptions → 水印/修订 → 回传。ERROR 时从上到下回看,比从日志猜字段更快。

回传层:把「成功打开」和「拿到文件」拆开断言

轻量阶段只断言 code === OK;需要落盘时再开 wpsTransferType。URI 模式读 data.fileUri;FD 模式读 transferFd 并校验 transferFileSize

typescript 复制代码
async function openAndCollect(ctx: common.UIAbilityContext, src: string) {
  const res = await openWithCapability(ctx, src, {
    editable: true,
    transfer: true,
    watermark: `${uid}|内部`,
    lockShare: true,
  });
  // 未关窗时可能没有 data,不要当失败
  if (!res.data?.fileUri) {
    return null;
  }
  const f = fs.openSync(res.data.fileUri, fs.OpenMode.READ_ONLY);
  const dest = `${ctx.filesDir}/wps_out/${Date.now()}.docx`;
  fs.mkdirSync(`${ctx.filesDir}/wps_out`, true);
  fs.copyFileSync(f.fd, dest);
  fs.closeSync(f);
  return dest;
}

日志建议统一前缀 [WPS][open],输出 requestType/code/msg。用户提示用短句:注册失败 / 路径无效 / 打开失败;详细 msg 进 HiLog。单测至少覆盖:未注册 reject、1013、ERROR、OK 无 data、OK 有 fileUri。

场景到能力的映射

  1. 资料库预览:注册 + 沙箱 + 默认只读。
  2. 表单附件批注:可编辑 + 可选修订参数。
  3. 内网公文:可编辑 + 关分享/打印/导出 + 水印。
  4. 结果回写:URI/FD 回传 + 本应用落盘上传。
  5. 高安全缓存策略:先确认 enableLocalization 语义,再配 extraOptions

把映射写成配置,比在 PR 里争论「要不要关云文档」更高效。HAR 升级后,对照文档刷新配置表即可。

小结

能力全景的工程意义,是让团队用同一套词汇讨论需求:打开模式、策略参数、回传语义。WPS Open SDK 在鸿蒙侧用 WPSApi.sendRequest 把这些能力收束到请求对象上;你要做的是分层封装,而不是在页面里堆字段。字段细节以官方对接文档为准。

模块边界与演进策略

能力分层落地后,模块边界会自然清晰:register.ts 不感知水印;open.ts 不感知上传队列;transfer.ts 不感知 UI toast。页面层只组合场景。这样换 HAR、改 flavor 凭据时,回归范围集中在注册与打开两层,而不是全仓搜索 extraOptions

演进时建议保持「配置驱动」:用一份 JSON/常量描述场景到字段的映射,例如 previeweditLockededitTransfer。新增场景时先改配置再改代码。Code review 关注三点:是否绕过 ensureRegistered;是否跳过沙箱拷贝;是否把回传成功与打开成功混为一谈。

性能与体验上,大文件不要一上来就开回传 + 全量策略。先用小样本验证组合正确,再放量。弱网下单独回归注册超时与 1013。WPS 客户端升级后,抽测只读、可编辑、关分享、关窗回传四条主路径即可覆盖大部分能力风险。把这些约定写进团队 checklist,比每次口头提醒更有效。

若后续要接断点上传或加密落盘,只需在拿到回传路径后扩展 transfer.ts,不必回头改注册与 enableEdit 分支。这正是能力分层的收益:打开链路稳定,业务差异向下沉。


基于 WPS Open SDK 鸿蒙版实践整理。

官方对接文档:365.kdocs.cn/l/clQl5cek2...

QQ 群:628436767

相关推荐
春卷同学2 小时前
HarmonyOS掌上记账APP开发实践第21篇:Tabs 底部导航 — 鸿蒙应用的标准导航范式
华为·harmonyos
子非鱼94272 小时前
03-Flutter 鸿蒙实战 03:登录注册与本地会话持久化
网络·flutter·华为·harmonyos
子非鱼94272 小时前
01-Flutter 鸿蒙实战 01:会说话的相册项目架构拆解
flutter·华为·harmonyos
不羁的木木2 小时前
《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第10篇:多设备协同——构建星闪网络实现数据同步
网络·华为·harmonyos
春卷同学2 小时前
HarmonyOS掌上记账APP开发实践第29篇:条件渲染与 ForEach — 动态 UI 的构建策略
ui·华为·harmonyos
春卷同学3 小时前
HarmonyOS掌上记账APP开发实践第22篇:Scroll + List 虚拟列表 — 长列表性能优化全攻略
性能优化·list·harmonyos
不羁的木木3 小时前
《HarmonyOS技术精讲-NearLink Kit(星闪服务)》第4篇:广播通信——一对多广播应用实现
华为·harmonyos
w139548564223 小时前
鸿蒙应用开发实战【91】— 完整功能串联从添加到换绑的全链路
华为·harmonyos·鸿蒙系统
2501_919749033 小时前
华为鸿蒙免费训练口才APP—小羊口才
华为·harmonyos