做鸿蒙文档二开时,我更愿意把 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。
场景到能力的映射
- 资料库预览:注册 + 沙箱 + 默认只读。
- 表单附件批注:可编辑 + 可选修订参数。
- 内网公文:可编辑 + 关分享/打印/导出 + 水印。
- 结果回写:URI/FD 回传 + 本应用落盘上传。
- 高安全缓存策略:先确认
enableLocalization语义,再配extraOptions。
把映射写成配置,比在 PR 里争论「要不要关云文档」更高效。HAR 升级后,对照文档刷新配置表即可。
小结
能力全景的工程意义,是让团队用同一套词汇讨论需求:打开模式、策略参数、回传语义。WPS Open SDK 在鸿蒙侧用 WPSApi.sendRequest 把这些能力收束到请求对象上;你要做的是分层封装,而不是在页面里堆字段。字段细节以官方对接文档为准。
模块边界与演进策略
能力分层落地后,模块边界会自然清晰:register.ts 不感知水印;open.ts 不感知上传队列;transfer.ts 不感知 UI toast。页面层只组合场景。这样换 HAR、改 flavor 凭据时,回归范围集中在注册与打开两层,而不是全仓搜索 extraOptions。
演进时建议保持「配置驱动」:用一份 JSON/常量描述场景到字段的映射,例如 preview、editLocked、editTransfer。新增场景时先改配置再改代码。Code review 关注三点:是否绕过 ensureRegistered;是否跳过沙箱拷贝;是否把回传成功与打开成功混为一谈。
性能与体验上,大文件不要一上来就开回传 + 全量策略。先用小样本验证组合正确,再放量。弱网下单独回归注册超时与 1013。WPS 客户端升级后,抽测只读、可编辑、关分享、关窗回传四条主路径即可覆盖大部分能力风险。把这些约定写进团队 checklist,比每次口头提醒更有效。
若后续要接断点上传或加密落盘,只需在拿到回传路径后扩展 transfer.ts,不必回头改注册与 enableEdit 分支。这正是能力分层的收益:打开链路稳定,业务差异向下沉。
基于 WPS Open SDK 鸿蒙版实践整理。
官方对接文档:365.kdocs.cn/l/clQl5cek2...
QQ 群:628436767