在这里插入图片描述

每日一句正能量

生活的勇者,便是不停地在苦的心里长出崭新的莲子。」
莲子生于苦莲心,但本身又是清甜的。勇者不是不苦,而是能在苦中持续新生。

一、案例背景:复制粘贴为何仍是跨设备办公的断点

在真实办公场景中,用户经常在手机上收到会议地址、验证码之外的业务文本、审批意见、客户信息或文档链接,却需要在平板上继续编辑。传统做法通常是把内容发给自己、存入备忘录,或者借助即时通信软件中转。操作看似简单,却会造成三个问题:

  1. 链路长:复制、切换应用、选择联系人、发送、再到另一设备打开并复制。
  2. 数据散落:业务文本可能永久留在聊天记录或云端。
  3. 安全边界不清晰:敏感内容与普通内容使用同一通道,缺少过期、撤销和历史清理机制。

本案例设计一个“手机复制→平板确认→授权粘贴”的办公协同方案。它不是绕过系统权限的后台剪贴板监听器,而是把系统剪贴板、可信设备发现、同应用跨设备同步、加密封装和历史记录管理组合成一条可解释、可控制、可审计的业务链路。

在这里插入图片描述

二、先讲清能力边界:系统剪贴板与应用级同步不是一回事

HarmonyOS 的剪贴板模块用于管理系统剪贴板,支持文本、HTML、URI、Want、PixelMap 等多种数据类型。应用可以创建数据、写入系统剪贴板,也可以在符合权限与交互规则的前提下读取内容。

但“跨设备剪贴板”不能简单理解为存在一个任意应用都可静默调用的“把系统剪贴板直接传到另一台设备”的接口。对于普通三方应用,更稳妥的工程方案是:

  • 本机复制阶段使用 Pasteboard Kit;
  • 用户明确开启“同步到可信设备”;
  • 应用对内容做分类、脱敏和加密;
  • 通过分布式设备管理、同应用数据同步或协同连接建立跨设备通道;
  • 接收端将数据写入应用私有历史库;
  • 用户在可见界面中点击“粘贴”或系统安全粘贴控件,完成最终读取或写入。

这样既保留“一步流转”的体验,也不突破系统对剪贴板读取、用户授权和隐私保护的边界。

三、目标与验收指标

本项目设定六项可测目标:

目标验收条件
一步流转手机复制后,平板在 1 秒内出现可确认卡片
安全传输业务内容使用 AES-GCM 加密,明文不落盘
可撤销用户可关闭设备同步并清空远端待接收项
防误传密码、验证码、银行卡等敏感内容默认不自动同步
历史可控普通项默认 24 小时过期,支持收藏和手动删除
弱网可恢复失败任务指数退避,恢复网络后可继续同步

四、总体架构:五层拆分避免“复制即上传”

方案分为交互层、策略层、加密层、同步层和存储层。

在这里插入图片描述

4.1 交互层

交互层负责三件事:告诉用户正在同步什么、同步到哪台设备、接收端如何粘贴。任何敏感操作都必须可见、可取消。

4.2 策略层

策略层决定“能不能同步”。典型规则包括:

  • 文本长度不超过 64 KB;
  • 图片不直接进入轻量剪贴历史,而是转为临时文件引用;
  • 验证码、密码、银行卡号、身份证号命中后默认拒绝;
  • 企业管理员可配置应用白名单、设备白名单和最长保存时间;
  • 仅同步到同账号、已认证、近期在线的可信设备。

4.3 加密层

每条剪贴项生成独立随机 IV,使用 AES-GCM 加密。GCM 同时提供机密性和完整性校验,接收端在认证标签校验失败时必须丢弃数据。消息还应包含唯一 itemId、时间戳和单调递增序列号,用于防重放。

4.4 同步层

同步层只处理密文和元数据。可选实现包括:

  • 同应用跨设备键值数据库同步;
  • 分布式数据对象;
  • 跨设备连接 UIAbility;
  • Service Collaboration Kit 的跨设备互通能力。

具体选择取决于应用模型、设备范围和 SDK 能力。教学项目可先用键值型数据库验证流程,再替换为企业自建通道。

4.5 存储层

本地仅保存最小字段:摘要、类型、长度、来源设备、创建时间、过期时间和密文。明文只在用户确认使用时短暂存在内存。

五、项目目录设计

entry/src/main/ets/
├── model/
│   ├── ClipboardItem.ets
│   └── SyncEnvelope.ets
├── service/
│   ├── PasteboardService.ets
│   ├── SensitivePolicy.ets
│   ├── CryptoService.ets
│   ├── DeviceSyncService.ets
│   └── ClipboardHistoryService.ets
├── pages/
│   ├── Home.ets
│   ├── ReceivePanel.ets
│   └── HistoryPage.ets
└── common/
    ├── Constants.ets
    └── Logger.ets

六、数据模型:业务内容与同步信封分离

export type ClipboardKind = 'TEXT' | 'HTML' | 'URI';

export interface ClipboardItem {
  itemId: string;
  kind: ClipboardKind;
  plainText: string;
  sourceDeviceId: string;
  sourceDeviceName: string;
  createdAt: number;
  expireAt: number;
  favorite: boolean;
  sensitive: boolean;
}

export interface SyncEnvelope {
  version: number;
  itemId: string;
  sourceDeviceId: string;
  targetDeviceId: string;
  createdAt: number;
  expireAt: number;
  sequence: number;
  ivBase64: string;
  cipherBase64: string;
  authTagBase64?: string;
  digestHex: string;
}

把模型拆开有两个好处:第一,跨设备通道永远看不到业务明文;第二,后续升级加密算法或增加字段时,不会破坏业务对象。

七、本机复制:封装 Pasteboard Kit

下面代码展示文本写入系统剪贴板的基本封装。接口名称和可用版本应以实际安装的 HarmonyOS 6.1 SDK 为准。

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

export class PasteboardService {
  private readonly systemPasteboard = pasteboard.getSystemPasteboard();

  async copyText(text: string): Promise<void> {
    if (!text || text.trim().length === 0) {
      throw new Error('复制内容不能为空');
    }
    const data = pasteboard.createData(
      pasteboard.MIMETYPE_TEXT_PLAIN,
      text
    );
    await this.systemPasteboard.setData(data);
  }

  async readTextAfterUserAction(): Promise<string> {
    const data = await this.systemPasteboard.getData();
    if (!data || data.getRecordCount() === 0) {
      return '';
    }
    const record = data.getRecordAt(0);
    return record.plainText ?? '';
  }
}

接收端不应在后台循环读取系统剪贴板。更推荐在页面中提供明确按钮,或者使用系统安全粘贴控件,让读取行为与用户点击绑定。

八、敏感内容识别:默认拒绝比“先传再说”更安全

export interface PolicyResult {
  allowed: boolean;
  reason: string;
  maskedPreview: string;
}

export class SensitivePolicy {
  private readonly otp = /\b\d{4,8}\b/;
  private readonly bankCard = /\b(?:\d[ -]*?){13,19}\b/;
  private readonly idCard = /\b\d{17}[\dXx]\b/;
  private readonly passwordHint = /(密码|password|passwd|口令)\s*[::]/i;

  evaluate(text: string): PolicyResult {
    const value = text.trim();
    if (value.length === 0) {
      return { allowed: false, reason: '内容为空', maskedPreview: '' };
    }
    if (value.length > 64 * 1024) {
      return { allowed: false, reason: '超过 64KB 限制', maskedPreview: '' };
    }

    const sensitive = this.bankCard.test(value)
      || this.idCard.test(value)
      || this.passwordHint.test(value);

    if (sensitive) {
      return {
        allowed: false,
        reason: '检测到高敏感信息,禁止自动同步',
        maskedPreview: this.mask(value)
      };
    }

    // 单独的短数字可能是验证码,仅允许用户手动确认。
    if (value.length <= 12 && this.otp.test(value)) {
      return {
        allowed: false,
        reason: '疑似验证码,需要手动确认',
        maskedPreview: this.mask(value)
      };
    }

    return {
      allowed: true,
      reason: '允许同步',
      maskedPreview: this.preview(value)
    };
  }

  private preview(value: string): string {
    return value.length <= 40 ? value : `${value.slice(0, 40)}`;
  }

  private mask(value: string): string {
    if (value.length <= 4) return '****';
    return `${value.slice(0, 2)}****${value.slice(-2)}`;
  }
}

正则策略不是万能的。企业项目还可以叠加 DLP 规则、字段标签、业务来源和用户选择。例如,来自密码管理器或银行应用的内容可以直接进入“永不同步”列表。

九、AES-GCM 加密封装

Crypto Architecture Kit 提供加解密、摘要、安全随机数等能力。下面给出结构化伪代码,重点展示工程流程;具体 API 参数以当前 SDK 文档为准。

import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { util } from '@kit.ArkTS';

export class CryptoService {
  private readonly encoder = new util.TextEncoder();
  private readonly decoder = util.TextDecoder.create('utf-8');

  async encrypt(plainText: string, key: cryptoFramework.SymKey): Promise<{
    iv: Uint8Array,
    cipher: Uint8Array
  }> {
    const random = cryptoFramework.createRandom();
    const ivBlob = await random.generateRandom(12);
    const cipher = cryptoFramework.createCipher('AES256|GCM|NoPadding');
    const params = this.buildGcmParams(ivBlob.data);
    await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, key, params);
    const input: cryptoFramework.DataBlob = {
      data: this.encoder.encodeInto(plainText)
    };
    const output = await cipher.doFinal(input);
    return { iv: ivBlob.data, cipher: output.data };
  }

  async decrypt(
    cipherText: Uint8Array,
    iv: Uint8Array,
    key: cryptoFramework.SymKey
  ): Promise<string> {
    const cipher = cryptoFramework.createCipher('AES256|GCM|NoPadding');
    await cipher.init(
      cryptoFramework.CryptoMode.DECRYPT_MODE,
      key,
      this.buildGcmParams(iv)
    );
    const output = await cipher.doFinal({ data: cipherText });
    return this.decoder.decodeWithStream(output.data);
  }

  private buildGcmParams(iv: Uint8Array): cryptoFramework.GcmParamsSpec {
    return {
      algName: 'GcmParamsSpec',
      iv: { data: iv },
      aad: { data: new Uint8Array() },
      authTag: { data: new Uint8Array(16) }
    };
  }
}

工程中不要把固定密钥写在 ArkTS 源码里。密钥应来自安全密钥管理、设备认证后协商的会话密钥,或者由服务端按设备和会话下发。密钥轮换、撤销和设备解绑必须纳入设计。

十、同步服务:只传密文,不传明文

export class DeviceSyncService {
  async sendEnvelope(envelope: SyncEnvelope): Promise<void> {
    this.validateEnvelope(envelope);
    const key = `clipboard/${envelope.targetDeviceId}/${envelope.itemId}`;

    // 这里可替换为分布式 KV、分布式数据对象或协同连接通道。
    await this.putDistributedValue(key, JSON.stringify(envelope));
  }

  async onRemoteEnvelope(raw: string): Promise<void> {
    const envelope = JSON.parse(raw) as SyncEnvelope;
    this.validateEnvelope(envelope);

    if (Date.now() > envelope.expireAt) {
      return;
    }
    if (await this.isReplay(envelope.itemId, envelope.sequence)) {
      return;
    }

    await ClipboardHistoryService.instance.saveEncrypted(envelope);
  }

  private validateEnvelope(value: SyncEnvelope): void {
    if (value.version !== 1) {
      throw new Error('不支持的同步协议版本');
    }
    if (!value.itemId || !value.cipherBase64 || !value.ivBase64) {
      throw new Error('同步信封字段不完整');
    }
  }

  private async putDistributedValue(key: string, value: string): Promise<void> {
    // 教学占位:接入实际跨设备数据同步能力。
  }

  private async isReplay(itemId: string, sequence: number): Promise<boolean> {
    return false;
  }
}

同步过程应支持幂等:同一个 itemId 多次到达,只保留一条。对于离线设备,发送端可以保留待发送队列,但过期后必须删除。

十一、历史记录管理:不是“永久收藏夹”

在这里插入图片描述

export interface HistoryMeta {
  itemId: string;
  type: ClipboardKind;
  sourceDeviceName: string;
  createdAt: number;
  expireAt: number;
  favorite: boolean;
  digestHex: string;
}

export class ClipboardHistoryService {
  static readonly instance = new ClipboardHistoryService();
  private readonly maxItems = 100;

  async saveEncrypted(envelope: SyncEnvelope): Promise<void> {
    if (Date.now() >= envelope.expireAt) return;
    if (await this.exists(envelope.itemId)) return;

    await this.insert({
      itemId: envelope.itemId,
      sourceDeviceName: '可信设备',
      createdAt: envelope.createdAt,
      expireAt: envelope.expireAt,
      favorite: false,
      digestHex: envelope.digestHex,
      encryptedPayload: JSON.stringify(envelope)
    });

    await this.trimToLimit();
  }

  async cleanExpired(now: number = Date.now()): Promise<number> {
    return this.deleteWhere(`expireAt < ${now} AND favorite = 0`);
  }

  async clearAll(): Promise<void> {
    await this.deleteWhere('1 = 1');
  }

  private async exists(itemId: string): Promise<boolean> { return false; }
  private async insert(value: Record<string, Object>): Promise<void> {}
  private async trimToLimit(): Promise<void> {}
  private async deleteWhere(where: string): Promise<number> { return 0; }
}

推荐默认策略:

  • 普通文本 24 小时过期;
  • 链接 72 小时过期;
  • 收藏项由用户主动保留;
  • 高敏感项不进入历史;
  • 历史上限 100 条;
  • 设备解绑时删除该设备产生的未收藏记录。

十二、冲突处理:最后写入胜出并不总是正确

两台设备可能在极短时间内产生不同内容。简单使用“最后写入胜出”会导致用户正在使用的内容被覆盖。本方案采用三步决策:

  1. itemId 相同:判定为重复消息,直接丢弃;
  2. 创建时间差小于 2 秒且来源设备不同:两条都保留,标记为“并发复制”;
  3. 非并发场景:按时间戳排序,但不自动覆盖当前正在预览的内容。

如果企业设备时间可能漂移,应使用服务端时间、逻辑时钟或序列号,不应完全依赖本地系统时间。

十三、接收端交互:让安全提示成为体验的一部分

在这里插入图片描述

接收端建议采用卡片式提示:

  • 显示来源设备;
  • 显示脱敏预览;
  • 提供“粘贴”“稍后处理”“删除”三个动作;
  • 超过有效期自动消失;
  • 敏感内容必须再次认证;
  • 用户关闭跨设备剪贴板后,不再接收新项。
@Entry
@Component
struct ReceivePanel {
  @State preview: string = '会议纪要:周五提交';
  @State sourceDevice: string = '办公手机';
  @State status: string = '等待操作';

  build() {
    Column({ space: 16 }) {
      Text('来自其他设备的新内容')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Text(`来源:${this.sourceDevice}`)
        .fontSize(14)

      Text(this.preview)
        .fontSize(18)
        .maxLines(3)

      Row({ space: 12 }) {
        Button('删除')
          .onClick(() => this.status = '已删除')

        Button('授权粘贴')
          .onClick(async () => {
            // 1. 用户点击触发
            // 2. 解密内存中的密文
            // 3. 写入本机系统剪贴板
            // 4. 更新使用状态
            this.status = '已写入本机剪贴板';
          })
      }

      Text(this.status).fontSize(13)
    }
    .padding(24)
  }
}

十四、完整时序:从发现设备到用户粘贴

在这里插入图片描述

关键点是把“同步完成”和“粘贴完成”分开。同步完成只代表密文到达平板应用的私有空间;只有用户点击后,应用才解密并写入本机剪贴板或填入目标输入框。

十五、失败恢复与异常处理

15.1 设备离线

待发送任务进入队列,重试间隔采用 1 秒、2 秒、4 秒、8 秒,最大 30 秒。超过有效期后直接删除。

15.2 解密失败

认证标签校验失败、协议版本不匹配或摘要异常时,记录安全事件但不记录明文。连续失败可触发设备重新认证。

15.3 目标设备解绑

立即撤销会话密钥、停止同步、删除该设备未完成任务,并提示用户历史数据仍保存在各设备本地,需要分别清理。

15.4 应用被杀

待发送队列和已接收密文写入应用私有数据库。恢复后只处理未过期、未确认完成的任务。

15.5 大内容

轻量剪贴板不适合直接承载大图和文件。超过阈值时转为“临时文件分享”,显示文件大小、类型和有效期,并使用专门的文件传输能力。

十六、性能测试与优化

测试环境为校企合作教学样例工程:两台同账号测试设备、局域网稳定连接,每个场景执行 20 次并取平均值。数据只用于说明优化趋势,不代表所有设备和网络环境。

指标优化前优化后优化手段
复制到加密完成46 ms31 ms复用编码器、减少对象创建
跨设备同步420 ms285 ms合并元数据、减少一次握手
接收端入库78 ms42 ms批量事务、摘要索引
历史列表打开115 ms68 ms分页查询、延迟解密

在这里插入图片描述

性能优化重点:

  1. 列表页只读取元数据,不批量解密正文;
  2. 对相同摘要进行去重;
  3. 小文本直接同步,大内容转文件;
  4. 加密和摘要计算放到任务线程;
  5. 页面只订阅新增项,不重复刷新整个列表;
  6. 数据库按 createdAtexpireAtdigestHex 建索引。

十七、录屏演示脚本

由于录屏必须在真实手机与平板上完成,本文提供可直接执行的 45 秒分镜:

时间画面操作与旁白
0–5 秒手机和平板并排展示两台可信设备已连接
5–12 秒手机打开会议纪要选中“周五 17:00 前提交方案”并复制
12–18 秒手机提示卡显示“已加密同步至办公平板”
18–26 秒平板弹出接收卡片展示来源设备、脱敏预览和有效期
26–34 秒点击“授权粘贴”在平板文档输入框中完成粘贴
34–40 秒打开历史记录展示收藏、删除、过期时间
40–45 秒关闭同步开关强调用户可撤销和清空数据

录屏时不要展示真实客户数据、手机号、验证码或账号信息。建议使用测试文本并开启系统触摸点显示,方便评审观察操作链路。

十八、安全检查清单

  • 是否只向可信、已认证设备同步?
  • 是否禁止固定密钥写入源码?
  • 是否使用随机 IV 和认证加密?
  • 是否默认拦截密码、验证码、银行卡等内容?
  • 是否有 TTL、历史上限和一键清空?
  • 是否能在设备解绑后撤销会话?
  • 是否避免后台静默读取系统剪贴板?
  • 是否在日志中彻底禁止打印明文?
  • 是否对重放、重复消息和时间漂移做处理?
  • 是否在性能测试中标注样本范围?

十九、教学落地建议

在高校课程中,可以把项目拆成四个实验:

  1. 基础实验:完成本机复制、粘贴与历史列表;
  2. 安全实验:实现敏感识别、AES-GCM 加密和过期清理;
  3. 协同实验:接入可信设备发现和跨设备数据同步;
  4. 工程实验:补齐弱网重试、冲突处理、性能测试和录屏验收。

企业导师负责定义办公场景和安全红线,高校教师负责能力拆解和代码规范,学生团队负责实现与测试。最终成果不仅是一篇文章,还可以形成课程设计、实训项目和企业原型。

二十、总结

跨设备剪贴板看似只是“手机复制、平板粘贴”,真正落地时却涉及剪贴板权限、可信设备、跨设备通道、端到端加密、历史生命周期、冲突处理和用户授权。

HarmonyOS 6.1 为剪贴板管理、分布式设备管理、同应用跨设备数据同步、协同连接和加密框架提供了组合能力。高质量方案的关键不是追求无感到看不见,而是在用户几乎不增加操作成本的前提下,让每一次同步都可见、可控、可撤销。

本文方案坚持三条原则:

  • 明文不跨设备通道
  • 敏感内容默认不自动同步
  • 最终粘贴由用户明确触发

只有同时满足效率与安全,跨设备办公的“无缝流转”才不是一句宣传语,而是可以被复现、测试和审计的工程能力。

转载自:https://blog.csdn.net/u014727709/article/details/162997320
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

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

更多推荐