鸿蒙应用要在端内打开 Office 文档,常见工程问题是:预览页、编辑页、带水印的审批页各自复制一份 OpenFileRequest 构造逻辑;换 HAR 或换交付包后,注册与打开参数又对不齐,联调日志里 reject、1013、ERROR 交替出现。WPS Open SDK 鸿蒙统一版把对外模型收成单例 WPSApi 与同一套 OpenFileRequest 字段,业务侧可以把「多套平行 Helper」压成一条 Facade。本文按调用链说明统一接口解决了哪些工程痛点,并给出 TypeScript 收敛写法;字段语义以官方对接文档为准。
一、痛点对照:从「多份打开代码」到「一条链路」
| 工程现象 | 根因 | 统一接口侧的收敛方式 |
|---|---|---|
| 预览/编辑各写一套打开 | 参数散落、默认值不一致 | 单一 openDoc(mode),内部设 enableEdit |
| 冷启动连点 reject | 未注册就 sendRequest |
ensureRegistered 门禁 + wpsReady 短路 |
| 正式包 1013 | bundleName 与凭据不匹配 |
flavor 注入 key,注册失败即停 |
| 选择器路径 ERROR | URI 未进沙箱 | copyToSandbox 后再 OpenFileRequest |
OK 却无 data |
未开回传却当上传成功 | 按是否配置 wpsTransferType 分支 |
推荐时序固定为:
onCreate → ensureRegistered
用户选文件 → copyToSandbox → buildOpenRequest → sendRequest
策略字段(水印、extraOptions、回传)在「最小打开」跑通后再叠加,避免一次堆参难以归因。
二、统一入口:WPSApi 与 Request 模型
对接文档对外入口是单例 WPSApi:注册走 RegisterAppRequest,打开走 OpenFileRequest,结果统一为 Result(requestType / code / msg / data)。统一版的工程价值不在于「页面零分支」,而在于学习成本与 Code Review 面收敛 :全仓搜索 new OpenFileRequest 应只有 Facade 一处命中。
typescript
import { common } from '@kit.AbilityKit';
import {
WPSApi,
RegisterAppRequest,
OpenFileRequest,
Result,
ResultCode,
} from '@wps/wps_sdk';
/** 由 flavor / 构建脚本注入:当前 HAR 是否需要 setWpsFileToken */
declare const BUILD_NEEDS_ACTIVATION_SN: boolean;
export let wpsReady = false;
export function logResult(tag: string, r: Result): void {
console.info(`[${tag}] type=${r.requestType} code=${r.code} msg=${r.msg ?? ''}`);
}
export async function ensureRegistered(ctx: common.UIAbilityContext): Promise<void> {
if (wpsReady) return;
const r = await WPSApi.sendRequest(
new RegisterAppRequest(ctx, APP_KEY, APP_SECRET)
);
logResult('register', r);
if (r.code === ResultCode.ERROR_CODE_AUTH_FAILURE) {
throw new Error(`register 1013: ${r.msg ?? ''}`);
}
if (r.code !== ResultCode.OK) {
throw new Error(`register code=${r.code}`);
}
// 是否注入激活序列号由构建配置决定(与当前 HAR 交付约定对齐),勿在页面猜客户端包名
if (BUILD_NEEDS_ACTIVATION_SN && ACTIVATION_SN) {
WPSApi.setWpsFileToken(ACTIVATION_SN);
}
wpsReady = true;
}
是否调用 setWpsFileToken 应由 构建配置 (BUILD_NEEDS_ACTIVATION_SN)与申请材料对齐,避免在页面层根据 WPS 包名做运行时猜测。序列号通过 setWpsFileToken 全局注入,不要在每次 OpenFileRequest 上重复赋值旧字段。多 flavor 工程把 key、secret、序列号放进 rawfile 或 CI 密钥,业务模块只读 Facade 导出常量。
三、打开层:沙箱路径与 enableEdit 显式化
统一接口下,打开失败最常见两类:ResultCode.ERROR(路径不可读)与「只能预览」(未设 enableEdit = true)。前者用沙箱拷贝解决,后者用模式参数显式化。
typescript
import fs from '@ohos.file.fs';
function copyToSandbox(ctx: common.UIAbilityContext, src: string): string {
const dir = `${ctx.filesDir}/wps_docs`;
fs.mkdirSync(dir, true);
const dest = `${dir}/${Date.now()}.docx`;
fs.copyFileSync(src, dest);
return dest;
}
export async function openDoc(
ctx: common.UIAbilityContext,
srcPath: string,
editable: boolean
): Promise<void> {
await ensureRegistered(ctx);
const path = copyToSandbox(ctx, srcPath);
const req = new OpenFileRequest(ctx, path);
req.enableEdit = editable;
try {
const r = await WPSApi.sendRequest(req);
logResult(editable ? 'open-edit' : 'open-read', r);
if (r.code !== ResultCode.OK) {
throw new Error(`open code=${r.code} msg=${r.msg ?? ''}`);
}
} catch (e) {
console.error('open failed (not registered?)', e);
throw e;
}
}
预览入口传 editable = false,编辑入口传 true。合入前全仓搜索 enableEdit,确认与产品入口一一对应。
四、结果层:回传与空 data 的语义
未配置 wpsTransferType 时,code === OK 且 data 为空表示「WPS 已拉起」,不是上传失败。开启关窗回传后,才在 Promise resolve 时读取 Result.data 并拷贝到本应用沙箱。把「拉起」与「回传落盘」拆成两个 UI 状态,可避免误报。
| 场景 | code |
data |
业务含义 |
|---|---|---|---|
| 只读预览 | OK | 空 | 正常 |
| 可编辑未开回传 | OK | 空 | 正常 |
| 已开回传且用户保存关窗 | OK | 有 fileUri 等 |
再消费 data |
| 路径/参数错误 | ERROR | --- | 查沙箱与字段 |
五、维护成本:Facade 与交付对齐
统一版降低的是接口分裂成本,不是抹掉交付差异。工程上建议:
- 依赖与凭据按 flavor 注入,业务模块只 import Facade。
- 注册断言集中在一处 ,页面禁止散落
new RegisterAppRequest。 - 打开策略收进
openDoc可选参数 ,水印、extraOptions后续扩展不复制构造代码。 - Release 禁止打印完整 secret ;日志带
stage=register|open|transfer。
换 HAR 后 clean 重装;核对当前 bundleName 与申请材料一致,可消除大半 1013。
六、策略扩展、联调清单与小结
联调清单:
- 冷启动在
wpsReady前禁用打开按钮 - 注册失败不继续
OpenFileRequest - 选择器文件已
copyToSandbox - 编辑入口显式
enableEdit = true - 区分 throw(未注册)与
result.code(已注册失败) - 未开回传时不把空
data当失败 - 全仓仅一处
new OpenFileRequest - 日志含
requestType/code/msg
最小打开稳定后,水印、wpsRevisionParams、extraOptions 仍挂在同一个 OpenFileRequest 上,只是赋值时机后移:
最小打开稳定后,水印、wpsRevisionParams、extraOptions 仍挂在同一个 OpenFileRequest 上,只是 赋值时机后移 。建议在 Facade 增加可选参数对象,而不是新建 openWithWatermark.ts:
typescript
type OpenPolicy = {
editable?: boolean;
waterMark?: WaterMark;
extra?: OpenFileExtraOptions;
};
export async function openWithPolicy(
ctx: common.UIAbilityContext,
sandboxPath: string,
policy: OpenPolicy
): Promise<void> {
await ensureRegistered(ctx);
const req = new OpenFileRequest(ctx, sandboxPath);
req.enableEdit = policy.editable ?? false;
if (policy.waterMark) {
req.wpsWaterMarkParams = policy.waterMark;
}
if (policy.extra) {
req.extraOptions = policy.extra;
}
const r = await WPSApi.sendRequest(req);
logResult('open-policy', r);
if (r.code !== ResultCode.OK) {
throw new Error(`open-policy code=${r.code}`);
}
}
评审时关注两点:WaterMark / OpenFileExtraOptions 是否在 Facade 内集中构造;页面是否仍直接 new OpenFileRequest。统一接口的价值在于 策略可组合,而不是页面各自拼字段。
关窗回传(wpsTransferType)与「能否编辑」正交:可先只读预览,再在「提交审批」入口开启回传并等待 data。日志建议打印 editable、transferOn 两个布尔,方便和 ERROR 区分。
鸿蒙 WPS 二开里,统一接口解决的核心工程痛点是:把多套平行打开链路收成单例 WPSApi + 单一 Facade,用注册门禁、沙箱路径与 enableEdit 显式化消掉高频联调噪声。策略字段在最小打开稳定后再叠,维护时按 flavor 对齐 HAR 与凭据即可,无需为每个页面重写一套 SDK 调用面。把 ensureRegistered、copyToSandbox、openWithPolicy 提交进基础库后,新需求通常只需扩可选参数,联调时间会从「猜原因」缩短为「对表排查」。发版评审建议同时核对 HAR 文件名、注册 code 与本次策略赋值,避免「能注册不能打开」被误判为 SDK 缺陷。
基于 WPS Open SDK 鸿蒙版对接实践整理,仅供开发者参考。