我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

前言

先来句大实话:跨应用数据交换这件事,99% 的坑都不是“拿不到文件”,而是“给多了权限、锁不住边界、传大文件卡成 PPT”。今天咱就把 URI/临时授权大文件分片与零拷贝隐私脱敏这三板斧掰开揉碎,用工程可落地的方式讲清楚,还配上 ArkTS 侧代码片段协议草案。保证你既能把文件递出去,又能把边界守住——**有请“能力化分享、按次授权、带审计的零拷贝”**登场。🙂

一、目标与威胁模型:先把“坑”画出来

目标:

  • App A(分享方)→ App B(接收方)安全交换文件/流/句柄
  • 沙箱原则不破坏:A 不能随意读 B;B 只能按授权读 A 指定对象;
  • 大文件不拷贝不断流不中断
  • 整条链路可审计可撤销可过期
  • 上线合规:最小权限、隐私脱敏、可本地留痕。

威胁:

  • 授权过宽(目录递归读、长时可用、可转授);
  • URI 被劫持或重放(截屏/粘贴板/日志泄漏);
  • 大文件复制造成时间/空间灾难
  • 元数据泄露(EXIF/GPS/人脸/水印);
  • 共享后无法撤销或追责。

口号:能力最小化 + 时间窗最短化 + 数据最轻化 + 过程可追溯

二、总架构:能力导向(Capability-based)而非“路径导向”

┌──────────────┐          ┌──────────────┐
│  App A ()URI     │  App B (宿)  │
│  Sandboxed   ├─────────►│  Sandboxed   │
└──────┬───────┘          └──────┬───────┘
       │                           │
       │ grant(token, scope, ttl)validate(token)
       ▼                           ▼
   ShareProvider /             ShareClient /
   FileBridge(IPC/Fd)          ZeroCopy(MMAP)
       │                           │
       └──────────►  OS/Service ⟂ ◄┘
                 (授权表 / 审计日志 / 回收器)
  • URI仅是能力指针(capability),真正的权限来自一次性/短期 token
  • 大文件通过 Fd 直传mmap/SharedMemory,避免二次拷贝;
  • OS/服务层维护授权表(who→what→until when),支持撤销/过期审计

三、URI 与临时授权:令牌就像“门票”,而非“钥匙复印件”

3.1 URI 规范(建议)

caps://share/<resource-id>?t=<opaque_token>&s=<scope>&e=<unix_exp>
  • resource-id:内容寻址(推荐 CID/哈希)或一次性随机 id;
  • t不可预测的密文 token(服务端校验或本地验证);
  • s:权限范围(r读 / rw读写 / meta元数据);
  • e:过期时间(**短!**比如 2~10 分钟);

不要在 URI 放明文路径或易识别信息;不要允许二次分享(绑定 aud = 目标包名)。

3.2 授权/校验流程(时序简图)

App A             OS/ShareSvc           App B
  |  createCaps()     |                  |
  |------------------>|  issue token     |
  |<------------------|  (bind aud, ttl) |
  |   send URI        |                  |
  |------------------------------------->|
  |                     validate token   |
  |<-------------------------------------|
  |    openFd()/stream |                  |
  |====================> zero-copy read   |
  • 绑定受众 (aud):只允许特定包名/签名指纹使用;
  • 一次性:用后即销,或最少次数(如 1 次读取);
  • 最小范围:面向对象授权,不授权目录或模式匹配。

3.3 ArkTS 端(分享方)生成一次性 URI(示例)

// share/Grant.ts
import crypto from '@ohos.security.crypto'

export type Scope = 'r'|'rw'|'meta'
export function createCaps(resourceId: string, audiencePkg: string, scope: Scope, ttlSec = 300) {
  const exp = Math.floor(Date.now()/1000) + ttlSec
  const nonce = crypto.randomUUID()
  const payload = JSON.stringify({ rid: resourceId, aud: audiencePkg, s: scope, e: exp, n: nonce })
  const sig = sign(payload) // 私钥/密钥签名(或HMAC)
  const token = base64url(enc(payload) + '.' + sig)

  const uri = `caps://share/${resourceId}?t=${token}&s=${scope}&e=${exp}`
  // 记一笔授权记录(审计/撤销用)
  Audit.logGrant({ rid: resourceId, aud: audiencePkg, scope, exp, nonce })
  return uri
}

sign() 可以落到 本地 Keystore 或私密服务;不要把可验证信息丢到日志。

3.4 接收方校验并打开数据通道(示例)

// share/Client.ts
export async function openCaps(uri: string) {
  const u = new URL(uri)
  const token = u.searchParams.get('t')!
  // 1) 先在本地/服务校验 aud/exp/signature
  const ok = await ShareSvc.validate(token, myPackageName(), Date.now())
  if (!ok) throw new Error('invalid or expired')

  // 2) 申请访问通道:优先 Fd / SharedMemory
  return await ShareSvc.openChannel(token) // 返回 { fd? , shmKey? , size , mime }
}

四、大文件分片与零拷贝:让“1GB 文件”不再变“复制地狱”

4.1 传输策略优先级

  1. 同设备、同 OS:优先 Fd 共享 / mmap(零拷贝);
  2. 进程隔离Ashmem/SharedMemory 或 OS 提供的共享区;
  3. 跨设备 / 云端分片 + 流式校验 + 断点续传
  4. 极端 fallback:短期缓存 + 流读取(谨慎)。

4.2 分片协议(Manifest + Merkle)

  • Manifest 记录:chunk size、总 size、每片哈希、顺序;
  • Merkle root 作为资源指纹(resource-id),防篡改、可局部校验;
  • 支持稀疏拉取并发下载(B 端按需请求片段)。

Manifest 示例(JSON):

{
  "rid": "bafkr...merkle_root",
  "size": 104857600,
  "chunk": 1048576,
  "parts": [
    {"i":0,"h":"sha256:..."},
    {"i":1,"h":"sha256:..."}
  ],
  "mime": "video/mp4"
}

4.3 ArkTS 端零拷贝(示意)

// share/FileBridge.ts
export async function openChannel(token: string) {
  // 通过 token 找到资源路径或文件描述符
  const { path, size, mime } = await Index.lookup(token)
  // 申请只读 FD 并限制范围(posix_fadvise/allowlist)
  const fd = await OS.openReadOnlyFd(path)
  return { fd, size, mime }
}

接收方 mmap 读取(伪代码):

// B 进程
const { fd, size, mime } = await ShareSvc.openChannel(token)
// @ts-ignore: native bridge
const view = Native.mmapReadOnly(fd, 0, size)
// 直接把 view 喂给播放器/解析器,无需复制到 JS 堆

优点常数级内存占用、CPU 不被 memcpy 拖垮;缺点:需要处理页对齐生命周期(及时 munmap)。

4.4 分片拉流(跨设备/云)ArkTS 侧请求(简化)

// Range 拉片段
async function fetchChunk(rid: string, i: number, chunkSize: number) {
  const start = i * chunkSize
  const end = start + chunkSize - 1
  const resp = await fetch(`/chunks/${rid}`, { headers: { Range: `bytes=${start}-${end}` } })
  const data = await resp.arrayBuffer()
  const ok = await verifyHash(data, manifest.parts[i].h)
  if (!ok) throw new Error('chunk corrupted')
  return new Uint8Array(data)
}

五、隐私脱敏:别把“把柄”交出去

5.1 脱敏策略矩阵

类型 风险 处理
图片/视频 EXIF GPS、设备型号、拍摄时间 去 EXIF、可选模糊人脸
文档(PDF/Office) 作者、修订历史、评论 扁平化导出、清理元数据
日志/文本 手机号、邮箱、姓名 正则掩码 + 名单脱敏
录音/语音 私人信息 截断静默、变声(选)、加水印提示

5.2 ArkTS 侧“分享前处理管线”

// privacy/Sanitize.ts
export interface SanitizeRule { mime: RegExp; run(buf: ArrayBuffer): Promise<ArrayBuffer> }

const stripExif: SanitizeRule = {
  mime: /^image\//,
  async run(buf) { return await Exif.strip(buf) }
}
const maskText: SanitizeRule = {
  mime: /^text\//,
  async run(buf) {
    const s = new TextDecoder().decode(buf)
    const masked = s
      .replace(/\b1[3-9]\d{9}\b/g, '1**********')
      .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, '***@***')
    return new TextEncoder().encode(masked).buffer
  }
}

export async function sanitizeBeforeShare(file: FileLike) {
  const rules = [stripExif, maskText]
  let data = await file.readAll()
  for (const r of rules) if (r.mime.test(file.mime)) data = await r.run(data)
  return { mime: file.mime, data }
}

强制策略:对“外发”场景默认开启脱敏;只有白名单受众(企业内部包签名)才允许跳过某些规则。

5.3 审计与水印(可选)

  • 审计记录:whowhatwhenaudscopehash
  • 视觉水印:对图片/文档添加不可见/半透明标识(含时间戳/设备号);
  • 最小留痕:审计日志脱敏,仅存哈希和时间窗。

六、撤销、过期与回收:授权不是“一锤子买卖”

  • 时间到即回收:后台定时器扫描授权表,过期即失效

  • 手动撤销:A 方可以在“分享记录”里主动 revoke;

  • 活跃句柄处理:对已打开的 Fd,从文件系统层撤销访问较难,策略是:

    • 新读请求拒绝,旧句柄仅在 session 内有效
    • 对流式接口,服务端中断(返回 4xx/FIN);
    • 对 SharedMemory,设置 版本号/签名 校验,后续消息拒绝。
// revoke.ts
export function revoke(token: string) {
  AuthTable.invalidate(token)
  Audit.logRevoke(token)
  // 如果存在流通道,发送 CLOSE 信号
  ChannelHub.closeByToken(token)
}

七、OS/服务端:少就是多(最小可行接口)

7.1 校验接口

  • POST /caps/validate { token, audience, now } -> { ok, rid, scope, exp }
  • POST /caps/open { token } -> { fd|shmKey|manifest, mime, size }
  • POST /caps/revoke { token } -> { ok }

可以下沉到端内服务(同设备 IPC),或企业网关(跨设备/云)。

7.2 存储设计

  • 授权表(KV/SQLite):token → { rid, aud, scope, exp, nonce, usedCount }
  • 审计表event(time, who, action, rid_hash, audience, scope)
  • 对象索引rid → path/driver/manifest

八、延迟补偿 UI:分享体验“稳、准、快”

  • 生成中占位:大文件脱敏/签名时显示“准备中…xx%”,避免误触取消;
  • 离线队列:无网时记录分享意图 + 本地 token(仅本机有效),联网后补发;
  • 失败可重试:token 过期自动刷新一次(需用户确认);
  • 安全提示:目标 App 验证失败时给出明确原因(签名不匹配/过期/撤销)。
// ShareSheet.ets 片段
SButton({ text: 'Share', onPress: async () => {
  try {
    setState('preparing')
    const clean = await sanitizeBeforeShare(file)
    const uri = createCaps(clean.rid, targetPkg, 'r', 180)
    await sendUriToTarget(uri) // 通过系统分享面板/跨进程
    toast('已发送,3分钟内有效')
  } catch (e) {
    toast(`发送失败:${String(e)}`)
  } finally {
    setState('idle')
  }
}})

九、性能与体积:让“工程处女座”住进你心里

  • 零拷贝优先:能 Fd 就别 Buffer,能 mmap 就别读全;
  • 分片并发:跨设备拉流并发 = min(带宽/RTT, CPU/IO 限制),默认 4~8;
  • Chunk 大小:1–4 MB 较平衡(移动网络);局域网可 8–16 MB;
  • 反压与速率:播放器/预览器反馈消费速率,避免缓存暴涨;
  • 指数退避:失败重试 3 次,100ms * 2^n
  • 体积预算:脱敏后的临时缓存 ≤ 50MB,超过走管道式处理(边读边脱敏边输出)。

十、常见翻车清单与止血法

  1. 把目录共享给对方

    • 只能授权对象级,禁止递归;URI 每次只对应一个 rid。
  2. Token 永不过期

    • 统一 TTL(2–10 分钟),完成即销;默认一次性
  3. 日志里出现了 URI

    • 日志里仅存 rid_hash 与事件摘要,URI/token 绝不落日志。
  4. EXIF 没清干净

    • 统一走分享管线,禁止绕过;CI 做图片/文档元数据扫描。
  5. 二次分享

    • Token 绑定受众包名/签名,App B 再转发 → 校验失败
  6. 大文件卡 UI

    • 脱敏/签名必须放后台 worker;UI 只显示进度与取消。
  7. 撤销不生效

    • 流式接口必须支持中断信号;本地句柄设置会话版本校验。

十一、可落地代码索引(便于你拷到工程里)

  • share/Grant.ts:一次性 capability URI 生成 + 审计
  • share/Client.ts:接收方校验与打开通道
  • share/FileBridge.ts:Fd 通道(零拷贝)
  • privacy/Sanitize.ts:分享前脱敏管线
  • revoke.ts:撤销与通道关闭
  • ShareSheet.ets:UI 交互与延迟补偿

结语:把“可分享”做成“可控分享”

分享这件事,一半是体验,一半是边界。当我们用 URI + 临时授权守住入口,用 零拷贝/分片守住性能,用 脱敏 + 审计 + 撤销守住信任,跨应用数据交换就不再是“拆门板”的粗暴动作,而是“交门票”的优雅邀请。下次当产品说“就一个分享按钮嘛”,你可以笑着点头:行,但我们要的是“可控地分享”。😉

(未完待续)

Logo

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

更多推荐