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

0 阅读4分钟

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

为什么需要「能力分层」

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

推荐目录约定:

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 当失败
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 回传。关键是把组合写成显式选项,避免魔法布尔散落。

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;
}

调用侧示例:

// 预览
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

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