在这里插入图片描述

每日一句正能量

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

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

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

  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 ms 31 ms 复用编码器、减少对象创建
跨设备同步 420 ms 285 ms 合并元数据、减少一次握手
接收端入库 78 ms 42 ms 批量事务、摘要索引
历史列表打开 115 ms 68 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、测试、元服务和应用上架分发等。

更多推荐