本文中的 PixelMemo、SR-308、图片文件名、界面及 HiLog 均为用于说明方案的设计数据,未执行图像超分模型、DevEco 编译或真机剪贴板测试。图示也不是实际测试证据。

一、复制成功,不代表分享内容安全

做图像超分结果预览时,页面上通常只有一张处理后的图片、一句质量提示和一个导出按钮。真正容易被忽略的是“复制说明”这条侧路:如果直接把处理任务对象转成 JSON,调试路径、操作员内部标识甚至缓存目录都可能跟着进入剪贴板。用户只是想把处理结果发给同事,没必要把应用内部的文件布局也交出去。

本文用 PixelMemo 作为独立的演示工程,页面叫 ExportPage,任务编号固定为 SR-308,公开结果文件名为 result_02.png,质量状态为“待人工复核”。假设内存里的原始业务对象还保存了 privatePath 和 operatorUid 两项内部字段,页面希望显示“已剥离 2 项”,并且只复制经过白名单组织的三行文字。这个例子不涉及图像超分算法本身,更不意味着模型已完成计算;它讨论的是处理结果的描述信息如何离开应用。

剪贴板相当于系统范围内的一块临时共享区域。写进去的数据可能被用户粘贴到聊天工具、笔记或其他应用,不宜把它视作应用私有数据库的延长线。复制事件与分享对象创建应当分开:先检查允许对外展示的字段,再构造文本,最后调用系统接口。失败时既不能继续显示“已复制”,也不能悄悄替用户再试多次。

二、先确定公开契约,再讨论调用接口

1. 一个内部对象可以有两套视图

SR-308 的原始记录包含任务编号、文件名、质量提示和内部定位信息。内部字段的存在是为了缓存管理、追踪问题,不代表外部说明也应该包含它们。如果用“排除敏感字段”思路,后续新增字段时,很容易因为漏加黑名单而泄露;更稳妥的做法是只挑选已知的公开字段。这也是为什么演示中不对原始对象直接执行 JSON.stringify。

下段代码解决的是公开文本从哪里来的问题。它只输出任务、结果文件和质量状态;对文件名、任务号实施形状检查。privatePath 和 operatorUid 仍保留在业务对象中供内部管理,但永远不参与拼装字符串。

interface SRRecord {
  id: string;
  previewName: string;
  quality: string;
  privatePath: string;
  operatorUid: string;
}

function buildPublicNote(record: SRRecord): string {
  const id = /^SR-[0-9]{3}$/.test(record.id) ? record.id : '未提供';
  const name = /^[A-Za-z0-9_-]+\.png$/.test(record.previewName)
    ? record.previewName : '文件名已隐藏';
  const quality = record.quality === '已复核' ? '已复核' : '待人工复核';
  return `任务:${id}\n结果文件:${name}\n质量:${quality}`;
}

演示工程目录按 pages/ExportPage.ets、model/SRRecord.ets、utils/ClipboardGuard.ets 拆分:页面不负责定义公开字段,字段过滤不负责操作系统剪贴板。这里的正则只是 PixelMemo 自己约定的文件命名规则,并非 HarmonyOS 平台要求。它故意不允许路径分隔符、查询串和扩展名随意变化。即使攻击者把文件名改成带有 ../ 的相对路径,输出也只会出现“文件名已隐藏”,不会把输入内容原样带走。正式产品还要考虑 Unicode 文件名和可访问资源的真实类型,不能照抄此处的狭窄白名单来处理所有文件。

有一个容易混淆的地方:这里剥离的是文本说明的字段,并没有删除磁盘上的原始图片,也没有对图像像素进行脱敏。若图片本身可能含人脸、车牌、EXIF 等隐私信息,必须走独立的图像审核和元信息清理流程。不能因为说明文字只剩三行,就认为“图片已完成隐私处理”。

2. 把写入完成和粘贴成功分开

华为官方剪贴板指南说明了 pasteboard.createData、pasteboard.getSystemPasteboard() 和 setData 的基本用法。setData 返回的 Promise 可以告诉调用方这次写入操作是否完成,但它不是对目标应用是否成功粘贴的回执。尤其在跨应用场景中,读取权限、目标组件支持的数据类型和用户操作都可能影响最终结果。

下面是 ExportPage 的核心事件处理。它只在点击后执行写入,使用 copying 阻止同一页面里的连续并发复制,等待 Promise 后才更新状态。示例把日志控制在 PixelMemo SR-308 dropped=2 与 PixelMemo SR-308 copy=OK 两行;异常分支使用 copy=FAILED,不包含私有路径或剪贴板正文。

import { pasteboard } from '@kit.BasicServicesKit';

@Entry
@Component
struct ExportPage {
  @State status: string = '待复制';
  @State copying: boolean = false;
  private mounted: boolean = true;
  private readonly record: SRRecord = {
    id: 'SR-308', previewName: 'result_02.png', quality: '待人工复核',
    privatePath: '/data/storage/el2/base/files/originals/secret.png',
    operatorUid: 'internal-42'
  };

  aboutToAppear(): void { this.mounted = true; }
  aboutToDisappear(): void { this.mounted = false; }

  async copyNote(): Promise<void> {
    if (this.copying) { return; }
    this.copying = true;
    try {
      const note = buildPublicNote(this.record);
      if (!isSafePublicNote(note)) { throw new Error('invalid public note'); }
      console.info('PixelMemo SR-308 dropped=2');
      const data = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, note);
      await pasteboard.getSystemPasteboard().setData(data);
      console.info('PixelMemo SR-308 copy=OK');
      if (this.mounted) { this.status = '已写入剪贴板'; }
    } catch (_) {
      console.error('PixelMemo SR-308 copy=FAILED');
      if (this.mounted) { this.status = '复制失败,请重试'; }
    } finally {
      this.copying = false;
    }
  }

  build() {
    Column({ space: 14 }) {
      Text('图像超分说明导出').fontSize(24)
      Text(`任务 ${this.record.id}`)
      Text(`结果文件 ${this.record.previewName}`)
      Text(`质量 ${this.record.quality}`)
      Text('敏感字段已剥离 2 项')
      Button(this.copying ? '正在写入' : '复制说明')
        .enabled(!this.copying)
        .onClick(() => { this.copyNote(); })
      Text(this.status)
    }.padding(20).width('100%')
  }
}

这一段既不读取剪贴板,也不申请绕过读取限制的权限。事实上,本场景不需要“复制后马上再读一遍”才能确认流程是否合理:比对输入构造规则与 setData 的完成/失败分支已经能实现基础的业务状态管理。官方文档还提示 API 12 及之后的剪贴板读取涉及隐私权限管控,所以把读取当作无条件可用的验收手段并不合适。

mounted 解决的是组件退出后 Promise 才返回的情况,而非取消系统的 setData。页面已经消失时,不再写 UI 状态;但已发起的系统复制操作可能照常完成。copying 在 finally 恢复,确保错误不会永久锁住按钮。需要更强一致性的正式应用,可以在复制动作前给任务加一次性序列号,避免不同页面对同一系统剪贴板竞争写入。

三、回头看调试路径:不能把“脱敏”留给日志

1. 用三类输入验证白名单而非按钮外观

准备 result_02.png、../private.png 和 result_02.png?token=abc 三个输入即可覆盖最关键的边界:正常文件名被保留,携带路径或者查询参数的值被改成“文件名已隐藏”。再检查公开文本没有出现 privatePath、operatorUid 与真实路径前缀。对于正常输入,最终应当只有三行:SR-308、result_02.png、“待人工复核”。这些是预期断言,不是当前工程的实测报告。

第三段代码承担轻量自检,不调用系统服务,只审阅准备写入剪贴板的字符串。特意放在系统调用之前,能让纯业务错误和系统异常分开定位。若断言失败,应当阻止复制,并记录不含原始文本的错误原因。

function isSafePublicNote(note: string): boolean {
  const lines = note.split('\n');
  if (lines.length !== 3) { return false; }
  if (!lines[0].startsWith('任务:SR-')) { return false; }
  if (note.includes('/data/') || note.includes('operatorUid')) { return false; }
  if (note.includes('privatePath') || note.includes('?token=')) { return false; }
  return true;
}

const expected = buildPublicNote({
  id: 'SR-308', previewName: 'result_02.png', quality: '待人工复核',
  privatePath: '/data/storage/el2/base/files/originals/secret.png',
  operatorUid: 'internal-42'
});
// 业务测试可以断言 isSafePublicNote(expected) === true。

真正接入时,应把这一层安全检查接在 createData 之前,不满足规则立即退出,而不是复制完再弹出警告。还要加入反例:文件名不合规、数据对象不存在、用户在异步写入期间退出页面、用户快速点两次按钮,以及其他应用恰好改写系统剪贴板。最后一种情况由系统共享剪贴板的性质决定,并不能靠页面自己的一个布尔标记彻底消除。

2. 日志应证明状态转换,而不是泄露正文

本示意图中,底部 HiLog 用 dropped=2 表示两个内部字段没有进入对外契约,用 copy=OK 表示示例中的成功分支;这是设计中的期望日志,不是设备运行记录。实际工程可用相同标签观测“组装→检查→写入”三个阶段,但严禁为了排查复制失败就把整段说明、绝对路径或用户提供的原始内容打印出来。日志最好只包含非敏感任务标识、状态、错误类别与必要的耗时。

图中的“已写入剪贴板”也必须解释清楚:它对应 setData 的成功回调,不意味着用户已经在第三方应用完成粘贴;手机示意页因此另外写明“待用户粘贴确认”。如果产品要求审核级证据,应通过受控测试数据与受测目标应用执行人工核验,而不是把系统 Promise 的完成等同于最终业务闭环。

四、与图像超分真正接线时,边界还会扩大

1. 任务状态不能替代人工质量结论

图像超分流程可能包含排队、处理、预览、存储和用户复核。此处 SR-308 的质量状态被固定成“待人工复核”,目的就是避免把演示 UI 做成“已通过算法质量检查”。如果未来系统提供清晰度评分或耗时指标,应把数据来源和置信程度单独记录;没有实际算法输入、设备型号与采样条件时,不应生成“处理耗时 200ms”或“画质提升 4 倍”等貌似可信的指标。

另一个边界是文件名与真实可分享资源之间的区别。result_02.png 在这里仅仅是一条描述文字,不代表系统暴露了对应文件,也不代表其他应用拥有读取权限。若后续要真正分享图片,应该研究 URI 授权与系统分享能力,不能把内部沙箱路径直接拼进纯文本。文本复制、图片分享、图像存储分别有不同权限和资源所有权,不应在一个 copyNote 中混做。

2. 收尾时要把“知道”和“尚未验证”分开

从代码层面能确认的是:公开文本只从白名单字段构造;复制按钮串行化调用;成功与失败有分开的状态;离开页面不会再更新已退出组件;日志没有直接打印内部字段。至于系统剪贴板是否在某个具体 SDK/设备组合下按预期写入、模拟器和真机的表现是否一致、复制后的内容能否被目标应用读取,仍然需要后续验证。

本文适合用作“应用内操作结果转为跨应用文字说明”的安全设计参考。它不是超分算法教程,也不构成完整的数据防泄露机制。做工程取舍时,最重要的一步并非选哪个系统 API,而是先决定哪些数据允许跨越应用边界。这个决定一旦写进公开契约,后面无论换成系统分享面板还是云端报告,都能沿用同一套字段审查原则。

五、官方资料与可核查范围

本文接口以华为官方《使用剪贴板进行复制粘贴》及《长按复制地址》场景为核对依据:pasteboard 导入路径、createData、MIMETYPE_TEXT_PLAIN、getSystemPasteboard、setData 和 Promise 写入方式均可在官方说明中找到。剪贴板读取权限是另一个问题,本例未调用读取接口,也没有声称获得读取豁免。对不同 SDK、设备与跨应用粘贴的兼容性,仍须在目标工程中验证。

  • 官方剪贴板指南(2025-03-17):https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-v5/use_pasteboard_to_copy_and_paste-V5
  • 华为官方长按复制地址示例(2026-09-10):https://developer.huawei.com/consumer/cn/doc/architecture-guides/copyaddress-0000002723024514
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐